3

I am using OneWayToSource binding and it seems that it always sets my source property to null. Why is that so? It's causing me troubles because I need value from the target property in my source property and not null.

Here is my code:

MyViewModel.cs:

public class MyViewModel
{
    private string str;

    public string Txt
    {
        get { return this.str; }
        set { this.str = value; }
    }
}

MainWindow.cs:

public MainWindow()
{
    InitializeComponent();
    MyViewModel vm = new MyViewModel();
    vm.Txt = "123";
    this.DataContext = vm;
}

MainWindow.xaml:

<Window x:Class="OneWayToSourceTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:local="clr-namespace:OneWayToSourceTest">
      <Grid>
        <local:MyButton Content="{Binding Path=Txt, Mode=OneWayToSource}"/>
      </Grid>
 </Window>

MyButton.cs:

public class MyButton : Button
{
    public MyButton()
    {
        this.Content = "765";
    }
}

The target property is MyButton.Content. The source property is MyViewModel.Txt. The Txt property should be set to "765" but instead it is null.

Why do I receive null instead 765?

EDIT:

Please take a look inside MyButton constructor. Actually If you would use simple TwoWay it will work. I tested it out and it has nothing to do with content being set inside constructor. Its something with OneWayToSource binding I guess.

Now to explain how I used TwoWay binding, I did set the value of dp inside the constructor by calling setvalue method but then inside the wrapper or better said the getter and setter I didn't offer any setter hence why I made my TwoWay kinda look like its OneWayToSource. I did it to test out if its constructor fault. I figured the property inside viewmodel had the value 765 so that's what I meant with TwoWay binding. I just tested if it was control constructor. Its all fine with setting a value inside the constructor.

By hiding setter I mean this set {}

Benjamin Gale
  • 12,977
  • 6
  • 62
  • 100
snowy hedgehog
  • 652
  • 1
  • 7
  • 23

3 Answers3

10

The Content property can only be set to a single value, and you are replacing the value "756" with a binding.

As I pointed out to you in my answer to your other question, WPF runs the code in this order:

  • Normal - Constructors run here
  • DataBind
  • Render
  • Loaded

So the first thing that is done is your MainWindow Constructor is run. This creates the button, which calls the Button's constructor, and sets Button.Content to "765".

However in the XAML for the Button, you have specified a different Content property - a binding. So your button object gets created, the Content property gets set to "765", and then the Content property is set to {Binding Path=Txt, Mode=OneWayToSource}.

This is the same as doing something like:

var b = new MyButton();
b.Content = "756";
b.Content = new Binding(...); 

You are replacing the Content property.

(And technically, the last line should be b.SetBinding(MyButton.ContentProperty, new Binding(...)); to bind the value correctly, however I am using a more basic syntax to make it easier to understand the problem.)

The next step in the cycle is Data Binding, so the binding on the Content property is evaluated.

Since it is a OneWayToSource binding, the property only updates the source element when it gets changed, and since this is the first time the binding is being evaluated, it sets the source to whatever the default is for this DependencyProperty so they're synchronized.

In the case of the Button.Content dependency property, the default value is null, so your source element gets set to null.

You don't see this behavior with TwoWay bindings because the DP gets it's value from the binding instead of using the default value.

If you were to set your value after the binding has been set up, such as in the Loaded event for your button, you would see that your source property correctly gets updated when you set your Button's Content

void MyButton_Loaded(object sender, EventArgs e)
{
    ((Button)sender).Content = "756";
}

And last of all, if you're trying to set the default value of the Content property from your custom control, you need to overwrite the MetaData for that property, like this:

// Note that this is the static constructor, not the normal one
static MyButton()
{
    ContentProperty.OverrideMetadata(typeof(MyButton), 
        new FrameworkPropertyMetadata("756"));
}

This will make it so the default value of the Content property be "756" instead of null

Community
  • 1
  • 1
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • +1 I deleted my answer as your is much better because it explains why the binding is working the way it does. I was also unaware that you could set a binding like this `b.Content = new Binding(...)` and thought that you had to use the `SetBinding(..)` method. Thanks for the tip. – Benjamin Gale Apr 29 '13 at 19:27
  • @Benjamin Errmmm that might have been a mistake :) I think setting `b.Content = new Binding(...)` will set the `Content` property to the binding object, and not evaluate it (have to test to be sure). I just used that syntax because it makes it easier to understand what's happening :) – Rachel Apr 29 '13 at 19:42
  • Yes, you are correct :) When I tested this I thought I had it working but I was mistaken (my test was flawed). – Benjamin Gale Apr 29 '13 at 19:57
  • Hi Rache you wrote this: "it sets the source to whatever the default is for this DependencyProperty so they're synchronized." Whaaat? Really? Where did you get that from? Or are you just assuming that is happening? I would really like to see where did you get that from. I ma so full of assumptations about this. How you know that happenning? – snowy hedgehog Apr 30 '13 at 07:04
  • @snowyhedgehog There is no assumption. You can verify this yourself by changing the default value of the property as shown in the last code example posted by Rachel. – Benjamin Gale Apr 30 '13 at 07:24
  • To sum up, you are already running the application, you have your custom values set on button.content and then at some point you decide to use OneWayToSource. So you change the mode and at that point you kill the value of button.content just because of that synchronization that Rachel mentioned. Really? Should it be something like at the point of time where you change the mode to onewaytosource you receive the value of button.content to your viewmodels property? Means the value of your button.content wont get killed. Just asking... – snowy hedgehog Apr 30 '13 at 07:59
  • This is how it works and speculating about how it should or could work isn't getting you anywhere. You have two solutions that work; either set the value of the property after the binding is established or set the default dependency property value as shown above. – Benjamin Gale Apr 30 '13 at 09:20
  • @sno You can verify that behavior by creating any custom DP with a default value, and binding it using a `OneWayToSource` binding. Or do like the last bit of my code says, and overwrite the default value for an existing DP. When you bind a DP, you are *replacing* the existing value, so it no longer exists in any form. Instead, the property is pointing to a location on another object, so when you set the property, it actually sets the bound source instead. When you replace the existing value with a binding, it needs some kind of default value to start out with, so it uses the DP default. – Rachel Apr 30 '13 at 12:03
