数据验证(Validation)是界面程序的常见需求,例如使用正则表达式验证用户输入的Email地址是否合法,然后在界面给出错误提示信息。在Sivlerlight的MVVM模式中,我们在Model和ViewModel可以做Validation,然后需要把Model和ViewModel的Validation结果和错误信息通知视图(View)。在WPF中,我们使用IDataErrorInfo,在Silverlight4中,建议使用INotifyDataErrorInfo。
IDataErrorInfo
先简单说一下IDataErrorInfo,这个接口实现了简单的数据验证和错误报告功能,只能说聊胜于无吧。例子:
1 <TextBox Text="{Binding Path=CurrentEmployee.Name, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True }" />
INotifyDataErrorInfo
这个接口只有Silverlight4以上支持,非常强大,支持一个绑定属性多重错误、异步数据验证、自动通知视图错误信息、ErrorChanged事件、HasErrors属性、GetErrors方法等等。定义:
1 public interface
INotifyDataErrorInfo 2
{ 3 bool HasErrors { get
; } 4
5 event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged; 6
7 IEnumerable GetErrors(string
propertyName); 8 }
实现这个INotifyDataErrorInfo接口也非常简单,来个简单的例子:
1 public class
SimpleModel : INotifyDataErrorInfo 2
{ 3 public event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged; 4
5 private Dictionary<string, List<String>> _errors = new Dictionary<string, List<String>>
(); 6
7 private string _accountID = null
; 8
9 public string
AccountID 10
{ 11 get { return
_accountID; } 12 set
13
{ 14 if (_accountID !=
value) 15
{ 16 var propertyName = "AccountID"
; 17
18 if (string
.IsNullOrEmpty(value)) 19
{ 20 if (!
_errors.ContainsKey(propertyName)) 21 _errors.Add(propertyName, new List<string>
()); 22
23 _errors[propertyName].Add("AccountID can't be null or empty"
); 24
} 25 else
26
{ 27 if
(_errors.ContainsKey(propertyName)) 28
_errors.Remove(propertyName); 29
} 30
31
NotifyErrorsChanged(propertyName); 32
33 //Maybe you don't want to set this field to a value if the validation fails
34 _accountID =
value; 35
} 36
} 37
38
} 39
40 public System.Collections.IEnumerable GetErrors(string
propertyName) 41
{ 42 if
(_errors.ContainsKey(propertyName)) 43 return
_errors[propertyName]; 44
45 return
_errors.Values; 46
} 47
48 public bool
HasErrors 49
{ 50 get { return _errors.Count > 0
; } 51
} 52
53
54 private void NotifyErrorsChanged(string
propertyName) 55
{ 56 if (ErrorsChanged != null
) 57 ErrorsChanged(this, new
DataErrorsChangedEventArgs(propertyName)); 58
} 59 }
异步Validation数据验证和INotifyDataErrorInfo接口
这个例子稍微复杂,实现了异步调用WCF RIA Service进行业务逻辑的validation并在ViewModel中把验证的错误提示通知视图,完整的代码下载,需要VS2010和Silverlight环境。
代码说明 ViewModel基类:
1 using
System; 2 using
System.Net; 3 using
System.Windows; 4 using
System.Linq; 5 using
System.Windows.Controls; 6 using
System.Windows.Documents; 7 using
System.Windows.Ink; 8 using
System.Windows.Input; 9 using
System.Windows.Media; 10 using
System.Windows.Media.Animation; 11 using
System.Windows.Shapes; 12 using
System.ComponentModel; 13 using
System.Collections.Generic; 14 using
System.Collections; 15
16 namespace
AsycValidation 17
{ 18 public class
BasicViewModel : INotifyPropertyChanged, INotifyDataErrorInfo 19
{ 20 public event
PropertyChangedEventHandler PropertyChanged; 21 public event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged; 22
23
24 private Dictionary<string, List<ValidationErrorInfo>> _errors =
25 new Dictionary<string, List<ValidationErrorInfo>>
(); 26
27
28 protected void
RemoveErrorFromPropertyAndNotifyErrorChanges( 29 string
propertyName, 30 int
errorCode) 31
{ 32 if
(_errors.ContainsKey(propertyName)) 33
{ 34
RemoveErrorFromPropertyIfErrorCodeAlreadyExist(propertyName, errorCode); 35
36
NotifyErrorsChanged(propertyName); 37
} 38
} 39
40 private void
RemoveErrorFromPropertyIfErrorCodeAlreadyExist( 41 string
propertyName, 42 int
errorCode) 43
{ 44 if
(_errors.ContainsKey(propertyName)) 45
{ 46 var errorToRemove =
_errors[propertyName].SingleOrDefault( 47 error => error.ErrorCode ==
errorCode); 48
49 if (errorToRemove != null
) 50
{ 51
_errors[propertyName].Remove(errorToRemove); 52
53
54
55
56 if (_errors[propertyName].Count == 0
) 57
_errors.Remove(propertyName); 58
} 59
} 60
} 61 protected void
AddErrorToPropertyAndNotifyErrorChanges( 62 string
propertyName, 63
ValidationErrorInfo errorInfo) 64
{ 65
RemoveErrorFromPropertyIfErrorCodeAlreadyExist(propertyName, errorInfo.ErrorCode); 66 if (!
_errors.ContainsKey(propertyName)) 67 _errors.Add(propertyName, new List<ValidationErrorInfo>
()); 68
69
_errors[propertyName].Add(errorInfo); 70
71
NotifyErrorsChanged(propertyName); 72
} 73
74
75 public IEnumerable GetErrors(string
propertyName) 76
{ 77 if (!
_errors.ContainsKey(propertyName)) 78 return
_errors.Values; 79
80 return
_errors[propertyName]; 81
} 82
83
84 public bool
HasErrors 85
{ 86 get { return this._errors.Count > 0
; } 87
} 88
89
90 private void NotifyErrorsChanged(string
propertyName) 91
{ 92 if (ErrorsChanged != null
) 93 ErrorsChanged(this, new
DataErrorsChangedEventArgs(propertyName)); 94
} 95
96
97 protected void NotifyPropertyChanged(string
propertyName) 98
{ 99 if (PropertyChanged != null
) 100 PropertyChanged(this, new
PropertyChangedEventArgs(propertyName)); 101
} 102
103
} 104 }
Model:
1 using
System; 2 using
System.Net; 3 using
System.Windows; 4 using
System.Windows.Controls; 5 using
System.Windows.Documents; 6 using
System.Windows.Ink; 7 using
System.Windows.Input; 8 using
System.Windows.Media; 9 using
System.Windows.Media.Animation; 10 using
System.Windows.Shapes; 11 using
System.ComponentModel; 12
13 namespace
AsycValidation 14
{ 15 public class
CompanyModel : INotifyPropertyChanged 16
{ 17 public event
PropertyChangedEventHandler PropertyChanged; 18
19 public int CompanyID { get; set
; } 20
21 private string
_CompanyName; 22 public string
CompanyName 23
{ 24 get { return
_CompanyName; } 25 set
26
{ 27 _CompanyName =
value; 28
29 if (PropertyChanged != null
) 30
{ 31 PropertyChanged(this, new PropertyChangedEventArgs("CompanyName"
)); 32
} 33
} 34
} 35
} 36 }
ViewModel,继承了BaseViewModel基类:
1 using
System; 2 using
System.Net; 3 using
System.Windows; 4 using
System.Windows.Controls; 5 using
System.Windows.Documents; 6 using
System.Windows.Ink; 7 using
System.Windows.Input; 8 using
System.Windows.Media; 9 using
System.Windows.Media.Animation; 10 using
System.Windows.Shapes; 11 using
AsycValidation.Web; 12
13 namespace
AsycValidation 14
{ 15 public class
CompanyViewModel : BasicViewModel 16
{ 17 public CompanyModel CompanyModelData { get; set
; } 18
19 public
CompanyViewModel(CompanyModel model) 20
{ 21 CompanyModelData =
model; 22
} 23
24 private string _CompanyName = null
; 25 private const int ACCOUNT_ALREADY_EXIST_ERROCODE = 100
; 26
27 DomainService1 service = new
DomainService1(); 28
29 public string
CompanyName 30
{ 31 get
32
{ 33 return
_CompanyName; 34
} 35 set
36
{ 37 if (_CompanyName !=
value) 38
{ 39 var propertyName = "CompanyName"
; 40
41
ValidateAccountAlreadyExists( 42
value, 43
propertyName, 44
ACCOUNT_ALREADY_EXIST_ERROCODE, 45 string.Format("Company with the ID {0} already exists"
, value)); 46
47 _CompanyName =
value; 48
NotifyPropertyChanged(propertyName); 49
} 50
} 51
} 52
53 private void
ValidateAccountAlreadyExists( 54 string
CompanyID, 55 string
propertyName, 56 int
errorCode, 57 string
errorMsg) 58
{ 59
service.DoesCompanyExists( 60
CompanyID, 61 invokeOperation =>
62
{ 63 if
(invokeOperation.Value) 64
{ 65
AddErrorToPropertyAndNotifyErrorChanges( 66
propertyName, 67 new
ValidationErrorInfo() 68
{ 69 ErrorCode =
errorCode, 70 ErrorMessage =
errorMsg 71
}); 72
} 73 else
74
{ 75
RemoveErrorFromPropertyAndNotifyErrorChanges( 76
propertyName, 77
errorCode); 78
} 79
}, 80 null
); 81
} 82
83
} 84 }
View / XAML
1 <UserControl x:Class="AsycValidation.MainPage"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:wm="clr-namespace:AsycValidation"
7 mc:Ignorable