通过 MVVM 模式创建 UserControl - DataContext 并绑定到父级

Posted

技术标签:

【中文标题】通过 MVVM 模式创建 UserControl - DataContext 并绑定到父级【英文标题】:Creating UserControl via MVVM pattern - DataContext and binding to parent 【发布时间】:2012-10-21 00:34:45 【问题描述】:

我使用 MVVM 模式,所以我自己的控件包含 View 和 ViewModel。 ViewModel 通过 DataContext 属性与 View 连接。这会导致绑定问题。为什么?

假设这种情况: 我创建了新的用户控件——例如——“SuperTextBox”。它有一个属性“SuperValue”。现在我做了这样的事情:

<Window>
    <Window.DataContext>
        <vm:WindowViewModel/>
    </Window.DataContext>

    <local:SuperTextBox SuperValue="Binding Test"/>
</Window>

我认为“绑定过程”将 SuperTextBox.SuperValue 与 Window.DataContext.Test 结合起来,但不是,“绑定过程”将 SuperTextBox.SuperValue 与 SuperTextBox.DataContext.Test 结合起来,这对我来说是不自然和误导的。

其他控件,如“TextBox”,我可以通过上述方式使用,因为它们没有 DataContext。

如何使用 MVVM 模式来创建保持自然绑定的用户控件(到父控件的 DataContext)?

编辑:

我得到了很多关于如何绑定到父级的答案,但我很早就知道这一点。问题是 - 我如何通过 MVVM 模式(具有 ViewModel)创建 UserControl 并保持自然绑定 - 默认为父 DataContext。

我想要 ViewMoldel 并且仍然可以像这样绑定:

<local:SuperTextBox SuperValue="Binding Test"/>

有可能吗?

【问题讨论】:

当您创建一个具有依赖属性的真实用户控件时,就像我发布的那样。但我会编辑我的答案以显示另一种“不自然”的方式:) 【参考方案1】:

应用于任何control always first look for the binding in its DataContext 的所有绑定。如果没有为控件设置 DataContext,则它walks up the Visual Tree 直到它的父级,除非它找到 DataContext。

即使您将 textBox 上的 DataContext 设置为与 Window 的 DatContext 不同的某个值,它也会始终在该特定 DataContext 上搜索属性 Test 而不是在您的 Window's DataContext 上。

<TextBox>
   <TextBox.DataContext>
      <vm:ViewModelForTextBox/>
   </TextBox.DataContext>
   <TextBox.Text>
      <Binding Path="Test"/>
   </TextBox.Text>
</TextBox>

现在,xaml 将在 ViewModelForTextBox 类中查找 Test 属性,而不是在 WindowViewModel 类中查找,如果在 ViewModelForTextBoxbinding will fail silently 类中找不到 Test 属性,并且不会查找Window 的 DataContext 类。

如果您仍想为自定义 UserControl 设置 DataContext 但仍想绑定到父级(窗口)的 dataContext,您必须像这样在绑定中使用 RelativeSource MarkupExtension -

<local:SuperTextBox SuperValue="Binding Path=DataContext.Test,
                                RelativeSource=RelativeSource Mode=FindAncestor,
                                                 AncestorType=x:Type Window">

有关更多说明,请参阅 MSDN 文章 here。

【讨论】:

【参考方案2】:

你应该发布你的 SuperTextBox 代码,因为有你的错误。

通常你创建一个具有依赖属性的用户控件 - 在你的例子中是“SuperValue” - 现在最重要的是你没有将你的 SuperTextBox 的数据上下文设置为它自己。

你必须在你的 SuperTextBox 中使用 elementname 绑定来绑定到“SuperValue”

 <SuperTextBox x:Name="uc">
   <TextBox Text="Binding ElementName=uc, Path=SuperValue/>
 </superTextBox>

如果你这样做 - 你的

 <local:SuperTextBox SuperValue="Binding Test"/>

应该可以工作并且应该绑定到您的 vm:WindowViewModel 的测试属性。这是编写上述绑定的唯一方法。

编辑:如果你想为你的用户控件创建一个视图模型,可以说 SuperTextViewmodel。那么它将有一个属性“SuperValue”。现在你不能设置 datacontext 两次所以我建议你必须添加一个属性到你的 SuperTextViewmodel 类型的 WindowViewmodel 并按照你想要的方式处理属性。

你的绑定看起来像这样

 <local:SuperTextBox DataContext="Binding MySuperTextViewmodelInstanceOnWindowViewmodel"/>

我会回答我的第一部分:) 我总是说视图需要一个视图模型但一个用户控件依赖属性。

【讨论】:

感谢您的回答,但是当我使用 MVVM 模式创建 UserControl 时,我必须拥有(或者我想要拥有)ViewModel。因此,要将 ViewModel 注入 View,我必须使用 DataContext,这会使绑定变得不自然。我的问题是如何避免这种情况 - 我可以以其他方式加入 ViewModel(不会破坏绑定)吗?【参考方案3】:

您需要“查找”它的窗口祖先的数据上下文。您的绑定将如下所示:

<local:SuperTextBox SuperValue="Binding Path=DataContext.Test, RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type Window">

【讨论】:

谢谢,但我知道。 :) 我需要有关通过 MVVM 模式创建 UserControl 的“模式和实践”的信息。我可以使用 MVVM 并且仍然像在“TextBox”控件中那样正常/自然地出价吗? 这没有什么不自然的。您必须对具有来自其 ItemsSource 的单个项目的数据上下文的任何控件元素执行相同的操作。例如,如果您想将 ListViewItem 的可见性绑定到 viewmodel 中的值,但 ListView 设置了 ItemsSource,那么您必须使用这种绑定方法。【参考方案4】:

我觉得回答我的问题很奇怪,但是... 在我自己的控制下,我做了类似的事情:

<UserControl>
    <Grid>
        <Grid.DataContext>
             <vm:UserControlViewModel />
        </Grid.DataContext>
        // here realy code of control
    </Grid>
</UserControl>

现在我可以不受控制地使用“自然”绑定。 :)

【讨论】:

以上是关于通过 MVVM 模式创建 UserControl - DataContext 并绑定到父级的主要内容,如果未能解决你的问题,请参考以下文章

如何从作为wpf mvvm模式中的窗口打开的视图模型中关闭用户控件?

使用该控件时的 MVVM UserControl 和绑定

关于在MVVM架构下WPF中UserControl的 visibility Binding问题。 UserControl MVVM

WPF MVVM 通过事件将 UserControl 从一个 ObservableCollection 移动到另一个

在 UserControl WPF MVVM caliburn 内的 UserControl 之间切换

如何在 MVVM 中的 UserControl 之间进行通信 - WPF 应用程序