当绑定的属性';s值不变

本文关键字:绑定 属性 | 更新日期: 2023-09-27 18:20:14

我的应用程序使用MVVM体系结构,ViewModel对视图一无所知。当ViewModel对象需要显示新视图时,它会公开一个公共ShowNewView属性,该属性是一个类基于我的ViewModel基类的对象。WPF视图将自定义DependencyProperty绑定到它,并使用PropertyChangedCallback来构造和显示相应的窗口。

第一次设置ShowNewView属性时,这一切都很好。但是,如果用户关闭窗口,然后尝试重新打开它,则在引发PropertyChanged事件并且未调用PropertyChangedCallback时,ShowNewView属性的值不会更改。

为了"欺骗"DependencyProperty检测值是否已更改(即使存储在ViewModel属性中的值可能实际上没有更改),我使用了Window类公开的SetCurrentValue方法将DependencyProperties的值强制为null

#region ShowNewViewProperty
private static readonly DependencyProperty _ShowNewViewProperty =
    DependencyProperty.RegisterAttached
    (
        "ShowNewView",
        typeof(IRootViewModel),
        typeof(WpfViewWindow),
        new PropertyMetadata(ShowNewViewPropertyChanged)
    );
    public static DependencyProperty ShowNewViewProperty { get { return _ShowNewViewProperty; } }
    public static IRootViewModel GetShowNewView(Window source)
    {
        return (IRootViewModel)source.GetValue(ShowNewViewProperty);
    }
    public static void SetShowNewView(Window target, IRootViewModel value)
    {
        target.SetValue(ShowNewViewProperty, value);
    }
    private static void ShowNewViewPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        WpfViewWindow window = d as WpfViewWindow;
        IRootViewModel newValue = e.NewValue as IRootViewModel;
        if ((null != window) && (null != newValue))
        {
            // Create a child WpfViewWindow.  This method is part of my
            // framework that uses ResourceDictionary entries, imported by MEF
            // to locate the View class corresponding to the ViewModel parameter's
            // class.
            WpfViewWindow modelessWindow = window.CreateWpfViewWindow(newValue);
            if (null != modelessWindow)
            {
                // Show the new WpfViewWindow.
                modelessWindow.Show();
            }
            // Clear the current value so that the next PropertyChanged event
            // is processed even if the underlying value has not actually changed.
            window.SetCurrentValue(ShowNewViewProperty, null);
        }
    }
    #endregion

从技术上讲,这是有效的,因为它会导致在PropertyChanged事件触发时运行回调,而不管值是否实际更改。但是,它会导致每次更新ViewModel的属性时(递归地)调用回调两次:一次是响应ViewModel的事件,一次是响应该调用的SetCurrentValue方法。

这里有许多问题与PropertyChangedCallback在其他情况下未被调用或未被多次调用有关。

  • DependencyProperty Only Fireing Once上的PropertyChangedCallback涵盖了属性是集合并且集合内容发生更改,但集合本身不发生更改的情况。然而,我的财产并不是一个收藏品,一切都完全按照记录进行
  • 当PropertyChanged被激发时,WPF依赖属性setter没有被激发,但源值没有被改变,这看起来很有希望,但答案只是建议使用我已经使用的回调

有没有一种更优雅的方法可以实现这一点,而不会导致ViewModel中每个PropertyChanged事件的回调运行两次?也就是说,有没有办法绕过框架的检查来验证新旧价值观的不同?

澄清

正在创建的视图不一定总是一个WPF窗口,例如,在我的单元测试中,它是一个mock,在项目的后期,它可能是一个单独的日志记录程序集。也不是来自同一程序集的所有ViewModel对象,众所周知,未来将需要额外的功能,但具体细节目前尚未定义。该应用程序允许用户通过简单的网络连接设备。最初的网络是RS-485上的ModbusRTU,然而,最终客户可能希望使用CANOpen或Profinet或其他传输层,我必须提供一种插件机制,允许在不更改现有代码的情况下添加新功能。

公平地说,我可以使用几种替代机制来实现相同的结果(即让ViewModel请求创建一个新视图),但我很想知道是否有办法让DependencyProperty"忘记"它以前的值。

当绑定的属性';s值不变

这类问题的常见解决方案是从ShowNewViewPropertyChanged方法中提取代码,并将其放入不同的方法中:

private void SomeNewMethod(IRootViewModel newValue)
{
    // Create a child WpfViewWindow.  This method is part of my
    // framework that uses ResourceDictionary entries, imported by MEF
    // to locate the View class corresponding to the ViewModel parameter's
    // class.
    WpfViewWindow modelessWindow = CreateWpfViewWindow(newValue);
    if (null != modelessWindow)
    {
        // Show the new WpfViewWindow.
        modelessWindow.Show();
    }
    // Clear the current value so that the next PropertyChanged event
    // is processed even if the underlying value has not actually changed.
    SetCurrentValue(ShowNewViewProperty, null);
}

现在,您可以简单地从ShowNewViewPropertyChanged处理程序和您想要的任何其他地方调用该方法:

private static void ShowNewViewPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    WpfViewWindow window = d as WpfViewWindow;
    IRootViewModel newValue = e.NewValue as IRootViewModel;
    if ((null != window) && (null != newValue))
    {
        window.SomeNewMethod(newValue);
    }
}