@jv9的数据验证系列文章:Silverlight实例教程Validation验证系列中已经详细介绍了silverlight下的各种数据验证的方法。我也看着这些文章学习过来的。
现在在实践MVVM,需要在MVVM下实现提交数据验证,一步一步来。
参考系列文章的第四篇,定义一个验证的基类实现:INotifyPropertyChanged和IDataErrorInfo。
public
abstract
class
DataErrorInfoBase : INotifyPropertyChanged, IDataErrorInfo
{
#region
INotifyPropertyChanged 成员
public
event
PropertyChangedEventHandler PropertyChanged;
public
void
NotifyPropertyChanged(
string
propertyName)
{
if
(
this
.PropertyChanged
!=
null
)
{
this
.PropertyChanged(
this
,
new
PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region
IDataErrorInfo 成员
private
string
_dataError
=
string
.Empty;
private
Dictionary
<
string
,
string
>
_dataErrors
=
new
Dictionary
<
string
,
string
>
();
public
string
Error
{
get
{
return
_dataError; }
}
public
string
this
[
string
columnName]
{
get
{
if
(_dataErrors.ContainsKey(columnName))
return
_dataErrors[columnName];
else
return
null
;
}
}
#endregion
}
封装2个添加删除错误的方法:
public
void
AddError(
string
name,
string
error)
{
_dataErrors[name]
=
error;
this
.NotifyPropertyChanged(name);
}
public
void
RemoveError(
string
name)
{
if
(_dataErrors.ContainsKey(name))
{
_dataErrors.Remove(name);
this
.NotifyPropertyChanged(name);
}
}
定义一个简单的model类:person
public
class
Person : INotifyPropertyChanged
{
string
_name;
public
string
Name
{
get
{
return
_name; }
set
{
_name
=
value;
this
.NotifyPropertyChanged(
"
Name
"
);
}
}
int
_age;
public
int
Age
{
get
{
return
_age; }
set
{
_age
=
value;
this
.NotifyPropertyChanged(
"
Age
"
);
}
}
#region
INotifyPropertyChanged 成员
public
event
PropertyChangedEventHandler PropertyChanged;
public
void
NotifyPropertyChanged(
string
propertyName)
{
if
(
this
.PropertyChanged
!=
null
)
{
this
.PropertyChanged(
this
,
new
PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
然后就是一个ViewModel类继承自DataErrorInfoBase的PersonViewModel,其中包括一个数据源persons,一个调用AddPerson方法的AddPersonCommand,最终的目的是执行AddPersonCommand的时候往persons里添加一个新的Person。
public
class
PersonViewModel : DataErrorInfoBase
{
public
ObservableCollection
<
Person
>
Persons {
get
;
private
set
; }
public
PersonViewModel()
{
AddPersonCommand
=
new
Command(
this
.AddPerson);
Persons
=
new
ObservableCollection
<
Person
>
();
Persons.Add(
new
Person() { Name
=
"
张三
"
, Age
=
25
});
Persons.Add(
new
Person() { Name
=
"
李四
"
, Age
=
35
});
Persons.Add(
new
Person() { Name
=
"
王五
"
, Age
=
45
});
Persons.Add(
new
Person() { Name
=
"
赵六
"
, Age
=
55
});
}
#region
Validate
string
_name;
int
_age;
public
string
Name
{
get
{
return
_name; }
set
{
_name
=
value;
this
.NotifyPropertyChanged(
"
Name
"
);
}
}
public
int
Age
{
get
{
return
_age; }
set
{
_age
=
value;
this
.NotifyPropertyChanged(
"
Age
"
);
}
}
public
ICommand AddPersonCommand {
get
;
private
set
; }
private
void
AddPerson()
{
}
}
实现AddPerson方法:在添加Person之前先进行数据验证,如果验证通过则添加。ValidateData方法里分别执行了Name和Age的验证,如果有错误就调用基类的AddError方法添加错误并返回false,反之则调用RemoveError方法移除错误,返回true。
private
void
AddPerson()
{
if
(
this
.ValidateData())
Persons.Add(
new
Person() { Name
=
Name, Age
=
Age });
}
private
bool
ValidateData()
{
return
ValidateName()
&
ValidateAge();
}
private
bool
ValidateName()
{
if
(
string
.IsNullOrEmpty(Name))
{
this
.AddError(
"
Name
"
,
"
名字不能为空
"
);
return
false
;
}
else
{
this
.RemoveError(
"
Name
"
);
return
true
;
}
}
private
bool
ValidateAge()
{
if
(Age
<=
0
)
{
this
.AddError(
"
Age
"
,
"
年龄需大于0
"
);
return
false
;
}
else
{
this
.RemoveError(
"
Age
"
);
return
true
;
}
}
下面是XAML代码的实现。一个实现数据源的DataGrid
<
sdk:DataGrid
Margin
="5"
ItemsSource
="
{Binding Persons}
"
/>
添加Person页面:绑定的字段需定义ValidatesOnDataErrors=True,并把添加按钮的Command绑定到AddPersonCommand
<
TextBlock
Margin
="5"
Text
="姓名"
/>
<
TextBox
Grid.Column
="1"
Margin
="5"
Text
="
{Binding Name,Mode=TwoWay,ValidatesOnDataErrors=True}
"
/>
<
TextBlock
Grid.Row
="1"
Margin
="5"
Text
="年龄"
/>
<
TextBox
Grid.Column
="1"
Grid.Row
="1"
Margin
="5"
Text
="
{Binding Age,Mode=TwoWay,ValidatesOnDataErrors=True}
"
/>
<
Button
Grid.Row
="2"
Grid.Column
="1"
Content
="添加"
Command
="
{Binding AddPersonCommand}
"
/>
页面和验证效果如下:
基本的验证已经实现了。只是对于每个属性都要写一个或多个验证方法实在是有点麻烦,让我们来改进下。
添加一套新的验证组合:
string
_newName;
int
_newAge;
[Required(ErrorMessage
=
"
姓名必填
"
)]
[Display(Name
=
"
姓名
"
)]
public
string
NewName
{
get
{
return
_newName; }
set
{
_newName
=
value;
this
.NotifyPropertyChanged(
"
NewName
"
);
}
}
[Range(
1
,
100
, ErrorMessage
=
"
年龄需在1-100之间
"
)]
[Display(Name
=
"
年龄
"
)]
public
int
NewAge
{
get
{
return
_newAge; }
set
{
_newAge
=
value;
this
.NotifyPropertyChanged(
"
NewAge
"
);
}
}
public
ICommand NewAddPersonCommand {
get
;
private
set
; }
private
void
NewAddPerson()
{
if
(
this
.NewValidateData())
Persons.Add(
new
Person() { Name
=
NewName, Age
=
NewAge });
}
我们在每个需要验证属性上添加了验证系列第三篇中的 DataAnnotation验证机制,但是我们不在set属性实现Validator.ValidateProperty方法,稍后再做。看看现在NewValidateData方法是怎么实现的。
public
bool
NewValidateData()
{
this
.ClearError();
var results
=
new
List
<
ValidationResult
>
();
if
(
!
Validator.TryValidateObject(
this
,
new
ValidationContext(
this
,
null
,
null
), results,
true
))
{
foreach
(var result
in
results)
{
this
.AddError(result.MemberNames.First(), result.ErrorMessage);
}
return
false
;
}
return
true
;
}
public
void
ClearError()
{
var keys
=
new
string
[_dataErrors.Count];
_dataErrors.Keys.CopyTo(keys,
0
);
foreach
(var key
in
keys)
{
this
.RemoveError(key);
}
}
在NewValidateData方法方法中先调用ClearError 方法清空原有的错误信息。然后再调用Validator.TryValidateObject 方法验证viewModel中所有需要验证的属性,捕获错误,再把错误信息添加到IDataErrorInfo接口中通知实现。看看新的xaml
<
sdk:Label
Margin
="5"
Target
="
{Binding ElementName=name}
"
/>
<
TextBox
x:Name
="name"
Grid.Column
="1"
Margin
="5"
Text
="
{Binding NewName,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}
"
/>
<
sdk:Label
Grid.Row
="1"
Margin
="5"
Target
="
{Binding ElementName=age}
"
/>
<
TextBox
x:Name
="age"
Grid.Column
="1"
Grid.Row
="1"
Margin
="5"
Text
="
{Binding NewAge,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}
"
/>
<
Button
Grid.Row
="2"
Grid.Column
="1"
Content
="新的添加"
Command
="
{Binding NewAddPersonCommand}
"
/>
这里使用label代替了textblock,label可以显示DisplayAttribute定义的信息。如果绑定属性定义了RequiredAttribute,那么Label会默认显示粗体。在实现了NotifyOnValidationError=True之后,如果验证错误,那么Label会显示红色。看看效果。
当然,可以把NewValidateData方法和ClearError方法移到基类封装,这样以后调用起来就更方便了。
个人觉得数据验证的关键还在于灵活组合应用,不要一个验证方法一条道走到黑。
如有问题,还请大家指正。
代码:SLValidation.rar