我对WPF绑定行为感到有些困惑。我在代码隐藏中绑定了一个依赖属性的滑块值(仅用于示例)。滑块的最小值在XAML中设置。加载窗口后,滑块的值设置为最小值,但依赖属性仍然具有默认值0.但是,正在调用滑块的ValueChanged回调,所以我实际上会期望更新绑定。
所以我有以下窗口,一个标签显示滑块值,另一个标签显示值绑定的属性。
<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>
以及包含依赖项属性的代码隐藏,以及在更改滑块值时只打印跟踪消息的事件回调。
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);
}
}
}
一旦移动滑块,两个值都相等。
我的问题是,当滑块值设置为最小值时,为什么依赖属性在启动时不会更新?
以斜体编辑。
答案 0 :(得分:2)
这里发生的是滑块控件的Value
属性强制,因此它位于Minimum
和Maximum
定义的范围内属性。
当加载XAML并且绑定绑定时,将读取SliderValue
零,并且WPF尝试设置Value
属性。但是,因为零不在其他属性定义的范围内,所以提供的值会被强制转换为该范围,因此Value
属性保持为10({{1}时给出的值}属性已设置)。因此,Minimum
的值保持为0,滑块的SliderValue
保持为10,正如您可以从标签绑定中看到的那样。
拖动滑块时,滑块的Value
属性会发生变化。这会导致双向绑定更新,但方向相反(从控件到依赖属性)。由于您的DP没有强制,其值将设置为滑块的值。从那时起,两者是同步的。
您可以通过向窗口添加按钮来验证此行为,然后在Value
处理程序中将OnClick
设置为滑块范围之外的内容。您将看到SliderValue
更改,但由于强制,滑块的值将设置为其最小值或最大值。
值强制是可以为依赖项属性配置的功能之一。阅读更多相关信息at MSDN。
答案 1 :(得分:1)
如果您查看Slider的ValueProperty的源代码,则会在其DP注册中提供 CoerceValueCallback
,以确保值始终保持在最小值和最大值范围内滑块的值。因此,每当绑定的source属性传递一些不在范围内的值时,它会根据源的传递值传递最小/最大值。(但它不会将值设置为source属性)。
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;
}
所以,你可以做的是在CoerceValueCallback for SliderValue属性 中使用 相同的逻辑,这样它总是与滑块值同步,但为此必须手动从值更改事件中强制滑块值。
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);
}