【问题标题】:M-V-VM - Any Examples of using commands in the ViewModel?M-V-VM - 在 ViewModel 中使用命令的任何示例?
【发布时间】:2010-09-30 07:25:36
【问题描述】:

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

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

这产生了一个后续问题:如何 你创建视图吗?我用 RelayCommands 绑定动作 视图到 ViewModel,所以视图 甚至不知道一个动作有 解雇了,不知道他应该开一个 新观点。解决方案:在 视图要订阅的 VM?

当我最初开始 M-V-VM 开发时,我有一个想法,即一切都应该存在于 ViewModel 中,并且研究了很多来自像 Josh SmithKarl Shifflett 这样的人的例子。但是,我还没有想出一个很好的例子来说明命令何时需要存在于 ViewModel 中。

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

谁有我应该在 View 和 ViewModel 中定义命令的示例?

Matthew Wright 状态如下:

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

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

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

【问题讨论】:

    标签: wpf mvvm


    【解决方案1】:

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

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

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

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

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

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

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

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

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

    【讨论】:

      【解决方案2】:

      对于您的删除消息框案例,我通过接口抽象出消息框。与此类似。我还为我的 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();
      }
      

      希望这会有所帮助!

      【讨论】:

      • 在我对 MVV 的理解中,从虚拟机“向上调用”到 V——即使是通过接口——也不好。我只需设置一个属性(如 ErrorText 或 IDataErrorInfo)并让 V 通过 PropertyChanged 处理它
      • 大卫,你能详细说明为什么“上调”不是一个好主意吗?这将非常有帮助!
      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • 那么如果我点击新按钮,会发生什么?视图应该创建 ViewModel 的新实例,并将其传递给 CurrentVM.Add(newlyCreatedVM) 对吧?
        • 假设我通过虚拟机上的 DeleteCommand 删除了一条客户记录。 VM 调用业务层并尝试删除记录。它不能,所以它会向虚拟机返回一条消息。我想在对话框中显示此消息。视图如何从命令动作中获取消息?
        猜你喜欢
        • 2010-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多