0

Why is it that when I enter text in the textbox, the Coerce function is called after MainWindow.Float.Set is called?

Upon entering text, I would have expected :

OnValueCoerce -> OnValueChanged -> MainWindow.Float.Set

but I get :

MainWindow.Float.Set -> OnValueCoerce -> OnValueChanged

<Window x:Class="WpfApplication10.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfApplication10"
        Title="MainWindow" Height="350" Width="525">
    <l:TextBoxEx Value="{Binding Float, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Window>

...

namespace WpfApplication10
{
    public class TextBoxEx : TextBox
    {
        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(TextBoxEx), 
            new UIPropertyMetadata(double.NaN, OnValueChanged, OnValueCoerce));

        static object OnValueCoerce(DependencyObject _dep, object _value)
        {
            return _value;
        }

        static void OnValueChanged(DependencyObject _obj, DependencyPropertyChangedEventArgs _arg)
        {
            int gotHere = 6;
        }

        public TextBoxEx()
        {
            var binding = new Binding("Value")
            {
                Source = this,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                Mode = BindingMode.TwoWay
            };

            SetBinding(TextBox.TextProperty, binding);
        }
    }

    public partial class MainWindow : Window
    {
        float m_float = 7.812345678f;
        public float Float
        {
            get { return m_float; }
            set { m_float = value; }
        }

        public MainWindow()
        {
            DataContext = this;
            InitializeComponent();
        }
    }
}
pastillman
  • 1,104
  • 2
  • 16
  • 27

1 Answers1

0

EDIT

It is the way binding works. Lets say you have 2 properties A and B. if A binds to B then A's value will be updated first. So you are perfectly right in saying that " the value gets written to Float without going through the coerce function of "Value".

In your example where binding = Value:

SetBinding(TextBox.TextProperty, binding);

Value is property A in our example and is secondary to TextBox.Text. Thus A (Value) will be updated before value B in a TwoWay Binding Mode.

in xaml, "Value" now is property B and Float is property A, because Float is binding to Value.

<l:TextBoxEx Value="{Binding Float, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

Hence Float will be updated before value is updated. Try switch Binding between Value and Float around in xaml or in code behind.

try this as an illustration of what I am trying to say:

  • Somewhere in Constructor:

    this.SetBinding(myControl.TesterappProperty, new Binding("TesterApp2") { Source=this,Mode=BindingMode.TwoWay});
    Testerapp = "hello";
    
    this.SetBinding(myControl.TesterApp2Property, new Binding("Testerapp") { Source=this,Mode=BindingMode.TwoWay});
    Testerapp = "hello2";
    
  • and the 2 properties looks as follows:

    public string Testerapp
    {
        get { return (string)GetValue(TesterappProperty); }
        set { SetValue(TesterappProperty, value); }
    }
    
    //Register Dependency Testerapp Property
    public static readonly DependencyProperty TesterappProperty = 
        DependencyProperty.Register("Testerapp", typeof(string), typeof(myControl), new PropertyMetadata("myDefault", OnTesterappPropertyChanged,mycoerce));
    
    private static object mycoerce(DependencyObject d, object baseValue)
    {
        G2ListBox obj = d as myControl;
        if(obj!= null)
        {
            string s = obj.Testerapp;
        }
        return baseValue;
    }
    
    //Register Dependency TesterApp2 Property
    public static readonly DependencyProperty TesterApp2Property = 
        DependencyProperty.Register("TesterApp2", typeof(string), typeof(myControl), new PropertyMetadata("myDefault2", OnTesterApp2PropertyChanged, mycoerce2));
    
    private static object mycoerce2(DependencyObject d, object baseValue)
    {
        G2ListBox obj = d as myControl;
        if (obj != null)
        {
            string s = obj.Testerapp;
        }
        return baseValue;
    }
    

Set a break point in each coerce method and see which one gets hit first each time. This might explain what I am trying to say a bit better.

Once again - I am not sure why it does it, but I ran into a similar problem before and solved it by creating another property (in yourcase - External Value property). The value property as you have it got bound to Textbox and became private; and External property got bounded as you have it at the moment in xaml. I guess you might also just subscribe to Textbox.PropertyChanged event instead of creating 2 properties. And then do the coerce through event handling.

This is however only some solutions and I know it does not fully explain your question as to Why it happens. I guess that to do binding you need a hierarchy of importance (which property value should be updated first) and this is how MS decided to do it.
My first thought also was to expect a logical flow (i.e. if Textbox value change then value change then Float change, and similarly if Float => value => textbox), but it does not seem like this is the real mechanics behind binding.
The way it is done now however might actually be better, because now you can choose which property value needs updating first.

DaClan
  • 329
  • 2
  • 15
  • The problem is that when you enter text, the value gets written to Float without going through the coerce function of "Value", which doesnt make any sense as Text is bound to Value, so it should go through Value first. It acts as if Text is bound straight to Float – pastillman Feb 14 '14 at 19:46