OneWayToSource Dilemma

こ雲淡風輕ζ 提交于 2019-12-23 00:25:33

问题


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 {}


回答1:


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




回答2:


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.




回答3:


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".


来源:https://stackoverflow.com/questions/16279890/onewaytosource-dilemma

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!