我一直在使用我的MV-VM开发一个非常大的LOB应用程序,我称之为MV-MC(Model-View-ModelController),它是MVC和MV-VM之间的一种组合.我已经发布了这个答案,关于如何在MV-VM中实例化视图,问题是" 什么是最常见的错误 - 在wpf开发中制造 ".
Sam就我的回答发表了以下评论:
这会产生一个后续问题:如何创建视图?我使用RelayCommands将视图中的操作绑定到ViewModel,因此视图甚至不知道某个操作已被触发,也不知道他应该打开一个新视图.解决方案:在VM中创建一个事件以供View订阅?
当我最初开始MV-VM开发时,我认为一切都应该存在于ViewModel中,并且已经研究了很多像Josh Smith和Karl Shifflett这样的人的例子.但是,我还没有提出一个很好的例子,说明命令何时需要存在于ViewModel中.
例如,假设我有一个显示客户的ListView,以及我点击的按钮,允许我编辑当前选定的客户.ListView(View)绑定到CustomerVM(ViewModel).单击该按钮将触发EditCustomerCommand,这将打开一个弹出窗口,允许我编辑CustomerVM的所有属性.这个EditCustomerCommand在哪里?如果它涉及打开一个窗口(UI功能),它不应该在视图的代码隐藏中定义吗?

有没有人有任何关于何时应该在View和ViewModel中定义命令的例子?
Matthew Wright在下面说:
从列表中删除和删除将是很好的例子.在这些情况下,会添加空白记录或ViewModel删除当前记录.视图采取的任何操作都应该响应发生的事件.
所以,如果我点击新按钮,会发生什么?Customer ViewModel创建了一个CustomerVM的新实例并添加到它的集合中吗?那么我的编辑屏幕怎么会打开呢?该视图应该创建Customer ViewModel的新实例,并将其传递给ParentVM.Add(newlyCreatedVM)方法吗?
假设我通过VM上的DeleteCommand删除客户记录.VM调用业务层并尝试删除记录.它不能这样它会向VM返回一条消息.我想在对话框中显示此消息.视图如何从命令操作中获取消息?
从来没有想过我会看到自己被引用在一个问题中。
我自己思考了这个问题一段时间,并为我的代码库做出了一个相当务实的决定:
在我的代码库中,当操作发生时,ViewModel 就会被调用,我希望它保持这种状态。另外,我不希望 ViewModel 控制视图。
我做了什么?
我添加了一个导航控制器:
public interface INavigation
{
void NewContent(ViewModel viewmodel);
void NewWindow(ViewModel viewmodel);
}
Run Code Online (Sandbox Code Playgroud)
该控制器确实包含两个操作:NewContent() 在当前窗口中显示新内容,NewWindow() 创建一个新窗口,用内容填充它并显示它。
当然,我的视图模型不知道要显示哪个视图。但他们确实知道他们想要显示哪个视图模型,因此根据您的示例,当执行 DeleteCommand 时,它将调用导航服务函数NewWindow(new ValidateCustomerDeletedViewModel())来显示一个窗口,说明“客户已被删除”(对于这个简单的消息框,但是对于简单的消息框来说,很容易有一个特殊的导航器功能)。
viewmodel如何获取导航服务?
我的视图模型类有一个导航控制器的属性:
public class ViewModel
{
public INavigation Navigator { get; set; }
[...]
}
Run Code Online (Sandbox Code Playgroud)
当视图模型附加到窗口(或显示视图的任何内容)时,窗口将设置 Navigator 属性,以便视图模型可以调用它。
导航器如何创建视图模型的视图?
您可以有一个简单的列表,为哪个视图模型创建哪个视图,在我的例子中,我可以使用简单的反射,因为名称是匹配的:
public static FrameworkElement CreateView(ViewModel viewmodel)
{
Type vmt = viewmodel.GetType();
// big bad dirty hack to get the name of the view, but it works *cough*
Type vt = Type.GetType(vmt.AssemblyQualifiedName.Replace("ViewModel, ", "View, "));
return (FrameworkElement)Activator.CreateInstance(vt, viewmodel);
}
Run Code Online (Sandbox Code Playgroud)
当然,视图需要一个接受视图模型作为参数的构造函数:
public partial class ValidateCustomerDeletedView : UserControl
{
public ValidateCustomerDeletedView(ValidateCustomerDeletedViewModel dac)
{
InitializeComponent();
this.DataContext = dac;
}
}
Run Code Online (Sandbox Code Playgroud)
我的窗户是什么样子的?
很简单:我的主窗口确实实现了 INavigation 接口,并在创建时显示一个起始页。你自己看:
public partial class MainWindow : Window, INavigation
{
public MainWindow()
{
InitializeComponent();
NewContent(new StartPageViewModel());
}
public MainWindow(ViewModel newcontrol)
{
InitializeComponent();
NewContent(newcontrol);
}
#region INavigation Member
public void NewContent(ViewModel newviewmodel)
{
newviewmodel.Navigator = this;
FrameworkElement ui = App.CreateView(newviewmodel);
this.Content = ui;
this.DataContext = ui.DataContext;
}
public void NewWindow(ViewModel viewModel)
{
MainWindow newwindow = new MainWindow(viewModel);
newwindow.Show();
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
(这对于导航窗口和将视图包装到页面中同样有效)
当然,这是可测试的,因为可以轻松模拟导航控制器。
我不太确定这是否是一个完美的解决方案,但它现在对我来说效果很好。有什么想法和意见欢迎留言!