使用或不使用(DataContext)



我在DataContext方面遇到了一个难题。让我们检查以下XAML:

<Window xmlns:my="clr-namespace:MyNamespace.Controls"
... >
...
<my:MyControl Name="{Binding Prop1}" Value="{Binding Prop2}" />
</Window>

很明显,Window背后的代码包含这样的内容:

DataContext = someViewModel;

作者的意图很明确——他想将MyControlNameValue绑定到WindowDataContextProp1Prop2。这当然会奏效。除非(戏剧性的停顿)

除非MyControl是一个复合UserControl,它也想利用绑定的简短表示法,并将其DataContext设置为自己的视图模型。因为很明显,Window'sXAML中的绑定实际上绑定到了MyControlDataContext(以前继承自Window的),现在它们将停止工作(或者更糟的是,如果MyControl的视图模型实际上包含名为Prop1Prop21的属性,它们将继续工作)。

在这种特殊情况下,解决方案是在Window的代码中显式绑定:

<Window x:Name="rootControl"
xmlns:my="clr-namespace:MyNamespace.Controls"
... >
...
<my:MyControl Name="{Binding ElementName=rootControl, Path=DataContext.Prop1}" 
Value="{Binding ElementName=rootControl, Path=DataContext.Prop2}" />
</Window>

TL;DR如果我们使用简短的绑定符号(当绑定到DataContext时),我们可能会遇到由于绑定突然指向错误的DataContext而导致的非常难以解决的错误。

我的问题是:如何在没有风险的情况下使用短绑定符号,我会绑定到错误的DataContext?当然,当我确信时,我可能会使用短符号,我将使用继承的DataContext,当我确定,该控件将修改其DataContext时,我可以使用长符号。但">我相信"只会一直工作到第一个错误,这将花费另外一个小时的调试时间。

也许我没有遵循MVVM规则?例如,DataContext应该只在顶层设置一次,并且所有组合控件都应该绑定到其他控件?


1事实上,我已经经历过了。WindowDataContext包含一个名为(比如)Prop的属性,该控件将其DataContext替换为一个类,该类也包含一个属性Prop,一切都正常。当我尝试(无意识地)将相同的模式与不匹配的属性名称一起使用时,问题出现了。


根据请求:

MyControl的代码片段:

public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
// Using a DependencyProperty as the backing store for Name.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(MyControl), new PropertyMetadata(null));
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(MyControl), new PropertyMetadata(0));

窗口的视图模型:

public class WindowViewmodel : INotifyPropertyChanged
{
// (...)
public string Prop1
{
get
{
return prop1;
}
set
{
prop1 = value;
OnPropertyChanged("Prop1");
}
}
public int Prop2
{
get
{
return prop2;
}
set
{
prop2 = value;
OnPropertyChanged("Prop2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

现在假设,在更改NameValue依赖属性时,MyControl生成一些视图模型并执行代码:

model = new MyControlViewModel(Name, Value);
this.DataContext = model;

并且内部MyControl控件绑定到此DataContext。

从现在起,原始的NameValue绑定将不再工作。

除非MyControl是一个复合UserControl,它也想利用绑定的简短表示法,并将其DataContext设置为自己的视图模型。

这就是我停止阅读的地方。这是一个MVVM反模式

原因有两个。首先,你搞砸了任何使用控制的人。"嘿,"你说,"你不能把你的臭VM绑定到我漂亮的UI上。你必须使用我的自定义VM!"但如果你的VM很难使用,缺乏整个应用程序所需的逻辑或功能呢?如果要使用您的UI,我们必须使用您的VM来回转换我们的VM/模型,会发生什么?屁股疼。

其次,您的自定义控件是UI。它的逻辑是UI逻辑,因此没有必要使用视图模型。最好在控件上公开DependencyProperty,并根据需要更新UI。这样,任何人都可以绑定到您的UI,并将其用于任何模型或视图模型。

您可以简单地而不是使用所谓的"复合控件"来解决问题。虽然我理解您希望在关联的视图模型中封装一些功能,但您不需要在内部将视图模型设置为UserControl.DataContext

我的意思是,可以为任何或所有UserControl拥有视图模型,但它们是数据类,而不是UI类

<DataTemplate DataType="{x:Type ViewModels:YourUserControlViewModel}">
<Views:YourUserControl />
</DataTemplate>

最后的区别是,应该将UserControl的视图模型添加为父视图模型中的属性。这样,您仍然没有重复的代码(可能只是一个属性声明),更重要的是,混合DataContext值时没有Binding问题。


更新>>>

当使用这种连接视图和视图模型的DataTemplate方法时,您可以通过Binding将视图显示为ContentControlContent属性,如下所示:

<ContentControl Content="{Binding YourViewModelProperty}" />

在运行时,此ContentControl将呈现为您在该属性的相关类型的DataTemplate中定义的任何视图或UserControl。请注意,您不应该设置DataTemplatex:Key,否则您还需要设置ContentControl.ContentTemplate属性,这可能会限制此方法提供的可能性。

例如,如果不在DataTemplate上设置x:Key属性,则可以有一个基类型的属性,通过将其设置为不同的子类,可以为每个ContentControl提供不同的视图。这是我所有观点的基础。。。像这个例子一样,我有一个基类视图模型数据绑定的属性,要更改视图,我只需将该属性更改为从基类派生的新视图模型。


更新2>>>

事情是这样的。。。你不应该让UserControl中的任何"代理"对象做任何事情。。。它应该所有都通过属性来完成。因此,只需声明该对象类型的DependencyProperty,并通过数据Binding从视图模型中提供它。这样做意味着测试该类的功能将很容易,而测试代码隐藏视图则不然。

最后,是的,在MVVM中这样做非常好:

<Controls:SomeUserControl DataContext="{Binding SomeViewModelProperty}" />

MVVM的首要目标只是在UI代码和视图模型代码之间提供分离,这样我们就可以轻松地测试视图模型中的内容。这就是为什么我们试图从视图中删除尽可能多的功能代码。

在用户控件中,永远不应该将数据上下文设置为"this"或新的视图模型。MyUsercontrol的开发人员/用户希望数据上下文从上到下继承(从主窗口到MyUsercontrol)。

您的usercontrol-xaml应该使用元素绑定

MyUserControl.xaml

<UserControl x:Name="uc">
<TextBlock Text="{Binding ElementName=uc, Path=Name}"/>
<TextBlock Text="{Binding ElementName=uc, Path=Value}"/>

这意味着你的以下代码现在可以在的任何情况下工作

<Window xmlns:my="clr-namespace:MyNamespace.Controls"> 
<my:MyControl Name="{Binding Prop1}" Value="{Binding Prop2}" />
</Window>

Datacontext主窗口中的属性Prop1绑定到MyUsercontrol和Textblock中的DP名称。MyUsercontrol中的文本绑定到DP名称。

我从未遇到过这样的问题。对我来说,这似乎有点理论化,但可能是因为我在WPF中使用DataContext的方法。

  1. 我最小化显式使用DataContext属性。我只为窗口手动设置它
  2. 我有一个专门的方法负责显示新窗口,它是唯一一个显式设置DataContext属性的地方
  3. Windows的DataContext属性设置为根ViewModel,它包含子ViewModels,它包含
  4. 我允许WPF通过使用DataTemplate来选择在给定ViewModel的情况下应该使用哪个视图来显示
  5. 在我的应用程序中,我有一个ResourceDictionary,它包含所有ViewModels和Views之间的映射

最新更新