如何使用 MVVM Light Toolkit 打开一个新窗口

Posted

技术标签:

【中文标题】如何使用 MVVM Light Toolkit 打开一个新窗口【英文标题】:How to open a new window using MVVM Light Toolkit 【发布时间】:2011-03-24 02:46:28 【问题描述】:

我在我的 WPF 应用程序中使用 MVVM Light 工具包。我想知道从现有窗口打开新窗口的最佳方法是什么。我有这个MainViewModel,它负责我的应用程序的MainWindow。现在在MainView 中,单击按钮,我想在其顶部打开第二个窗口。我将RelayCommmand 绑定到ButtonCommand。在RelayCommand 的方法中,我可以创建一个新的窗口对象并简单地调用Show(),如下所示:

var view2 = new view2()
view2.Show()

但我认为 ViewModel 不应该负责创建新的 view2 对象。我已经阅读了这篇帖子WPF MVVM Get Parent from VIEW MODEL,Bugnion 建议将消息​​从viewmodel1 传递给view1,然后view1 应该创建新的view2。但我不确定他将消息传递给view1 究竟是什么意思? view1 应该如何处理消息?在它背后的代码还是什么?

问候, 纳比尔

【问题讨论】:

见***.com/questions/16993433/… 【参考方案1】:

从 ViewModel1 向 View1 传递消息意味着使用messaging capabilities in the MVVM Light Toolkit。

例如,您的 ViewModel1 可能有一个名为 ShowView2Command 的命令,然后它会发送一条消息来显示视图。

public class ViewModel1 : ViewModelBase

    public RelayCommand ShowView2Command  private set; get; 

    public ViewModel1() : base()
    
        ShowView2Command = new RelayCommand(ShowView2CommandExecute);
    

    public void ShowView2CommandExecute()
    
        Messenger.Default.Send(new NotificationMessage("ShowView2"));
    

View1 会在其后面的代码中注册接收消息,并在收到正确消息时显示 View2。

public partial class View1 : UserControl

    public View1()
    
        InitializeComponent();
        Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
    

    private void NotificationMessageReceived(NotificationMessage msg)
    
        if (msg.Notification == "ShowView2")
        
            var view2 = new view2();
            view2.Show();
        
    

【讨论】:

感谢马特的回复。除了使用消息传递之外,还有其他方法/最佳实践可以在 mvvm 中打开新视图吗? 我看到人们使用的另一种方法是使用类的服务样式来打开视图。您的 ViewModel 将使用此服务的接口来显示 ChildWindow、MessageBox 或其他任何内容。这是那些希望在他们的视图代码隐藏中使用零代码的人特别喜欢的。此外,它更易于测试,因为您可以模拟服务并断言显示视图的方法已被调用。 是的,我也看到人们在谈论它。但是我在这种方法中不明白的是,当您使用某些服务从视图模型打开子窗口时,比如说 IDialogService.OpenChild(),您将如何设置子窗口的所有者,作为调用 IDialogService 的视图模型。 OpenChild() 不知道或不引用自己的视图? 嗯,有多种原因需要设置子窗口的所有权。所有权关系强制执行某些行为,包括最小化、最大化和恢复等(msdn.microsoft.com/en-us/library/…)。至于使用 RootVisual,这又意味着向视图发送消息,因为 rootvisual 可以在代码隐藏中访问,而不是在 viewmodel 中? 这很酷,但同时也让我想起了 MFC,大约在 1997 年使用消息泵......【参考方案2】:

你为什么要走这条路?这很简单。如果您将按钮替换为切换按钮、超链接或任何其他数量的类似按钮的控件,则无需更新“隐藏代码”——这是 MVVM 模式的基本原则。在您的新切换按钮(或其他)中,您最终仍会绑定到相同的命令。

例如,我正在为一个想要拥有 2 个 UI 的客户创建一个项目 - 就演示而言,一个在各个方面都将完全不同。用于导航的水平选项卡与垂直 RadPanelBar(想想 Accordion)。这两个视图都可以指向同一个视图模型 - 当用户单击视图 1 中的工作订单选项卡时,它会触发在面板栏中的工作订单标题中触发的相同“WorkOrderCommand”。

在代码隐藏模型中,您必须编写两个单独的事件。在这里,您只需编写一个代码。

此外,它允许设计师使用 Blend 来创建他们想要的任何布局。只要他们有钩子(EventToCommand 控件),我自己(作为开发人员)就不会关心最终产品的样子。

松散耦合非常强大。

【讨论】:

