胖模型,瘦瘦的ViewModel和哑视图,最好的MVVM方法?

时间:2009-05-12 12:15:08

标签: wpf mvvm architecture

通过对this question的慷慨帮助,我整理了以下MVVM结构,该结构在XAML(当前日期/时间)中实时显示模型的变化,非常好。

  

这种设置的一个很好的优势是   当你看到你的视图时   Visual Studio或。的设计模式   混合,你看到时间滴答,   这意味着在设计时你   可以访问您的实时数据   模型。

在实现此功能的过程中,我很惊讶地看到大部分批量从我的ViewModel迁移到我的模型,包括实现INotifyPropertyChange。另一个变化是我不再绑定到ViewModel上的属性,而是绑定到方法

所以目前这是我最喜欢的MVVM风格:

  1. 视野愚蠢:

    • 您需要从模型中获取的每个对象的一个​​ObjectDataProvider
    • 每个ObjectDataProvider映射到ViewModel上的方法(不是属性)
    • no x:XAML元素中的名称属性
  2. ViewModel是瘦的:

    • ViewModel中唯一的东西是视图绑定的方法
  3. 模特很胖:

    • 该模型在其每个属性上实现了INotifyPropertyChanged。
    • 对于ViewModel上的每个方法(例如GetCurrentCustomer),模型中都有相应的单例方法(例如GetCurrentCustomer)。
    • 该模型负责处理任何实时线程功能,如本例所示
  4. 问题:

    1. 那些在真实场景中实施过MVVM的人,这是你们已经确定的基本结构,如果没有,你们的变化如何?
    2. 您如何将其扩展为包含路由命令和路由事件?
    3. 如果您只是将XAML和代码复制到新的WPF项目中,以下代码将起作用。

      XAML:

      <Window x:Class="TestBinding99382.Window1"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:local="clr-namespace:TestBinding99382"
          Title="Window1" Height="300" Width="300">
      
          <Window.Resources>
              <ObjectDataProvider 
                   x:Key="DataSourceCustomer" 
                   ObjectType="{x:Type local:ShowCustomerViewModel}" 
                              MethodName="GetCurrentCustomer"/>
          </Window.Resources>
      
          <DockPanel DataContext="{StaticResource DataSourceCustomer}">
              <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
                  <TextBlock Text="{Binding Path=FirstName}"/>
                  <TextBlock Text=" "/>
                  <TextBlock Text="{Binding Path=LastName}"/>
              </StackPanel>
              <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
                  <TextBlock Text="{Binding Path=TimeOfMostRecentActivity}"/>
              </StackPanel>
      
          </DockPanel>
      </Window>
      

      代码背后:

      using System.Windows;
      using System.ComponentModel;
      using System;
      using System.Threading;
      
      namespace TestBinding99382
      {
          public partial class Window1 : Window
          {
              public Window1()
              {
                  InitializeComponent();
              }
          }
      
          //view model
          public class ShowCustomerViewModel
          {
              public Customer GetCurrentCustomer() {
                  return Customer.GetCurrentCustomer();
              }
          }
      
          //model
          public class Customer : INotifyPropertyChanged
          {
              private string _firstName;
              private string _lastName;
              private DateTime _timeOfMostRecentActivity;
              private static Customer _currentCustomer;
              private Timer _timer;
      
              public string FirstName
              {
                  get
                  {
                      return _firstName;
                  }
                  set
                  {
                      _firstName = value;
                      this.RaisePropertyChanged("FirstName");
                  }
              }
      
              public string LastName
              {
                  get
                  {
                      return _lastName;
                  }
                  set
                  {
                      _lastName = value;
                      this.RaisePropertyChanged("LastName");
                  }
              }
      
              public DateTime TimeOfMostRecentActivity
              {
                  get
                  {
                      return _timeOfMostRecentActivity;
                  }
                  set
                  {
                      _timeOfMostRecentActivity = value;
                      this.RaisePropertyChanged("TimeOfMostRecentActivity");
                  }
              }
      
              public Customer()
              {
                  _timer = new Timer(UpdateDateTime, null, 0, 1000);
              }
      
              private void UpdateDateTime(object state)
              {
                  TimeOfMostRecentActivity = DateTime.Now;
              }
      
              public static Customer GetCurrentCustomer()
              {
                  if (_currentCustomer == null)
                  {
                      _currentCustomer = new Customer 
                           {  FirstName = "Jim"
                              , LastName = "Smith"
                              , TimeOfMostRecentActivity = DateTime.Now 
                           };
                  }
                  return _currentCustomer;
              }
      
              //INotifyPropertyChanged implementation
              public event PropertyChangedEventHandler PropertyChanged;
              private void RaisePropertyChanged(string property)
              {
                  if (PropertyChanged != null)
                  {
                      PropertyChanged(this, new PropertyChangedEventArgs(property));
                  }
              }
          }
      }
      

2 个答案:

答案 0 :(得分:28)

这是我的观点,因为它值得:

我真的不同意你建议的方法(除了愚蠢的观点)。在现实生活中,您通常必须使用现有模型:它可能是您没有时间(或将要)更改的遗留代码,甚至是您没有代码的库。在我看来,模型应该完全不知道它的显示方式,并且应该可以在非WPF应用程序中轻松使用。因此,它不必实现INotifyPropertyChanged INotifyCollectionChanged之类的任何特定接口,以使其在MVVM中可用。我认为与UI相关的所有逻辑都应该存在于ViewModel中。

关于RoutedEventsRoutedCommands,它们并不适合与MVVM模式一起使用。我通常尝试使用尽可能少的RoutedEvents,而根本不使用RoutedCommands。相反,我的ViewModel公开了我绑定到XAML中的UI的RelayCommand属性(有关RelayCommand的详细信息,请参阅Josh Smith的this article)。当我真的需要为某些控件处理事件时,我使用附加行为将事件映射到ViewModel命令(看看Marlon Grech's implementation

所以,总结一下:

  • Dumb View
  • 大而聪明的ViewModel
  • 您想要或必须使用的任何型号

当然这只是我的方法,它可能不是最好的,但我觉得很舒服;)

答案 1 :(得分:2)

我同意托马斯的观点。 我对WPF架构的任何人的建议是:

  • 没有INotifyPropertyChange,状态跟踪,BL等的普通POCO实体
  • 即时通知视图的简单和小型ViewModel
  • 具有智能导航系统的简单可重用UI,可避免复杂的数据层次结构和复杂的基础ViewModel
  • 使用View First方法保持依赖关系简单的MVVM
  • 使用Tasks或Rx执行异步操作
  • 一个简单的主题
  • 没有复杂的强大UI,保持简单,只需利用WPF的UI组合和绑定功能
  • 不要犹豫使用代码隐藏动态生成内容(表单,列表等),并在声明性眼睛配置上节省大量时间(适用于大多数情况) - 对我来说,必须在2015年使用。使用扩展方法为此创建一个Fluent API。