一篇关于C# WPF MVVM 实战与总结

MVVM 就是 Model – View – ViewModel 三组功能(类)分割的设计模式。废话不多说,不知道的自己上网查。

用 MVVM 我认为最大好处是能对 ViewModel 做单元测试。另外,MVVM 分工也比较明显,方便安排程序员分组分工进行项目,基本设计有了之后可以各自敲。

这样的话,写出来,类(class)最起码有三个。比如 Window1 作为 View,Window1ViewModel 作为 ViewModel,实际业务类比如 Sales Order 销售订单作为 Model。

View 不一定要是 System.Control.Window,UserControl 也可以,Page也行,总之,是 UI 用来显示用的。

常用基类

有两个基类,做 MVVM 你是几乎必定要有,不然写起来很麻烦,没效率:

  1. ViewModelBase 
  2. RelayCommand / DelegateCommand

我以下所有代码都不会列这两个出来。

ViewModelBase 代码到处都是,随便 Google 一下抄下来,然后写 ViewModel 比如 Window1ViewModel 类时候,继承自 ViewModelBase 即可。它的主要作用,是提供 OnPropertyChange(string propertyName) 这方法,告诉视图 View 知道,值发生变化需要更新显示。

好的 ViewModelBase 设计,会加入 Conditional Attribute 让Debug 运行时检查 OnPropertyChanged 的参数值是否与属性名相同。

RelayCommand / DelegateCommand 代码也是到处都是,随便 Google 一下抄下来就是了。它是一个实现了 ICommand 接口的类。做命令的绑定,比如 Button 中的 Command 属性,绑定时它的类型要求是 ICommand 的东西。ICommand 比起点击事件优胜的地方是,ICommand 除了委托执行方法以外,还有一个 CanExecute 的委托,可以自动(呃,其实也需要 requery ) Enable / Disable 按钮。

要注意的杂项

初学的童鞋们有几点要注意:

  1. ICommand 一般不传参数,不是不可以,只是一般来说没必要。一切值都在 ViewModel 内的时候,你不用让 View 再告诉你什么新鲜事 
  2. 一般来说一个 ViewModel 对一个 View,有可能一个 ViewModel 对两个 View,但不会一个 View 有多个 ViewModel 
  3. WPF 的 PasswordBox 做不了绑定,据说是安全性原因,虽然这是灰常无聊加无奈,没办法,微软设计的。当然有办法可以越过,自己想想 
  4. 不一定在 View 的背后代码一句都不写才叫做 MVVM,但操作数据的不会在 View 内出现,操作 View 的不会在 ViewModel 出现,这分工做不到的就尝不到 MVVM 的优点

示范

来个简单的示范:

View 部分

  1. <Window x:Class="WPF_Binding_Example.MainView"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    Title="Window1" Height="300" Width="300">
  5.    
  6.     <Window.Resources>
  7.         <Style x:Key="myStyle" TargetType="StackPanel">
  8.             <Style.Triggers>
  9.                 <DataTrigger Binding="{Binding Path=Age}"
  10.                             Value="0">
  11.                     <Setter Property="Background"
  12.                            Value="Yellow"/>
  13.                 </DataTrigger>
  14.             </Style.Triggers>
  15.         </Style>
  16.         <DataTemplate x:Key="PersonTemplate">
  17.             <StackPanel Margin="2"
  18.                        Orientation="Horizontal"
  19.                        Style="{StaticResource myStyle}">
  20.                 <TextBlock Text="{Binding Name}"
  21.                           Width="50"/>
  22.                 <TextBlock Text="{Binding Age}"/>
  23.             </StackPanel>
  24.         </DataTemplate>
  25.     </Window.Resources>
  26.    
  27.     <Grid>
  28.         <ListView Margin="38,50,33,59"
  29.                  Name="listView1"
  30.                  ItemsSource="{Binding PersonList}"
  31.                  ItemTemplate="{StaticResource PersonTemplate}"
  32.                  />
  33.         <Button Command="{Binding AddRowCommand}"
  34.                Height="23"
  35.                Width="75"
  36.                HorizontalAlignment="Right"
  37.                Margin="0,0,11,10"
  38.                VerticalAlignment="Bottom"
  39.                Content="加一行" />
  40.     </Grid>
  41. </Window>

画这界面的同事,只需要知道三件事:

  1. ViewModel 内会有一个叫做 PersonList 的公共类集合(30 行),要用 ListView 显示,按内容做些样式 
  2. PersonList 类集合内的单个对象,有两个公共属性,分别是 Name 和 Age (20、22行),但不需要知道实际是什么类 
  3. ViewModel 内会有一个实现了 ICommand 接口的实例引用,名字是 AddRowCommand(33行)