【参考方案3】:

您可以这样做,就像您需要创建一些事件并在 view 中注册这些事件并在视图模型中调用它们。然后打开该弹出窗口。

喜欢这个例子

public class Mainclass : MainView

    public delegate abc RegisterPopUp(abc A);
    public RegisterPopUp POpUpEvent ;

    public RelayCommand ShowCommand  private set; get;   


    public void ShowCommand() 
     
        ShowCommand("Your parameter");
     

在视图内MainView mn=new MainView();

在这里注册事件就像mn.POpUpEvent +=而不是双击标签按钮

并在右侧注册弹出方法中打开弹出窗口的代码。

【讨论】:

【参考方案4】:

除非我错过了这里的重点——如果我要使用后面的代码,那为什么不直接实现 button_click 事件并打开第二个视图呢?

Bugnion 似乎暗示的是 view1 -> button click -> relay command -> viewmodel1 -> message -> view1 -> view1.cs -> open view 2.

无论如何你都会通过编写代码隐藏来牺牲可测试性,那么为什么要走这么长的路呢?

【讨论】:

漫长的路线将确保:当您测试视图模型时,您至少可以测试已广播消息/请求以打开新视图。您可以将消息请求代码包装在 IDialogService 中,使其在测试期间可模拟。 我同意普拉茨的观点,走这么长的路有点疯狂。 代码隐藏方法适用于具有少量窗口/视图的小型应用程序。如果您的主窗口只是偶尔打开第二个窗口只是为了显示一些细节,那么增加的复杂性似乎有点过头了。如果应用变大,后面的代码将无法很好地扩展,测试也会受到影响。【参考方案5】:

您可以使用通用接口将视图特定功能抽象为服务。在视图层中,您可以提供这些服务的具体实例,并使用 IoC 容器和依赖注入技术构建视图模型。

在您的情况下,您可以构建一个接口 IWindowManager 或类似的具有所需方法的东西。这可以在您的视图层中实现。我最近写了一篇小博文,演示了如何从视图模型中抽象出对话行为。类似的方法可用于任何与用户界面相关的服务,如导航、消息框等。

此链接可能对您有所帮助http://nileshgule.blogspot.com/2011/05/silverlight-use-dialogservice-to.html

许多人还使用从 view.cs 文件上订阅的视图模型触发事件的方法,并从那里执行 MessageBox 或任何其他与 UI 相关的操作。我个人喜欢注入服务的方法,因为这样您就可以提供同一服务的多个实现。一个简单的例子是如何在 Silverlight 和 Windows Phone 7 应用程序中处理导航。您可以使用相同的视图模型,但根据应用程序类型注入不同的导航服务实现。

【讨论】:

【参考方案6】:

我发现解决这个问题的最佳方法是从 ViewModel 打开和关闭窗口。正如this 链接所暗示的,

    创建一个DialogCloser
公共静态类 DialogCloser public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(DialogCloser), new PropertyMetadata(DialogResultChanged)); 私有静态无效DialogResultChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) var window = d 作为窗口; if (window != null) window.Close(); 公共静态无效SetDialogResult(窗口目标,布尔?值) target.SetValue(DialogResultProperty, value);
    创建一个继承自GalaSoft.MvvmLight.ViewModelBase 的基础视图模型,其中包含其他成员。完成后,将此视图模型用作其他视图模型的基础。
布尔? _closeWindowFlag; 公共布尔?关闭窗口标志 得到 返回 _closeWindowFlag; 放 _closeWindowFlag = 值; RaisePropertyChanged("CloseWindowFlag"); 公共虚拟 void CloseWindow(bool?result = true) Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, 新动作(() => 关闭窗口标志 = 关闭窗口标志 == 空?真:!关闭窗口标志; ));
    在视图中,将DialogCloser.DialogResult 依赖属性与基础视图模型中的CloseWindowFlag 属性绑定。

然后您可以在视图模型中打开/关闭/隐藏窗口。

【讨论】:

你能提供一个打开窗口的例子吗?据我了解,这只会关闭窗口/视图。

以上是关于如何使用 MVVM Light Toolkit 打开一个新窗口的主要内容,如果未能解决你的问题,请参考以下文章

MVVM Light Toolkit 示例 [关闭]

Mvvm Light Toolkit 入门

如何将 RelayCommand 与 MVVM Light 框架一起使用

如何使用 MVVM Light for WPF 在窗口中导航?

MVVM Light:如何注销 Messenger

MVVM Light和MVVM有什么区别?