根据我的测试,如果我仅通过 XAML 设置
DataContext
和 Dependency Property
一次(请参阅下面的示例),则似乎总是先分配 DataContext
,然后再分配 Dependency Property
。
但这总是正确的吗?是否有一个 C# 语言规范说——不,保证——这个?我需要这个,因为我的程序恰恰依赖于这种行为。
这是我可以用来说明这一点的代码:
查看型号
public class MainWindowVM
{
public SimpleTextBoxVM SimpleText => new SimpleTextBoxVM();
}
public class SimpleTextBoxVM
{
}
public class Updater
{
}
用户界面
public class Updater
{
}
public partial class SimpleTextBoxExt : UserControl
{
public SimpleTextBoxExt()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
Console.WriteLine("DataContext Assigned."); //this seems always executed first
}
}
public static readonly DependencyProperty FractionalNumberProperty = DependencyProperty.Register(
nameof(FractionalNumber), typeof(Updater), typeof(SimpleTextBoxExt), new PropertyMetadata(default(Updater), PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is SimpleTextBoxExt simpleTextBox)
{
Console.WriteLine("Dependency property set"); //this seems always executed later
}
}
public Updater FractionalNumber
{
get { return (Updater)GetValue(FractionalNumberProperty); }
set { SetValue(FractionalNumberProperty, value); }
}
}
<Window x:Class="DependencyPropertiesUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DependencyPropertiesUI"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowVM/>
</Window.DataContext>
<local:SimpleTextBoxExt DataContext="{Binding SimpleText}">
<local:SimpleTextBoxExt.FractionalNumber>
<local:Updater/>
</local:SimpleTextBoxExt.FractionalNumber>
</local:SimpleTextBoxExt>
</Window>
一般来说,自定义控件不得依赖于其
DataContext
或其他元素的 DataContext
。这是一种反模式。 DataContext
用于外部绑定或数据模板。 DataContext
应该是预期的,并允许根据控件所在的位置进行外部更改。
如果自定义控件依赖于外部数据,则它必须定义可以将这些数据分配给的依赖属性,例如通过数据绑定。就像任何其他框架控件一样。
自定义控件由客户端通过属性(而不是通过内部视图模型)进行初始化和配置。
例如,
Button
从ICommand
属性获取它执行的Button.Command
,而不是从DataContext
。因为它内部没有定义 DataContext
,所以您可以轻松地将 Button:Command
绑定到您自己的数据上下文。 Button
内部并不知道它的DataContext
。
这导致了下一个反模式:每个控件一个视图模型。现在应该有道理了,控件不需要视图模型,因为它不依赖于任何
DataContext
。
换句话说,如果您避免反模式,您就可以避免很多问题,例如事件之间的竞争条件或类型成员的初始化。依赖这些细节总是会导致代码脆弱。
从设计模式的角度来看,MVVM 是一种描述应用程序结构的架构设计模式。该模式本身不关心数据绑定和单独的控件。数据绑定是有助于实现所需依赖图的技术。
以下示例强调了正确的控制设计。
如您所见,它完全消除了原始版本的竞争条件问题。自定义控件与
DataContext
无关,此外,它还具有高度可重用性。例如,本机 WPF 控件是可重用的,因为它们不依赖于它们的 DataContext
:
SimpleTextBoxExt.xaml.cs
public partial class SimpleTextBoxExt : UserControl
{
// A property to retrieve external data.
// This data can be internally processed and/or displayed by child elements.
// No data context necessary.
public string SomeText
{
get => (string)GetValue(SomeTextProperty);
set => SetValue(SomeTextProperty, value);
}
public static readonly DependencyProperty SomeTextProperty = DependencyProperty.Register(
"SomeText",
typeof(string),
typeof(SimpleTextBoxExt),
new PropertyMetadata(default));
public SimpleTextBoxExt()
{
InitializeComponent();
}
}
SimpleTextBoxExt.xaml
<UserControl x:Name="Root">
<!--
Get external data from the parent's dependency properties that are set
by the client of this control, e.g. via data binding.
-->
<TextBlock Text="{Binding ElementName=Root, Path=SomeText}" />
</UserControl>
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<!--
Feed external data to the custom control or configure the
custom control by using dedicated properties.
-->
<SimpleTextBoxExt SomeText="{Binding ViewModelTextValue}" />
</Window>