locked
Binding problem in dependency property RRS feed

  • Question

  • Hi,

    I am trying to make a Numeric Box which shoul take numbers from 0 to 100. The numeric box has a text box and a slider which are bound to dependence properties Value, Min, Max.I have a custom control from custom control library. I m applying the templated file for the default style (Through generic XAML). If the value is lesser than 0 i should reset it to 0, if its greater than 100i should reset it to 100. I have wrote the code logic in Coercevalue callback event handler. My aim is when the Value dependency changes it should be reflected in the text box. This is happening only at the first time nt on the second can any one help me out in this please.

    Any help will be appreciated.

    Regards

    Harsha

    Monday, August 6, 2012 8:25 AM

Answers

  • Hi Bhat

    Seems I don't really understand, what your concern is.
    My previous posts all related to your original code (except a subtle nuance with the way the template is linked to the code behind, which has zero effect on the issue discussed here, for sure).
    Basically it is correct, that assigning a value to a DP will trigger a change notification, which will be reflected by a bound control (the target is updated).
    However since DPs are more complex than plain CLR-properties, the value that is about to be assigned first
    goes through the coersion callback(s) if there is (at least) one. The final value returned by the coersion call back(s) is the one
    that will be effectively assigned to the internal store of the DP. If this value is the same as the one already assigned
    then nothing happens, no change, so no notification, so the control input remains unchanged as well.
    So if we have coersion

    obj.Value = 23444;
    might effectively assign the value "99" to "obj.Value"

    A little Demo project for demo purpose:

    Create a new default WPF App "WPFApplication2", remove MainWindow.xaml(.cs)
    add a new class: SimpleDependencyObject.cs
    So WPFApplication2 contains these code files:

    App.xaml
    App.xaml.cs
    SimpleDependencyObject.cs

    App.xaml:

    <Application x:Class="WpfApplication2.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />


    App.xaml.cs:

    using System;
    using System.Diagnostics;
    using System.Windows;
    
    namespace WpfApplication2
    {
        public partial class App : Application
        {
            private SimpleDependencyObject _sdo;
    
            protected override void OnStartup(StartupEventArgs e)
            {
                _sdo = new SimpleDependencyObject();
    
                TryChange(4);   //fires BOTH Coerce AND PropChange;
                TryChange(111); //fires BOTH Coerce AND PropChange;
                TryChange(99);  //fires ONLY Coerce but NOT PropChange;
                TryChange(98);  //fires BOTH Coerce AND PropChange;
                TryChange(99);  //fires BOTH Coerce AND PropChange;
                TryChange(101); //fires ONLY Coerce but NOT PropChange;
    
                Shutdown();
    
            }
            private void TryChange(int newValue)
            {
                int oldValue = _sdo.Value;
                _sdo.Value = newValue;
                Debug.WriteLine("{0:HH:mm:ss.ffff}  oldValue {1}, proposed Value: {2}, new effective Value: {3}, coerced? {4}, prop changed? {5}\n", DateTime.Now, oldValue, newValue, _sdo.Value, newValue != _sdo.Value, oldValue != _sdo.Value);
            }
        }
    }
    

    SimpleDependencyObject.cs


    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Data;
    
    namespace WpfApplication2
    {
        public class SimpleDependencyObject : DependencyObject
        {
            public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(SimpleDependencyObject), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.None, OnValueChanged, OnValueCoerce, false, UpdateSourceTrigger.PropertyChanged));
            public int Value
            {
                get { return (int)this.GetValue(ValueProperty); }
                set { this.SetValue(ValueProperty, value); }
            }
            private static object OnValueCoerce(DependencyObject d, object basevalue)
            {
                int orgValue = (int)basevalue;
                int newValue = orgValue < 0 ? 0 : orgValue > 99 ? 99 : orgValue;
                Debug.WriteLine("{0:HH:mm:ss.ffff}  coerce {1} to {2}, old value: {3}", DateTime.Now, orgValue, newValue, ((SimpleDependencyObject)d).Value);
                return newValue;
            }
            private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                Debug.WriteLine("{0:HH:mm:ss.ffff}  change {1} to {2}", DateTime.Now, e.OldValue, e.NewValue);
            } } }





    Output:

    13:58:11.8023  coerce 4 to 4, current value: 0
    13:58:11.8033  change 0 to 4
    13:58:11.8043  oldValue 0, proposed Value: 4, new effective Value: 4, coerced? False, prop changed? True

    13:58:11.8043  coerce 111 to 99, current value: 4
    13:58:11.8043  change 4 to 99
    13:58:11.8053  oldValue 4, proposed Value: 111, new effective Value: 99, coerced? True, prop changed? True

    13:58:11.8053  coerce 99 to 99, current value: 99
    13:58:11.8053  oldValue 99, proposed Value: 99, new effective Value: 99, coerced? False, prop changed? False

    13:58:11.8063  coerce 98 to 98, current value: 99
    13:58:11.8063  change 99 to 98
    13:58:11.8073  oldValue 99, proposed Value: 98, new effective Value: 98, coerced? False, prop changed? True

    13:58:11.8073  coerce 99 to 99, current value: 98
    13:58:11.8073  change 98 to 99
    13:58:11.8083  oldValue 98, proposed Value: 99, new effective Value: 99, coerced? False, prop changed? True

    13:58:11.8083  coerce 101 to 99, current value: 99
    13:58:11.8083  oldValue 99, proposed Value: 101, new effective Value: 99, coerced? True, prop changed? False


    So any time the value returned by the coersion equals the current value of "Value"
    no property change occurs (Just 2 output lines in the demo)

    But I fear I still misunderstood you :-(

    Chris




    Tuesday, August 14, 2012 11:28 AM

All replies

  • Set Binding.Mode to OneWay. Else if possible share your code. 

    Gaurav Khanna | Microsoft VB.NET MVP

    Monday, August 6, 2012 8:30 AM
  • Copy Paste it and check out::

    cs:

                    

    public partial class Window1 : Window
        {
            public static readonly DependencyProperty ValueProperty;
            public Window1()
            {
                InitializeComponent();
                DataContext = this;
            }

            static Window1()
            {
                PropertyMetadata metaData = new PropertyMetadata("0");
                ValueProperty = DependencyProperty.Register("Value", typeof(string), 
                    typeof(Window1), metaData);
            }

            public string Value
            {
                get
                {
                    return (string)GetValue(ValueProperty);
                }
                set
                {
                    SetValue(ValueProperty, value);
                }

            }


        }

    xaml:

     <Grid>
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Height="40" VerticalAlignment="Top" Margin="10" />
                <Slider Minimum="0" Ticks="1" Maximum="100" Value="{Binding Value, Mode=TwoWay}" VerticalAlignment="Top" Width="100" Margin="10" />
            </StackPanel>
        </Grid>

    __________________________________________________________________

    Mark as answer if it helps..

    Monday, August 6, 2012 1:23 PM
  • Hello,

    Try set textbox text value  in control template  with   templatebinding like...

    <ControlTemplate>

    .....

    <TextBox Text="{TemplateBinding Property=Value}" />

    Regards...


    • Edited by MDinc Monday, August 6, 2012 7:06 PM
    Monday, August 6, 2012 7:06 PM
  • Hi 

    Here is my code

    1. My default style code mentioned in the custom control library

                                                    

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CustomControlLibrary"
        x:Class="NumericBox">
        <Style TargetType="{x:Type local:NumericBox}" >        
            <Setter Property="Template">         
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:NumericBox}">
                        <Grid Margin="3">
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>    

                            <TextBox x:Name="EditBox"  Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=Value, UpdateSourceTrigger=PropertyChanged}" 
                                     TextAlignment="Right"  Padding="5" Grid.Column="0" Grid.Row="0"  />

                            <Slider x:Name="Slider" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay}" 
                                    Minimum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Min, Mode=TwoWay }"  
                                    Maximum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Max, Mode=TwoWay}" 
                                    Grid.Column="1" Grid.Row="0"></Slider>

                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

    2. My code behind in the custom control library

                                                                 

    using System;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.Windows.Media.Imaging;
    using System.Diagnostics;


    namespace CustomControlLibrary
    {
        [TemplatePart(Name = "EditBox", Type = typeof(TextBox))]
        [TemplatePart(Name = "Slider", Type = typeof(Slider))]
        public class NumericBox : TextBox
        {
            static NumericBox()
            {

                DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericBox), new FrameworkPropertyMetadata(typeof(NumericBox)));
            }


            #region Properties
            #region Value
            public int Value
            {
                get { return (int)GetValue(ValueProperty); }
                set { SetValue(ValueProperty, value); }
            }

            public static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register(
                    "Value", typeof(int), typeof(NumericBox),new FrameworkPropertyMetadata(10,FrameworkPropertyMetadataOptions.AffectsMeasure,

                        new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValue)
                       ));





     private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
          {


              obj.CoerceValue(MaxProperty);
              obj.CoerceValue(MinProperty);           
            }

     private static object CoerceValue(DependencyObject element, object value)
     {
         int newValue = (int)value;
         NumericBox control = (NumericBox)element;

         if (newValue < control.Min)
         {
             newValue = control.Min;
         }
         else if (newValue > control.Max)
         {
             newValue = control.Max;
         }

         return newValue;
     }

            #endregion

            #region Min
            public int Min
            {
                get { return (int)GetValue(MinProperty); }
                set { SetValue(MinProperty, value); }
            }

            public static readonly DependencyProperty MinProperty =
                DependencyProperty.Register(
                    "Min", typeof(int), typeof(NumericBox), new FrameworkPropertyMetadata(0, 
                        new PropertyChangedCallback(OnMinimumChanged)));

            private static void OnMinimumChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
            {
                element.CoerceValue(MaxProperty);
                element.CoerceValue(ValueProperty);
            }
            //private static object CoerceMinimum(DependencyObject element, object value)
            //{
            //    //int minimum = (int)value;
            //    //NumericBox control = (NumericBox)element; 
            //    //return minimum;
            //}    


             #endregion
            #region Max
            public int Max
            {
                get { return (int)GetValue(MaxProperty); }
                set { SetValue(MaxProperty, value); }
            }

            public static readonly DependencyProperty MaxProperty =
                DependencyProperty.Register(
                    "Max", typeof(int), typeof(NumericBox), new FrameworkPropertyMetadata(99, new PropertyChangedCallback(OnMaximumChanged),
                        new CoerceValueCallback(CoerceMaximum)));

            private static void OnMaximumChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
            {
                element.CoerceValue(ValueProperty);
            }

            private static object CoerceMaximum(DependencyObject element, object value)
            {
                NumericBox control = (NumericBox)element; 
                int newMax = (int)value;
                return (Math.Max(newMax, control.Min));
            }

            #endregion


            #endregion




            public override void OnApplyTemplate()
            {
                //base.OnApplyTemplate();
                //TextBox editbox = base.GetTemplateChild("EditBox") as TextBox;
                //Slider slider = base.GetTemplateChild("Slider") as Slider;

                //if (editbox != null )
                //{               
                //    //editbox.Text = Value.ToString();
                //    slider.Minimum = Min;
                //    slider.Maximum = Max;  

                //}

            }
        }
    }

    3. My main window with overriding style and the inheritance of custom control.

    <Window x:Class="NumericBoxWithRangSelector.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:cc="clr-namespace:CustomControlLibrary;assembly=CustomControlLibrary"
            xmlns:uc="clr-namespace:UserControlLibraryTest;assembly=UserControlLibraryTest"

            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <Style x:Key="Resource" TargetType="{x:Type cc:NumericBox}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type cc:NumericBox}">
                            <Grid Margin="3">
                                <Grid.RowDefinitions>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition/>                              
                                </Grid.ColumnDefinitions>

                                <TextBox x:Name="EditBox"  Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, UpdateSourceTrigger=PropertyChanged}"  
                                         TextAlignment="Right" Padding="5" Grid.Column="0" Grid.Row="0" />
                                <Slider x:Name="Slider" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}" 
                                        Minimum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Min }"  
                                        Maximum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Max}" 
                                        Grid.Column="0" Grid.Row="1"></Slider>

                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Window.Resources>

        <Grid>
            <cc:NumericBox   HorizontalAlignment="Stretch" VerticalAlignment="Top"></cc:NumericBox>
            <cc:NumericBox   Style="{StaticResource Resource}" HorizontalAlignment="Stretch" VerticalAlignment="Center"></cc:NumericBox>
            <!--<uc:UserControl1></uc:UserControl1>-->
        </Grid>
    </Window>

    Please have a look and let me know asap

    Regards

    Harsha

                                                                        
    Tuesday, August 7, 2012 4:36 AM
  • Hi MDinc,

        Actually i am using templated parent resource method for text binding because my main aim is to achieve default style using custom control library.So can you help me with that? Even i want to know tat i could achieve the same using template binding also? 

    Regards

    Harsha

    Tuesday, August 7, 2012 4:40 AM
  • Hello,

    Templated Binding and TemplatedParent achives the same result. But templateBinding is more efficent.

    http://msdn.microsoft.com/en-us/library/system.windows.templatebindingextension(v=vs.100).aspx

    http://msdn.microsoft.com/en-us/library/system.windows.data.relativesource.templatedparent.aspx


    Why did you derive your control from textbox? Derive from Control or TextboxBase



    Tuesday, August 7, 2012 7:56 AM
  • Hi Harsha,

    I test the code that you post here, and it works fine in my side.

    For your first question, " My aim is when the Value dependency changes it should be reflected in the text box. This is happening only at the first time nt on the second can any one help me out in this please."

    When the number lesser than 0 or more than 99, then TextBox's text will turn to 0 or 99, TextBox's text indeed change when DP Value is being changed.

    For your second question, you can't use templatebinding here to achieve your goal because of TemplateBinding is in oneway mode in default. For your reference:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0bb3858c-30d6-4c3d-93bd-35ad0bb36bb4/ And http://stackoverflow.com/questions/5913176/in-wpf-why-doesnt-templatebinding-work-where-binding-does

    Hope it helps.

    Have a nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    Tuesday, August 7, 2012 8:32 AM
  • hi  Annabella Luo,

        In the post which you have referred he quoted like

    TemplateBinding is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with{Binding RelativeSource={RelativeSource TemplatedParent} Mode=OneWay}. The TemplateBinding transfers data from the templated parent to the property that is template bound. If you need to transfer data in the opposite direction or both ways, create a Binding with RelativeSource of TemplatedParent with the Mode property set to OneWayToSource or TwoWay

    But in my binding i have already made the  mode to two way that means it has to send the data both the ways.This is happening till the value goes out of range. Once it goes out of range if u insert any value inbetween ur no (ex 919) its changing the value properties value but not reflecting it on the text box. i could not understand this behavior.

    Please verify the code of generic file which is posted at the top. in override style i removed mode property to check some other behavior.

     

    Regards

    Harsha D 



    • Edited by Harsha Bhat Tuesday, August 7, 2012 9:30 AM
    Tuesday, August 7, 2012 9:24 AM
  • Hi Bhat,

    "Once it goes out of range if u insert any value inbetween ur no (ex 919) its changing the value properties value but not reflecting it on the text box."

    Do you mean when the number inputed in Textbox is beyoung the particular range, the Text of TextBox didn't change it's value? But I test your code in my side and this part works fine. If I misunderstood you, please feel free to let me know.

    Thank you.

    Have a nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    Tuesday, August 7, 2012 9:59 AM
  • Hi Annabella Luo,

        I guess you are checking for the first reset.Let me explain the scenario

    - Initial at the first load default value is 10 add 0 i.e now the value is 100

    - the whole logic runs fine and the value will be reset to 99. Till now i am OK with the logic

    - Now value is 99. try to insert 1 between two 9's i.e, now value is 919.

    - here my logic runs as in the first time and goes out. It should populate the reset value to 99 but it dont do that. Text box wont get updated. It shows 919. Please try this once.Regards

    Regards

    Harsha


    Harsha Bhat

    Tuesday, August 7, 2012 10:06 AM
  • Hi Bhat,

    I just can't repro the issue, in my side once I inset the 1 between two 9's, the Textbox trun out 99 as the result at once. Even I can't see a 919 in textbox, because the value change back to 99 immediately. Also in your code I didn't find any thing can lead this kind of situation. Can you repro this issue? And do I understand this("Now value is 99. try to insert 1 between two 9's i.e, now value is 919.") correctly? 

    Wait for your voice.

    Have a nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    Friday, August 10, 2012 6:42 AM
  • Hi Annabella Luo,

        As i told u earlier it resets at the first. My problem is after resetting if you insert another Number (between/at the end), its not resetting. That is... Its resetting only once..... Please check with updatesourcetrigger to propertychanged.

    please try resetting once then insert another no and try. Let me know the result. I am really happy abt your keen interest and help. I really admire it.

    Regards

    Harsha


    Harsha Bhat

    Friday, August 10, 2012 11:40 AM
  • I'd like to assist Annabel that your code works fine here on my side too.
    The coercevalue callbacks work properly with either of your two controltemplates.
    Also multiple times. They don't destroy the binding to the Value DP.
    The only modification I did, is that I did not use the x:Class
    attribute for the default control template, but rather moved to code
    to Themes\Generic.xaml - shouldn't make any difference.

    Chris


    EDIT:
    Sorry now I see what you meant. If you enter e.g. 199 for the first it's reset to 99,
    if you then enter another numer out of range immediately afterwards, while the textbox still has the focus - nothing happens.
    That's odd, seem the coerce callback a/o propchanged callbacks don't fire.
    Friday, August 10, 2012 12:22 PM
  • Hi Chris,

       Please try resetting once(may be max) after that try to alter the value without closing the window. I am not getting any resetting from the second time. I have put the break points in the code its working absolutely fine but its not replicating that result in the textbox.Please try the scenario once.

    Regards

    Harsha


    Harsha Bhat

    Friday, August 10, 2012 12:30 PM
  • Yes I noticed it just 3 minutes after posting.
    The 2nd change invokes the CoerceValueCallback,
    but not the PropertyChangedCallback of the "Value" DP.
    Strange.



    Chris


    Friday, August 10, 2012 12:40 PM
  • Hi

    Found the explanation (not the solution):

    I think the reason the textbox won't get reset for the 2nd time,
    is that the value of "Value" actually stays the same after the 2nd
    coercion!

    If "Value" is 10 and you enter 111 its coerced to 99,
    followed by an update of the DP to 99 -
    it'll trigger the PropertyChangedCallback and
    and finally reset the Textbox in TwoWay-binding.

    If you then enter 1111 its coerced to 99 again but "Value" is already 99,
    so no PropertyChanged gets fired and the TextBox will not get reset
    because no update of the source is reported.
    So it's kindof "by design".

    Chris
    Friday, August 10, 2012 12:59 PM
  • Hi Chris,

         You can paste the same code directly assigning the values to the property their every time value changes it triggers property changed callback (for this you have to omit coercevalue callback) the value will be reset every time but it wont be replicated in the textbox even though these both are binded via two way binding. So is this a CANNOT DO thing in WPF?

    Regards

    Harsha

      


    Harsha Bhat

    Monday, August 13, 2012 4:03 AM
  • Hi Harsha

    I don't want to disagree to your observations and do confirm that the behaviour is dissatisfaying.
    I just want to note that to my observations, the PropertyChangedCallback will not fire
    after the second manual edit of the TextBox - here on my system using the your code with C&P.
    When Value is already 99 and the new value entered is again > Max, the CoerceValueCallback returns 99 again and Value is assigned 99 again.
    The "change" from 99 to 99 doesn't fire the callback, just because the data didn't change.
    This is just my explanation to what I think is going on under the hood.
    If the binding doesn't report a source change the targets input remains unchanged, which under most circumstances is a desired behaviour - in this scenario of course it's irritating and appears buggy.
    I tried to fix it using textBox.GetBindingExpression(TextBox.TextProperty).UpdateTarget().
    However it didn'thelp.

    Chris
    Monday, August 13, 2012 1:03 PM
  • Hi Chris,

       Its not like that chris, i was trying to tell my another observation,, i.e, what if we paste the entire code in property changed callback and trying to assign the values directly (like class.value = max). In this case u can clearly see that the property value is changing. according to logic suggested and depenndency property, when the text content changes the bounded dependency value should change and when dependency value change then textbox content should change. My main question is why is this not performing that way? are am i missing something crucial here?

    Regards

    Harsha


    Harsha Bhat

    Tuesday, August 14, 2012 4:06 AM
  • Hi Bhat

    Seems I don't really understand, what your concern is.
    My previous posts all related to your original code (except a subtle nuance with the way the template is linked to the code behind, which has zero effect on the issue discussed here, for sure).
    Basically it is correct, that assigning a value to a DP will trigger a change notification, which will be reflected by a bound control (the target is updated).
    However since DPs are more complex than plain CLR-properties, the value that is about to be assigned first
    goes through the coersion callback(s) if there is (at least) one. The final value returned by the coersion call back(s) is the one
    that will be effectively assigned to the internal store of the DP. If this value is the same as the one already assigned
    then nothing happens, no change, so no notification, so the control input remains unchanged as well.
    So if we have coersion

    obj.Value = 23444;
    might effectively assign the value "99" to "obj.Value"

    A little Demo project for demo purpose:

    Create a new default WPF App "WPFApplication2", remove MainWindow.xaml(.cs)
    add a new class: SimpleDependencyObject.cs
    So WPFApplication2 contains these code files:

    App.xaml
    App.xaml.cs
    SimpleDependencyObject.cs

    App.xaml:

    <Application x:Class="WpfApplication2.App"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />


    App.xaml.cs:

    using System;
    using System.Diagnostics;
    using System.Windows;
    
    namespace WpfApplication2
    {
        public partial class App : Application
        {
            private SimpleDependencyObject _sdo;
    
            protected override void OnStartup(StartupEventArgs e)
            {
                _sdo = new SimpleDependencyObject();
    
                TryChange(4);   //fires BOTH Coerce AND PropChange;
                TryChange(111); //fires BOTH Coerce AND PropChange;
                TryChange(99);  //fires ONLY Coerce but NOT PropChange;
                TryChange(98);  //fires BOTH Coerce AND PropChange;
                TryChange(99);  //fires BOTH Coerce AND PropChange;
                TryChange(101); //fires ONLY Coerce but NOT PropChange;
    
                Shutdown();
    
            }
            private void TryChange(int newValue)
            {
                int oldValue = _sdo.Value;
                _sdo.Value = newValue;
                Debug.WriteLine("{0:HH:mm:ss.ffff}  oldValue {1}, proposed Value: {2}, new effective Value: {3}, coerced? {4}, prop changed? {5}\n", DateTime.Now, oldValue, newValue, _sdo.Value, newValue != _sdo.Value, oldValue != _sdo.Value);
            }
        }
    }
    

    SimpleDependencyObject.cs


    using System;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Data;
    
    namespace WpfApplication2
    {
        public class SimpleDependencyObject : DependencyObject
        {
            public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(SimpleDependencyObject), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.None, OnValueChanged, OnValueCoerce, false, UpdateSourceTrigger.PropertyChanged));
            public int Value
            {
                get { return (int)this.GetValue(ValueProperty); }
                set { this.SetValue(ValueProperty, value); }
            }
            private static object OnValueCoerce(DependencyObject d, object basevalue)
            {
                int orgValue = (int)basevalue;
                int newValue = orgValue < 0 ? 0 : orgValue > 99 ? 99 : orgValue;
                Debug.WriteLine("{0:HH:mm:ss.ffff}  coerce {1} to {2}, old value: {3}", DateTime.Now, orgValue, newValue, ((SimpleDependencyObject)d).Value);
                return newValue;
            }
            private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                Debug.WriteLine("{0:HH:mm:ss.ffff}  change {1} to {2}", DateTime.Now, e.OldValue, e.NewValue);
            } } }





    Output:

    13:58:11.8023  coerce 4 to 4, current value: 0
    13:58:11.8033  change 0 to 4
    13:58:11.8043  oldValue 0, proposed Value: 4, new effective Value: 4, coerced? False, prop changed? True

    13:58:11.8043  coerce 111 to 99, current value: 4
    13:58:11.8043  change 4 to 99
    13:58:11.8053  oldValue 4, proposed Value: 111, new effective Value: 99, coerced? True, prop changed? True

    13:58:11.8053  coerce 99 to 99, current value: 99
    13:58:11.8053  oldValue 99, proposed Value: 99, new effective Value: 99, coerced? False, prop changed? False

    13:58:11.8063  coerce 98 to 98, current value: 99
    13:58:11.8063  change 99 to 98
    13:58:11.8073  oldValue 99, proposed Value: 98, new effective Value: 98, coerced? False, prop changed? True

    13:58:11.8073  coerce 99 to 99, current value: 98
    13:58:11.8073  change 98 to 99
    13:58:11.8083  oldValue 98, proposed Value: 99, new effective Value: 99, coerced? False, prop changed? True

    13:58:11.8083  coerce 101 to 99, current value: 99
    13:58:11.8083  oldValue 99, proposed Value: 101, new effective Value: 99, coerced? True, prop changed? False


    So any time the value returned by the coersion equals the current value of "Value"
    no property change occurs (Just 2 output lines in the demo)

    But I fear I still misunderstood you :-(

    Chris




    Tuesday, August 14, 2012 11:28 AM
  • I'm stuck at the same problem, and this looks like a good explanation. But still, is there any solution for the problem? Something that forces the coerced value into the text box?

    While your try at invoking UpdateTarget() or UpdateSource() (depending on the declaration direction of the binding, obviously) seems promising, I'm not sure where to insert it. Within the CoerceValueCallback it's certainly too early, as the coerced value has not been returned to the property system yet. But after that callback, there's only the PropertyChangedCallback, which is not invoked, as we're all noticing.

    So, how can we capture the very moment after coercion has just taken place to force the coerced value back into the text box?

    Thursday, December 13, 2012 9:28 PM