Reputation: 1758
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?
edits in italic.
Upvotes: 0
Views: 6004
Reputation: 81253
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);
}
Upvotes: 1
Reputation: 5421
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.
Upvotes: 2