为什么我的数据绑定会看到真实值而不是强制值?

我正在编写一个真正的NumericUpDown/Spinner控件作为练习来学习自定义控件创作。 我有大部分的行为,我正在寻找,包括适当的强制。 然而,我的一项测试揭示了一个缺陷。

我的控件有3个依赖项属性: ValueMaximumValueMaximumValue 。 我使用强制来确保Value保持在最小值和最大值之间,包括在内。 例如:

 // In NumericUpDown.cs public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, HandleValueChanged, HandleCoerceValue)); [Localizability(LocalizationCategory.Text)] public int Value { get { return (int)this.GetValue(ValueProperty); } set { this.SetCurrentValue(ValueProperty, value); } } private static object HandleCoerceValue(DependencyObject d, object baseValue) { NumericUpDown o = (NumericUpDown)d; var v = (int)baseValue; if (v  o.MaximumValue) v = o.MaximumValue; return v; } 

我的测试只是为了确保数据绑定符合我的预期。 我创建了一个默认的wpf windows应用程序,并引入了以下xaml:

           

非常简单的代码隐藏:

 public partial class MainWindow : Window { public int NumberValue { get { return (int)GetValue(NumberValueProperty); } set { SetCurrentValue(NumberValueProperty, value); } } // Using a DependencyProperty as the backing store for NumberValue. This enables animation, styling, binding, etc... public static readonly DependencyProperty NumberValueProperty = DependencyProperty.Register("NumberValue", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0)); public MainWindow() { InitializeComponent(); } } 

(我省略了控件演示的xaml)

现在,如果我运行它,我会看到NumericUpDown的值在文本框中正确反映,但是如果我输入的值超出范围,则超出范围值将显示在测试文本框中,而NumericUpDown显示正确的值。

这是强制价值应该如何行动的? 在ui中强制它是好的,但我也期望强制值也可以通过数据绑定。

哇,这很令人惊讶。 在依赖项属性上设置值时,绑定表达式会在值强制运行之前更新!

如果查看Reflector中的DependencyObject.SetValueCommon,可以在方法的中途看到对Expression.SetValue的调用。 在绑定已经更新之后,对将调用CoerceValueCallback的UpdateEffectiveValue的调用处于最后。

您也可以在框架类上看到这一点。 从新的WPF应用程序中,添加以下XAML:

     

和以下代码:

 private void SetInvalid_Click(object sender, RoutedEventArgs e) { var before = this.Value; var sliderBefore = Slider.Value; Slider.Value = -1; var after = this.Value; var sliderAfter = Slider.Value; MessageBox.Show(string.Format("Value changed from {0} to {1}; " + "Slider changed from {2} to {3}", before, after, sliderBefore, sliderAfter)); } public int Value { get; set; } 

如果您拖动滑块然后单击按钮,您将收到一条消息,如“值从11更改为-1;滑块从11更改为10”。

旧问题的新答案:-)

ValueProperty的注册中,使用FrameworkPropertyMetadata实例。 将此实例的UpdateSourceTrigger属性设置为Explicit 。 这可以在构造函数重载中完成。

 public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), new FrameworkPropertyMetadata( 0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, HandleValueChanged, HandleCoerceValue, false UpdateSourceTrigger.Explicit)); 

现在, ValueProperty的绑定源不会在PropertyChanged自动更新。 在HandleValueChanged方法中手动执行更新(请参阅上面的代码)。 在调用强制方法之后,仅在属性的“实际”更改时调用此方法。

你可以这样做:

 static void HandleValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { NumericUpDown nud = obj as NumericUpDown; if (nud == null) return; BindingExpression be = nud.GetBindingExpression(NumericUpDown.ValueProperty); if(be != null) be.UpdateSource(); } 

通过这种方式,您可以避免使用DependencyProperty的非强制值更新绑定。

你正在强制v这是一个int和这样的值类型。 因此它存储在堆栈中。 它绝不与baseValue连接。 所以改变v不会改变baseValue。

同样的逻辑适用于baseValue。 它是通过值(而不是通过引用)传递的,因此更改它不会更改实际参数。

返回v并且显然用于更新UI。

您可能希望调查将属性数据类型更改为引用类型。 然后它将通过引用传递,所做的任何更改都将反映回源。 假设数据绑定过程不创建副本。