4

MyButton gets instantiated by InitializeComponent before the DataContext is set, so the binding is not in force when its contructor runs. Try setting a value on the click of the button.

johndsamuels
  • 329
  • 1
  • 10
  • I cannot force the user to click on a button just to set the value. :) – snowy hedgehog Apr 29 '13 at 14:31
  • 1
    Possibly not but I have explained to you why it isn't working. Thank you very much for the downvote!! – johndsamuels Apr 29 '13 at 14:34
  • When datacontext is not set, the value of Txt should be 123. Please take a look at my code again. Once DataContext is set the binding should update the value of Txt to 765. Though the problem is that once binding updated the value, the value is null. I am sorry but no matter if before or after InitializeComponent, its still not working with OneWayToSource. Also please take a look at my question again. I edited it. – snowy hedgehog Apr 29 '13 at 14:38
  • 2
    Upvoted - this looks to me like the right answer. I'd imagine you need to set the value after the button is loaded, not in its constructor. – Dan Puzey Apr 29 '13 at 14:38
  • Mr Puzey why does it look to you like the right answer? Why should everytime somebody uses OneWayToSource binding set the value after initalizatin? It doesnt make sense to me. Please take a look at my question again. I edited it. It works all fine with TwoWay binding even when you set the content inside constructor. Therefore for me this answer is invalid. – snowy hedgehog Apr 29 '13 at 14:42
  • It works with twoway binding even though i set the value inside constuctor. – snowy hedgehog Apr 29 '13 at 14:43
  • 2
    @snowyhedgehog: having tested your code locally, I can confirm that your binding is fine, but setting the value in the button's constructor is the problem. It may not make sense to you, but it's the way the framework works: if you set the value *after* the button is created (and once the binding is in place), the binding works. I tested this by setting the value in the `Loaded` event of `MyButton` and it works fine. – Dan Puzey Apr 29 '13 at 14:46
  • 2
    With a two way binding, and your code, the value from the viewmodel overrides the value in the button. That's what I'd expect to see - but it's not the behaviour you're asking for. If you want the value in the control to take precedence, you have to set it after the control is loaded. – Dan Puzey Apr 29 '13 at 14:48
  • 3
    +1 negative vote is unfair for this answer. It is very much correct. @snowyhedgehog your Binding would not give you the output you expect even in "Mode=TwoWay". Yes the vm Text property would not be made null, but it does not get 765 either – Viv Apr 29 '13 at 14:48
  • I am using 4.0 - I am not using 4.5 and Mr. Puzey why does it work with TwoWay binding and the content of button is being set inside constructor? Is something with OneWayToSource. The constructor is fine. Also setting the value of DP inside constructor is also fine. – snowy hedgehog Apr 29 '13 at 14:50
  • The only reason why it's set to null in OneWayToSource is because the View control appears to take precedence over the VM property when DataContext is initialised and when TwoWay, it see's the VM have Text value and does not replace it – Viv Apr 29 '13 at 14:50
  • @snowyhedgehog: it doesn't work with TwoWay. If you switch to TwoWay, the value of the button content is set to `123`, which is the *opposite* of the behaviour you're asking for. Honestly: you have been given the answer that works. I urge you to at least try it before suggesting that it's wrong. You won't get a better answer! – Dan Puzey Apr 29 '13 at 14:52
  • No guys, the value of control is not null, the control has a constructor and inside i do set content to 765. Now to explain how i used twoway binding, i did set the value of dp inside the constructor by calling setvalue method but then inside the wrapper or better said the getter and setter i didnt offer any setter hence why i made my twoway kinda look like its onewaytosource. I did it to test out if its constructor fault. I figured the property inside viewmodel had the value 765 so thats what i ment with twoway binding. In case its precedence what level of precedence is taking place? And why? – snowy hedgehog Apr 29 '13 at 15:01
  • Its obviously not the constructor fault. It has to do something with stupid OneWayToSource as if it would reset the value. :) – snowy hedgehog Apr 29 '13 at 15:02
  • I've now replicated the problem. The property does get set to null as a consequence of the DataContext getting set. That's just how it is. So, either stay with two-way binding and set the default in the VM or else contrive to set the value on MyButton after the DataContext has been set. – johndsamuels Apr 29 '13 at 15:03
  • @johndsamuels Thanks for replicating the problem. But its onewaytosouce so why would the control.content set its value to null when datacontext gets set? – snowy hedgehog Apr 29 '13 at 15:05
  • Please read my question once again. I just edited it. I used twoway binding just to test the constructor and it worked. The constructor is fine for me. I wish there would be somebody who could give me details about this and just claim the value should be set after initalization. Why is that the case especially when you used OneWayToSource? – snowy hedgehog Apr 29 '13 at 15:14
  • I've investigated further and I do not think it is anything to do with OneWayToSource. Set a breakpoint on this.DataContext = vm; and check the value of MyButton.Content. You should see that it is null. Setting Content in the constructor of MyButton does not seem to have the desired effect. – johndsamuels Apr 29 '13 at 15:19
  • Set the initial value in the button's Loaded event handler and all should be well: – johndsamuels Apr 29 '13 at 15:26
  • As far I understood you think the content of button is null because of datacontext change. I still cant believe that. What if another programmer or user wishes to use MyButton as a customcontrol. It doesnt have to be button but lets say a custom TextBox. And he/she might want to change from TwoWay to OneWayToSource since you are allowed to pick mode by yourself. It means that even though he/she set a value at initialization, it will rolledback to null once the datacontext get changed, and then null will get transmitted to viewmodel's property. Basically she/he would need that loaded event. – snowy hedgehog Apr 29 '13 at 15:33
  • @johndsamuels have you tried the Loaded event setter? That would still not work. – Viv Apr 29 '13 at 15:38
  • does not work for me even if the Button is a standard wpf Button. nor does setting a Style and applying a default Setter. vm property is not updated after initial null either cases. Having another button click event set vm property does update correctly making it even more strange. Am on .net45 – Viv Apr 29 '13 at 15:45
  • @Snowy. It's not because of the DataContext change. The value gets set in the constructor but then it get set to null as a result of processing Content="{Binding Path=Txt, Mode=OneWayToSource}". Comment out the setting of the DataContext and just look at the screen - the button has null content. – johndsamuels Apr 29 '13 at 15:45
  • @snowyhedgehog If you're looking to set the default value of the `Content` property from your custom control, you need to change the `Metadata` for that property to replace `null` with your custom value. See the bottom part of [my answer](http://stackoverflow.com/a/16286284/302677) for an example. – Rachel Apr 29 '13 at 19:39
