【问题标题】:Big smart ViewModels, dumb Views, and any model, the best MVVM approach?大型智能视图模型、哑视图和任何模型,最好的 MVVM 方法?
【发布时间】:2010-10-25 20:41:30
【问题描述】:

以下代码是对我之前的 MVVM 方法 (Fat Models, skinny ViewModels and dumb Views, the best MVVM approach?) 的重构,在该方法中,我将逻辑和 INotifyPropertyChanged 实现从模型移回了 ViewModel。这更有意义,因为正如所指出的,您通常必须使用无法更改或不想更改的模型,因此您的 MVVM 方法应该能够使用碰巧存在的任何模型类。

此示例仍然允许您在 Visual Studio 和 Expression Blend 中以设计模式查看模型中的实时数据,我认为这很重要,因为您可以拥有设计器连接到的模拟数据存储其中有例如UI 可能遇到的最小和最大字符串,以便他可以根据这些极端情况调整设计。

问题:

  • 我有点惊讶,我什至必须在我的 ViewModel 中“放置一个计时器”,因为这似乎是 INotifyPropertyChanged 的​​一个功能,它似乎是多余的,但这是我可以让 XAML UI 的唯一方法不断(每秒一次)反映我的模型的状态。因此,如果您在未来遇到任何不利情况,听到任何可能采取这种方法的人会很有趣,例如具有线程或性能。

如果您只是将 XAML 和后面的代码复制到新的 WPF 项目中,则以下代码将起作用。

XAML:

<Window x:Class="TestMvvm73892.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestMvvm73892"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <ObjectDataProvider 
              x:Key="DataSourceCustomer" 
              ObjectType="{x:Type local:CustomerViewModel}" 
             MethodName="GetCustomerViewModel"/>
    </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;
using System.Windows;
using System.ComponentModel;
using System.Threading;

namespace TestMvvm73892
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }

    //view model
    public class CustomerViewModel : INotifyPropertyChanged
    {
        private string _firstName;
        private string _lastName;
        private DateTime _timeOfMostRecentActivity;
        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 CustomerViewModel()
        {
            _timer = new Timer(CheckForChangesInModel, null, 0, 1000);
        }

        private void CheckForChangesInModel(object state)
        {
            Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();
            MapFieldsFromModeltoViewModel(currentCustomer, this);
        }

        public static CustomerViewModel GetCustomerViewModel()
        {
            CustomerViewModel customerViewModel = new CustomerViewModel();
            Customer currentCustomer = CustomerViewModel.GetCurrentCustomer();

            MapFieldsFromModeltoViewModel(currentCustomer, customerViewModel);

            return customerViewModel;
        }

        public static void MapFieldsFromModeltoViewModel
             (Customer model, CustomerViewModel viewModel) 
        {
            viewModel.FirstName = model.FirstName;
            viewModel.LastName = model.LastName;
            viewModel.TimeOfMostRecentActivity = model.TimeOfMostRecentActivity;
        }

        public static Customer GetCurrentCustomer()
        {
            return Customer.GetCurrentCustomer();
        }


        //INotifyPropertyChanged implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

    }

    //model
    public class Customer
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime TimeOfMostRecentActivity { get; set; }

        public static Customer GetCurrentCustomer()
        {
            return new Customer 
                       { FirstName = "Jim"
                         , LastName = "Smith"
                         , TimeOfMostRecentActivity = DateTime.Now 
                       };
        }

    }

}

【问题讨论】:

标签: c# wpf mvvm architecture


【解决方案1】:

我喜欢你上面的示例,我认为它实现了 MVVM 的精神。不过,为了澄清起见,ViewModel 代码和 Model 代码不应与实际的背后代码位于同一源文件中。事实上,我认为他们不应该在同一个项目中。

这是我理解的 MVVM:

M - 模型是从业务层 (BL) 返回的数据。这应该是轻量级的,包含只读数据。 Model 类是哑的,不包含 Update、Write 或 Delete 逻辑,并且由 BL 作为请求、命令、操作等的结果生成。Model 类不知道消费应用程序的呈现需求,因此它们可以被任何方式的应用程序使用。为了真正利用这种可重用性,我们希望模型类独立于 UI 项目。

VM - ViewModel 包含通信层:它向 BL 发出请求并以适合呈现的方式处理结果。与上面的示例一样,它也接收模型并重新格式化它以满足特定的演示需求。将其视为“绑定类”。在上面的示例中,数据只是从一个对象移动到下一个对象,但 ViewModel 将负责诸如公开“FullName”类型属性或将前导零添加到 ZipCode 之类的事情。绑定类是实现 INotifyPropertyChanged 的​​类是正确的。再一次,为了可重用性,我可能会将这一层提取到它自己的项目中。这将允许您在不更改管道的情况下尝试各种 UI 选项。

V - View 绑定到在 VM 中创建的 Binding 类对象。 View 超级愚蠢:它对 BL 或 VM 一无所知。数据可以双向绑定,但 VM 会处理错误、验证等。任何数据同步操作都是通过将请求传回 BL 并再次处理结果来处理的。

这取决于应用程序的类型,但不断检查模型以查看它是否已更改似乎很笨拙。假设您正在连接到从 DAL 构建业务对象(BO)的 BL,该 DAL 正在连接到数据库。在这种情况下,您将不断地重新创建 BO,我敢肯定这会成为性能杀手。您可以在 BL 上实现一个收听通知的结帐系统,或者有一种方法来将最后已知的更改时间与实际时间进行比较,或者您可以在 BL 上缓存 BO。只是一些想法。

另外,我上面说过模型应该是轻量级的。有一些重量级的选项,比如 CSLA,但我不确定它们与 MVVM 理念的契合程度。

我并不是要冒充专家,到目前为止,我只是在设计我们的新软件架构时研究了这些想法。我很想阅读一些关于这个主题的讨论。

【讨论】:

  • 需要考虑很多谢谢,是的,我的 MV 和 M 都在后面的代码中,只是为了方便复制,一个问题:如果你的模型不包含更新、写入和删除方法,是您将那些“由请求、命令、操作处理”的方法放在 ViewModel 中?但是另一个需要使用您的模型的 UI 层呢,它肯定需要以相同的方式更新、写入和删除
  • CRUD 方法属于业务层。 VM 只是将请求传递给 BL。所以流程是这样的:V响应用户输入并触发一个ICommand,它在VM上执行一个方法,它将请求发送给BL,BL返回一个M对象,VM处理并转换为Binding Object,它被传递回 V。
  • 糟糕,忘了回答一个问题:另一个 UI 层可以以相同的方式使用相同的 VM(这是它在自己的项目中的另一个原因)
【解决方案2】:

我个人的看法是,虽然 Model 应该用于加载和存储数据,但 ViewModel 的 的职责是知道何时需要这些数据,因此在 ViewModel 中使用计时器说得通。这样您就可以将您的模型与不同的 ViewModel 一起使用(对于这种情况,只检索一次数据就足够了,而不是每秒一次)。

需要考虑的几件事:

  • 实施您的模型以支持 异步数据检索(非常 如果您想定位 Silverlight,这很重要)
  • 注意从后台线程更新集合(在您的示例中不是问题,但如果您需要使用 ObservableCollection 而不是记住它不能从非 UI 线程更新,read more here

【讨论】:

    猜你喜欢
    • 2010-10-25
    • 2010-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多