为什么我的数据绑定会看到真实值而不是强制值?
我正在编写一个真正的NumericUpDown/Spinner
控件作为练习来学习自定义控件创作。 我有大部分的行为,我正在寻找,包括适当的强制。 然而,我的一项测试揭示了一个缺陷。
我的控件有3个依赖项属性: Value
, MaximumValue
和MaximumValue
。 我使用强制来确保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。
您可能希望调查将属性数据类型更改为引用类型。 然后它将通过引用传递,所做的任何更改都将反映回源。 假设数据绑定过程不创建副本。