在邻居帖子中:How should the ViewModel close the form? 我发布了如何使用MVVM关闭Windows的愿景。现在我有一个问题:如何打开它们。
我有一个主窗口(主视图)。如果用户单击“显示”按钮,则应显示“演示”窗口(模态对话框)。使用MVVM模式创建和打开窗口的首选方法是什么?我看到两种一般方法:
第一个(可能是最简单的)。事件处理程序“ShowButton_Click”应该在主窗口的代码中实现,如下所示:
private void ModifyButton_Click(object sender, RoutedEventArgs e)
{
ShowWindow wnd = new ShowWindow(anyKindOfData);
bool? res = wnd.ShowDialog();
if (res != null && res.Value)
{
// ... store changes if neecssary
}
}
另一种方法:
在MainWindowViewModel中,我们将实现“ShowCommand”属性,该属性将返回命令的ICommand接口。 Comman反过来:
这种方法更适合MVVM但需要额外的编码:ViewModel类不能“显示对话框”,所以MainWindowViewModel只会引发“ShowDialogEvent”,MainWindowView我们需要在其MainWindow_Loaded方法中添加事件处理程序,像这样的东西:
((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;
(ShowDialog - 类似于'ModifyButton_Click'方法。)
所以我的问题是: 你看到其他方法了吗? 你认为其中一个是好还是坏? (为什么?)
欢迎任何其他想法。
感谢。
答案 0 :(得分:17)
某些MVVM框架(例如MVVM Light)使用Mediator pattern。 因此,要打开一个新窗口(或创建任何视图),某些特定于视图的代码将订阅来自中介的消息,而ViewModel将发送这些消息。
像这样:
Subsription
Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
{
// Instantiate new view depending on the message details
}
在ViewModel中
Messenger.Default.Send(new DialogMessage(...));
我更喜欢在单例类中进行订阅,只要应用程序的UI部分可以“生存”。 总结一下:ViewModel传递“我需要创建视图”之类的消息,UI会监听这些消息并对其进行操作。
当然,没有“理想”的方法。
答案 1 :(得分:16)
我最近也在考虑这个问题。如果你在项目中使用Unity作为'容器'或依赖注入的任何东西,我有一个想法。我想通常你会覆盖App.OnStartup()
并创建你的模型,视图模型,并在那里查看,并给每个人提供适当的参考。使用Unity,您可以为容器提供对模型的引用,然后使用容器“解析”视图。 Unity容器会注入您的视图模型,因此您永远不会直接实例化它。解决后,您可以在其上调用Show()
。
在我观看的示例视频中,Unity容器在OnStartup
中创建为本地变量。如果您在App类中将其创建为公共静态只读属性,该怎么办?然后,您可以在主视图模型中使用它来创建新窗口,自动注入新视图所需的任何资源。类似于App.Container.Resolve<MyChildView>().ShowDialog();
。
我想你可以在测试中以某种方式模拟调用Unity容器的结果。或者,也许您可以在App类中编写类似ShowMyChildView()
的方法,这基本上就是我上面所描述的。模拟对App.ShowMyChildView()
的调用可能很容易,因为它只返回bool?
,是吗?
嗯,这可能不仅仅比使用new MyChildView()
更好,但这是我的一个小想法。我以为我会分享它。 =)
答案 2 :(得分:4)
我有点晚了,但我觉得现有的答案不够。我会解释原因。一般来说:
App.Container.Resolve<MyChildView>().ShowDialog();
这实际上并没有解决任何问题。您正在以一种紧密耦合的方式从ViewModel访问您的View。与new MyChildView().ShowDialog()
的唯一区别在于您通过了一层间接。我没有看到直接调用MyChildView ctor的任何优势。
如果您为视图使用了接口,那将会更清晰:
App.Container.Resolve<IMyChildView>().ShowDialog();`
现在ViewModel没有与视图紧密耦合。但是我发现为每个视图创建界面是非常不切实际的。
Messenger.Default.Send(new DialogMessage(...));
它更好。似乎Messenger或EventAggregator或其他发布/订阅模式是MVVM中每个人的通用解决方案:)缺点是调试或导航到DialogMessageHandler
更难。这太间接了。例如,您如何从Dialog中读取输出?通过修改DialogMessage?
你可以像这样从MainWindowViewModel打开窗口:
var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true) {
//you can read user input from childWindowViewModel
}
DialogService只使用对话框的ViewModel,因此您的视图模型完全独立于视图。在运行时,DialogService可以找到适当的视图(例如使用命名约定)并显示它,或者可以在单元测试中轻松模拟它。
在我的情况下,我使用这个接口:
interface IDialogService
{
void Show(IDialogViewModel dialog);
void Close(IDialogViewModel dialog);
bool? ShowModal(IDialogViewModel dialog);
MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
}
interface IDialogViewModel
{
string Caption {get;}
IEnumerable<DialogButton> Buttons {get;}
}
其中DialogButton指定DialogResult或ICommand或两者。
答案 3 :(得分:2)
查看我当前的MVVM解决方案,以便在Silverlight中显示模态对话框。 它解决了您提到的大部分问题,但它完全从平台特定事物中抽象出来,可以重复使用。此外,我没有使用代码隐藏仅与实现ICommand的DelegateCommands绑定。 Dialog基本上是一个View - 一个单独的控件,它有自己的ViewModel,它从主屏幕的ViewModel显示,但是通过DelagateCommand绑定从UI触发。
在此处查看完整的Silverlight 4解决方案Modal dialogs with MVVM and Silverlight 4
答案 4 :(得分:1)
我使用一个控制器来处理视图之间传递的所有信息。所有视图模型都使用控制器中的方法来请求更多信息,这些信息可以作为对话框,其他视图等实现。
它看起来像这样:
class MainViewModel {
public MainViewModel(IView view, IModel model, IController controller) {
mModel = model;
mController = controller;
mView = view;
view.DataContext = this;
}
public ICommand ShowCommand = new DelegateCommand(o=> {
mResult = controller.GetSomeData(mSomeData);
});
}
class Controller : IController {
public void OpenMainView() {
IView view = new MainView();
new MainViewModel(view, somemodel, this);
}
public int GetSomeData(object anyKindOfData) {
ShowWindow wnd = new ShowWindow(anyKindOfData);
bool? res = wnd.ShowDialog();
...
}
}
答案 5 :(得分:0)
我的方法类似于adrianm。但是,在我的情况下,Controller永远不会使用具体的View类型。 Controller与View完全分离 - 与ViewModel完全相同。
如何在WPF Application Framework (WAF)的ViewModel示例中看到它的工作原理。
。
最诚挚的问候,
JBE