前言 MVVM 就是 Model – View – ViewModel 三组功能(类)分割的设计模式。废话不多说,不知道的自己上网查。 用 MVVM 我认为最大好处是能对 ViewModel 做单元测试。另外,MVVM 分工也比较明显,方便安排程序员分组分工进行项目,基本设计有了之后可以各自敲。 这样的话,写出来,类(class)最起码有三个。比如 Window1 作为 View,Window1ViewModel 作为 ViewModel,实际业务类比如 Sales Order 销售订单作为 Model。 View 不一定要是 System.Control.Window,UserControl 也可以,Page也行,总之,是 UI 用来显示用的。 常用基类 有两个基类,做 MVVM 你有的话会方便一些:
- ViewModelBase
- RelayCommand / DelegateCommand
我以下代码都不会列这两个出来。 ViewModelBase 代码比较常见,搜索然后抄下来就可以了,然后写 ViewModel 比如 Window1ViewModel 类时候,继承自 ViewModelBase 即可。它的主要作用,是提供 OnPropertyChange(string propertyName) 这方法,告诉视图 View 知道,值发生变化需要更新显示。它的代码,比如如下: /// <summary> /// Provides common functionality for ViewModel classes /// </summary> public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable { public virtual string DisplayName { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } #region IDisposable Members public void Dispose() { this.OnDispose(); } protected virtual void OnDispose() { } #endregion }
个人爱好,删掉 DisplayName 或者自己加其他属性请自便,以它为基类做 ViewModel 时候代码会简单一些。 RelayCommand / DelegateCommand 代码也是比较常见,搜索一下抄下来就是。它是一个实现了 ICommand 接口的类。做命令的绑定,比如 Button 中的 Command 属性,绑定时它的类型要求是 ICommand 的东西。ICommand 比起点击事件优胜的地方是,ICommand 除了委托执行方法以外,还有一个 CanExecute 的委托,可以自动 Enable / Disable 按钮。代码比如: /// <summary> /// Base Relay Command implements ICommand for easy delegation /// </summary> public class RelayCommand : ICommand { #region Fields readonly Action<object> _execute; readonly Predicate<object> _canExecute; #endregion // Fields #region Constructors public RelayCommand(Action<object> execute) : this(execute, null) { } public RelayCommand(Action<object> execute, Predicate<object> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion // Constructors #region ICommand Members [DebuggerStepThrough] public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute(parameter); } #endregion // ICommand Members } WPF 入门请点这里:十五分钟 WPF 入门 要注意的杂项 有几点要注意:
- ICommand 一般不传参数,不是不可以,只是一般来说没必要。一切值都在 ViewModel 内的时候,你不用让 View 再告诉你什么新鲜事
- 一般来说一个 ViewModel 对一个 View
- WPF 的 PasswordBox 做不了绑定,据说是安全性原因,没办法
- 不一定在 View 的背后代码一句都不写才叫做 MVVM,但操作数据的不会在 View 内出现,操作 View 的不会在 ViewModel 出现
示范 来个简单的示范: View 部分
<Window x:Class="WPF_Binding_Example.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Window.Resources> <Style x:Key="myStyle" TargetType="StackPanel"> <Style.Triggers> <DataTrigger Binding="{Binding Path=Age}" Value="0"> <Setter Property="Background" Value="Yellow"/> </DataTrigger> </Style.Triggers> </Style> <DataTemplate x:Key="PersonTemplate"> <StackPanel Margin="2" Orientation="Horizontal" Style="{StaticResource myStyle}"> <TextBlock Text="{Binding Name}" Width="50"/> <TextBlock Text="{Binding Age}"/> </StackPanel> </DataTemplate> </Window.Resources> <Grid> <ListView Margin="38,50,33,59" Name="listView1" ItemsSource="{Binding PersonList}" ItemTemplate="{StaticResource PersonTemplate}" /> <Button Command="{Binding AddRowCommand}" Height="23" Width="75" HorizontalAlignment="Right" Margin="0,0,11,10" VerticalAlignment="Bottom" Content="加一行" /> </Grid>
</Window>
画这界面的同事,只需要知道三件事:
- ViewModel 内会有一个叫做 PersonList 的公共类集合(30 行),要用 ListView 显示,按内容做些样式
- PersonList 类集合内的单个对象,有两个公共属性,分别是 Name 和 Age (20、22行),但不需要知道实际是什么类
- ViewModel 内会有一个实现了 ICommand 接口的实例引用,名字是 AddRowCommand(33行)
然后,ViewModel 部分:
using System.Collections.ObjectModel;
using System.Windows.Input;
using WPF_Binding_Example.Domain;
using WPF_Binding_Example.Infrastructure;
namespace WPF_Binding_Example { public class MainViewModel : ViewModelBase { public MainViewModel() { personList = new ObservableCollection<Person>(); //演示用 Add_Dummy_Data(); } ///<summary> /// 演示用 ///</summary> private void Add_Dummy_Data() { PersonList.Add( new Person { Name = "张三", Age = 26 } ); PersonList.Add( new Person { Name = "李四", Age = 24 } ); } private ObservableCollection<Person> personList; public ObservableCollection<Person> PersonList { get { return personList; } } private RelayCommand addRowCommand; public ICommand AddRowCommand { get { if (addRowCommand == null) { addRowCommand = new RelayCommand(x => this.AddRow()); } return addRowCommand; } } private void AddRow() { this.PersonList.Add( new Person { Name = "我是新人", Age = 0 } ); } } }
负责这个类的同事,不需要知道界面的任何东西,这类的代码也非常简单。 ObservableCollection 在 System.Collections.ObjectModel 内,它与其他集合的分别是,集合有变化时,比如加减 Item 等,它会发出通知告诉视图。如果你有自己很有个性的 Collection,要做绑定的话,让它实现 INotifyCollectionChanged 和 INotifyPropertyChanged 即可。 RelayCommand 上面说了,是实现了 ICommand,所以出现了 44-47 行这样的写法。刚才说了 ICommand 还有个 CanExecute 的委托,这里没用到。RelayCommand 的设计是,构造函数参数只有一个方法委托时候,CanExecute 默认返回 True,即永远可执行。 然后是 Model 部分
namespace WPF_Binding_Example.Domain { public class Person { private string name; public string Name { get { return name; } set { if (value != name) { name = value; } } } private int age; public int Age { get { return age; } set { if (value != age) { age = value; } } } } }
View 连接 ViewModel 说了半天,除了 ViewModel 和 Model 在上面代码有点关系以外,View 不认识 ViewModel,ViewModel 不认识 View,怎样连在一起? 这样:
using System.Windows;
namespace WPF_Binding_Example { ///<summary> /// Interaction logic for App.xaml ///</summary> public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); MainViewModel vm = new MainViewModel(); MainView view = new MainView(); view.DataContext = vm; view.Show(); } } }
请把 WPF 新建项目时,模板自带 app.xaml 的 XAML 代码中 StartupUri 属性删除,然后在 App 类重写 OnStartup 方法,如上。 把 View 和 ViewModel 连在一起的,就一句 view.DataContext = vm; 有人会用 view 构造函数注入 ViewModel,也可以反过来在 ViewModel 构造函数注入 view,然后在 IoC 注册后直接 resolve 出来用,或者写在 XAML 内也行,但都离不开这一句 DataContext = vm。本例子中是在 Application 类重写 OnStartup 方法,把两者创建实例,然后 view.Show 来实现。 接下去会介绍怎样单元测试,和各种情景各种数据结构和控件,怎样用 MVVM 模式做绑定。下一篇,是采购订单做法的一个示例。 我在这群里,欢迎加入交流: .Net 开发交流 595769918
开发板玩家群 578649319 硬件创客 (10105555)
|
请发表评论