10

I am trying (and failing) to do data binding on a dependency property in xaml. It works just fine when I use code behind, but not in xaml.

The user control is simply a TextBlock that bind to the dependency property:

<UserControl x:Class="WpfTest.MyControl" [...]>
     <TextBlock Text="{Binding Test}" />
</UserControl>

And the dependency property is a simple string:

public static readonly DependencyProperty TestProperty 
= DependencyProperty.Register("Test", typeof(string), typeof(MyControl), new PropertyMetadata("DEFAULT"));

public string Test
{
    get { return (string)GetValue(TestProperty); }
    set { SetValue(TestProperty, value); }
}

I have a regular property with the usual implementation of INotifyPropertyChanged in the main window.

private string _myText = "default";
public string MyText
{
   get { return _myText; }
   set {  _myText = value; NotifyPropertyChanged(); }
}

So far so good. If I bind this property to a TextBlock on the main window everything works just fine. The text update properly if the MyText changes and all is well in the world.

<TextBlock Text="{Binding MyText}" />

However, if I do the same thing on my user control, nothing happens.

<local:MyControl x:Name="TheControl" Test="{Binding MyText}" />

And now the fun part is that if I do the very same binding in code behind it works!

TheControl.SetBinding(MyControl.TestProperty, new Binding
{
    Source = DataContext,
    Path = new PropertyPath("MyText"),
    Mode = BindingMode.TwoWay
});

Why is it not working in xaml?

Oliver
  • 115
  • 1
  • 1
  • 6
  • Show more of your XAML. I suspect if you bind to {Binding}, you'll find that you are binding to the wrong data context. – SledgeHammer Oct 21 '16 at 19:31
  • You should pass property name ("Test") as first parameter to the Register method. I couldn't find an overload that receives a Type as the first parameter. – Mehrzad Chehraz Oct 21 '16 at 19:32
  • What is the DataContext of the MyControl? It sould be the MainWindow –  Oct 21 '16 at 19:37
  • 1
    If the data context was wrong it would not work with the code behind solution either. `DataContext` is set to `this` in both constructors (window and usercontrol). – Oliver Oct 21 '16 at 19:40
  • Yes I have tried `RegisterAttached`. Unfortunatly it did not work either. – Oliver Oct 21 '16 at 19:41
  • Also, the code behind solution comes from here: http://timgthomas.com/2011/09/binding-to-a-usercontrols-dependency-property/ – Oliver Oct 21 '16 at 19:44
  • Then the only difference I can see is that you explicitly set Mode = BindingMode.TwoWay. Try that in XAML –  Oct 21 '16 at 19:45
  • Already did and I also tried to set the default mod in the dependency property. Nothing worked so far. I cannot see the difference either... – Oliver Oct 21 '16 at 19:54

1 Answers1

32

The dependency property declaration must look like this:

public static readonly DependencyProperty TestProperty =
    DependencyProperty.Register(
        nameof(Test),
        typeof(string),
        typeof(MyControl),
        new PropertyMetadata("DEFAULT"));

public string Test
{
    get { return (string)GetValue(TestProperty); }
    set { SetValue(TestProperty, value); }
}

The binding in the UserControl's XAML must set the control instance as the source object, e.g. by setting the Bindings's RelativeSource property:

<UserControl x:Class="WpfTest.MyControl" ...>
     <TextBlock Text="{Binding Test,
         RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</UserControl>

Also very important, never set the DataContext of a UserControl in its constructor. I'm sure there is something like

DataContext = this;

Remove it, as it effectively prevents inheriting a DataContext from the UserConrol's parent.

By setting Source = DataContext in the Binding in code behind you are explicitly setting a binding source, while in

<local:MyControl Test="{Binding MyText}" />

the binding source implicitly is the current DataContext. However, that DataContext has been set by the assignment in the UserControl's constructor to the UserControl itself, and is not the inherited DataContext (i.e. the view model instance) from the window.

Clemens
  • 123,504
  • 12
  • 155
  • 268
  • 1
    I'm afraid it did not work. By the way, you wrote exactly the same declaration for `DependencyProperty ` as what is in my question. – Oliver Oct 21 '16 at 19:51
  • Far from *exactly the same*. In your question you forgot the property name ("Test"), and the *new* keyword on PropertyMetadata. These details are important! – Clemens Oct 21 '16 at 19:52
  • omg, you are totally right! I'm very sorry. I must have pasted the code wrong. – Oliver Oct 21 '16 at 19:58
  • 1
    See my updated answer about setting the DataContext in the UserControl's ctor. You must not do that! – Clemens Oct 21 '16 at 20:00
  • Still changing the source object did not help. The data context is already set as `MyControl` in the constructor (I forgot to mentioned that in the question ) So changing to a relative source doesn't change much. – Oliver Oct 21 '16 at 20:02
  • You must **not set** the DataContext in the UserControl's constructor. Read the answer for an explanation. – Clemens Oct 21 '16 at 20:05
  • How is inheriting the DataContext from the parent important? Doesn't setting the dependency property and binding to it sufficient? – Oliver Oct 21 '16 at 20:07
  • 11
    `DataContext = this;` Tutorials that include this are universally authored by trolling assholes. –  Oct 21 '16 at 20:07
  • @Oliver bindings are *against the DataContext*. If you change the DC to something unexpected, your bindings will fail. –  Oct 21 '16 at 20:08
  • `{Binding MyText}` expects a `MyText` property in the current DataContext, which in your case is the UserControl itself. Does it have such a property? Of course not! – Clemens Oct 21 '16 at 20:09
  • @Clemens When you say the UserControl itself, isn't it an instance of `MyControl` ? In which case it should have access to the property... I am trying to understand the nuance here. – Oliver Oct 21 '16 at 20:16
  • MyControl does not have a MyText property. It is your MainWindow instance that has this property, not the control. Why don't you just remove the assignment in the control's constructor and see that it works? – Clemens Oct 21 '16 at 20:19
  • But I dont want to bind to "MyText" I want to bind to the "Test" property of the usercontrol. Are you telling me the usercontrol need access to "MyText" in order to interpret "Test" binding? – Oliver Oct 21 '16 at 20:23
  • I think I just understood what you meant. You mean that by setting `DataContext = this` in the ctor, when I declare `local:MyControl Test="{Binding MyText}" />` the context changed to the `UserControl` which does not have `MyText` – Oliver Oct 21 '16 at 20:26
  • "Remove it, as it effectively prevents inheriting a DataContext from the UserConrol's parent." But then you can't have custom control with the xaml, or your window's context needs to have all the properties that xaml of custom control needs. ? All system controls do not have xaml. – Hrvoje Batrnek Nov 24 '19 at 03:01
  • By setting DataContext = this; to custom control, you are effectively making that control as if it does not have a XAML that you need to worry about, and to me that is a good solution, solution where you do not need to carry (inherit) around the view model that this control needs for it's XAML. I don't see a more practical solution. Thoughts? – Hrvoje Batrnek Nov 24 '19 at 03:12
  • @HrvojeBatrnek Sorry, it seems you totally misunderstood this. The point is that you do not explicitly set the DataContext of a control, because then you break the property value inheritance of the DataContext property. This does not mean you can't directly bind to properties of a view model in a UserControl's XAML. Just let the DataContext be inherited and thus have a view model that is supplied by the application code or XAML. I don't like to go into that discussion again. There are a lot of other questions and answers here where people made the same mistake, please search a bit. – Clemens Nov 24 '19 at 08:59