这只是一个讨论的问题-在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>
Run Code Online (Sandbox Code Playgroud)
我希望这个想法很明确。我现在看到的两个选项
这只是一个讨论问题,没有太多麻烦,但我只是想知道是否还有其他可能性和解决方案。
自动锁类
我编写了一个“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;
}
}
Run Code Online (Sandbox Code Playgroud)
此代码将允许您在逐个控件的基础上指定自动锁定模板,或者允许您使用在包含 AutoLock 类的程序集中、在包含锁定模板应用的自定义控件的程序集中定义的默认模板到您的可视化树中的本地资源(包括您的应用程序资源)
如何定义AutomaticLock模板
WPF 标准控件的默认模板在包含合并到 Themes/Generic.xaml 的 ResourceDictionary 中的AutomaticLock 类的程序集中定义。例如,此模板会导致所有 TextBox 在锁定时变成 TextBlock:
<ControlTemplate TargetType="{x:Type TextBox}"
x:Key="{ComponentResourceKey ResourceId=TextBox, TypeInTargetAssembly={x:Type lc:AutomaticLock}}">
<TextBlock Text="{TemplateBinding Text}" />
</ControlTemplate>
Run Code Online (Sandbox Code Playgroud)
自定义控件的默认模板可以在包含自定义控件的程序集中定义,该程序集中的 ResourceDictionary 合并到其 Themes/Generic.xaml 中。在这种情况下,ComponentResourceKey 有所不同,例如:
<ControlTemplate TargetType="{x:Type prefix:MyType}"
x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}">
...
Run Code Online (Sandbox Code Playgroud)
如果应用程序想要重写特定类型的标准AutomaticLock 模板,它可以将自动锁定模板放置在其App.xaml、Window XAML、UserControl XAML 中或单个控件的ResourceDictionary 中。在每种情况下,ComponentResourceKey 的指定方式都应与自定义控件相同:
x:Key="{ComponentResourceKey ResourceId=AutomaticLockTemplate, TypeInTargetAssembly={x:Type prefix:MyType}}"
Run Code Online (Sandbox Code Playgroud)
最后,可以通过设置其属性将自动锁定模板应用于单个控件AutomaticLock.LockTemplate。
如何在 UI 中使用 AutomaticLock
要使用自动锁定:
有关在查看和编辑模式之间有效切换的一些技巧
这个AutomaticLock类非常适合在视图和编辑模式之间切换,即使它们有很大不同。我有几种不同的技术来构建视图,以适应编辑时的布局差异。他们之中有一些是:
通过将控件的Template 或AutomaticLockTemplate 设置为空模板(视情况而定),使控件在编辑或查看模式期间不可见。例如,假设“年龄”在查看模式下位于布局的顶部,在编辑模式下位于布局的底部。在两个地方添加“年龄”文本框。在顶部将模板设置为空模板,这样它就不会在编辑模式下显示。在底部将AutomaticLockTemplate 设置为空模板。现在一次只能看到一个。
使用 ContentControl 替换边框、布局面板、按钮等周围内容,而不影响内容。ContentControl 的模板具有用于编辑模式的周围边框、面板、按钮等。它还具有具有视图模式版本的AutomaticLockTemplate。
使用控件替换视图的矩形部分。(我实际上指的是“Control”类的对象,而不是其子类。)同样,您将编辑模式版本放入模板中,将查看模式版本放入 AutomaticLockTemplate 中。
使用具有额外自动调整大小的行和列的网格。使用 AutomaticLock.DoLock 属性上的触发器来更新网格内项目的 Row、Column、RowSpan 和 ColumnSpan 属性。例如,您可以通过将 Grid.Row 从 6 更改为 0,将包含“Age”控件的面板移至顶部。
触发 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>
Run Code Online (Sandbox Code Playgroud)
一般来说,在编辑和查看模式之间调整一些小位置和内容会更容易,并且对您的用户体验更好,因为用户将具有一致的布局,但如果您确实需要完全替换,AutomaticLock 也可以为您提供这种功能。