带有强制值回调的依赖属性的行为显得有些异常
我现在正在为WPF的 DependencyProperties苦恼。 完整代码可以在这里找到:https://github.com/JYPDWhite/WPFValueCorrectionTest
在这段代码里,我有两个我还没想清楚如何修复的问题。
代码包含一个 OwnControl,它派生自 UserControl,其中包含一个DependencyPropertyNumber,也就是 NumberProperty。
有一个 coerceValueCallback 已注册,当给定的值大于5 时会把数字强制转换为 0。
public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
nameof(Number),
typeof(int),
typeof(OwnControl),
new FrameworkPropertyMetadata(
defaultValue: 2,
flags: FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
propertyChangedCallback: PropertyChangedCallback,
coerceValueCallback: CoerceNumberValue));
private static object CoerceNumberValue(DependencyObject d, object baseValue)
{
OwnControl depObject = (OwnControl)d;
var newValue = (int)baseValue;
var correctedNumber = depObject.CheckNumber(newValue);
return correctedNumber;
}
public int Number
{
get { return (int)GetValue(dp: NumberProperty); }
set { SetValue(dp: NumberProperty, value); }
}
private int CheckNumber(int newValue)
{
if (newValue < 5)
{
return newValue;
}
return 0;
}
被强制转换后的值被正确地保存在 OwnControl,为了把修正传播回视图模型,我使用这个 propertyChangedCallback,在其中通过BindingOperations更新绑定。
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!Equals(e.OldValue, e.NewValue))
{
d.SetCurrentValue(dp: NumberProperty, e.NewValue);
d.Dispatcher.BeginInvoke(new Action(() =>
{
var be = BindingOperations.GetBindingExpression((OwnControl)d, dp: NumberProperty);
if (be == null)
{
Debug.WriteLine("Istnull");
}
be?.UpdateSource();
}), DispatcherPriority.ContextIdle);
}
}
看起来效果很好。如果我把 2 作为输入,ViewModel 和 OwnControl 得到正确的数值。然后我输入 5,这个值会被强制转换,变化在 OwnControl 和 ViewModel 中得到反映。
但是当我现在输入例如 6(在输入字段中替换 0)时,我看到强制转换确实被执行,OwnControl 得到正确的值 0。
但调试器并没有经过 PropertyChangedCallback,因此在 ViewModel 6 仍然是存储的值。
问题1:为什么在我多次输入一个“错误”的数字时,强制转换后的值的更新会失败?
问题2:在 DependencyProperty.Register 中,defaultValue 被设为 2,但在启动时我在调试器看到,当在Breakpoint在 PropertyChangedCallback 处击中时,初始值被设为 2,但在调试器在 PropertyChangedCallback 处第一次命中时,oldValue 为2,newValue 为0。这个 0 来自哪里?
解决方案
你把属性的有效表示与源属性的值混淆了。 例如,你有RangeBase.Value绑定到一个值为15的源。但是RangeBase.Maximum = 10。 无论源值是多少,RangeBase.Value的值都是10。 但现在把RangeBase.Maximum改成20。RangeBase.Value的值会变成15。 但设想如果RangeBase.Value改变了源值会怎样?那么源值会变成10。但是即使RangeBase.Maximum变为20,它仍然是10。换句话说,视图会改变业务逻辑。
这有必要吗?有时是的,但更多时候并非如此。因此,默认实现是让这种情况不会发生。如果开发者确实需要,它也可以很容易自行实现。
下面给出你控件的一个示例。
public partial class OwnControl : UserControl
{
public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
nameof(Number),
typeof(int),
typeof(OwnControl),
new FrameworkPropertyMetadata(
defaultValue: 2,
flags: FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
propertyChangedCallback: PropertyChangedCallback));
private static async void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!Equals(e.OldValue, e.NewValue))
{
OwnControl own = (OwnControl)d;
int value = (int)e.NewValue;
int valid = own.CheckNumber(value);
if (value != valid)
{
await d.Dispatcher.BeginInvoke(new Action(() => d.SetCurrentValue(dp: NumberProperty, valid)));
}
}
}
public int Number
{
get { return (int)GetValue(dp: NumberProperty); }
set { SetValue(dp: NumberProperty, value); }
}
private int CheckNumber(int newValue)
{
if (newValue < 5)
{
return newValue;
}
return 0;
}
public OwnControl()
{
InitializeComponent();
}
}