2010-06-15 64 views
4

這只是一個需要討論的問題 - 在WPF中製作視圖/編輯控件的最佳方式是什麼?例如。我們有一個實體對象Person,它有一些道具(名字,姓氏,地址,電話等)。控件的一個表示將是隻讀視圖。另一個可以爲同一個人編輯視圖。例如:WPF製作視圖/編輯控件的好方法?

<UserControl x:Name="MyPersonEditor"> 
    <Grid> 
     <Grid x:Name="ViewGrid" Visibility="Visible"> 
      <TextBlock Text="Name:"/> 
      <TextBlock Text="{Binding Person.Name}"/> 
      <Button Content="Edit" Click="ButtonEditStart_Click"/> 
     </Grid> 

     <Grid x:Name="EditGrid" Visibility="Collapsed"> 
      <TextBlock Text="Name:"/> 
      <TextBox Text="{Binding Person.Name}"/> 
      <Button Content="Save" Click="ButtonEditEnd_Click"/> 
     </Grid> 
    </Grid> 
</UserControl> 

我希望這個想法很清楚。這兩個選項我現在看到的

  1. 兩個網格,能見度切換和
  2. 一個TabControl沒有它的頭面板

這僅僅是一個討論的問題 - 它沒有太大的麻煩,但我只是想知道是否有任何其他的可能性和優雅的解決方案。

回答

3

自動鎖類

我寫了有一個繼承連接「DoLock」財產「AutomaticLock」級。

將「DoLock」屬性設置爲true將所有文本框組合框,複選框等重新模板化爲TextBlocks,不可編輯複選框等。我的代碼已設置,以便其他附加屬性可以指定任意模板以用於鎖定(「查看」)模式,不應該自動鎖定的控件等。

因此,可以輕鬆地將同一視圖用於編輯和觀看。設置一個屬性來回更改它,它是完全可定製的,因爲視圖中的任何控件都可以通過「DoLock」屬性觸發,以任意方式更改其外觀或行爲。

實現代碼

下面是代碼:

public class AutomaticLock : DependencyObject 
{ 
    Control _target; 
    ControlTemplate _originalTemplate; 

    // AutomaticLock.Enabled: Set true on individual controls to enable locking functionality on that control 
    public static bool GetEnabled(DependencyObject obj) { return (bool)obj.GetValue(EnabledProperty); } 
    public static void SetEnabled(DependencyObject obj, bool value) { obj.SetValue(EnabledProperty, value); } 
    public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(AutomaticLock), new FrameworkPropertyMetadata 
    { 
    PropertyChangedCallback = OnLockingStateChanged, 
    }); 

    // AutomaticLock.LockTemplate: Set to a custom ControlTemplate to be used when control is locked 
    public static ControlTemplate GetLockTemplate(DependencyObject obj) { return (ControlTemplate)obj.GetValue(LockTemplateProperty); } 
    public static void SetLockTemplate(DependencyObject obj, ControlTemplate value) { obj.SetValue(LockTemplateProperty, value); } 
    public static readonly DependencyProperty LockTemplateProperty = DependencyProperty.RegisterAttached("LockTemplate", typeof(ControlTemplate), typeof(AutomaticLock), new FrameworkPropertyMetadata 
    { 
    PropertyChangedCallback = OnLockingStateChanged, 
    }); 

    // AutomaticLock.DoLock: Set on container to cause all children with AutomaticLock.Enabled to lock 
    public static bool GetDoLock(DependencyObject obj) { return (bool)obj.GetValue(DoLockProperty); } 
    public static void SetDoLock(DependencyObject obj, bool value) { obj.SetValue(DoLockProperty, value); } 
    public static readonly DependencyProperty DoLockProperty = DependencyProperty.RegisterAttached("DoLock", typeof(bool), typeof(ControlTemplate), new FrameworkPropertyMetadata 
    { 
    Inherits = true, 
    PropertyChangedCallback = OnLockingStateChanged, 
    }); 

    // CurrentLock: Used internally to maintain lock state 
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
    public static AutomaticLock GetCurrentLock(DependencyObject obj) { return (AutomaticLock)obj.GetValue(CurrentLockProperty); } 
    public static void SetCurrentLock(DependencyObject obj, AutomaticLock value) { obj.SetValue(CurrentLockProperty, value); } 
    public static readonly DependencyProperty CurrentLockProperty = DependencyProperty.RegisterAttached("CurrentLock", typeof(AutomaticLock), typeof(AutomaticLock)); 


    static void OnLockingStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
    { 
    AutomaticLock current = GetCurrentLock(obj); 
    bool shouldLock = GetDoLock(obj) && (GetEnabled(obj) || GetLockTemplate(obj)!=null); 
    if(shouldLock && current==null) 
    { 
     if(!(obj is Control)) throw new InvalidOperationException("AutomaticLock can only be used on objects derived from Control"); 
     new AutomaticLock((Control)obj).Attach(); 
    } 
    else if(!shouldLock && current!=null) 
     current.Detach(); 
    } 

    AutomaticLock(Control target) 
    { 
    _target = target; 
    } 

    void Attach() 
    { 
    _originalTemplate = _target.Template; 
    _target.Template = GetLockTemplate(_target) ?? SelectDefaultLockTemplate(); 
    SetCurrentLock(_target, this); 
    } 

    void Detach() 
    { 
    _target.Template = _originalTemplate; 
    _originalTemplate = null; 
    SetCurrentLock(_target, null); 
    } 

    ControlTemplate SelectDefaultLockTemplate() 
    { 
    for(Type type = _target.GetType(); type!=typeof(object); type = type.BaseType) 
    { 
     ControlTemplate result = 
     _target.TryFindResource(new ComponentResourceKey(type, "AutomaticLockTemplate")) as ControlTemplate ?? 
     _target.TryFindResource(new ComponentResourceKey(typeof(AutomaticLock), type.Name)) as ControlTemplate; 
     if(result!=null) return result; 
    } 
    return null; 
    } 
} 

