【问题标题】:Fat Models, skinny ViewModels and dumb Views, the best MVVM approach?胖模型、瘦视图模型和哑视图,最好的 MVVM 方法?
【发布时间】:2010-10-25 13:04:17
【问题描述】:

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

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

在实现此功能的过程中,我惊讶地发现大部分内容从我的 ViewModel 转移到了我的模型中,包括 INotifyPropertyChange 的实现。另一个变化是我不再绑定到 ViewModel 上的 properties,而是绑定到 methods

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

  1. 视图很笨:

    • 一个 ObjectDataProvider 用于您模型中需要的每个对象
    • 每个 ObjectDataProvider 都映射到 ViewModel 上的一个方法(不是属性)
    • XAML 元素中没有 x:Name 属性
  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>

代码背后:

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));
            }
        }
    }
}

【问题讨论】:

标签: wpf mvvm architecture


【解决方案1】:

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

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

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

所以,总结一下:

  • 愚蠢的看法
  • 大而智能的 ViewModel
  • 您想要或必须使用的任何型号

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

【讨论】:

  • 非常有见地,特别是关于模型,您应该能够使用 MVVM 模式连接到不了解 WPF 的旧项目的数据类。有趣的是,您认为 RoutedEvents 和 RoutedCommands 并不真正适合 MVVM,我认为它们被用于解耦模式,例如 MVVM。感谢您的反馈。
  • 我根据这个反馈重构了这个例子,把逻辑放到 ViewModel 中:stackoverflow.com/questions/857820/…
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2010-10-25
    • 2010-12-07
    • 1970-01-01
    • 2011-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多