我一直在使用我的M-V-VM开发一个非常大的LOB应用程序,我称之为M-V-MC(Model-View-ModelController),它是M-V-C和M-V-VM之间的一种组合。我已经发布this answer关于如何在M-V-VM中将视图实例化为问题“what-are-the-most-common-mistakes-made-in-wpf-development”。
Sam就我的回答发表了以下评论:
这会产生一个后续问题:如何 你创建了视图吗?我用 RelayCommands绑定动作 查看ViewModel,以便查看 甚至不知道某个行动 解雇了,不知道他应该开个 新观点。解决方案:在中创建一个事件 要查看的视图的VM?
当我最初开始M-V-VM开发时,我认为一切都应该存在于ViewModel中,并且已经研究了很多像Josh Smith和Karl Shifflett这样的人的例子。但是,我还没有提出一个很好的例子,说明命令何时需要存在于ViewModel中。
例如,假设我有一个显示Customers的ListView,以及我点击的按钮,允许我编辑当前选定的客户。 ListView(View)绑定到CustomerVM(ViewModel)。单击该按钮将触发EditCustomerCommand,这将打开一个弹出窗口,允许我编辑CustomerVM的所有属性。这个EditCustomerCommand在哪里?如果它涉及打开一个窗口(UI功能),它不应该在视图的代码隐藏中定义吗?
有没有人有任何关于我应该在View与ViewModel中定义命令的例子?
下面有新的并从列表中删除即可 很好的例子。在那些情况下,一片空白 添加记录或当前记录 被ViewModel删除。任何 视图采取的行动应该在 对那些事件的反应。
所以,如果我点击新按钮,会发生什么? Customer ViewModel创建了一个CustomerVM的新实例并添加到它的集合中吗?那么我的编辑屏幕怎么会打开呢?该视图应该创建Customer ViewModel的新实例,并将其传递给ParentVM.Add(newlyCreatedVM)方法吗?
假设我通过VM上的DeleteCommand删除客户记录。 VM调用业务层并尝试删除记录。它不能这样它会向VM返回一条消息。我想在对话框中显示此消息。视图如何从命令操作中获取消息?
答案 0 :(得分:3)
从未想过我会在一个问题中看到自己被引用。
我在一段时间内一直在思考这个问题,并为我的代码库做了一个相当务实的决定:
在我的代码库中,ViewModel在动作发生时被调用,我希望它保持这种状态。另外,我不希望ViewModel控制视图。
我做了什么?
我添加了一个导航控制器:
public interface INavigation
{
void NewContent(ViewModel viewmodel);
void NewWindow(ViewModel viewmodel);
}
此控制器包含两个操作:NewContent()确实在当前窗口中显示新内容,NewWindow()创建一个新窗口,用内容填充它并显示它。
当然,我的viewmodels不知道哪个视图可以显示。但是他们确实知道他们想要显示哪个视图模型,所以根据你的例子,当执行DeleteCommand时,它会调用导航服务函数 NewWindow(new ValidateCustomerDeletedViewModel())来显示一个窗口,说明客户已被删除'(这个简单的消息框有点过分,但对于简单的消息框很容易有一个特殊的导航功能)。
viewmodel如何获取导航服务?
我的viewmodel类有一个导航控制器的属性:
public class ViewModel
{
public INavigation Navigator { get; set; }
[...]
}
当视图模型附加到窗口(或任何显示视图的视图)时,窗口将设置Navigator属性,因此viewmodel可以调用它。
导航器如何创建视图模型的视图?
你可以有一个简单的列表,为哪个viewmodel创建视图,在我的例子中,我可以使用简单的反射,因为名称是匹配的:
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);
}
当然,视图需要一个构造函数接受viewmodel作为参数:
public partial class ValidateCustomerDeletedView : UserControl
{
public ValidateCustomerDeletedView(ValidateCustomerDeletedViewModel dac)
{
InitializeComponent();
this.DataContext = dac;
}
}
我的窗口如何?
简单:我的主窗口确实实现了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
}
(这对于NavigationWindow和将视图包装到页面中同样有效)
当然这是可测试的,因为导航控制器可以轻松嘲笑。
我不确定这是否是一个完美的解决方案,但它现在对我很有用。欢迎任何想法和意见!
答案 1 :(得分:1)
对于你的删除消息框,我通过界面抽象出消息框。与此类似。我还为我的WPF应用程序注入了这些接口。
构造
public MyViewModel(IMessage msg)
{
_msg = msg;
}
然后,在ViewModel上的方法delete方法......类似
public void Delete()
{
if(CanDelete)
{
//do the delete
}
else
{
_msg.Show("You can't delete this record");
}
}
这将使其可测试,您可以插入不实际显示消息框的不同IMessage实现。出于测试目的,这些可能只是打印到控制台。显然,您的WPF应用程序可能具有类似
的实现public class MessageBoxQuestion : IMessage
{
public void Show(string message)
{
MessageBox.Show(message);
}
}
这样做可以非常简单直接地测试不同的路线(想想是/否对话)。您可以想象删除确认。您可以使用IMessage的具体实例返回true / false进行确认,也可以在测试期间模拟容器。
[Test]
public void Can_Cancel_Delete()
{
var vm = new ProductViewModel(_cancel);
...
}
[Test]
public void Can_Confirm_Delete()
{
var vm = new ProductViewModel(_yes);
...
}
关于何时使用Command的其他问题,我从相关视图中实例化Add New或Details视图。就像你的例子中一样。 视图仅在我们的应用中由其他视图实例化。在这些情况下我不使用命令。但是,我确实将父视图的ViewModel属性用于子视图。
public void Object_DoubleClick(object sender, EventArgs e)
{
var detailView = new DetailView(ViewModel.Product);
detailView.Show();
}
希望这有帮助!
答案 2 :(得分:0)
新的和从列表中删除将是很好的例子。在这些情况下,会添加空白记录或ViewModel删除当前记录。视图采取的任何行动都应该是对发生的事件的回应。
答案 3 :(得分:0)
一种方法是使用业务层可以修改的命令参数对象,并且您的VM可以在执行命令后进行处理。