然后,ViewModel 部分

  1. using System.Collections.ObjectModel; 
  2. using System.Windows.Input; 
  3. using WPF_Binding_Example.Domain; 
  4. using WPF_Binding_Example.Infrastructure; 
  5.  
  6. namespace WPF_Binding_Example 
  7.     public class MainViewModel : ViewModelBase
  8.     { 
  9.         public MainViewModel() 
  10.         { 
  11.             personList = new ObservableCollection<Person>(); 
  12.  
  13.             //演示用
  14.             Add_Dummy_Data(); 
  15.         } 
  16.  
  17.         ///<summary>
  18.         /// 演示用
  19.         ///</summary>
  20.         private void Add_Dummy_Data() 
  21.         { 
  22.             PersonList.Add( 
  23.                 new Person { Name = "张三", Age = 26 } 
  24.                 ); 
  25.             PersonList.Add( 
  26.                 new Person { Name = "李四", Age = 24 } 
  27.                 ); 
  28.         } 
  29.  
  30.         private ObservableCollection<Person> personList; 
  31.         public ObservableCollection<Person> PersonList 
  32.         { 
  33.             get { return personList; } 
  34.         } 
  35.  
  36.         private RelayCommand addRowCommand; 
  37.         public ICommand AddRowCommand 
  38.         { 
  39.             get
  40.             { 
  41.                 if (addRowCommand == null
  42.                 { 
  43.                     addRowCommand = 
  44.                         new RelayCommand(x => this.AddRow()); 
  45.                 } 
  46.                 return addRowCommand; 
  47.             } 
  48.         } 
  49.  
  50.         private void AddRow() 
  51.         { 
  52.             this.PersonList.Add( 
  53.                 new Person { Name = "我是新人", Age = 0 } 
  54.                 ); 
  55.         } 
  56.     } 
  57. }

负责这个类的同事,不需要知道界面的任何东西,这类的代码也非常简单。

ObservableCollection 在 System.Collections.ObjectModel 内,它与其他集合的分别是,集合有变化时,比如加减 Item 等,它会发出通知告诉视图。如果你有自己很有个性的 Collection,要做绑定的话,让它实现  INotifyCollectionChanged 和 INotifyPropertyChanged 即可。

RelayCommand 上面说了,是实现了 ICommand,所以出现了 44-47 行这样的写法。刚才说了 ICommand 还有个 CanExecute 的委托,这里没用到。RelayCommand 的设计是,构造函数参数只有一个方法委托时候,CanExecute 默认返回 True,即永远可执行。有空的自己看看 RelayCommand 的代码,不多,很容易看懂。

 

然后是 Model 部分

  1. namespace WPF_Binding_Example.Domain 
  2.     public class Person
  3.     { 
  4.         private string name; 
  5.         public string Name 
  6.         { 
  7.             get { return name; } 
  8.             set
  9.             { 
  10.                 if (value != name) 
  11.                 { 
  12.                     name = value
  13.                 } 
  14.             } 
  15.         } 
  16.  
  17.         private int age; 
  18.         public int Age 
  19.         { 
  20.             get { return age; } 
  21.             set
  22.             { 
  23.                 if (value != age) 
  24.                 { 
  25.                     age = value
  26.                 } 
  27.             } 
  28.         } 
  29.     } 
  30. }

 

View 连接 ViewModel

说了半天,除了 ViewModel 和 Model 在上面代码有点关系以外,View 不认识 ViewModel,ViewModel 不认识 View,怎样连在一起?

这样:

  1. using System.Windows; 
  2.  
  3. namespace WPF_Binding_Example 
  4.     ///<summary>
  5.     /// Interaction logic for App.xaml
  6.     ///</summary>
  7.     public partial class App : Application
  8.     { 
  9.         protected override void OnStartup(StartupEventArgs e) 
  10.         { 
  11.             base.OnStartup(e); 
  12.             MainViewModel vm = new MainViewModel(); 
  13.             MainView view = new MainView(); 
  14.             view.DataContext = vm; 
  15.             view.Show(); 
  16.         } 
  17.     } 
  18. }

初学的童鞋们请把 app.xaml 的 StartupUri 删除,然后在 App 类重写 OnStartup 方法,如上。

把 View 和 ViewModel 连在一起的,就一句 view.DataContext = vm;  有人会用 view 构造函数注入 ViewModel,也可以反过来在 ViewModel 构造函数注入 view,然后在 IoC 注册后直接 resolve 出来用,或者写在 XAML 内也行,但都离不开这一句 DataContext = vm。用哪种是看情况而定,我一般是在 VM 注入 V。

接下去会介绍怎样单元测试,和各种情景各种数据结构和控件,怎样用 MVVM 模式做绑定。下一篇,是采购订单做法的一个示例。

posted @ 2012-12-07 15:06  JunBird  阅读(2329)  评论(0编辑  收藏  举报