大智能ViewModels,哑视图和任何模型,最好的MVVM方法?

时间:2009-05-13 13:03:13

标签: c# wpf mvvm architecture

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

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

问题:

  • 我有点惊讶我甚至不得不在我的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 
                       };
        }

    }

}

2 个答案:

答案 0 :(得分:13)

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

根据我的理解,这是MVVM:

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

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

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

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

另外,我在上面说过,模型应该是轻量级的。有很多重量级的选择,比如CSLA,但我不确定它们是否适合MVVM的想法。

我并不是故意让自己成为一名专家,到目前为止,我只是在设计新软件架构时一直在研究这些想法。我很想阅读有关这个​​主题的一些讨论。

答案 1 :(得分:3)

我个人认为,虽然模型应该用于加载和存储数据, ViewModel的职责是知道何时需要此数据,因此在ViewModel中使用计时器说得通。通过这种方式,您可以将模型与不同的ViewModel一起使用(为此,只需检索一次数据,而不是每秒检索一次)。

很少有事情需要考虑:

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