此代碼將允許你在控制由控制的基礎上指定的自鎖模板或者它可以讓你使用默認模板定義在包含自動鎖定類的程序集中,包含應用鎖定模板的自定義控件的程序集中,在您的可視樹中的本地資源(包括應用程序資源)中定義。

如何定義AutomaticLock模板

爲WPF標準控件默認模板在包含在ResourceDictionary中的AutomaticLock類所述組件限定合併到主題/ Generic.xaml。例如,該模板導致所有文本框變成鎖定的TextBlocks時:

<ControlTemplate TargetType="{x:Type TextBox}" 
    x:Key="{ComponentResourceKey ResourceId=TextBox, TypeInTargetAssembly={x:Type lc:AutomaticLock}}"> 
    <TextBlock Text="{TemplateBinding Text}" /> 
</ControlTemplate> 

定製控件默認模板可以在包含自定義控制在ResourceDictionary中梅里德到其主題/ Generic.xaml組件被定義。該ComponentResourceKey是在這種情況下不同,例如:

<ControlTemplate TargetType="{x:Type prefix:MyType}" 
    x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}"> 
    ... 

如果應用程序要替換爲特定類型的標準模板AutomaticLock,它可以將一個自動鎖模板在其App.xaml中,窗口XAML,用戶控件XAML或單獨控件的ResourceDictionary中。通過設置其屬性AutomaticLock.LockTemplate

x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}" 

最後,自動閉鎖模板可以適用於一個單一的控制:在每種情況下ComponentResourceKey應該被指定相同的方式用於定製控件。

如何使用AutomaticLock在UI

要使用自動鎖定:

  1. 設置AutomaticLock.Enabled = 「true」,從而應自動鎖定的控制。這可以通過風格或直接在單個控件上完成。它可以鎖定控件,但不會導致控件實際鎖定。
  2. 想要鎖定時,只要希望實現自動鎖定,就可以在頂級控件(窗口,視圖,用戶控件等)上設置AutomaticLock.DoLock =「True」。您可以將AutomaticLock.DoLock綁定到複選框或菜單項,也可以用代碼控制它。

上查看和編輯模式之間切換有效