2

Actually If you would use simple TwoWay it will work.

I can't replicate this. Given the code you have supplied, if you use a TwoWay binding mode, the viewmodels Txt property will equal "123", the value which it is given inside your MainWindows constructor.

I tested it out and it has nothing to do with content being set inside constructor

As Rachel has pointed out, the XAML Binding clears the local value. The binding occurs after your buttons constructor code is run. The binding then retrieves it's default value from the dependency property's metadata. This is easily fixed by setting the value at a more appropriate time, after the binding has been established, such as in the Loaded event.

This simple modification will give you the desired result:

public class MyButton : Button
{
    public MyButton()
    {
        this.Loaded += MyButton_Loaded;
    }

    void MyButton_Loaded(object sender, RoutedEventArgs e)
    {
        this.Content = "765";
    }
}

Rachels answer also provides an alternative method for making this work (overriding the properties default metadata).

Why should everytime somebody uses OneWayToSource binding set the value after initalizatin? It doesnt make sense to me.

I think the reason this is not making sense to you is because your TwoWay binding test does not work the way you think it does.

Using OneWayToSource binding:

Using OneWayToSource binding the following is happening:

  1. You are setting MyButton.Content to "123" in it's constructor.
  2. You are setting a OneWayToSource binding in your XAML. This CLEARS the value you have set.
  3. The binding retrieves the default property value (null) from the property metadata and sets the ViewModel.Txt property equal to this value.

If you set the MyButton.Content property in the buttons loaded event, this is after the above events have taken place so your property is set to the value you want it to be.

You can verify this for yourself by placing a breakpoint in your MyViewModel.Txt properties getter. the value will be set to "123", null and "756" in that order.

Using TwoWay binding:

Now if you were to change your XAML to use a TwoWay binding the following will occur:

  1. You are setting MyButton.Content to "123" in it's constructor.
  2. You are setting a TwoWay binding in your XAML. This CLEARS the value you have set.
  3. Your controls value (MyButton.Content) is updated using the Source which in this case is your viewModels Txt property resulting in your MyButton.Content property being equal to "123".
Benjamin Gale
  • 12,977
  • 6
  • 62
  • 100
  • i am not a fan of just use things without to know why. seems in this case i just must believe you guys. still as i have said with my customizes twoway binding example i succeeded proving its not the constructor. however lets accept this as an answer. thanks – snowy hedgehog Apr 29 '13 at 19:01