关于MVVM设计模式
1、MVVM设计模式基础
如下是MVVM设计模式的图示(这是一个基本模型):
注意事项:
1)ViewModel应实现某些特定接口
- INotifyPropertyCHanged接口:用于ViewModel的属性与XAML中的控件属性进行绑定;
- ICommand接口:指定的命令可以绑定到任何XAML控件中,用于确定控件是否可以执行指定的操作;
- DataTemplate:是一种XAML结构,用于定义如何(在不同状态下)利用ViewModel的数据;
2)简单的MVVM实现:
/// <summary> /// ViewModel for the Employee view /// </summary> public sealed class EmployeeViewModel : INotifyPropertyChanged { public EmployeeViewModel() { var employee = new Employee { FirstName = "John", LastName = "Smith", Company = "Microsoft" }; //Bind the model to the viewmodel this.Firstname = employee.FirstName; this.Lastname = employee.LastName; this.Company = employee.Company; } #region INotifyPropertyChanged /// <summary> /// Occurs when a property value changes. /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Called when [property changed]. /// </summary> /// <param name="name">The name.</param> public void OnPropertyChanged(string name) { var handler = PropertyChanged; if (handler != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } #endregion /// <summary> /// Private accessor for the Firstname /// </summary> private string firstname; /// <summary> /// Gets or sets the firstname. /// </summary> /// <value>The firstname.</value> public string Firstname { get { return firstname; } set { if (firstname != value) { firstname = value; OnPropertyChanged("Firstname"); } } } // 其余属性的INotifyPropertyChanged接口的实现略去 }
View:
<UserControl.DataContext> <vm:EmployeeViewModel /> </UserControl.DataContext> <StackPanel Orientation="Vertical"> <TextBlock>FirstName :</TextBlock> <TextBox Text="{Binding Path=Firstname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock>Lastname :</TextBlock> <TextBox Text="{Binding Path=Lastname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock>Company :</TextBlock> <TextBox Text="{Binding Path=Company, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel>
实现效果如下:
2、实现INotifyPropertyChanged接口
该接口位于System.ComponentModel名称空间。只需要类实现 PropertyChanged事件即可。如前述例子的实现过程,在编写属性的Set访问器时,务必要要注意属性名称的大小写及拼写,否则可能出错也无法进行跟踪解决;除了手工编写代码以外,使用Notify Property Weaver是实现该接口的最简单就去。这是一个Visual Studio的扩展,书写的类只需要继承INotifyPropertyChanged接口,其余的事由该扩展实现 。使用方法如下:
1)从VS扩展管理器获取Notify Property Weaver,下载并安装;
2)将Silverlight项目作为激活项目,选择项目—NotifyPropertyWeaver—Config,打开如下对话框:
保持默认设置并点击OK按钮。项目需要重新加载,一旦类需要实现INotifyPropertyChanged接口,该工具就会为类中的每个属性在编译生成后自动生成属性变更通知代码。
比如:
之前的类代码为:
public class Person : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public string GivenNames { get; set; } public string FamilyName { get; set; } public string FullName { get { return string.Format("{0} {1}", GivenNames, FamilyName); } } }
编译以后:
public class Person : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; string givenNames; public string GivenNames { get { return givenNames; } set { if (value != givenNames) { givenNames = value; OnPropertyChanged("GivenNames"); OnPropertyChanged("FullName"); } } } string familyName; public string FamilyName { get { return familyName; } set { if (value != familyName) { familyName = value; OnPropertyChanged("FamilyName"); OnPropertyChanged("FullName"); } } } public string FullName { get { return string.Format("{0} {1}", GivenNames, FamilyName); } } public virtual void OnPropertyChanged(string propertyName) { var propertyChanged = PropertyChanged; if (propertyChanged != null) { propertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
使用工具可以快速正确地获得该类的实现,为什么不用呢?
3、IEditableObject接口实现
这个接口也位于System.ComponentModel名称空间。该接口可以实现取消或回滚任何对象的变更操作(就是Undo操作)。任何对象的变更都可以使用事务进行跟踪,并根据需要实施取消作业。当事务取消时,任何对象的变更就会回滚到事务开始时的状态,对象的原始状态得以重新加载。
实现 IEditableObject接口需要实现三个方法:BeginEdit,EndEdit和CancelEdit。BeginEdit方法应该在通过表单将任何变更施加到对象之前进行调用,而EndEdit方法是应该在变更数据提交之后调用。
1)实现BeginEdit方法:需要编写代码将对象变更前的所有属性值进行暂存;来得及使用MemberwiseClone方法获得对象的浅表复制版本:
private Product _originalState = null; // Member variable public void BeginEdit() { _originalState = this.MemberwiseClone() as Product; }
2)实现EndEidt方法:只需要释放掉保存的对象原始状态即可:
public void EndEdit() { _originalState = null; }
3)实现CancelEdit方法:就是将对象恢复至原始状态,原始状态的数据已经被BeginEdit方法保存在内存中:
public void CancelEdit() { //将表单的字段逐个利用已经存储的对象进行赋值if(_originalState!=null) { Name = _originalState.Name; ProductNumber = _originalState.ProductNumber; //等等... _originalState = null; }
}
//可以使用反射将所有字段进行遍历并进行比较逐个赋值,但多少影响性能 public void CancelEdit() { if (_originalState != null) { PropertyInfo[] objectProperties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo propInfo in objectProperties) { object originalValue = propInfo.GetValue(_originalState, null); propInfo.SetValue(this, originalValue, null); } _originalState = null; } }
4)IEditableObject接口的使用
DataForm与DataGrid控件绑定到实现了IEditableObject接口的对象以后,都支持调用这三个方法.
当首次通过被绑定的控件改变绑定对象时,DataForm控件就会调用BeginEdit方法;当用户通过导航切换到其他对象实例(AutoCommit设置为True)或点击提交数据按钮后(AutoCommit设置为False),则DataForm控件会调用EndEdit方法。只要绑定到实现了IEditableObject接口的对象,就会在数据输入表单上显示Cancel按钮,点击该按钮则调用对象的CancelEdit方法返回对象的原始状态。
对DataGrid控件作为编辑控件而言,当首次对当前行数据进行更改时调用BeginEdit方法,从当前行跳出提交数据时调用EndEdit方法,在当前行按下Esc键则调用CancelEdit方法返回到编辑前的状态;
4、向类中添加计算属性
如果使用MVVM模式,直接在ViewModel对象中添加即可;使用Notify Property Weaver工具可以直接为类实现属性变更通知,便于数据绑定:
public decimal LineTotal { get { return Quantity * UnitPrice; } }