rhe1980
rhe1980

Reputation: 1577

Why WPF Databinding is using DataContext from target Element

I often have the following scenario:

I have a custom UserControl:

<UserControl x:Class="BindingBindingBindingTest.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"
                     DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
    <TextBox Text="{Binding MyUserControlValue}"></TextBox>
</Grid>
</UserControl>

with its code behind:

public partial class MyUserControl
{
    public MyUserControl()
    {
        InitializeComponent();
    }

    public int MyUserControlValue
    {
        get { return (int)GetValue(MyUserControlValueProperty); }
        set { SetValue(MyUserControlValueProperty, value); }
    }

    public static readonly DependencyProperty MyUserControlValueProperty =
            DependencyProperty.Register("MyUserControlValue", typeof(int), typeof(MyUserControl), new PropertyMetadata(0));     
}

This usercontrol I'm using in a other control or window:

<Window x:Class="BindingBindingBindingTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:BindingBindingBindingTest">
   <Grid>
      <controls:MyUserControl MyUserControlValue="{Binding MainWindowValue}" />
   </Grid>
</Window>

In its code behind there is a property which should be passed to the usercontrol. This property is initialized in its constructor:

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        MainWindowValue = 34;
        DataContext = this;
    }

    public int MainWindowValue { get; set; }
}

Running the application, the usercontrol binding works well.

But the binding defined in the MainWindow.xaml doesnt work. The error messing is following:

BindingExpression path error: 'MainWindowValue' property not found on 'object' ''MyUserControl' (Name='')'. BindingExpression:Path=MainWindowValue; DataItem='MyUserControl' (Name=''); target element is 'MyUserControl' (Name=''); target property is 'MyUserControlValue' (type 'Int32')

As described in the error message, the property MainWindowValue is not fount on MyUserControl.

And here is my question: Why the property is expected on MyUserControl? In my opinion, the binding is made to the MainWindowValue of the MainWindow because the DataContext is set to its own instance (DataContext = this).

Of course it's simple to fix this issue by defining the source of the binding. But I'm interesting about the reason of this behavior.

Any idea about this behavior?

Thanks in advance

Upvotes: 0

Views: 612

Answers (1)

Mike Strobel
Mike Strobel

Reputation: 25623

The property is expected on MyUserControl because MyUserControl.xaml contains the following:

DataContext="{Binding RelativeSource={RelativeSource Self}}"

This points the DataContext of the MyUserControl instance back to itself. This happens as part of the call to the MyUserControl() constructor, which happens before the MyUserControlValue setter is evaluated in MainWindow:

<controls:MyUserControl MyUserControlValue="{Binding MainWindowValue}" />

By the time this setter is evaluated, the DataContext of the MyUserControl has been set to itself. When the binding engine looks for the MainWindowValue property, it looks on the MyUserControl, because that's the data context. The binding evaluates against the data context of the target object, not the data context of the "parent" object.

A simple workaround would be to give the window a name, and use ElementName to make the binding use the window as its binding root:

<Window x:Class="BindingBindingBindingTest.MainWindow"
        x:Name="Root"
        ...>
   <Grid>
      <controls:MyUserControl
          MyUserControlValue="{Binding ElementName=Root, Path=MainWindowValue}" />
   </Grid>
</Window>

Also, as a matter of style, I would recommend against initializing control properties in both the constructor and the Xaml. When possible, you should stick to initializing values in one place (preferably the Xaml). If you want to specify a default value, then specify it in the DependencyProperty metadata when you Register() the property. If the value you're specifying is specific to the control instance, then set the value where you declare the instance.

Upvotes: 3

Related Questions