用户控件的数据上下文

53

我正在创建一个UserControl,我想使用类似于这样的东西:

<controls:ColorWithText Color="Red" Text="Red color" />

到目前为止,我已经实现了类似这样的控件:

<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
    <StackPanel Orientation="Horizontal" >
        <Border Width="15" Height="15" Background="{Binding Color, ElementName=ThisControl}" />
        <TextBlock Text="{Binding Text, ElementName=ThisControl}" />
    </StackPanel>
</UserControl>

其中ColorText是在代码中定义的控件的依赖属性。这样做是可以的,但每次都指定ElementName似乎有些不必要。

另一种可行的选择是使用

<UserControl x:Class=… DataContext="{Binding ElementName=ThisControl}" Name="ThisControl">

我也尝试过不指定ElementName,但那对我来说似乎也不是一个干净的解决方法。

我的两个问题是:

  1. 为什么<UserControl DataContext="{RelativeSource Self}">不起作用?
  2. 最好的做法是什么?
6个回答

66

对于第一个问题,请尝试:

<UserControl DataContext="{Binding RelativeSource={RelativeSource Self}}">

对于第二个问题,我认为使用ElementNameAncestorBinding是绑定到UserControl属性的最佳方式。


问题。如果您像这样设置RelativeSource,它如何知道此控件的VM是什么?您如何设置它? - cd491415
13
我知道这是一篇旧帖子,但对于其他人来说......你不需要为单个控件设置虚拟机。您在控件上设置属性,这些属性应足以使其“工作”。如果控件依赖于某些虚拟机或与之紧密耦合/依赖于放置到特定上下文中才能工作,则它不是一个“控件”。您违反了关注点分离原则。 - Newclique
5
据我所知,这会破坏任何带有DependencyPropertyUserControl。请参见https://nikolalukovic.com/programming/WPF-Custom-UserControl-datacontext-binding-gotcha.html和/或@jdawiz的回答。 - Chris
一个UserControl不应该明确设置自己的DataContext。这样做会破坏标准的绑定机制,该机制使用继承的DataContext中的视图模型实例作为控件属性绑定的源对象。这已经给新手WPF用户带来了大量麻烦。 - undefined

37

为什么不能使用<UserControl DataContext="{RelativeSource Self}">

这是您如何使用该控件。

<Grid DataContext="{StaticResource ViewModel}">
    <!-- Here we'd expect this control to be bound to -->
    <!-- ColorToUse on our ViewModel resource          -->
    <controls:ColorWithText Color="{Binding ColorToUse}" />
</Grid>

现在,因为我们在控件中硬编码了数据上下文,它将尝试在ColorWithText对象而不是您的ViewModel上查找ColorToUse属性,这显然会失败。

这就是为什么您无法设置用户控件上的DataContext。感谢Brandur让我明白了这一点。

如何最好地实现这样的功能?

相反,您应该在控件中的第一个子UI元素中设置DataContext。

在您的情况下,您需要

<StackPanel 
  DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
  Orientation="Horizontal" >
现在你拥有一个DataContext,它引用了你的控件,因此你可以使用相对绑定来访问该控件的任何属性。

这绝对是最好的解决方案!它保留了控件绑定,并且不需要任何特定的元素命名。 - Pedro Henrique

35

我知道这个问题已经有答案了,但是没有一个解释能够理解DataContext及其工作原理。这个链接做得很好。

WPF、Silverlight和WP7中数据绑定的所有你想要知道的东西(第二部分)

回答你的第一个问题:

为什么 <UserControl DataContext="{RelativeSource Self}"> 不起作用?

这是上面链接的摘要。 DataContext不能在UserControl元素层级上设置为Self。这是因为它会打破DataContext的继承关系。如果你确实将它设置为self,并将此控件放置在窗口或其他控件上,则不会继承窗口的DataContext

DataContext会被继承到XAML的所有下一级元素以及所有UserControl的XAML,除非在某个地方被覆盖。通过将UserControlDataContext设置为自身,这将覆盖DataContext并破坏继承关系。相反,在XAML中将其嵌套到一个元素深度(在您的情况下是StackPanel)中。在这里放置DataContext绑定并将其绑定到UserControl。这样可以保留继承关系。

此外,还可参考下面的链接以获得更详细的说明。

WPF / Silverlight中创建可重用UserControls的简单模式

回答你的第二个问题:
最佳方法是什么?

请参见下面的代码示例。

<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
    <StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=ThisControl}">
        <Border Width="15" Height="15" Background="{Binding Color" />
        <TextBlock Text="{Binding Text}" />
    </StackPanel>
</UserControl>
请注意,一旦进行此操作,您将不需要每个绑定上的ElementName

唯一优雅的解决方案,可以保留UserControl的外部绑定。谢谢。 - net_prog
好的解释! - yonexbat
这是迄今为止最有帮助的答案,因为它不会破坏数据上下文继承。 - Max Young

13

1
我每次都要写这个吗?我不想绑定到控件的其他部分,而且我认为重复代码是不好的。 - svick

9
你可以在构造函数中将数据上下文设置为self。
public ColorWithText()
{
 InitializeComponent();
 DataContext = this;
}

现在,您可以直接说
<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
    <StackPanel Orientation="Horizontal" >
        <Border Width="15" Height="15" Background="{Binding Color}" />
        <TextBlock Text="{Binding Text}" />
    </StackPanel>
</UserControl>

2
在窗口加载事件或者Loaded事件中添加以下代码:this.Loaded += (sender, e) => { this.DataContext = this; }; - Tarek Khalil
那非常简单而优雅。我喜欢它。 - rollsch

0

对于那些正在努力让pdross的答案起作用但却无法实现的绝望灵魂:

它缺少一个重要的细节 - Path = DataContext。 当您将其添加到下面的代码段中时,它开始工作,并产生以下结果:

    <StackPanel DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext}">

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接