简体   繁体   中英

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

I'm building a WPF app with custom UserControls, and I'm trying to understand how property bindings are supposed to work. I can't get even the most basic binding to work, and it's simple enough to distill into a tiny example, so I figured someone with more WPF experience might be able to put me on the right track.

I've defined a custom UserControl called TestControl , which exposes a Foo property, which is intended to be set in XAML whenever a UserControl is placed.

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();
        }
    }
}

The markup for TestControl just defines it as a control with a single button, whose label text displays the current value of the Foo property:

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>

In my MainWindow class, I just place a single instance of TestControl with its Foo property set to "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>

I would expect that when I build and launch this app, I'd see a window with a single button reading "Hello". However, the button is blank: the Binding doesn't seem to work.

If I add a click handler to the TestControl's button, I can verify that the value is being updated behind the scenes:

// 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" />

When I click the button, I get Button clicked; Foo is 'Hello' Button clicked; Foo is 'Hello' , but the GUI never updates. I've tried using Path=Foo , XPath=Foo , etc., as well as setting UpdateSourceTrigger=PropertyChanged and verifying updates with NotifyOnTargetUpdated=True ... nothing seems to result in the text in the UI being updated to match the underlying property value, even though the property value seems to be getting updated just fine.

What am I doing wrong? I feel like there's just a simple and fundamental misunderstanding in how I'm approaching this.

edit:

Poking around a bit more and reading similar questions has led me to a potential fix: namely, adding a name to the root UserControl element in TestControl.xaml ( x:Name="control" ), and changing the binding to explicitly specify that control ( {Binding Foo, ElementName=control} ).

I'm guessing that by default, {Binding Foo} on the Button element just means "find a property named 'Foo' on this Button control" , whereas I'd assumed it'd mean "find a property named 'Foo' in the context that this Button is being declared in, ie on the TestControl" .

Is specifying an explicit ElementName the best fix here?

You have to set the source object of the Binding to the UserControl instance, eg like this:

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

or

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

If you have many such Bindings, you may also set the DataContext of the top level element in the UserControl's XAML to the UserControl instance:

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

You must however avoid to set the DataContext of the UserControl (which is often recommend by "expert" bloggers), because that would break DataContext-based Bindings of the UserControl properties like

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM