如何通过绑定在 WPF UserControl 中引用自定义 属性 值?

How do I reference a custom property value in a WPF UserControl via Binding?

我正在构建一个带有自定义用户控件的 WPF 应用程序,我正在尝试了解 属性 绑定应该如何工作。我连最基本的绑定都无法工作,而且它很简单,可以提炼成一个小例子,所以我想有更多 WPF 经验的人可能会让我走上正轨。

我定义了一个名为 TestControl 的自定义 UserControl,它公开了一个 Foo 属性,旨在每当放置 UserControl 时在 XAML 中设置。

TestControl.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace BindingTest
{
    public partial class TestControl : UserControl
    {
        public static readonly DependencyProperty FooProperty = DependencyProperty.Register("Foo", typeof(string), typeof(TestControl));
        public string Foo
        {
            get { return (string)GetValue(FooProperty); }
            set { SetValue(FooProperty, value); }
        }

        public TestControl()
        {
            InitializeComponent();
        }
    }
}

TestControl 的标记只是将其定义为具有单个按钮的控件,其标签文本显示 Foo 的当前值 属性:

TestControl.xaml

<UserControl x:Class="BindingTest.TestControl"
             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" 
             xmlns:local="clr-namespace:BindingTest"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Button Content="{Binding Foo}" />
    </Grid>
</UserControl>

在我的 MainWindow class 中,我只放置了一个 TestControl 实例,其 Foo 属性 设置为 "Hello"。

MainWindow.xaml

<Window x:Class="BindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BindingTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:TestControl Foo="Hello" />
    </Grid>
</Window>

我希望在构建和启动此应用程序时,我会看到 window 和一个显示为 "Hello" 的按钮。但是,按钮是空白的:绑定似乎不起作用。

如果我向 TestControl 的按钮添加点击处理程序,我可以验证该值是否正在幕后更新:

// Added to TestControl.xaml.cs:
private void Button_Click(object sender, RoutedEventArgs e)
{
    Console.WriteLine("Button clicked; Foo is '{0}'", Foo);
}

// Updated in TestControl.xaml:
// <Button Content="{Binding Foo}" Click="Button_Click" />

当我点击按钮时,我得到 Button clicked; Foo is 'Hello',但是 GUI 永远不会更新。我试过使用 Path=FooXPath=Foo 等,以及设置 UpdateSourceTrigger=PropertyChanged 并使用 NotifyOnTargetUpdated=True 验证更新... UI 正在更新以匹配基础 属性 值,即使 属性 值似乎正在更新得很好。

我做错了什么?我觉得我处理这个问题的方式存在一个简单而根本的误解。

编辑:

仔细研究和阅读 使我找到了一个潜在的解决方法:即,向 TestControl.xaml (x:Name="control") 中的根 UserControl 元素添加一个名称,以及更改绑定以显式指定该控件 ({Binding Foo, ElementName=control})。

我猜默认情况下,Button 元素上的 {Binding Foo} 仅表示 "find a property named 'Foo' on this Button control",而我假设它表示 "find a property named 'Foo' in the context that this Button is being declared in, i.e. on the TestControl".

指定一个明确的 ElementName 是最好的解决方法吗?

您必须将绑定的源对象设置为 UserControl 实例,例如像这样:

<Button Content="{Binding Foo, RelativeSource={RelativeSource AncestorType=UserControl}}"/>

<UserControl ... x:Name="theControl">
...
<Button Content="{Binding Foo, ElementName=theControl}"/>

如果您有很多这样的绑定,您还可以将 UserControl 的 XAML 中顶级元素的 DataContext 设置为 UserControl 实例:

<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
    <Button Content="{Binding Foo}" />
    <Button Content="{Binding Bar}" />
</Grid>

但是,您必须避免设置 UserControl 的 DataContext("expert" 博主经常推荐),因为这会破坏 UserControl 属性的基于 DataContext 的绑定,例如

<local:TestControl Foo="{Binding SomeFoo}" />