WPF制作视图/编辑控件的好方法?

问题描述:

这只是一个需要讨论的问题 - 在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没有它的头面板

这仅仅是一个讨论的问题 - 它没有太大的麻烦,但我只是想知道是否有任何其他的可能性和优雅的解决方案。

自动锁类

我写了有一个继承连接“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

我会用2个不同的配置选项创建单个视图f.e. 2层不同的构造,使相关领域编辑/只读或可见/隐藏

这种方式使用MVVM

<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

听起来像是给我的DataTemplateSelector的工作。如果您宁愿将各个控件切换到位,我会做类似于Veer建议的操作。