一、Data Binding
Data Binding在MVVM模式中起到了重要的作用,WPF提供了强大的数据绑定功能,因此在设计view和model时应该充分利用这些能力,这意味着你必须实现正确的接口。
1)WPF支持one-way binding和two-way binding,two-way binding会将用户对界面数据的修改自动更新到底层数据对象。
2)为了将view model或者model中的数据更新通知到view,需要实现INotifyPropertyChanged接口或者INotifyCollectionChanged接口(如果model是一个集合)。
3)此外,ICollectionView接口在view和view model/model底层集合对象之间提供了排序、过滤、分组以及选择元素的跟踪操作。WPF的ListCollectionView实现了ICollectionView接口。
二、Commands
在WPF中,用户通过UI进行的操作被定义为Commands。Commands为操作和UI上的控件进行绑定提供了一种便利的方式。
WPF的一些控件提供了Command属性,这个属性可以绑定到viewModel中实现了ICommand接口的对象,例如:
- public class QuestionnaireViewModel
- {
- public QuestionnaireViewModel()
- {
- this.SubmitCommand = new DelegateCommand<object>(
- this.OnSubmit, this.CanSubmit );
- }
- public ICommand SubmitCommand { get; private set; }
- private void OnSubmit(object arg) {...}
- private bool CanSubmit(object arg) { return true; }
- }
public class QuestionnaireViewModel
{
public QuestionnaireViewModel()
{
this.SubmitCommand = new DelegateCommand<object>(
this.OnSubmit, this.CanSubmit );
}
public ICommand SubmitCommand { get; private set; }
private void OnSubmit(object arg) {...}
private bool CanSubmit(object arg) { return true; }
}
<Button Command="{Binding Path=SubmitCommand}" CommandParameter="SubmitOrder"/>
- <Button Content="Submit" IsEnabled="{Binding CanSubmit}">
- <i:Interaction.Triggers>
- <i:EventTrigger EventName="Click">
- <i:InvokeCommandAction Command="{Binding SubmitCommand}"/>
- </i:EventTrigger>
- </i:Interaction.Triggers>
- </Button>
<Button Content="Submit" IsEnabled="{Binding CanSubmit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding SubmitCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
三、数据验证
在MVVM模式中,数据验证可以通过在view model / model实现IDataErrorInfo接口和INotifyDataErrorInfo接口来实现,这些接口允许view model / model对一个或多个属性进行数据验证,并且向view返回errir message。
IDataErrorInfo接口提供了基本的数据验证和错误报告的功能,它包含两个属性,一个索引器属性(索引器使用属性名作为参数)和一个Error属性。索引器属性根据传递的属性名返回错误消息,如果返回值为Empty或者null,表示属性change合法,Error属性为整个对象提供error message,但是现在的WPF和Silverlight引擎都没有使用这个属性。
IDataError的索引器属性将会在第一次数据绑定或者属性的每次改变被调用,因为索引器属性在每个属性发生改变的时候都会被调用,所以要保证这个数据验证函数要尽可能的高效。
- <TextBox
- Text="{Binding Path=CurrentEmployee.Name, Mode=TwoWay, ValidatesOnDataErrors=True,
- NotifyOnValidationError=True }"
- />
<TextBox
Text="{Binding Path=CurrentEmployee.Name, Mode=TwoWay, ValidatesOnDataErrors=True,
NotifyOnValidationError=True }"
/>
- public class Agent : NotificationObject, IDataErrorInfo
- {
- /// <summary>
- /// Name of Agent
- /// </summary>
- [DisplayOrder(1)]
- [DisplayName("Agent Name")]
- public string DisplayName
- {
- get
- {
- return displayName;
- }
- set
- {
- if (displayName != value)
- {
- displayName = value;
- this.isSelfChanged = true;
- RaisePropertyChanged<string>(() => this.DisplayName);
- RaisePropertyChanged<string>(() => this.Error);
- }
- }
- }
- /// <summary>
- /// the address client can connect to the agent
- /// </summary>
- [DisplayOrder(2)]
- [DisplayName("EndPoint Address")]
- public string Address
- {
- get
- {
- return address;
- }
- set
- {
- address = value;
- this.isSelfChanged = true;
- RaisePropertyChanged<string>(() => this.Address);
- RaisePropertyChanged<string>(() => this.Error);
- }
- }
- #region IDataErrorInfo Members
- string IDataErrorInfo.Error
- {
- get { throw new NotImplementedException(); }
- }
- string IDataErrorInfo.this[string columnName]
- {
- get
- {
- if (!string.IsNullOrEmpty(columnName))
- {
- Error = string.IsNullOrEmpty(Error) ? Error : (Error + Environment.NewLine);
- switch (columnName)
- {
- case "DisplayName":
- if (string.IsNullOrEmpty(DisplayName))
- return "Display name is required.";
- case "Address":
- if (string.IsNullOrEmpty(Address))
- return "Address is required.";
- default:
- return string.Empty;
- }
- }
- return string.Empty;
- }
- }
- #endregion
- }
public class Agent : NotificationObject, IDataErrorInfo
{
/// <summary>
/// Name of Agent
/// </summary>
[DisplayOrder(1)]
[DisplayName("Agent Name")]
public string DisplayName
{
get
{
return displayName;
}
set
{
if (displayName != value)
{
displayName = value;
this.isSelfChanged = true;
RaisePropertyChanged<string>(() => this.DisplayName);
RaisePropertyChanged<string>(() => this.Error);
}
}
}
/// <summary>
/// the address client can connect to the agent
/// </summary>
[DisplayOrder(2)]
[DisplayName("EndPoint Address")]
public string Address
{
get
{
return address;
}
set
{
address = value;
this.isSelfChanged = true;
RaisePropertyChanged<string>(() => this.Address);
RaisePropertyChanged<string>(() => this.Error);
}
}
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get { throw new NotImplementedException(); }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (!string.IsNullOrEmpty(columnName))
{
Error = string.IsNullOrEmpty(Error) ? Error : (Error + Environment.NewLine);
switch (columnName)
{
case "DisplayName":
if (string.IsNullOrEmpty(DisplayName))
return "Display name is required.";
case "Address":
if (string.IsNullOrEmpty(Address))
return "Address is required.";
default:
return string.Empty;
}
}
return string.Empty;
}
}
#endregion
}
INotifyDataErrorInfo接口比IDataErrorInfo更灵活,它支持一个属性的多个错误描述,异步数据验证以及在错误状态改变时通知view的功能。然而,INotifyDataError当前只在Silverlight4中支持,WPF4尚不支持。
- public abstract class DomainObject : INotifyPropertyChanged,
- INotifyDataErrorInfo
- {
- private ErrorsContainer<ValidationResult> errorsContainer =
- new ErrorsContainer<ValidationResult>(
- pn => this.RaiseErrorsChanged( pn ) );
- public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
- public bool HasErrors
- {
- get { return this.ErrorsContainer.HasErrors; }
- }
- public IEnumerable GetErrors( string propertyName )
- {
- return this.errorsContainer.GetErrors( propertyName );
- }
- protected void RaiseErrorsChanged( string propertyName )
- {
- var handler = this.ErrorsChanged;
- if (handler != null)
- {
- handler(this, new DataErrorsChangedEventArgs(propertyName) );
- }
- }
- ...
- }