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

Edw*_*uay 18 architecture wpf mvvm

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

这方面的一个凉爽的优势建立的是,当你看到你的观点在设计模式中的Visual Studio或混合的,你看时间流逝的,这意味着在设计时可以访问从模型的实时数据.

在让它工作的过程中,我惊讶地发现大部分批量从我的ViewModel移动到我的模型中,包括INotifyPropertyChange的实现.另一个变化是我不再绑定ViewModel上的属性而是绑定到方法.

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

  1. 视野愚蠢:

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

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

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

问题:

  1. 那些在真实场景中实施MVVM的人,这是你已经确定的基本结构,如果没有,你的变化如何?
  2. 您如何扩展它以包括路由命令和路由事件?

如果您只是将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>
Run Code Online (Sandbox Code Playgroud)

代码背后:

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));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Tho*_*que 28

这是我的意见,因为它的价值:

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

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

所以,总结一下:

  • 愚蠢的观点
  • 大而聪明的ViewModel
  • 您想要或必须使用的任何型号

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