locked
How to check range in twoway binding and How to sync source and target while source is having out of range value. RRS feed

  • Question

  • Hello,

    My problem is regarding range checking for dependency property.

    I have one user control (named TestControl) which has Value property (It is a dependency property) of type nullable DateTime.

    public static readonly DependencyProperty ValueProperty =
              DependencyProperty.Register(
                "Value",
                typeof(DateTime?),
                typeof(CoerceTestControl),
                new FrameworkPropertyMetadata(
                  (DateTime?)null,            
    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, new PropertyChangedCallback(OnValueChanged)));

    Moreover, I want that always Value property should have date between 1/1/2000 and 12/31/2010. So in PropertyChangedCallback OnValueChanged I do following thing.

    //Call back for Value property change.
    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
           TestControl datepicker = (TestControl)d;
           DateTime? oldValue = (DateTime?)e.OldValue;
           DateTime? newValue = (DateTime?)e.NewValue;
           DateTime min = new DateTime(2000,1,1);
           DateTime max = new DateTime(2010,12,31);
           //If date is less than min, then Re-adjust it to min and return from this call back.
            if (newValue != null && newValue < min)
            {
                datepicker.Value = min;
                return;
            }
            //If date is greater than max, then Re-adjust it to max and return from this call back.
            else if (newValue != null && newValue > max)
            {
                datepicker.Value = max;
                return;
            }       
            //If date is in range then update display for date on control.And do other side effects things.
            else
            {
                datepicker.tBlock.Text = newValue.ToString();
    … … … } }

     


    Now I have following class as my DataModel class. Which provides me data to bind with control.

    public class DataModel : INotifyPropertyChanged
    {
        private DateTime showDate;
        public DateTime ShowDate
       {
            get { return showDate; }
            set { showDate = value; PropertyChanged(this, new PropertyChangedEventArgs("ShowDate")); }
        }
    
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
    }
    
    

     

    Now I have one Window1 class derived from Window.

    For this class I have set datacontext as following in Window Loaded event handler.

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
           DataModel modelObj = new DataModel();
             this.DataContext = modelObj;
    }
    

     

    I have put this user control, one text box and one button on this window. And I have bound “ShowDate” property to usercontrol in twoway binding. Text box and button are used to change source’s (ShowDate property’s)vlue. Xaml is as below.

    <Window x:Class="BindingSample.Window1"
    Xmlns……………………………….
    …………………………………………> 
        <StackPanel>
               <TextBox Margin="5" Width="150" x:Name="modelDateTxtBox"/>
              <Button Margin="5" Click="Button_Click">Assign Date in Textbox to ShowDate property of DataContext of this window</Button>
               <local:TestControl Value="{Binding Path=ShowDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>                
            </StackPanel>
    </Window> 
    
    

    Button_Click handler on Window1 is as below. When it will be called it will change Source’s (ShowDate’s) value. In short Button and TextBox (modelDateTxtBox) are used on window to change Source's (ShowDate of DataContext) value. When we will press Button it will parse string in modelDateTxtBox and will assign it to ShowDate of DataContext (Source).

    private void Button_Click(object sender, RoutedEventArgs e)
    {
                //Change source’s(ShowDate’s)  value
                (this.DataContext as DataModel).ShowDate = DateTime.Parse(modelDateTxtBox.Text);
    }
    
    

    Now Problem is following.

     

    When window is opened (starting application) that time DataContext for window is set and value of ShowDate property of DataContext(this is Source) will be ‘1/1/0001’. Now Binding will update Value property(this is target) of user control as it is bound to ShowDate. So OnValueChanged callback will be called. Here this callback will see that ‘1/1/0001’ is out of range and less than min(1/1/2000) so it will change Value property to ‘1/1/2000’. Now my expectation is that as Value is changed in OnValueChanged call back ShowDate of DataContext of Window1(Source)should also be changed to ‘1/1/2000’ from ‘1/1/0001’ but it is not happening.

     

    I would like to add one more observation.

    If I change Value property(Target) from inside the control to 1/1/2050 that time ShowDate is changed to ‘1/1/2050’ then OnValueChanged call back is called which will change Value to ‘12/31/2010’ as date is greater than max. This time ShowDate(Source) is changed to ‘12/31/2010’ from ‘1/1/2050’. And both Source and Target are in sync. So here my problem is that when I set Target out of range from inside the control Source ShowDate is updated twice first time for out of range value and second time for inrange value set by OnValueChanged callback. So source(ShowDate property) and Target(Value property) both are in sync. But if I set Source(ShowDate property) out of range from window using modelDateTxtBox and Button, that time Target(Value property) is changed twice first for out of range value and second time for inrange (min or max) value set by  OnValueChanged callback but Source(ShowDate) is not updated second time. So still Source contains original out of range value set to it and Target is having inrange (min or max) value. So both are out of sync. If I set inrange value to Source, Target is also changed acoordingly and both are in sync. Problem occurs while I set Source out of range.

     

    Can any one explain why this is the behavior? And what is the resolution for such range checking scenarios?


    Sorry for this much long question.
    Regards.


    Wednesday, February 3, 2010 6:43 AM