這AutomaticLock類的一些技巧是偉大的,介乎查看和編輯模式之間切換,即使他們有顯著不同。我有幾種不同的技巧來構建視圖,以便在編輯時適應佈局差異。其中一些是:

  1. 製作控制通過它們的模板或AutomaticLockTemplate設置的情況下可能是一個空的模板編輯或視圖模式中不可見。例如,假設「年齡」在視圖模式下位於佈局的頂部,而在編輯模式下位於底部。在兩個地方爲「年齡」添加一個文本框。在最上面的一個將模板設置爲空模板,因此它不會在編輯模式下顯示。在底部,將AutomaticLockTemplate設置爲空模板。現在一次只能看到一個。

  2. 使用ContentControl替換周圍內容的邊框,佈局面板,按鈕等,而不影響內容。 ContentControl的模板具有用於編輯模式的周圍邊框,面板,按鈕等。它也有一個具有視圖模式版本的AutomaticLockTemplate。

  3. 使用控件替換視圖的矩形部分。 (通過這個,我實際上是指類「Control」的一個對象,而不是子類。)同樣,您將編輯模式版本放入Template中,並將視圖模式版本放入AutomaticLockTemplate中。

  4. 使用具有額外自動調整大小的行和列的網格。在AutomaticLock.DoLock屬性上使用觸發器來更新網格中項目的Row,Column,RowSpan和ColumnSpan屬性。例如,您可以將包含「Age」控件的面板通過將Grid.Row從6更改爲0.

  5. 觸發DoLock以將LayoutTranform或RenderTransform應用於項目或設置其他屬性像寬度和高度。如果你想在編輯模式下做更大的事情,或者如果你想擴大文本框並將它旁邊的按鈕移動到邊緣,這很有用。

請注意,您可以對整個視圖使用選項#3(帶有用於編輯和視圖模式的單獨模板的控件對象)。如果編輯和視圖模式完全不同,這將會完成。在這種情況下,AutomaticLock仍然爲您提供了手動設置兩個模板的便利。它應該是這樣的:

<Control> 
    <Control.Template> 
    <ControlTemplate> 
     <!-- Edit mode view here --> 
    </ControlTemplate> 
    </Control.Template> 
    <lib:AutomaticLock.LockTemplate> 
    <ControlTemplate> 
     <!-- View mode view here --> 
    </ControlTemplate> 
    </lib:AutomaticLock.LockTemplate> 
</Control> 

一般來說是比較容易調整的編輯和查看模式之間的一些小的位置和事物,併爲您的用戶體驗更好,因爲用戶將有一致的佈局,但如果這樣做需要一個完整的替代品AutomaticLock也爲您提供這種功能。

+1

共享您的AutomaticLock類的例子的任何可能性? – 2010-06-16 04:38:21

+0

沒問題。這裏是。 – 2010-06-16 15:55:22

+0

此代碼看起來不錯:) 一個減號可能是無法重構視圖/編輯(例如,如果我們需要「查看」模式與「編輯」模式不同)。但這不是解決方案的問題,因爲它不是爲此目的而制定的。我一定會在我的項目中嘗試一下! – Jefim 2010-06-17 05:22:34

0

我會用2個不同的配置選項創建單個視圖f.e. 2層不同的構造,使相關領域編輯/只讀或可見/隱藏

這種方式使用MVVM

1
<Grid> 
    <TextBlock Text="Name:"/> 
    <LabelText="{Binding Person.Name}" Cursor="IBeam" MouseDoubleClick="lblName_dblClick"/> <!-- set the IsEditMode to true inside this event --> 
    <TextBox Text="{Binding Person.Name}" Visibility="{Binding IsEditMode, Converter={StaticResource BoolToVisConverter}}"/> 
    <Button Content="OK" Click="btnSave_Click" Visibility="{Binding IsEditMode, Converter={StaticResource BoolToVisConverter}}"/> <!-- set the IsEditMode to false inside this event --> 
</Grid> 

,當你不寫多餘的XAML,你可以在代碼configurate各個領域的背後或視圖模型如果您熟悉,請使用命令。

+0

我目前有一個非常類似的解決方案,但隨着更改依賴於IsEditMode屬性的面板。非常類似的工作機制(僅在不同層次的元素上)。 無論如何,我在這裏提出的問題的原因是這個方法帶來了大量的XAML(例如,所有控件的可見性都必須被綁定;在我的情況下,兩個選項卡只有兩個綁定,但這意味着我必須複製第二個選項卡中的所有控件)。 而這使得代碼的可讀性稍差一些......又是如何去做的。謝謝回覆! – Jefim 2010-06-17 05:35:30

0

聽起來像是給我的DataTemplateSelector的工作。如果您寧願將各個控件切換到位,我會做類似於Veer建議的操作。