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

发布于 2024-09-05 20:50:29 字数 912 浏览 1 评论 0原文

这只是一个要讨论的问题 - 在 WPF 中制作视图/编辑控件的最佳方法是什么?例如,我们有一个实体对象 Person,它有一些属性(姓名、地址、电话等)。控件的一种表示形式是只读视图。另一个人将拥有同一个人的编辑视图。示例:

<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

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

this is just a question to discuss - what is the best way to make a view/edit control in WPF? E.g. we have an entity object Person, that has some props (name, surname, address, phone etc.). One presentation of the control would be a read-only view. And the other would have the edit view for this same person. Example:

<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>

I hope that the idea is clear. The two options I see right now

  1. two grids with visibility switching and
  2. a TabControl without its header panel

This is just a discussion question - not much trouble with it, yet I am just wondering if there are any other possibilities and elegant solutions to this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

故乡的云 2024-09-12 20:50:30
<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>

如果您熟悉的话,请使用命令。

<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>

Use a command rather, if you're familiar with.

凉世弥音 2024-09-12 20:50:30

我将创建一个具有 2 个不同配置选项的单个视图,以及 2 个不同的构造函数,以使相关字段可编辑/只读或可见/隐藏

这样,您就不会编写冗余的 XAML,并且在使用时可以通过代码隐藏或 ViewModel 配置所有字段MVVM

I would create a single View with 2 different configoptions , f.e. 2 different constructors to make the the relevant field editable/readonly or visible/hidden

This way you don't write redundant XAML and you can configurate all fields over code behind or ViewModel when using MVVM

丶情人眼里出诗心の 2024-09-12 20:50:30

听起来像是 DataTemplateSelector 的工作我。如果你愿意切换各个控件,我会做类似于 Veer 建议的事情。

Sounds like a job for a DataTemplateSelector to me. If you would rather switch the individual controls in place I would do something similar to what Veer suggested.

贵在坚持 2024-09-12 20:50:29

自动锁定类

我编写了一个“AutomaticLock”类,它具有继承的附加“DoLock”属性。

将“DoLock”属性设置为 true 会将所有文本框、组合框、复选框等重新模板化为文本块、不可编辑的复选框等。我的代码经过设置,以便其他附加属性可以指定在锁定(“视图”)模式下使用的任意模板、永远不应自动锁定的控件等。

因此,可以轻松地将同一视图用于编辑和查看。设置单个属性会来回更改它,并且它是完全可自定义的,因为视图中的任何控件都可以触发“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 标准控件的默认模板在包含 AutoLock 类的程序集中,ResourceDictionary 中合并到 Themes/Generic.xaml 中。例如,此模板会导致所有 TextBox 在锁定时变成 TextBlock:

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

自定义控件的默认模板可以在包含自定义控件的程序集中定义,该程序集位于合并到其 Themes/Generic.xaml 中的 ResourceDictionary 中。在这种情况下 ComponentResourceKey 是不同的,例如:

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

如果应用程序想要覆盖特定类型的标准 AutomaticLock 模板,它可以将自动锁定模板放置在其 App.xaml、Window XAML、UserControl XAML 中,或者在 ResourceDictionary 中单独的控制。在每种情况下,应以与自定义控件相同的方式指定 ComponentResourceKey:

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

最后,可以通过设置其 AutomaticLock.LockTemplate 属性将自动锁定模板应用于单个控件。

如何在用户界面中使用AutomaticLock

要使用自动锁定:

  1. 在任何应自动锁定的控件上设置AutomaticLock.Enabled="True"。这可以在样式中完成,也可以直接在单独的控件上完成。它允许锁定控件,但不会导致控件实际锁定。
  2. 当您想要锁定时,只要您希望自动锁定实际发生,请在顶级控件(窗口、视图、用户控件等)上设置AutomaticLock.DoLock =“True”。您可以将AutomaticLock.DoLock绑定到复选框或菜单项,也可以在代码中控制它。

有关在查看和编辑模式之间有效切换的一些技巧

此AutomaticLock 类非常适合在查看和编辑模式之间切换,即使它们有很大不同。我有几种不同的技术来构建视图,以适应编辑时的布局差异。其中一些是:

  1. 通过将控件的Template 或AutomaticLockTemplate 设置为空模板(视情况而定),使控件在编辑或查看模式下不可见。例如,假设“年龄”在查看模式下位于布局的顶部,在编辑模式下位于布局的底部。在两个地方添加“年龄”文本框。在顶部将模板设置为空模板,这样它就不会在编辑模式下显示。在底部将AutomaticLockTemplate 设置为空模板。现在一次只有一个可见。

  2. 使用 ContentControl 来替换内容周围的边框、布局面板、按钮等,而不影响内容。 ContentControl 的模板具有用于编辑模式的周围边框、面板、按钮等。它还具有具有视图模式版本的AutomaticLockTemplate。

  3. 使用控件替换视图的矩形部分。 (我实际上指的是“Control”类的对象,而不是其子类。)同样,您将编辑模式版本放入模板中,将查看模式版本放入 AutomaticLockTemplate 中。

  4. 使用具有额外自动调整大小的行和列的网格。使用 AutomaticLock.DoLock 属性上的触发器来更新网格内项目的 Row、Column、RowSpan 和 ColumnSpan 属性。例如,您可以通过将 Grid.Row 从 6 更改为 0,将包含“年龄”控件的面板移动到顶部。

  5. 触发 DoLock 以将 LayoutTranform 或 RenderTransform 应用于您的项目,或设置其他属性(如宽度)和高度。如果您希望编辑模式下的内容更大,或者您想要使 TextBox 更宽并将其旁边的按钮移到边缘,这非常有用。