Answers

  • Hi,

    thanks for the link, it is shedding a new light on the problem.

    So, I think first it would be necessary to decide who is responsible for the check: the control, the bound class, or a Converter, as suggested in the Connect post.

    I don't like using converters for anything but converting values, so I wouldn't do that. However, having a IValueConverter that converts any out ouf range value to the closest limit, and combining that with the CoerceValue callback, should work. (The CoerceValue callback would force the value for the control, the Converter for the bound object).

    If the control is responsible for coercing the value, the main point is to get the coercion code outside the PropertyChanged callback. In one example, I tried this using the control's Dispatcher. In the property's PropertyChanged callback, you'd use Dispatcher.BeginInvoke in order to schedule a call to a method that corrects the value if necessary. The problem with this approach though is that you have a very short but indeterminate intervall between the time the property changes and the time the value has been corrected.

    The cleanest solution in my opinion would be to make the data-bound object responsible for coercing the value in the setter of the property, adjusting it if necessary, and raising the PropertyChanged event if the value had to be adjusted.

    I am going to try this out, since I remember dimly running into some problems with that in a similar scenario. You might check whether this helps already, and I'll try to get back to you when I have a working example.
    http://wpfglue.wordpress.com
    Wednesday, February 3, 2010 10:59 AM

All replies

  • Hi,

    I think the problem is that you change the value inside the PropertyChanged callback. It seems that change notifications do not occur for value changes inside this callback, even though the value is saved to the DependencyProperty.

    If you want to coerce the date value to a specific date range, you could try using the CoerceValue callback in the DependencyProperty's PropertyMetadata instead of the PropertyChanged callback. This callback is intended especially for the kind of rule you want to implement here.
    http://wpfglue.wordpress.com
    Wednesday, February 3, 2010 7:41 AM
  • Hello,

    First of all thanks for reply.

    We used Coercion also but coerced values are not used by DataBindings. Data bindings always use pre-coerced values. So coercion did not work in this case so we tried above mentioned way.

    At following URL, For coercion Microsoft says that it is not designed for data binding scenarios. http://connect.microsoft.com/VisualStudio/feedback/details/489775/wpf-binding-expression-updates-source-before-coercevaluecallback-is-called

    Regards.
    Wednesday, February 3, 2010 8:11 AM
  • Hi,

    thanks for the link, it is shedding a new light on the problem.

    So, I think first it would be necessary to decide who is responsible for the check: the control, the bound class, or a Converter, as suggested in the Connect post.

    I don't like using converters for anything but converting values, so I wouldn't do that. However, having a IValueConverter that converts any out ouf range value to the closest limit, and combining that with the CoerceValue callback, should work. (The CoerceValue callback would force the value for the control, the Converter for the bound object).

    If the control is responsible for coercing the value, the main point is to get the coercion code outside the PropertyChanged callback. In one example, I tried this using the control's Dispatcher. In the property's PropertyChanged callback, you'd use Dispatcher.BeginInvoke in order to schedule a call to a method that corrects the value if necessary. The problem with this approach though is that you have a very short but indeterminate intervall between the time the property changes and the time the value has been corrected.

    The cleanest solution in my opinion would be to make the data-bound object responsible for coercing the value in the setter of the property, adjusting it if necessary, and raising the PropertyChanged event if the value had to be adjusted.

    I am going to try this out, since I remember dimly running into some problems with that in a similar scenario. You might check whether this helps already, and I'll try to get back to you when I have a working example.
    http://wpfglue.wordpress.com
    Wednesday, February 3, 2010 10:59 AM
  • Hello hbrack,

    Thank you for reply.

    ----> I think using convertor I will not be able to re-adjust the source. If it is possible please give me direction.

    -----> Using Dispatcher.BeginInvoke is good but I would like to try it last if no other solution is there.

    ------> And I also feel that problem here is, changing property agin in side PropertyChanged callback. But that only happens when chain of property changes is started by Source (When Data Model calss's ShowDate property chnages and its value is out of range.). If chain is started by Target(Value property on control) and though Target is out of range at last both Target and Source comes in Sync.

    ---------> Moreover in our case we have custom control and it can be used by anyone. So responsibility of range checking is of control. So We can not make data-bound object responsible for coercing the value because, Then it will be work of user of the control. But we can not force user to do so.

    Please direct me if you find something good to resolve this.

    Moreover I would like to know how this Range-Checking scenarios are handled in WPF/Data Binding world? Because this is common scenario we can encounter in DatePickerControls, Spinner(Number) controls etc. Has microsoft some way for this?

    Once again thanks for your reply and your suggestions.

    Regards.

    Thursday, February 4, 2010 2:27 PM
  • Hi,

    in a data binding application, the job of checking for valid values is clearly with the object that is data bound, since this object knows what values it can use, while the control doesn't know anything about that. This (solution number three) is the solution I tried out, and it works. I didn't get very far with the other approaches, yet.

    If you want to write a control which includes this kind of check, that's a different story. I have tried creating a UserControl which exposes a Value property, and does the value coercion between the TextBox it contains and this Value property. However, the problem here is that depending on whether you change the object's property, the control's Value property or the TextBox's Text property the coercion is applied or not. Unfortunately, I didn't have the time to solve this completely, yet.
    http://wpfglue.wordpress.com
    Sunday, February 7, 2010 8:45 AM
  • Hello hbrack,

    Thank you for the reply.

    But our case is that we have one custom control for Date selection with dependency property Value(of type DateTime). And we can set MAX(suppose 1/1/2000) and Min(1/1/2010) date for this control. Now suppose some data object with property ShowDate (of type DateTime) is bound to this control's Value proeprty and it contains date as 1/1/1990. 1/1/1990 is out of range. So inside control we want to adjust it in range so we set Value property as 1/1/2000 (min). But at the same time we also want to update date contained in ShowDate proeprty of source to 1/1/2000 from 1/1/1990.

    Now as this is custom control we can not expect Data bound Object or its property (ShowDate) to do range checking. Moreover Min and Max date values are inside control they are not with Data Bound object or its property(ShowDate).

    To solve this scenario first we used coercion but it did not work. Coercion adjusted Value property proeprly but it could not adjust ShowDate property of source. Moreover Microsoft tells that Coercion is not for Data Binding scenarios and Bindings will always use pre-coerced values. Then we tried to readjust Value property from OnValueChanged call back of property changed callback (we removed coercion call back this time). Here also it did not re-adjust source's ShowDate property to 1/1/2000 from 1/1/1990.

    I also tried Dispatcher.BeginInvoke. It worked because it got "changing Value property thing" out from OnValueCgannged call back. But as you told there will be indeterministic interval between OnValueChanged callback and Re-adjustment.

    So in short we are not able to find cleaner way to Re-adjust source while it is out of range. In short we are not able to acheive following

    Set ShowDate to 1/1/1990 ---> Binding will set Value property of control to 1/1/1990 -----> Range Checking logic will check that 1/1/1990 is less than Min (1/1/2000) ----> So Adjust Value property to 1/1/2000 from 1/1/1990 -----> Now Source ShowDate is having value 1/1/1990 and Value property is having value 1/1/2000 and both are out of sync so we want Source ShowDate also be set to 1/1/2000.

    So last step (Now Source ShowDate is having value 1/1/1990 and Value property is having value 1/1/2000 and both are out of sync so we want Source ShowDate also be set to 1/1/2000.) in above chain is not achieved by either Coercion or re-adjusting Value property in OnValueChanged call back. Dispatcher.BeginInvoke works but with that indetrministic interval thing.

    Can we have more cleaner way. Has Microsoft considered this type of scenarios?

    Regards.
    Monday, February 8, 2010 6:12 AM