Data Binding in WPF User Controls
Asked Answered
D

3

46

I am creating a UserControl for a series of controls shared by several windows. One of the controls is a Label which shows the flow of some other process in terms of "protocol numbers".

I am trying to offer DataBinding with this Label so the Window automatically reflects the state of the process as the protocol number variable changes.

This is the User control XAML:

<UserControl Name="MainOptionsPanel"
    x:Class="ExperienceMainControls.MainControls"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    >
<Label Height="Auto" Name="numberLabel">Protocol:</Label>
<Label Content="{Binding Path=ProtocolNumber}" Name="protocolNumberLabel"/>
(...)
</UserControl>

And this is the Code-Behind:

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

    public int ProtocolNumber
    {
        get { return (int)GetValue(ProtocolNumberProperty); }
        set { SetValue(ProtocolNumberProperty, value); }
    }

    public static DependencyProperty ProtocolNumberProperty = 
       DependencyProperty.Register("ProtocolNumber", typeof(int), typeof(MainControls));
}

This seems to be working because if on the constructor I set ProtocolNumber to an arbitrary value, it is reflected in the user control.

However, when using this usercontrol on the final window, the data binding breaks.

XAML:

<Window x:Class="UserControlTesting.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:expControl="clr-namespace:ExperienceMainControls;assembly=ExperienceMainControls"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    >
    <StackPanel>
        <expControl:MainControls ProtocolNumber="{Binding Path=Number, Mode=TwoWay}" />
    </StackPanel>

</Window>

Code-Behind for window:

public partial class Window1 : Window
{
    public Window1()
    {
        Number= 15;
        InitializeComponent();
    }

    public int Number { get; set; }
}

This sets the Protocol Number to zero, ignoring the value set to Number.

I've read example

Dictaphone answered 27/6, 2012 at 13:2 Comment(1)
in your output window you will see a binding error, something like object MainOptionsPanel has no Property Number - and thats true. simply change your usercontrol xaml to that in my answer.Perkoff
P
53

if you look at your output window you should see the binding exception.

The problem you have is the following: within your usercontrol you will bind the label to the DP ProtocolNumber of your usercontrol and not the DataContext, so you have to add for example the element name to the binding.

<UserControl Name="MainOptionsPanel"
    x:Class="ExperienceMainControls.MainControls"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="uc"
    >
<Label Height="Auto" Name="numberLabel">Protocol:</Label>
<Label Content="{Binding Path=ProtocolNumber, ElementName=uc}" Name="protocolNumberLabel"/>
(...)
</UserControl>

EDIT: to clear some things up, your usercontrol also works if you change the binding in your MainWindow. but you have to bind to the DataContext of the MainWindow with RelativeSource.

    <expControl:MainControls ProtocolNumber="{Binding Path=Number, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
Perkoff answered 27/6, 2012 at 13:18 Comment(0)
C
9

What you have in effect:

<expControl:MainControls DataContext="{Binding RelativeSource={RelativeSource Self}}"
                         ProtocolNumber="{Binding Path=Number, Mode=TwoWay}"/>

=> Do not set the DataContext in UserControl declarations, use RelativeSource or ElementName bindings instead.

Cheddar answered 27/6, 2012 at 13:5 Comment(8)
Why not set the DataContext? I know there are ways it propagates to child controls (I think) but we haven't ran into any troubles so far in our application.Enyo
@mizipzor: It's bad practice, setting the DataContext like that is invisible "from the outside" and impractical as inheritance of the DataContext is usually what you want and expect.Cheddar
Actually, inheritance of the DataContext is (thus far) not what I want. CustomControls is another story. But for UserControls I'll argue its good practice.Enyo
So what you're saying is to remove the DataContext line from Usercontrol definition? Because there I also defined it as RelativeSource.Dictaphone
@kelmer: Yes. It't the same object, anything you do in the definition applies to your instances, which in the case of setting a DataContext, as you just have shown, is a bad idea as it overwrites the inherited DataContext.Cheddar
@mizipzor: It's not good practice, seriously, this question is proof enough. If you so far had no problems with that you are probably using UserControls in a way that was not quite intended.Cheddar
I removed the dataContext line and still does not work. I get the following error: System.Windows.Data Error: 39 : BindingExpression path error: 'Number' property not found on 'object' ''MainControls' (Name='MainOptionsPanel')'. BindingExpression:Path=Number; DataItem='MainControls' (Name='MainOptionsPanel'); target element is 'MainControls' (Name='MainOptionsPanel'); (...) Could this be cause by this line? public static DependencyProperty ProtocolNumberProperty = DependencyProperty.Register("ProtocolNumber", typeof(int), typeof(MainControls));Dictaphone
Just use whatever is simple. This maniac idea of putting everything in xaml is non-sense and it actually makes reading the whole thing way more difficult, not to mention that the bindings syntax is a complete mess.Bushed
E
4

If you're not specifying the RelativeSource of the binding, try set the DataContext in the constructor:

    public Window1()
    {
        Number= 15;
        DataContext = this;
        InitializeComponent();
    }
Enyo answered 27/6, 2012 at 13:5 Comment(1)
To be honest this approach is bad because you can change data context from the outside.Hack

© 2022 - 2024 — McMap. All rights reserved.