我对DataContext感到困惑。让我们检查下面的XAML:
<Window xmlns:my="clr-namespace:MyNamespace.Controls"
... >
...
<my:MyControl Name="{Binding Prop1}" Value="{Binding Prop2}" />
</Window>
显然,Window的代码隐藏包含类似的内容:
DataContext = someViewModel;
作者的意图很清楚 - 他希望将MyControl
的{{1}}和Name
绑定到Value
Window
的{{1}} DataContext
和Prop1
。这当然会奏效。除非。 (戏剧性停顿)
除非Prop2
是复合MyControl
,它还希望利用绑定的简短表示法并将其UserControl
设置为自己的viewmodel。因为那时很明显,DataContext
XAML中的绑定实际上绑定到Window's
的{{1}}(之前继承自MyControl
的绑定),现在他们将停止工作(或者更糟糕的是,如果DataContext
的viewmodel实际上包含名为Window
和MyControl
1 的属性,则会继续工作。
在这种特殊情况下,解决方案是明确地绑定Prop1
的代码:
Prop2
TL; DR 如果我们使用绑定的简短表示法(当绑定到Window
时),我们可能会遇到很难修复由于绑定突然指向错误{{1 }}
我的问题是:如何使用没有风险的短绑定表示法,我将绑定到错误的<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>
?当然,当我确定时,我可以使用简短的符号,当我确定时,我将使用继承的DataContext
和长符号,控件将修改其DataContext
。但是“我确定”只会在第一次出错之前工作,这将耗费一小时的调试时间。
也许我没有遵循一些MVVM规则?例如。例如DataContext
应该只在顶层设置一次,所有合成控件应该绑定到其他东西?
<小时/> 1 我实际上经历过那个。
DataContext
的{{1}}包含一个名为(例如)DataContext
的属性,控件将其DataContext
替换为一个类,该类还包含一个属性Window
和一切正常。当我试图(无意识地)使用具有不匹配属性名称的相同模式时出现问题。
按要求:
MyControl代码片段:
DataContext
Window的viewmodel:
Prop
现在假设,在更改DataContext
和Prop
依赖项属性时, 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));
会生成一些viewmodel并执行代码:
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;
}
内部Name
控件绑定到此DataContext。
从现在开始,原始Value
和MyControl
绑定将不再有效。
答案 0 :(得分:3)
除非MyControl是一个复合UserControl,它还希望利用绑定的简短表示法并将其DataContext设置为自己的viewmodel。
这就是我停止阅读的地方。这就是imho,MVVM 反模式 。
原因是双重的。首先,你搞砸了使用控件的任何人。 “嘿,”你说,“你不能将臭臭的虚拟机绑定到我漂亮的用户界面。你必须使用我的自定义虚拟机!”但是,如果您的VM难以使用,缺少整个应用程序所需的逻辑或功能,该怎么办?如果要使用您的UI,我们必须使用您的VM来回转换我们的VM /模型,会发生什么?屁股疼痛。
其次,您的自定义控件是UI。它的逻辑是UI逻辑,因此不必使用视图模型。最好在控件上公开DependencyProperties并根据需要更新UI。这样任何人都可以绑定到您的UI并将其用于任何模型或视图模型。
答案 1 :(得分:2)
您可以使用您称之为“复合控件”的而不是来解决您的问题。虽然我知道您希望在关联的视图模型中封装某些功能,但您无需在内部将视图模型设置为UserControl.DataContext
。
我的意思是你可以拥有任何或所有UserControl
的视图模型,但它们是数据类,不是 UI类,所以请将它们排除在视图代码之外。如果您使用这种方法将DataTemplate
添加到Resources
,那么您根本不需要设置任何DataContext
属性:
<DataTemplate DataType="{x:Type ViewModels:YourUserControlViewModel}">
<Views:YourUserControl />
</DataTemplate>
最后的区别是,您应该将UserControl
的视图模型添加为父视图模型中的属性。这样,您仍然没有重复的代码(可能只是一个属性声明),更重要的是,混合Binding
值时没有DataContext
个问题。
更新&gt;&gt;&gt;
使用此DataTemplate
方法连接视图和视图模型时,您可以通过Binding
视图模型属性将视图显示为Content
的{{1}}属性像这样:
ContentControl
在运行时,此<ContentControl Content="{Binding YourViewModelProperty}" />
将呈现为您在该属性的相关类型的ContentControl
中定义的任何视图或UserControl
。请注意,您不应设置DataTemplate
的{{1}},否则您还需要设置x:Key
属性,这可能会限制此方法提供的可能性。
例如,没有在DataTemplate
上设置ContentControl.ContentTemplate
属性,您可以拥有基本类型的属性,并通过将其设置为不同的子类,每个x:Key
可以有不同的视图。这是我所有视图的基础......我有一个像这个例子绑定的基类视图模型数据的属性,并且更改视图,我只是将属性更改为从基类派生的新视图模型。
更新2&gt;&gt;&gt;
这就是......你不应该在DataTemplate
做任何事情的任何'代理'对象......它应该所有通过属性来完成。因此,只需声明该对象类型的ContentControl
,并通过数据UserControl
从视图模型中提供该对象。这样做意味着可以很容易地测试该类的功能,而不是在视图后面测试代码。
最后,是的,在MVVM中完成此操作非常好:
DependencyProperty
MVVM的首要目标只是提供UI代码和视图模型代码之间的分离,以便我们可以轻松地测试视图模型中的内容。这就是我们尝试从视图中删除尽可能多的功能代码的原因。
答案 2 :(得分:1)
我从未遇到过这样的问题。这似乎对我来说有点理论,但也许是因为我在WPF中使用DataContext的方法。
DataContext
属性。我只为手动设置它。DataContext
属性的地方。DataContext
属性设置为根ViewModel,其中包含子ViewModels,其中包含... DataTemplate
ResourceDictionary
,其中包含所有ViewModel和Views之间的映射。答案 3 :(得分:1)
你的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中的DP名称,并且MyUsercontrol中的Textblock.Text绑定到DP名称。