使用(DataContext)或不使用

时间:2014-01-13 14:15:04

标签: c# wpf xaml mvvm binding

我对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}} DataContextProp1。这当然会奏效。除非。 (戏剧性停顿)

除非Prop2是复合MyControl,它还希望利用绑定的简短表示法并将其UserControl设置为自己的viewmodel。因为那时很明显,DataContext XAML中的绑定实际上绑定到Window's的{​​{1}}(之前继承自MyControl的绑定),现在他们将停止工作(或者更糟糕的是,如果DataContext的viewmodel实际上包含名为WindowMyControl 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

现在假设,在更改DataContextProp依赖项属性时, 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。

从现在开始,原始ValueMyControl绑定将不再有效。

4 个答案:

答案 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的方法。

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

答案 3 :(得分:1)

在用户控件中,您不应该将datacontext设置为“this”或新的viewmodel。您的MyUsercontrol的开发人员/用户期望datacontext从上到下继承(从主窗口到您的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中的DP名称,并且MyUsercontrol中的Textblock.Text绑定到DP名称。