WPF(MVVM):如何从ViewModel中关闭View?

47

有没有人在使用MVVM时遇到过一种巧妙的方法来关闭视图(view)的情况?

也许可以使用绑定(binding)的方式来向视图(window)发送关闭信号?

我非常感谢大家的任何建议。

基本上,我有一个登录视图(loginView),它绑定到一个登录视图模型(loginViewModel),在视图模型中(使用命令绑定)测试登录是否成功,如果成功,我就加载一个新的视图(mainView)并附加其数据上下文...

但是,登录视图仍然显示出来 - 所以需要发信号通知其卸载(unload)。。

我还希望有一种通用的解决方案,因为我相信我将来会在其他情况下需要这样做

有什么想法吗?

11个回答

36

编辑:请查看我的博客文章,获取更详细的解释。

当我需要实现这个功能时,我使用了一个由我创建的IRequestCloseViewModel接口。

这个接口只包含一个事件:RequestClose。当ViewModel(继承自ViewModelBase类并实现IRequestCloseViewModel)希望关闭与其相关联的视图时,它会触发该事件。

在我的应用程序中,所有的窗口都继承自一个抽象类ApplicationWindow。每当DataContext发生变化时,这个抽象类就会被通知,并且在处理程序中检查DataContext是否支持IRequestCloseViewModel。如果是这样,就会设置一个事件处理程序,在事件触发时关闭窗口。

或者,就像Kent所说的那样,您可以使用屏幕控制器来将此机制处理在外部类中。


嗨Jalfp,这听起来真的很棒。你有一个抽象类的例子,我需要所有窗口都继承吗?如果你有包括Irquestcloseviewmodel的例子,那就太好了。 - mark smith
我将在接下来的几个小时内在我的博客上发布一篇文章。一旦它上线了,我会立即通知你 :-) - japf
1
这是链接:http://www.japf.fr/2009/09/how-to-close-a-view-from-a-viewmodel/希望能对您有所帮助 :) - japf

24

我不确定你使用的是哪种MVVM框架,但大多数都包含某种消息/通知解决方案,很容易让事物注册收到发送的消息。我无法想象任何理由,你的视图不能注册一个这样的消息,例如“CloseWindowsBoundTo”,并且viewModel作为发送者。然后在你的视图中,你只需要注册那个消息,并将你当前的数据上下文与发送者进行比较。如果它们匹配,关闭窗口。

简单而且保持你的视图与你的视图模型分离。

以下是我使用MVVM-light工具包的方法:

在ViewModel中:

public void notifyWindowToClose()
{
    Messenger.Default.Send<NotificationMessage>(
        new NotificationMessage(this, "CloseWindowsBoundToMe")
    );
}

在视图中:

Messenger.Default.Register<NotificationMessage>(this, (nm) =>
{
    if (nm.Notification == "CloseWindowsBoundToMe")
    {
        if (nm.Sender == this.DataContext)
            this.Close();
    }
});

只是想知道,如果你传递一个链接到一个应该被处理的对象,是否会导致内存泄漏?.. - Alex Klaus

10

我曾经使用过附加行为(dialogcloser attached behavior),但我发现下面的解决方案更简单,我可以在这个方案中使用它。下面的示例以窗口上的关闭按钮为例。

将窗口作为命令参数传递。

在视图(view)的按钮XAML中:

CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"

在视图模型中的命令执行方法中:

if (parameter is System.Windows.Window)
{
    (parameter as System.Windows.Window).Close();
}

如果您坚持实现正确的MVVM,那么这是最佳实践。 - Ihab
如果您正在实现“正确”的MVVM,那么这实际上是可怕的做法。ViewModel层不应引用UI控件。此问题的正确解决方案在此答案中给出 https://dev59.com/nXRB5IYBdhLWcg3wz6ad#3329467 - Peregrine

8
通常情况下,您需要使用某种控制器/展示器/服务来驱动屏幕的激活/停用。MVVM并不意味着是“万能的模式”。在任何非平凡的应用程序中,您都需要将其与其他模式结合使用。
尽管如此,在某些情况下,拥有一个视图模型来管理子视图模型的生命周期是有意义的。例如,您可能拥有一个“EditorViewModel”,它管理一组子视图模型——每个正在编辑的文档都有一个。在这种情况下,简单地向该集合添加/删除内容就可以导致视图的激活/停用。但这似乎不符合您的使用情况。

7

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

<Style.Triggers> <DataTrigger Binding="{Binding CloseSignal}" Value="true"> <Setter Property="Behaviours:WindowCloseBehaviour.Close" Value="true" /> </DataTrigger> </Style>

这段代码是关于 WPF 的样式触发器。当 CloseSignal 属性被绑定为 true 时,数据触发器会设置 WindowCloseBehavior 类的 Close 属性为 true,从而关闭窗口。

4
您可以创建一个附加到窗口的命令,当执行该命令时,将关闭窗口。然后,您可以将该命令绑定到视图模型上的属性,并在需要关闭窗口时执行该命令。

2

这个答案展示了另一种实现方式:

如何让ViewModel关闭窗口?

它使用了一个附加属性将DialogResult窗口属性绑定到ViewModel属性。当将DialogResult的值设置为true或false时,视图会关闭。


2
我会使用一个ApplicationController来实例化LoginViewModel并显示LoginView。当用户继续登录屏幕时,ApplicationController关闭LoginView并显示其MainViewModel的MainView。
如何实现这一点在WPF应用程序框架(WAF)项目的示例应用程序中有展示。

2

在代码后台中关闭一个EventHandler,并在视图模型中处理所有其他内容,您可以在那里使用命令绑定。


我这样做会不会被 ViewModel 形状的闪电击中,因为我没有编写自己的 IRequestCloseViewModel 接口? - Ted

1
你也可以使用事件来完成这个操作。虽然在视图代码后台需要写三行代码(某些MVVM纯粹主义者不喜欢这种方式);
在你的视图模型中,你需要创建一个视图可以订阅的事件:
    public event CloseEventHandler Closing;
    public delegate void CloseEventHandler();
    private void RaiseClose()
    {
        if (Closing != null)
            Closing();
    }

在您的看法中,您可以在initializecomponent方法之后订阅事件,如下所示:
        public View
        {
           *//The event can be put in an interface to avoid direct dependence of the view on the viewmodel. So below becomes
            //ICloseView model = (ICloseView)this.DataContext;*
            ProgressWindowViewModel model = (ProgressWindowViewModel)this.DataContext;
            model.Closing += Model_Closing;
        }
        private void Model_Closing()
        {
             this.Close();
        }

当您准备从ViewModel关闭视图时,只需简单地调用RaiseClose()。
您甚至可以使用此方法从ViewModel向View发送消息。
该事件可以放在接口中,以避免视图直接依赖于ViewModel。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接