0
votes

I'm somewhat confused about WPF binding behavior. I've got a sliders value bound to a dependency property in code-behind (just for the example). The Minimum value of the slider is set in XAML. After the window is loaded, the value of the slider is set to the minimum, but the dependency property still has the default value of 0. However, the ValueChanged callback of the slider is being called, so I would actually expect the binding to be updated.

So I have the following window, one label shows the slider value, the other the property the value is bound to.

    <Window x:Class="SliderBinding.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" x:Name="window">
        <StackPanel DataContext="{Binding ElementName=window}">
            <Slider Minimum="10" Maximum="100" x:Name="slider" Value="{Binding SliderValue, Mode=TwoWay}" ValueChanged="Slider_OnValueChanged"/>
            <Label Content="{Binding Value, ElementName=slider}"></Label>
            <Label Content="{Binding SliderValue}"></Label>
        </StackPanel>
    </Window>

and the code-behind which contains the dependency property and the event callback which simply prints a trace message when the slider value is changed.

    using System;
    using System.Diagnostics;
    using System.Windows;

    namespace SliderBinding
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {

            public MainWindow()
            {
                InitializeComponent();
            }

            /* Sorry, wrong code
            public double SliderValue
            {
                get; set;
            }*/

            public double SliderValue
            {
                get { return (double)GetValue(SliderValueProperty); }
                set { SetValue(SliderValueProperty, value); }
            }

            public static readonly DependencyProperty SliderValueProperty =
                DependencyProperty.Register("SliderValue", typeof(double), typeof(MainWindow), new PropertyMetadata(default(double)));


            private void Slider_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
            {
                Trace.TraceInformation("Slider value changed to {0}", e.NewValue);
            }
        }
    }

As soon as I move the slider, both values are equal.

My question is, why is the dependency property not updated on startup, when the slider value is set to its minimum?

see hereedits in italic.

2
Please implement INotifyPropertyChanged correctly for SliderValue . Your label that is bound to that value will not work correctly if you don't.nvoigt
Ah, sorry, my mistake. I was using a dependency property in the first place and replaced it because I thought this might work better...Andreas Duering

2 Answers

2
votes

What is happening here is that the Value property of the slider control is coerced so that it lies in the range defined by the Minimum and Maximum properties.

When the XAML is loaded and the bindings are wired-up, your SliderValue of zero is read, and WPF attempts to set the Value property. However, because zero does not lie in the range defined by the other properties, the supplied value is coerced into that range instead, and thus the Value property remains at 10 (the value it was given when the Minimum property was set). Thus the value of SliderValue remains at 0 and the Value of the slider remains at 10, as you can see from the label bindings.

When you drag the slider thumb, the slider's Value property changes. This causes the two-way binding to be updated, but going in the opposite direction (from the control to your dependency property). As there is no coercion on your DP, its value gets set to that of the slider. From that point, the two are in-sync.

You can verify this behaviour by adding a button to your window, and then in the OnClick handler setting the SliderValue to something outside of the range of the slider. You will see that the SliderValue changes, but due to coercion the value of the slider gets set to its minimum or maximum value.

Value coercion is one of the features that can be configured for a dependency property. Read more about it at MSDN.

1
votes

If you look into the source code of ValueProperty of Slider, in its DP registration CoerceValueCallback is provided which make sure that value always remains in bounds within the minimum and maximum values of slider. So, whenever bound source property passes some value which is not in range, it passes the min/max value dependent on passed value from source. (But it doesn't set back the value to source property).

    public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(
                    "Value",
                    typeof(double),
                    typeof(RangeBase),
                    new FrameworkPropertyMetadata(
                            0.0d,
                            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | 
                            FrameworkPropertyMetadataOptions.Journal,
                            new PropertyChangedCallback(OnValueChanged),
                            new CoerceValueCallback(ConstrainToRange)),
                    new ValidateValueCallback(IsValidDoubleValue));

    internal static object ConstrainToRange(DependencyObject d, object value)
    {
        RangeBase ctrl = (RangeBase) d;
        double min = ctrl.Minimum;
        double v = (double) value;
        if (v < min)
        {
            return min;
        }

        double max = ctrl.Maximum;
        if (v > max)
        {
            return max;
        }

        return value;
    }

So, what you can do is have the same logic in CoerceValueCallback for SliderValue property so that it always be in sync with slider value but for that you have to manually Coerce slider value from value changed event.

public static readonly DependencyProperty SliderValueProperty =
    DependencyProperty.Register("SliderValue", typeof(double), typeof(MainWindow),
    new PropertyMetadata(default(double), null, CoerceSliderValue));


private static object CoerceSliderValue(DependencyObject d, object value)
{
    // Of course good idea would be to be have min/max as CLR properies
    // in your class and bind slider with those values.
    // So, that you can have refer to those values directly here.

    double min = ((MainWindow)d).slider.Minimum;
    double max = ((MainWindow)d).slider.Maximum;
    double passedValue = (double)value;

    if (passedValue < min)
    {
        return min;
    }
    else if (passedValue > max)
    {
        return max;
    }
    return passedValue;
}

private void Slider_OnValueChanged(object sender, 
                                   RoutedPropertyChangedEventArgs<double> e)
{
    CoerceValue(SliderValueProperty);
}