【问题标题】:Can a PropertyChangedCallback be persuaded to run when the bound property's value is unchanged?当绑定属性的值不变时,是否可以说服 PropertyChangedCallback 运行?
【发布时间】:2014-09-01 08:59:04
【问题描述】:

我的应用程序使用 MVVM 架构,而 ViewModel 不了解 View。当 ViewModel 对象需要显示新视图时,它会公开一个公共 ShowNewView 属性,该属性是一个对象,其类基于我的 ViewModel 基类。 WPF 视图将自定义的 DependencyProperty 绑定到此,并使用 PropertyChangedCallback 构造和显示适当的窗口。

这在第一次设置ShowNewView 属性时运行良好。但是,如果用户关闭窗口然后尝试重新打开它,则在引发 PropertyChanged 事件并且不调用 PropertyChangedCallback 时,ShowNewView 属性的值没有改变。

为了“欺骗”DependencyProperty 检测值已更改(即使存储在 ViewModel 属性中的值可能实际上并未更改),我使用了 Window 类公开的 SetCurrentValue 方法来强制DependencyProperty 的值为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 相关的问题。

是否有更优雅的方法来实现这一点,不会导致来自 ViewModel 的每个 PropertyChanged 事件运行两次回调? IE。有没有办法绕过框架的检查来验证新旧值是否不同?

澄清

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

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

【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    此类问题的通常解决方案是让您从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);
        }
    }
    

    【讨论】:

    • 问题是如果底层 ViewModel 属性值没有更改,则不会调用 ShowNewPropertyChanged 回调。我可以随心所欲地引发INotifyPropertyChanged.PropertyChanged 事件,但如果该属性的值与上次触发事件时相同,则不会回调回调。
    • 所以现在您可以随时调用SomeNewMethod(与调用ShowNewPropertyChanged 处理程序 时的效果相同)。
    • 啊,我明白你的意思了。这将起作用,除了需要回调功能的 ViewModel 对实现它的视图一无所知 - 在本例中是拥有 DependencyProperty 的 WpfViewWindow 类。实际上,回调“绑定”了响应 ViewModel 显示新视图的请求所需的功能。
    • 你确实把一个相对简单的场景复杂化了。视图模型与数据一起工作,视图与 UI 控件一起工作......所以只需为您的视图模型定义一些 DataTemplates 以呈现它们的相关视图,然后您所要做的就是创建一个新的视图模型并让 WPF 创建从DataTemplate 查看。简单得多。
    • 同意,这很复杂。但是,复杂性来自于 View 和 ViewModel 类作为插件由 MEF 加载的事实。 ViewModel 类(需要 Window 或其他顶级视图)必须提供的最小契约是实现 IRootViewModel 接口。 View 的导出 ResourceDictionary 提供了为任何特定 ViewModel 显示哪个 View 的映射。我可以通过在IRootViewModel 界面中包含一个事件来简化它,但这不会回答我的问题:-)。
    猜你喜欢
    • 2015-05-10
    • 1970-01-01
    • 2019-12-27
    • 2011-11-10
    • 1970-01-01
    • 1970-01-01
    • 2013-01-30
    • 1970-01-01
    • 2011-08-04
    相关资源
    最近更新 更多