绑定到UserControl DependencyProperty

23

我创建了一个带有一些依赖属性的UserControl(在此示例中仅有一个字符串属性)。当我实例化Usercontrol时,我可以设置UserControl的属性,并且它会按预期显示。但是当我尝试用绑定替换静态文本时,没有显示任何内容。

我的UserControl如下所示:

<User Control x:Class="TestUserControBinding.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="100">
    <Grid>
    <Label Content="{Binding MyText}"/>
  </Grid>
</UserControl>

背后的代码是:

namespace TestUserControBinding {

  public partial class MyUserControl : UserControl {
    public MyUserControl() {
      InitializeComponent();
      this.DataContext = this;
    }

    public static readonly DependencyProperty MyTextProperty = 
                   DependencyProperty.Register(
                         "MyText", 
                          typeof(string), 
                          typeof(MyUserControl));

    public string MyText {
      get {
        return (string)GetValue(MyTextProperty);
      }
      set {
        SetValue(MyTextProperty, value);
      }
    }// MyText
    
  }
}

当我在我的MainWindow中尝试这样做时,一切都符合预期:

<Window x:Class="TestUserControBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestUserControBinding"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <local:MyUserControl MyText="Hello World!"/>
  </StackPanel>
</Window>

但这样行不通:

<Window x:Class="TestUserControBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestUserControBinding"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <local:MyUserControl MyText="{Binding Path=Text}"/>
    <Label Content="{Binding Path=Text}"/>
  </StackPanel>
</Window>

我犯了什么错误?

2个回答

33
在你的UserControl中使用以下绑定:
<Label Content="{Binding MyText}"/>

我不确定直接将文本设置为 MyText 属性的工作方式。您必须在某个位置设置 UserControl 上的 DataContext 才能使其正常工作。

无论如何,这个绑定是问题所在——根据我对您情况的理解,您不想绑定到 UserControlDataContext,因为那里可能没有 MyText 属性。您想要绑定到 UserControl 本身,具体来说就是您创建的 DependencyProperty。为此,您需要使用一个 RelativeSource 绑定,如下所示:

<Label Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}}, Path=MyText}"/>
这将沿着可视树向上导航到MyUserControl,然后在那里找到MyText属性。它不会依赖于DataContext,后者会根据您放置UserControl的位置而改变。
在这种情况下,local是指您需要在UserControl中定义的命名空间:
<UserControl x:Class="TestUserControBinding.MyUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:TestUserControBinding"
         ...>

你的第二个例子应该在那时能够工作。


1
@Brian... 代码"this.DataContext = this;"应该负责将DataContext设置为本地的。不是吗? - Nishant
7
好的,我会尽力将内容翻译为通俗易懂的中文,但不会改变原意。以下是需要翻译的内容: 哦,是的,我漏掉了那个。但是如果你正在创建一个“UserControl”,我认为手动设置“DataContext”并不是一个好主意。 “DataContext”旨在表示从容器继承或分配的上下文。 “RelativeSource”绑定允许您实现所需的结果(绑定到“DependencyProperty”),同时不会中断标准的“DataContext”继承流程。如果尝试像你一样覆盖“DataContext”,则会失败,如果使用你的“UserControl”的消费者设置了他们自己的“DataContext”。 - Brian S
5
那正是问题所在。我现在使用x:Name="MyName"替代将 MyUserControl 的 DataContext 设置为本身,然后将绑定更改为:<Label Content="{Binding ElementName=MyName, Path=MyText}"/>。我认为你的解决方案也可行,但有点笨重。 - Lightbringer
是的,使用“ElementName”绑定可以达到相同的结果,并且更简洁,特别是如果您确信其他人不会修改您的代码并错误地更改/删除名称。 - Brian S
1
这个答案与问题完全无关。我不知道为什么它得到这么多赞和被接受了。 - Özgür
1
我一直在使用这个解决方案,但是因为ΩmegaMan的解决方案更清晰,所以我切换了。 - Kaloyan Manev

9

关于如何设置DataContext存在误解,这会对你造成影响...

最终,对于用户控件上的MyText属性进行的绑定并没有与控件的MyText依赖属性绑定,而是与页面的DataContext绑定,因此并不存在MyText属性。

让我来解释一下:


解释 当将用户控件放在主页上时,它会继承其控件父级的DataContextStackPanel)。如果父级DataContext未设置,则会向上查找到StackPanel的父级DataContext(无限循环),直到找到页面的DataContext(在你的示例中已设置且有效)。

当在主页上进行绑定,例如<local:MyUserControl MyText="{Binding Path=Text}"/>时,它会在主页的DataContext上查找Text属性,并将依赖属性MyText设置为该值。这正是你所期望的,并且它能够工作!

当前状态 因此,在你的代码中,用户控件的状态是这样的:它的DataContext绑定到了页面的DataContext,并且MyText依赖属性已设置。但是内部控件对MyText属性的绑定失败了。为什么?

用户控件具有其父级数据上下文,并且你要求控件绑定到数据上下文的MyText属性。但是,不存在这样的属性,因此绑定失败。


解决方法

要绑定到控件的实例并从MyText属性获取值,只需在控件上加入一个名称(元素名称)即可:

<User Control x:Class="TestUserControBinding.MyUserControl"
             ...
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             x:Name="ucMyUserControl"

然后将绑定正确地从默认的DataContext路径到名为ucMyUserControlelementnamed命名实例。例如:

  <Label Content="{Binding MyText, ElementName=ucMyUserControl }"/>

请注意,命名控件后,VS2017/2019将实际上智能感知ElementName

仅使用父级数据上下文的副作用

在没有提到的解决方法的原始情况下,副作用是您可以将用户控件的绑定直接绑定到Text,因为绑定默认为页面的数据上下文。 微妙...

<User Control x:Class="TestUserControBinding.MyUserControl"
             mc:Ignorable="d" 
             d:DesignHeight="30" d:DesignWidth="100">
 <Grid>
    <Label Content="{Binding Text}"/>

那是可行的,从技术上讲,您可以删除依赖属性。如果控件未在项目外使用,则还可以设计为绑定到其他命名属性,并且不会产生任何不良影响。
然后,所有用户控件都可以成为主页面的事实子控件,就像您将内部XAML粘贴到页面上一样。

1
你混淆了控件和它们的数据上下文。你应该重新表述你的帖子以解决这种混淆。 - Özgür

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