我一直在使用我的M-V-VM开发一个非常大的LOB应用程序,我称之为M-V-MC(Model-View-ModelController),它是M-V-C和M-V-VM之间的一种组合。我发布了这个答案视图如何在MV-VM中实例化为问题“什么-是最最常见-错误造合WPF的发展"

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

  

这会产生一个后续问题:如何   你创建了视图吗?我用   RelayCommands绑定动作   查看ViewModel,以便查看   甚至不知道某个行动   解雇了,不知道他应该开个   新观点。解决方案:在中创建一个事件   要查看的视图的VM?

当我最初开始MV-VM开发时,我认为一切都应该存在于ViewModel中,并且已经研究了很多像 Josh Smith Karl Shifflett 。但是,我还没有提出一个很好的例子,说明命令何时需要存在于ViewModel中。

例如,假设我有一个显示Customers的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);
}

此控制器包含两个操作: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和将视图包装到页面中同样有效)

当然这是可测试的,因为导航控制器可以轻松嘲笑。

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

其他提示

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

希望这有帮助!

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top