Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of Operations in Regards to DataContext, Hard-Coded Values, Binding Expressions, Templates, and Nested Controls

This has bothered me for a while and I'm tired of working around the issue. In WPF, what is the "order of operations" when it comes to:

  • Setting DataContext
  • Inheriting DataContext
  • Evaluating a "hard-coded" property value
  • Evaluating a {Binding} property value

All this taking into consideration nested controls and templates (when templates are applied).

I've had a number of problematic scenarios, but here's just one example:

Custom user control

<UserControl x:Class="UserControls.TestUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             >
    <StackPanel>
        <Label Content="{Binding Label1}" />
        <Label Content="{Binding Label2}" />
    </StackPanel>
</UserControl>

User control code-behind

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

namespace UserControls
{
    public partial class TestUserControl : UserControl
    {
        public static readonly DependencyProperty Label1Property = DependencyProperty.Register("Label1", typeof(String), typeof(TestUserControl), new FrameworkPropertyMetadata(OnLabel1PropertyChanged));
        public String Label1
        {
            get { return (String)GetValue(Label1Property); }
            set { SetValue(Label1Property, value); }
        }

        public static readonly DependencyProperty Label2Property = DependencyProperty.Register("Label2", typeof(String), typeof(TestUserControl), new FrameworkPropertyMetadata(OnLabel2PropertyChanged));
        public String Label2
        {
            get { return (String)GetValue(Label2Property); }
            set { SetValue(Label2Property, value); }
        }

        public TestUserControl()
        {
            DataContext = this;

            InitializeComponent();
        }

        private static void OnLabel1PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            //used for breakpoint
        }

        private static void OnLabel2PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            //used for breakpoint
        }
    }
}

Window to use the user control

<Window x:Class="Windows.TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:UC="clr-namespace:UserControls"
        >
    <StackPanel>
        <Label Content="Non user control label" />

        <UC:TestUserControl x:Name="uc" Label1="User control label 1" Label2="{Binding Label2FromWindow}" />
    </StackPanel>
</Window>

And the code-behind for the window

using System;
using System.Windows;

namespace Windows
{
    public partial class TestWindow : Window
    {
        public String Label2FromWindow
        {
            get { return "User control label 2"; }
        }

        public TestWindow()
        {
            DataContext = this;

            InitializeComponent();
        }
    }
}

So in this scenario, why doesn't "Label2" in the user control ever get the value from "Label2FromWindow" from the window? I feel like it's a timing issue, where the user control evaluates all of its expressions first, and then the window evaluates its expressions later, and the user control is never "notified" of the window's evaluated values.

The example is hopefully helpful to illustrate one problem, but my real question is:

What is the order of operations with regards to DataContext, Hard-Coded Values on properties, Binding Expressions, Templates, and Nested Controls?

EDIT:

H.B. helped me come to this realization. When the window's DataContext is set to itself, the user control will "inherit" the DataContext. This lets the Binding work on the user control's property, but then within the user control Binding to its local properties won't work. When DataContext is set directly on the user control, the window's Binding to the user control's property no longer works, but the user control can then Bind to its own local properties. Below is the updated code sample that works.

User control:

<UserControl x:Class="UserControls.TestUserControl"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                Name="uc">
    <StackPanel>
        <Label Content="{Binding ElementName=uc, Path=Label1}" />
        <Label Content="{Binding ElementName=uc, Path=Label2}" />
    </StackPanel>
</UserControl>

User control code-behind:

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

namespace UserControls
{
    public partial class TestUserControl : UserControl
    {
        public static readonly DependencyProperty Label1Property = DependencyProperty.Register("Label1", typeof(String), typeof(TestUserControl));
        public String Label1
        {
            get { return (String)GetValue(Label1Property); }
            set { SetValue(Label1Property, value); }
        }

        public static readonly DependencyProperty Label2Property = DependencyProperty.Register("Label2", typeof(String), typeof(TestUserControl));
        public String Label2
        {
            get { return (String)GetValue(Label2Property); }
            set { SetValue(Label2Property, value); }
        }

        public TestUserControl()
        {
            InitializeComponent();
        }
    }
}

Test window:

<Window x:Class="Windows.TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:UC="clr-namespace:UserControls"
        >
    <StackPanel>
        <Label Content="Non user control label" />

        <UC:TestUserControl Label1="User control label 1" Label2="{Binding Label2FromWindow}" />
    </StackPanel>
</Window>

Test window code-behind:

using System;
using System.Windows;

namespace Windows
{
    public partial class TestWindow : Window
    {
        public String Label2FromWindow
        {
            get { return "User control label 2"; }
        }

        public TestWindow()
        {
            DataContext = this;
            InitializeComponent();
        }
    }
}
like image 421
tyriker Avatar asked Apr 25 '11 22:04

tyriker


1 Answers

It's not really about order I think, but rather precedence (maybe I am splitting hairs here). You explicitly set the DataContext of the UserControl -- this means that it will not be inherited, thus your binding looks for the property Label2FromWindow inside the UserControl. Obviously, it does not find it.

Just never set the DataContext of UserControl instances and you should not run into such problems. (Name your UserControl and use ElementName for internal bindings)

For a full precedence list see MSDN.

like image 85
H.B. Avatar answered Oct 29 '22 17:10

H.B.