请注意,您可以对整个视图使用选项#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 会给您提供那个力量也是如此。

Automatic Lock class

I wrote an "AutomaticLock" class that has an inherited attached "DoLock" property.

Setting the "DoLock" property to true re-templates all TextBoxes ComboBoxes, CheckBoxes, etc to be TextBlocks, non-editable CheckBoxes,etc. My code is set up so that other attached property can specify arbitrary template to use in locked ("view") mode, controls that should never automatically lock, etc.

Thus the same view can easily be used for both editing and viewing. Setting a single property changes it back and forth, and it is completely customizable because any control in the view can trigger on the "DoLock" property to change its appearance or behavior in arbitrary ways.

Implementation code

Here is the code:

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;
  }
}

This code will allow you to specify an automatic lock template on a control-by-control basis or it will allow you to use default templates defined either in the assembly containing the AutomaticLock class, in the assembly containing your custom control that the lock template applies to, in your local resources in your visual tree (including your application resources)

How to define AutomaticLock templates

Default templates for WPF standard controls are defined in the assembly containing the AutomaticLock class in a ResourceDictionary merged into Themes/Generic.xaml. For example, this template causes all TextBoxes to turn into TextBlocks when locked:

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

Default templates for custom controls may be defined in the assembly containing the custom control in a ResourceDictionary mered into its Themes/Generic.xaml. The ComponentResourceKey is different in this case, for example:

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

If an application wants to override the standard AutomaticLock template for a specific type, it can place an automatic lock template in its App.xaml, Window XAML, UserControl XAML, or in the ResourceDictionary of an individual control. In each case the ComponentResourceKey should be specified the same way as for custom controls:

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

Lastly, an automatic lock template can be applied to a single control by setting its AutomaticLock.LockTemplate property.

How to use AutomaticLock in your UI

To use automatic locking:

  1. Set AutomaticLock.Enabled="True" on any controls that should be automatically locked. This can be done in a style or directly on individual controls. It enables locking on the control but does not cause the control to actually lock.
  2. When you want to lock, set AutomaticLock.DoLock="True" on your top-level control (Window, view, UserControl, etc) whenever you want the automatic locking to actually happen. You can bind AutomaticLock.DoLock to a checkbox or menu item, or you can control it in code.

Some tips on effectively switching between view and edit modes

This AutomaticLock class is great for switching betwen view and edit modes even if they are significantly different. I have several different techniques for constructing my views to accomodate layout differences while editing. Some of them are:

  1. Make controls invisible during edit or view mode by setting either their Template or AutomaticLockTemplate to an empty template as the case may be. For example, suppose "Age" is at the top of your layout in view mode and at the bottom in edit mode. Add a TextBox for "Age" in both places. In the top one set Template to the empty template so it doesn't show in Edit mode. In the bottom one set AutomaticLockTemplate to the empty template. Now only one will be visible at a time.

  2. Use a ContentControl to replace borders, layout panels, buttons, etc surrounding content without affecting the content. The ContentControl's Template has the surrounding borders, panels, buttons, etc for edit mode. It also has an AutomaticLockTemplate that has the view mode version.

  3. Use a Control to replace a rectangular section of your view. (By this I actually mean an object of class "Control", not a subclass therof.) Again, you put your edit mode version in the Template and your view mode version in the AutomaticLockTemplate.

  4. Use a Grid with extra Auto-sized rows and columns. Use a trigger on the AutomaticLock.DoLock property to update the Row, Column, RowSpan, and ColumnSpan properties of the items within the Grid. For example you could move a panel containing an "Age" control to the top by changing its Grid.Row from 6 to 0.

  5. Trigger on DoLock to apply a LayoutTranform or RenderTransform to your items, or to set other properties like Width and Height. This is useful if you want things to be bigger in edit mode, or if you want to make a TextBox wider and move the button beside it over against the edge.

Note that you can use option #3 (a Control object with separate templates for edit and view modes) for the entire view. This would be done if the edit and view modes were completely different. In this case AutomaticLock still gives you the convenience of being able to set the two templates manually. It would look like this:

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

Generally it is easier to tweak a few little positions and things between the edit and view modes, and better for your user experience because the user will have consistent layout, but if you do need a complete replacement AutomaticLock gives you that power as well.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文