带有强制值回调的依赖属性的行为显得有些异常

编程语言 2026-07-10

我现在正在为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 作为输入,ViewModelOwnControl 得到正确的数值。然后我输入 5,这个值会被强制转换,变化在 OwnControlViewModel 中得到反映。 但是当我现在输入例如 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();
        }
    }
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章