欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MVVM探索:从ViewModel关闭Window的最佳实践

程序员文章站 2022-03-29 11:59:10
在WPF里使用MVVM开发的时候,似乎总是不可避免的会遇到这样一个问题:ViewModel在处理完业务之后需要关闭这个Window,这时候要怎么处理? 网上有很多解决方案:有的在ViewModel抛出一个事件,在View端使用(XXXViewModel)this.DataContext的方式去响应事 ......

在WPF里使用MVVM开发的时候,似乎总是不可避免的会遇到这样一个问题:ViewModel在处理完业务之后需要关闭这个Window,这时候要怎么处理?

网上有很多解决方案:有的在ViewModel抛出一个事件,在View端使用(XXXViewModel)this.DataContext的方式去响应事件;有的通过Trigger、Behavior、Action之类的方式曲线救国;还有的使用了其他的第三方框架。

这些操作从某个层面上来说确实能实现这个功能,但是有的操作起来过于麻烦,有的实现功能了但是大大的违反了MVVM的原则,有的则有很多局限性(比如只能针对关闭了Window之后什么都不做,或者必须要求Window有无参的构造函数)。直到我发现了还可以有这样一种操作之后,我觉得这应该处理这个问题的最佳实践了:优雅,简洁,符合MVVM的思想还没有局限性。

在MVVM里,View和ViewModel之间通过绑定完成了大部分的操作,这也是MVVM最为推荐的做法。那么,为什么View的关闭这个事情不能通过绑定来实现呢?是因为Window没有控制关闭这个操作的属性么?不,在没有使用MVVM,直接在后台写代码创建了一个Window的时候,我们只需要将这个Window的DialogResult属性赋值(不管是true还是false)就可以将这个窗口关闭。那么我们为什么不直接将Window的DialogResult属性在ViewModel绑定呢?

秉着这样的思想我去做了这个实验,编译通过,运行的时候得到了这样的异常提示:

“不能在“ChildWindow”类型的“DialogResult”属性上设置“Binding”。只能在 DependencyObject 的 DependencyProperty 上设置“Binding”。

这个提示已经很明显了:为什么不能直接对Window的DialogResult做绑定,因为DialogResult这个属性不是依赖属性,WPF里面所有的绑定都必须只能绑定依赖属性,而WPF里绝大部分的属性都是依赖属性,但是DialogResult恰恰不是依赖属性,所以不能绑定。

此路不通之后就有了上面的各种解决方法,但是为什么不这样想:DialogResult不是依赖属性,那我注册一个依赖属性不就完了么?WPF又不是不让注册。

注册依赖属性代码如下:

public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
            {
                window.DialogResult = e.NewValue as bool?;
            }
        }

        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }

然后在View端绑定这个依赖属性DialogResult:

<Window x:Class="mvvm_demo_close_window.ChildWindow"
        ...
        xmlns:xc="clr-namespace:mvvm_demo_close_window"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

然后在ViewModel端将这个当做正常的依赖属性去操作就行了,当this.DialogResult=true的时候就自动在ViewModel关闭了子窗口:

public class ChildWindowViewModel : ViewModelBase
    {
        private bool? dialogResult;
        public bool? DialogResult
        {
            get { return this.dialogResult; }
            set
            {
                this.dialogResult = value;
                RaisePropertyChanged("DialogResult");
            }
        }

        //用来接收关闭按钮的Command
        public ICommand CloseCmd
        {
            get
            {
                return new DelegateCommand((obj) =>
                {
                    this.DialogResult = true;
                });
            }
        }
    }

这是我目前发现的最优雅的解决方案,DialogCloser也完全可以复用,如果大家还有更好的方案,欢迎提出来一起讨论。源代码已在下方给出,需要的自行下载。

点击下载源代码