16

我一直在使用我称之为 MV-MC (Model-View-ModelController) 的 MV-VM 风格开发一个非常大的 LOB 应用程序,它是 MVC 和 MV-VM 的一种组合。我已经发布了这个关于视图如何在 MV-VM 中实例化到问题“ what-are-the-most-common-mistakes-made-in-wpf-development ”的答案。

Sam对我的回答发表了以下评论:

这就产生了一个后续问题:您如何创建视图?我使用 RelayCommands 将视图中的动作绑定到 ViewModel,因此视图甚至不知道动作已经触发,也不知道他应该打开一个新视图。解决方案:在VM中创建一个事件供View订阅?

当我最初开始 MV-VM 开发时,我认为一切都应该存在于 ViewModel 中,并且研究了Josh SmithKarl Shifflett等人的大量示例。但是,我还没有想出一个很好的例子来说明命令何时需要存在于 ViewModel 中。

例如,假设我有一个显示客户的 ListView,以及一个单击以允许我编辑当前选择的客户的按钮。ListView (View) 绑定到 CustomerVM (ViewModel)。单击该按钮会触发 EditCustomerCommand,它会打开一个弹出窗口,允许我编辑 CustomerVM 的所有属性。这个 EditCustomerCommand 在哪里?如果它涉及打开一个窗口(UI 功能),它不应该在视图的代码隐藏中定义吗? 替代文字

有没有人有任何我应该在 View 和 ViewModel 中定义命令的示例?

马修赖特在下面陈述:

从列表中新建和删除将是很好的例子。在这些情况下,ViewModel 会添加一条空白记录或删除当前记录。视图采取的任何行动都应响应发生的这些事件。

所以如果我点击新按钮,会发生什么?CustomerVM 的新实例由 Parent ViewModel 创建并添加到它的集合中,对吗?那么如何打开我的编辑屏幕呢?视图应该创建 Customer ViewModel 的新实例,并将其传递给 ParentVM.Add(newlyCreatedVM) 方法,对吗?

假设我通过虚拟机上的 DeleteCommand 删除客户记录。VM 调用业务层并尝试删除记录。它不能,所以它会向虚拟机返回一条消息。我想在对话框中显示此消息。视图如何从命令操作中获取消息?

4

4 回答 4

3

从来没有想过我会看到自己在一个问题中被引用。

我自己思考了一段时间这个问题,并为我的代码库做出了一个相当务实的决定:

在我的代码库中,ViewModel 在动作发生时被调用,我希望它保持这种状态。此外,我不希望 ViewModel 控制视图。

我做了什么?
我添加了一个导航控制器:

public interface INavigation
{
  void NewContent(ViewModel viewmodel);
  void NewWindow(ViewModel viewmodel);
}

这个控制器确实包含两个动作:NewContent() 确实在当前窗口中显示新内容,NewWindow() 创建一个新窗口,用内容填充它并显示它。
当然,我的视图模型不知道要显示哪个视图。但是他们确实知道要显示哪个视图模型,因此根据您的示例,当执行 DeleteCommand 时,它将调用导航服务函数NewWindow(new ValidateCustomerDeletedViewModel())以显示一个窗口,说明“客户已被删除”(对于这个简单的消息框,但很容易为简单的消息框提供一个特殊的导航器功能)。

viewmodel如何获取导航服务?

我的 viewmodel 类有一个导航控制器的属性:

public class ViewModel
{
  public INavigation Navigator { get; set; }
  [...]
}

当视图模型附加到窗口(或任何显示视图)时,窗口将设置 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);
}

当然,视图需要一个接受视图模型作为参数的构造函数:

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 并将视图包装到页面中)

当然这是可测试的,因为导航控制器可以很容易地模拟。

我不确定这是否是一个完美的解决方案,但它现在对我来说效果很好。欢迎任何想法和意见!

于 2009-01-16T11:47:47.687 回答
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);
  ...

}

对于您关于何时使用命令的其他问题,我从相关视图中实例化添加新视图或详细信息视图。就像你在你的例子中一样。 视图仅由我们应用程序中的其他视图实例化。在这些情况下,我不使用命令。但是,我确实将父视图的 ViewModel 属性用于子视图。

public void Object_DoubleClick(object sender, EventArgs e)
{
  var detailView = new DetailView(ViewModel.Product);
  detailView.Show();
}

希望这可以帮助!

于 2009-01-09T20:03:20.377 回答
0

从列表中新建和删除将是很好的例子。在这些情况下,ViewModel 会添加一条空白记录或删除当前记录。视图采取的任何行动都应响应发生的这些事件。

于 2009-01-08T17:00:09.990 回答
0

一种方法是使用业务层可以修改的命令参数对象,并且您的 VM 可以在执行命令后处理。

于 2009-01-09T14:15:07.880 回答