MVVM之Validation
Binding Validation Rules:
<TextBox>
<TextBox.Text>
<Binding>
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
在WPF的验证中引入了ValidationRule验证规则概念,提供一种为检查用户输入的有效性而创建自定义规则的方法。
属性:
ValidatesOnTargetUpdated 获取或设置一个值,该值指示当Binding 的目标更新时是否运行验证规则。
ValidationStep(枚举) 获取或设置验证规则的运行时间。
ValidationStep值:
RawProposedValue 在进行任何转换之前运行ValidationRule。
ConvertedProposedValue 在转换了值后运行ValidationRule。
UpdatedValue 在更新了源后运行ValidationRule。
CommittedValue 在将值提交到源后运行ValidationRule。
方法:
Validate 当在派生类中重写时,对值执行验证检查(子类必须重写)。
public abstract ValidationResult Validate(
Object value,
CultureInfo cultureInfo
)
该方法有两个参数,第一个为要检查的来自绑定目标的值;第二个为要在此规则中使用的区域性。
先看个自定义ValidationRule例子:
class MyValidationRule:ValidationRule
{
public int MinValue
{
get;
set;
}
public int MaxValue
{
get;
set;
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
int price = 0;
try
{
if (((string)value).Length > 0)
price = Int32.Parse((String)value);
}
catch (Exception e)
{
return new ValidationResult(false, "Illegal characters or " + e.Message);
}
if ((price < MinValue) || (price > MaxValue))
{
return new ValidationResult(false,
"Please enter an age in the range: " + MinValue + " - " + MaxValue + ".");
}
else
{
return new ValidationResult(true, null);
}
}
}
其实例子很简单,实现了ValidationRule类,同时定义了两个属性MinValue和MaxValue,用于在XAML中设定值的范围,当绑定控件的值超出这个范围将会出现错误。
说到这里就要提到ValidationResult返回类型,表示ValidationRule返回的结果。指示选中值是否通过ValidationRule的Validate方法。
构造函数:
public ValidationResult(
bool isValid,
Object errorContent
)
第一个参数为根据 ValidationRule,选中值是否有效;第二个为有关无效性的信息。(错误的信息),可以看到上个自定义例子中如果验证通过则第一个参数为true,错误信息为空,如果验证失败,则第一个参数为false,第二个参数为错误信息。
主要属性:
ErrorContent 获取提供有关无效性的附加信息的对象,也就是错误信息。
IsValid 获取一个值,该值指示根据ValidationRule,选中值是否有效。
下面看下在XAML中的使用:
<Window x:Class="View.Wpf.Validation.CustomeValidateRule"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:validation="clr-namespace:View.Wpf.Validation"
Title="CustomeValidateRule" Height="300" Width="300">
<Window.Resources>
<validation:Product x:Key="product"></validation:Product>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Height="20" Width="200" Margin="0 10 0 0" >
<TextBox.Text>
<Binding Source="{StaticResource ResourceKey=product}" Path="Price" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validation:MyValidationRule ValidatesOnTargetUpdated="True" MinValue="1" MaxValue="100"></validation:MyValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</Grid>
</Window>
可以看到我们的代码和之前直接绑定TextBox.Text有区别的,现在我们每个节点都是独立的,因为要进行Binding其他属性的绑定。
在Binding.ValidationRules中放置了我们自定义的ValidationRule类,同时设置属性MinValue和MaxValue值用于验证用户键入的值是否符合验证要求,也可以在这里设置ValidatesOnTragetUpdated和ValidationStep属性。
我们设置Binding的UpdateSourceTrigger属性为PropertyChagned意为当属性改变后更新源,所以当我们在文本框中输入的数字不在1到100范围中则会出现默认的红色边框表示出现了验证错误。
如果你感觉这个错误的效果不满意,没关系,可以自定义错误模板如下:
其实我们的模板页很简单,放置了一个TextBlock显示文本为红色的叹号,同时模板中最重要的是AdornedElementPlaceholder标签(表示 ControlTemplate 中使用的元素,该元素用于指定修饰控件相对于 ControlTemplate 中的其他元素所放置的位置,仅当创建用作自定义验证 ErrorTemplate 的 ControlTemplate 以便在用户输入无效时提供可见反馈时,才使用此类.)
<ControlTemplate x:Key="validateTemplate" >
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
使用如下:
<TextBox Height="20" Width="200" Margin="0 10 0 0" Validation.ErrorTemplate="{StaticResource ResourceKey=validateTemplate}">
<TextBox.Text>
<Binding Source="{StaticResource ResourceKey=product}" Path="Price" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validation:MyValidationRule ValidatesOnTargetUpdated="True" MinValue="1" MaxValue="100"></validation:MyValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
可以看到我们在TextBox的Validation.ErrorTemplate中指定为我们定义的错误模板,当验证出错时候则显示如下:
可是这个错误总归有点不好,没有告诉用户出错的信息,所以我们继续修改,添加样式如下:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
添加了TextBox的样式,同时指定Trigger的属性为Validation.HasError,即有错误发生,然后设置TextBox的ToolTip为错误的信息,也就是我们在ValidationResult的参数的值。
指定TextBox的样式:
<TextBox Height="20" Width="200" Margin="0 10 0 0" Style="{StaticResource textBoxInError}" Validation.ErrorTemplate="{StaticResource ResourceKey=validateTemplate}">
<TextBox.Text>
<Binding Source="{StaticResource ResourceKey=product}" Path="Price" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validation:MyValidationRule ValidatesOnTargetUpdated="True" MinValue="1" MaxValue="100"></validation:MyValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
效果如下,可以看到当鼠标放到文本框上会显示错误信息,也就是ValidationResult的参数的值。
验证框架之IDataErrorInfo:
如果你看过我之前一篇Silverlight之validation,那你则对INotifyDataErrorInfo有点印象的,该对象是在Silverlight中使用,而在WPF中则使用IDataErrorInfo对象。INotifyDataErrorInfo为异步执行,而IDataErrorInfo则为同步执行。
接下来看看如何在MVVM中使用IDataErrorInfo完成验证:
首先创建Model类如下:
public class Customer : IDataErrorInfo
{
private string _name;
private int _age;
private static readonly char[] IllegalCharacters = new char[] { '_', '!', '@', '%' };
public Customer(string name, int age)
{
Name = name;
Age = age;
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public int Age
{
get { return _age; }
set { _age = value; }
}
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string error = string.Empty;
switch (columnName)
{
case "Name":
if (_name.IndexOfAny(IllegalCharacters) > 0)
{
error = string.Format("The customer's name contains illegalcharacters ({0})", new string(IllegalCharacters));
}
break;
case "Age":
if (_age < 18 || _age > 100)
{
error = "Customers must be 18 or over to shop here!";
}
break;
default:
break;
}
return error;
}
}
#endregion
}
就是一个简单的Model类,实现了IDataErrorInfo接口,同时可以看到里边有一个索引器,在索引器中加上了对索引值(属性名称)的判断,当为Age时候则判断值在18到100,
如果为Name则不能包含定义的字符串。
或者在ViewModel实现该类,代码为下:
public class CustomerViewModel : IDataErrorInfo,INotifyPropertyChanged
{
private static readonly char[] IllegalCharacters = new char[] { '_', '!', '@', '%' };
private string _name;
private int _age;
private Customer _customer;
public CustomerViewModel()
{
_customer = new Customer(Name, Age);
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public int Age
{
get { return _age; }
set {
OnPropertyChanged("Age");
_age = value; }
}
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string error = string.Empty;
switch (columnName)
{
case "Name":
if (_name.IndexOfAny(IllegalCharacters) > 0)
{
error = string.Format("The customer's name contains illegalcharacters ({0})", new string(IllegalCharacters));
}
break;
case "Age":
if (_age < 18 || _age > 100)
{
error = "Customers must be 18 or over to shop here!";
}
break;
default:
break;
}
return error;
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged!=null)
{
PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
其实验证的部分和Model是相同的,主要区别在ViewModel定义了Model的对象,用来初始化和操作,其他均为相同的原理。
XAML的使用也很简单,有两个选择,使用DataErrorValidationRule规则,或者使用ValidatesOnDataErrors属性为ture(暗中已经添加了DataErrorValidationRule规则):
<Grid>
<Grid.Resources>
<viewModel:CustomerViewModel x:Key="customer"></viewModel:CustomerViewModel>
</Grid.Resources>
<TextBox Height="20" Width="200">
<TextBox.Text>
<Binding Source="{StaticResource customer}" Path="Age" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="True"></DataErrorValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
上边为使用了DataErrorValidationRule规则,或者使用属性:
<TextBox Height="20" Width="200">
<TextBox.Text>
<Binding Source="{StaticResource customer}" Path="Age" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
</Binding>
</TextBox.Text>
</TextBox>
两者的效果均为相同。
以上为MVVM中的验证部分,如果需要更多的验证方法请参考本人的另一篇博客Silverlight之Validation。