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

48

我正在我的WPF应用程序中使用MVVM Light工具包。我想知道从现有窗口打开新窗口的最佳方法是什么。我有一个名为MainViewModel的对象,它负责我的应用程序的MainWindow。现在,在MainView中,当用户点击一个按钮时,我想在其上方打开第二个窗口。我绑定了一个RelayCommand到该ButtonCommand属性。在RelayCommand的方法中,我可以创建一个新的窗口对象,并简单地调用Show()方法,就像这样:

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

但我认为ViewModel不应该负责创建新的view2对象。我已阅读了这篇帖子WPF MVVM Get Parent from VIEW MODEL,在其中Bugnion建议从viewmodel1view1传递消息,然后view1应该创建新的view2。但我不确定他实际上是通过将消息传递给view1来做什么?view1应该如何处理这个消息?在它的代码后面还是其他地方?

问候, Nabeel


请参见以下链接:https://dev59.com/YHPYa4cB1Zd3GeqPehOz#16994523。 - reggaeguitar
6个回答

60

将来自ViewModel1的消息传递到View1意味着使用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();
        }
    }
}

11
另一种方法是使用服务风格的类来打开视图。您的ViewModel将与该服务接口一起工作,以显示ChildWindow、MessageBox或其他内容。这是那些希望在视图代码后台中零代码的人特别喜欢的方法。此外,它更易于测试,因为您可以模拟服务并断言调用显示视图的方法。 - Matt Casto
2
是的,我也看到有人谈论过这个。但是我不理解这种方法的一点是,当您使用某些服务(比如IDialogService.OpenChild())从视图模型打开子窗口时,您如何设置子窗口的所有者,因为调用IDialogService.OpenChild()的视图模型不知道或没有对其自己的视图的引用? - nabeelfarid
2
有很多原因需要设置子窗口的所有权。所有权关系强制执行某些行为,包括最小化、最大化和还原等(http://msdn.microsoft.com/en-us/library/system.windows.window.owner.aspx)。至于使用RootVisual,这意味着向视图发送消息,因为RootVisual将在代码后台中访问,而不是在视图模型中访问? - nabeelfarid
2
这很酷,但同时也让我想起了1997年使用消息泵的MFC... - Mike Caron
我使用了问题5829413中的另一种解决方案。那个解决方案看起来不错。我用它——它能工作,但我不确定它是否是一个好的解决方案,因为我不得不在App.cs中添加另一个非静态构造函数。你能给我一个提示,在哪里应该正确地注册负责显示视图的消息?App是一个好的解决方案吗?或者可能是Locator,但定位器呢?我将感激不尽您的建议。 - komizo
显示剩余9条评论

4
为什么要这样做呢?很简单。如果您将按钮替换为toggleButton、超链接或任何其他类似于按钮的控件,您就不需要更新“代码后端”——这是MVVM模式的基本原则。在新的toggleButton(或其他控件)中,您仍然会绑定到完全相同的Command。
例如,我正在为一个客户创建一个项目,他希望有两个UI界面——一个在展示方面在每个方面都有根本性的不同。水平选项卡与垂直RadPanelBar(类似手风琴)用于导航。这些视图都可以指向同一个viewModel——当用户在视图1中点击工作订单选项卡时,它会触发与面板栏中工作订单标头中触发的“WorkOrderCommand”相同的命令。
在代码后端模型中,您必须编写两个单独的事件。而在这里,您只需要编写一个事件。
此外,它允许使用Blend的设计人员创建任何他们想要的布局。只要他们有钩子(EventToCommand控件)放置在适当的位置,那么我(作为开发者)就不会关心最终产品的外观如何。
松散耦合非常强大。

3

你可以通过创建一些事件并在视图中注册它们,并在视图模型中调用这些事件来完成此操作。然后打开弹出窗口。

像这个例子:

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 += 然后双击选项卡按钮

并且在注册弹出方法时编写打开弹出窗口的代码。


2
您可以使用通用接口将视图特定功能抽象为服务。在视图层中,您可以提供这些服务的具体实例,并使用IoC容器和依赖注入技术构建视图模型。
在您的情况下,您可以构建一个类似IWindowManager的接口,其中包含所需的方法。这可以在您的视图层中实现。我最近写了一篇小博客文章,演示了如何将对话框行为从视图模型中抽象出来。类似的方法可以用于任何用户界面相关的服务,如导航、消息框等。
这个链接可能对您有帮助http://nileshgule.blogspot.com/2011/05/silverlight-use-dialogservice-to.html 许多人还使用从视图模型触发事件的方法,这些事件在view.cs文件上被订阅,然后执行MessageBox或任何其他UI相关操作。我个人喜欢注入服务的方法,因为这样可以提供相同服务的多个实现。一个简单的例子是Silverlight和Windows Phone 7应用程序中如何处理导航。您可以使用相同的视图模型,但根据应用程序类型注入不同的导航服务实现。

2
除非我在这里误解了重点-如果我要使用代码后台,那么为什么不直接实现button_click事件并打开第二个视图呢?
Bugnion似乎建议的是view1->按钮点击->relay command->viewmodel1->消息->view1->view1.cs->打开view2。
无论如何编写代码后台都会牺牲可测试性,那么为什么要走这么长的路呢?

4
长路线的设计可以确保:在测试您的视图模型时,您至少可以测试到是否已经发送了消息/请求以打开新视图。您可以将消息请求代码封装在一个IDialogService中,在测试过程中可以进行模拟。 - nabeelfarid
2
我同意Pratz的观点,这样走一条如此漫长的路线有些疯狂。 - Vincent
2
代码后台方法对于具有少量窗口/视图的小型应用程序来说是很好的。如果您只有一个主窗口,偶尔打开第二个窗口以显示一些详细信息,则添加复杂性似乎过于冗余。如果应用程序变得庞大,代码后台将无法很好地扩展,并且测试将会受到影响。 - srock

0

我发现最好的方法是从ViewModel打开和关闭窗口,就像this链接所建议的那样:

  1. 创建一个DialogCloser
    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.Close(); }
public static void SetDialogResult(Window target, bool? value) { target.SetValue(DialogResultProperty, value); } }
  1. 创建一个继承自GalaSoft.MvvmLight.ViewModelBase的基本ViewModel,并添加三个成员。完成后,将此ViewModel用作其他ViewModel的基础。
    bool? _closeWindowFlag;
    public bool? CloseWindowFlag
    {
        get { return _closeWindowFlag; }
        set
        {
            _closeWindowFlag = value;
            RaisePropertyChanged("CloseWindowFlag");
        }
    }
public virtual void CloseWindow(bool? result = true) { Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => { CloseWindowFlag = CloseWindowFlag == null ? true : !CloseWindowFlag; })); }
  1. 在视图中,将DialogCloser.DialogResult依赖属性与基本视图模型中的CloseWindowFlag属性进行绑定。

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


你能提供一个打开窗口的例子吗?据我所知,这只会关闭窗口/视图。 - br0ken.pipe

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