在MVVM WPF中打开新窗口
在MVVM WPF中打开新窗口
我有一个按钮,我将这个按钮绑定到ViewModel中的一个命令,称为OpenWindowCommand
。当我点击按钮时,我想要打开一个新的窗口。但是,在ViewModel中创建窗口实例并显示窗口违反了MVVM的原则。我创建了一个接口,如下所示:
interface IWindowService { void showWindow(object dataContext); }
并且WindowService
类实现了这个接口,如下所示:
class WindowService : IWindowService { public void showWindow(object dataContext) { ChildWindow window=new ChildWindow(); window.DataContext=dataContext; window.Show(); } }
在这个类中,我指定了ChildWindow
。所以,这个类与显示ChildWindow
紧密耦合。当我想要显示另一个窗口时,我必须实现另一个具有相同接口和逻辑的类。如何使这个类通用,以便我只需传递任何窗口的实例,该类就能够打开任何窗口呢?
我没有使用任何内置的MVVM框架。我已经在StackOverflow上阅读了很多文章,但没有找到任何解决方案。
在MVVM WPF中打开新窗口的问题出现的原因是需要在ViewModel中打开一个新的View,但是ViewModel不应该直接与View进行交互。解决方法是使用消息总线和ViewManager来实现View的打开。
首先,我们可以编写一个ViewManager类,其中包含一个ShowView方法,该方法接受一个泛型参数T和一个ViewModelBase对象作为参数。在ShowView方法中,我们实例化一个T类型的View对象,并将其DataContext属性设置为传入的viewModel对象,然后显示该View。
接下来,我们需要在ViewModelBase类中编写一个ShowView方法,该方法接受一个viewName字符串和一个viewModel对象作为参数。在ShowView方法中,我们创建一个Message对象,其中包含Action、ViewName和ViewModel属性,然后将该Message对象通过消息总线发送出去。
为了让ViewManager不需要知道具体的View类型,我们可以将viewName作为参数传递给ViewManager,并由ViewManager将逻辑名称转换为具体的View类型。
需要注意的是,在ViewBase类中需要有一个DataContext属性,可以通过继承UserControl来实现。
总结一下,为了在MVVM WPF中打开新窗口,我们可以使用消息总线和ViewManager来实现。ViewModel通过发送消息的方式请求打开一个View,并传递需要显示的数据。ViewManager接收到消息后,根据传递的逻辑名称实例化具体的View,并将ViewModel对象作为其DataContext属性的值,然后显示该View。这样就实现了在MVVM框架下打开新窗口的功能,同时避免了ViewModel直接与View进行交互的问题。
代码示例:
class ViewManager { void ShowView(ViewModelBase viewModel) where T : ViewBase, new() { T view = new T(); view.DataContext = viewModel; view.Show(); // or something similar } } abstract class ViewModelBase { public void ShowView(string viewName, object viewModel) { MessageBus.Post( new Message { Action = "ShowView", ViewName = viewName, ViewModel = viewModel }); } }
在MVVM WPF中打开新窗口的问题是由于需要在ViewModel中打开View窗口而引起的。问题的解决方法是创建一个WindowService类,实现IWindowService接口,其中有一个showWindow方法,该方法接受一个泛型参数T和一个对象DataContext。在该方法中,创建一个T类型的窗口实例,并设置其DataContext属性为传入的DataContext对象,然后调用show方法显示窗口。
代码如下:
class WindowService:IWindowService { public void showWindow<T>(object DataContext) where T: Window, new() { ChildWindow window=new T(); window.Datacontext=DataContext; window.show(); } }
然后在ViewModel中可以通过windowService.showWindow方法来打开窗口,示例代码如下:
windowService.showWindow<Window3>(windowThreeDataContext);
需要注意的是,上述代码中的new()约束只适用于窗口具有无参数构造函数的情况。在更一般的情况下,可以参考stackoverflow上的相关问题Create instance of generic type?。
有人提出了一个问题,即在ViewModel中调用windowService.showWindow方法时,需要传入窗口的名称,这是否违反了MVVM的原则。对于这个问题,有人回答称,在MVVM的方法中,需要创建窗口或视图,因此上述方法是有用的。可能需要根据ViewModel到View的映射关系或者一些约定来确定窗口的名称。如果想要采用更严格的MVVM方式,可以考虑使用一些框架,例如Caliburn Micro,它可以帮助处理这些问题。
通过创建一个WindowService类来实现在MVVM WPF中打开新窗口的功能,可以很好地遵循MVVM的原则。
在MVVM模式中,从视图模型中创建窗口实例并显示窗口是违反MVVM的。为了解决这个问题,可以创建一个接口,该接口接受视图模型指定的视图类型,将创建逻辑抽象到接口后面,但仍然从视图模型中请求视图创建。视图模型只关心创建视图模型,如果确实需要一个新窗口来托管新视图模型,那么可以提供一个接口,但不需要传递视图。为什么需要视图?大多数(以视图模型为先)的MVVM项目使用隐式数据模板将视图与特定的视图模型关联起来,视图模型对它们一无所知。
解决方法是创建一个WindowService类实现IWindowService接口,其中的ShowWindow方法接受一个viewModel参数,然后创建一个新窗口实例,并将窗口的Content属性设置为viewModel,最后显示窗口。需要确保在app.xaml中设置了VM->View的隐式数据模板,这是标准的以视图模型为先的MVVM模式。
如果需要打开一个窗口而不传递窗口给函数,可以使用通用的窗口类型,并像平常一样使用隐式数据模板将视图模型映射到视图。如果需要不同的窗口类型,可以使用隐式数据模板中的触发器等简单方法将不同的视图添加到ContentControl中。这种方法使得视图和视图模型之间具有一对一的映射。
需要注意的是,如果视图是一个Window,而不是UserControl,那么上述解决方案将不起作用,会抛出"Can't put window in style"的错误。WindowService类应该放在视图中,这样可以从视图模型中调用它。视图应该是UserControl类型而不是Window类型。
此外,需要注意在app.xaml中设置xmlns:vw="clr-namespace:My.App.Views"这个命名空间,它指向视图的定义位置。如果有多个命名空间中的视图,需要在app.xaml中定义多个命名空间来指向它们。
总结起来,MVVM模式中视图模型不应该直接创建和显示窗口,可以通过创建一个WindowService类来实现窗口的创建和显示,并使用隐式数据模板将视图模型映射到视图。如果需要不同的窗口类型,可以使用隐式数据模板中的触发器等方法将不同的视图添加到ContentControl中。这样可以实现视图和视图模型的解耦,遵循MVVM模式的原则。