文档下载地址:http://www.ctdisk.com/file/1758324 前面我们讨论了关于如何在客户端进行同步数据校验以及如何在服务器端进行异步数据校验(其中包括如何创建一个等待异步数据校验的信息提示界面)。它们有两个共同点,那就是 1、校验操作是在Person类定义中进行的 2、针对单个绑定属性的校验,没有对校验结果进行集中管理 在这里,我们继续完善我们的数据校验功能。我们需要达到的目标是: 1、把校验工作从Person类中分离出来,打包进一个独立的类(校验类)中进行。也即:类定义和类校验分属两个不同的实体。 2、实现针对整个类对象的整体校验并对各属性的校验结果进行管理。 一、创建一个独立的校验类 我们将示范如何创建和使用一个独立的校验类。 1、在SLApplicationDataTest项目中新添加一个类,此类专门负责针对Person类数据的校验工作,将此类命名为PersonValidator.cs。 代码如下: using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using SLApplicationDataTest.SexServiceReference; //因为要使用WCF服务 using System.Windows.Browser; //因为要用到HtmlPage.Window.Alert using System.ComponentModel; //因为要用到INotifyPropertyChanged namespace SLApplicationDataTest { public class PersonValidator { #region 年龄属性及年龄校验#region 年龄属性及年龄校验 private bool _invalidAge; public bool InvalidAge { get { return _invalidAge; } set { if (_invalidAge == value) return; _invalidAge = value; OnPropertyChanged("InvalidAge"); } } public void ValidateAge(int newValue) { InvalidAge = (newValue < 0 || newValue > 200);//年龄应该介于0到200之间 } #endregion 姓名属性及姓名校验#region 姓名属性及姓名校验 private bool _invalidName; public bool InvalidName { get { return _invalidName; } set { if (_invalidName == value) return; _invalidName = value; OnPropertyChanged("InvalidName"); } } public void ValidateName(string newValue) { if (string.IsNullOrEmpty(newValue)) //姓名输入不能为空 { InvalidName = true; //如果为空,则返回true return; } InvalidName = (0 == newValue.Trim().Length); } #endregion 性别属性及性别校验#region 性别属性及性别校验 private bool _invalidSex; public bool InvalidSex { get { return _invalidSex; } set { if (_invalidSex == value) return; _invalidSex = value; OnPropertyChanged("InvalidSex"); } } 性别输入校验程序:通过调用WCF服务来完成校验工作#region 性别输入校验程序:通过调用WCF服务来完成校验工作 private void ValidateSex(string SexStr) { if (string.IsNullOrEmpty(SexStr)) { InvalidSex = true; return; } SexWCFClient sexCs = new SexWCFClient(); sexCs.SexValidationCompleted += new EventHandler<SexValidationCompletedEventArgs>(sexCs_SexValidationCompleted); ((Pa, ge)Application.Current.RootVisual).StartWait("请稍候,正在校验性别输入!"); //打开等待提示界面,提示用户指定的提示信息 sexCs.SexValidationAsync(SexStr, SexStr); //其中第二个SexStr为传入的UserState参数,将在sexCs_SexValidationCompleted中使用 } public void sexCs_SexValidationCompleted(object sender, SexValidationCompletedEventArgs e) { ((Page)Application.Current.RootVisual).EndWait(null); //关闭等待提示界面 InvalidSex = !e.Result; } #endregion #endregion 整体校验属性#region 整体校验属性 private bool _isValid; public bool IsValid { get { return _isValid; } set { if (_isValid == value) return; _isValid = value; OnPropertyChanged("IsValid"); } } #endregion Person _data; //定义一个Person类对象(此对象将会被关联到需要校验的实际Person对象) PersonValidator构造函数(把要校验的Person类对象做为参数传递给PersonValidator类的构造函数以建立相互之间的关联)#region PersonValidator构造函数(把要校验的Person类对象做为参数传递给PersonValidator类的构造函数以建立相互之间的关联) 构造函数一#region 构造函数一 public PersonValidator(Person data) { _data = data; //当它所关联的Person类对象实例的任何属性发生变更时则会激活_data_PropertyChanged事件处理 //_data_PropertyChanged事件处理实际就是对变更的属性值进行数据校验 _data.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_data_PropertyChanged); _isValid = true; } #endregion 构造函数二#region 构造函数二 public PersonValidator(Person data, bool defaultInvalid) : this(data) { if (defaultInvalid) { _invalidAge = true; _invalidName = true; _invalidSex = true; _isValid = false; } } #endregion 当Person类对象的任一属性发生改变时重新进行校验#region 当Person类对象的任一属性发生改变时重新进行校验 void _data_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "IsValid") { return; } switch (e.PropertyName) { case "Age": ValidateAge(_data.Age); break; case "Name": ValidateName(_data.Name); break; case "Sex": ValidateSex(_data.Sex); break; } } #endregion #endregion PersonValidator类的Validate方法#region PersonValidator类的Validate方法 public void Validate() { ValidateAge(_data.Age); ValidateName(_data.Name); ValidateSex(_data.Sex); } #endregion INotifyPropertyChanged Members#region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion #endregion } } 分析代码我们可以看到: i、此类定义的每个属性都对应于Person类的相关属性,只不过在这里,由于它是校验类,所以它的每个属性都为Bool类型,以表示Person类的对应属性的数据校验是true还是false。如果结果为False,则表示校验正常。否则为true。 ii、PersonValidator类的构造函数需要传入Person类对象实例,从而把Person类对象和PersonValidator类之间关联起来,也即:每个PersonValidator类对象负责校验与之对应的Person类实例对象数据。 iii、PersonValidator类的每个属性都有与之对应的Validate方法如:ValidateAge(), ,ValidateName(),ValidateSex(),这些校验方法直接返回和影响对应的PersonValidator属性(Bool类型) iv、创建了传入参数Person类对象实例的PropertyChanged事件处理函数。当外部的Person类对象相关属性值发生变化时,此变化就会激活PersonValidator类对象内部对应的PropertyChanged事件处理,则它来接管对应属性值的校验工作,并把校验结果直接反应到PersonValidator类的对应属性值上。例如:当某个Person类对象的Age属性发生了变化,由于此Person类对象是做为参数传递给某个PersonValidator类对象的构造函数的,所以它就会触发PersonValidator类对象中的Person类传参的PropertyChanged事件,在此事件中遍历找到是年龄属性在变化,于是调用对应的ValidateAge()方法进行校验,如果校验通过则PersonValidator类对象的InvalidAge属性为False。 2、修改Person类定义代码,建立Person类与PersonValidator校验类的关联,代码如下: using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ComponentModel; //因为要用到INotifyPropertyChanged接口 using SLApplicationDataTest.SexServiceReference; //因为要使用WCF服务 using System.Windows.Browser; //因为要使用HtmlPage.Window.Alert namespace SLApplicationDataTest { public class Person : INotifyPropertyChanged { private PersonValidator _personValidator; //定义一个PersonValidator类对象 private string _name; private string _sex; private int _age; private string _address; Person类对象的构造函数#region Person类对象的构造函数 构造函数一#region 构造函数一 public Person() { //在新创建一个Person类对象的同时,创建一个PersonValidator对象,并将新创建的Person类对象传递给PersonValidator对象 //从而在Person类对象和PersonValidator之间建立了关联 _personValidator = new PersonValidator(this); } #endregion 构造函数二#region 构造函数二 public Person(string NameStr, string SexStr, int AgeInt, string AddressStr):this() { this._name = NameStr; this._sex = SexStr; this._age = AgeInt; this._address = AddressStr; } #endregion #endregion Person类对象的属性#region Person类对象的属性 姓名属性#region 姓名属性 public string Name { get { return _name; } set { if (value == _name) return; _name = value; OnPropertyChanged("Name"); } } #endregion 性别属性#region 性别属性 public string Sex { get { return _sex; } set { if (value == _sex) return; _sex = value; OnPropertyChanged("Sex"); } } #endregion 年龄属性#region 年龄属性 public int Age { get { return _age; } set { if (value == _age) return; 年龄校验#region 年龄校验 /**/如果输入的不是整数,则抛出异常 //try //{ // Convert.ToInt32(value); //} //catch(Exception ex) //{ // throw new Exception(ex.ToString()); &n, bsp; //} /**/如果输入的整数不在合理范围同,则也抛出异常 //if (value < 0 || value > 200) //{ // throw new Exception("Age must be between 0 and 200"); //} #endregion _age = value; OnPropertyChanged("Age"); } } #endregion 地址属性#region 地址属性 public string Address { get { return _address; } set { if (value == _address) return; _address = value; OnPropertyChanged("Address"); } } #endregion Person类的Validator属性#region Person类的Validator属性 public PersonValidator Validator { get { return this._personValidator; } } #endregion #endregion INotifyPropertyChanged 接口实现#region INotifyPropertyChanged 接口实现 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion } } 我们做了如下修改 i、去掉了相关属性的校验代码段,因为在这里我们已经把相关校验工作分离到了PersonValidator类中,由PersonValidator类来完成对应属性的校验工作。 ii、定义一个私有的PersonValidator类对象,并在Person类的构造函数中创建此PersonValidator类对象,创建的同时,把自己作为参数传递给PersonValidator类对象的构造函数,从而建立起了Person类对象和PersonValidator类对象之间的紧密关联。 iii、新添加了一个名为Validator的属性,通过此属性把与Person类对象关联的PersonValidator类对象实例公开出来以便于使用(绑定和读取校验结果值). 3、应用重构的数据校验类PersonValidator 我们在绑定Person类对象的数据时,可以通过Person类对象的Validator属性来得到与其对应的PersonValidator类对象实例,并可以读取它的相关属性。因为PersonValidator类的属性都是Bool类型,它表示对应的Person类属性的数据校验是否通过。因此,如何使用它? 在这里,我们想通过改变对应数据的背景颜色来反应PersonValidator类对应属性的校验结果值。那么,我们的问题是:如果把不同的bool结果反应成对应的背景颜色值并在前端进行表现。所以,我们要用到IValueConverter接口,使用它,我们可以顺利的完成数据绑定过程中的表现类型的转换。IValueConverter使用的相关文章见另一篇(SilverLight学习笔记--关于使用IValueConvert对绑定数据的格式化操作 )。 在改变绑定数据的背景颜色的同时,我们还想显示不同的错误提示信息。也即根据校验结果的不同,绑定不同的信息提示。 总之,对PersonValidator类校验的结果,我们要 (1)、改变绑定数据的背景颜色 (2)、改变绑定数据栏的提示信息 它们都需要用到IValueConverter接口。 为此,我们创建两个类 InvalidToBrushConverter.cs类定义代码如下 : Code using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Data;//要使用IValueConverter就要引用此命名空间 using System.Globalization; namespace SLApplicationDataTest { public class InvalidToBrushConverter : IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { //根据绑定的属性校验结果是true(若为true,则表示结果值异常)还是false(false是正常值),返回不同的颜色 return new SolidColorBrush(((bool)value ? Colors.Red : Colors.Transparent)); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } } ErrMsgConverter.cs类定义代码如下: Code using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Data;//因为要用到IValueConverter接口 namespace SLApplicationDataTest { public class ErrMsgConverter : IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { //根据绑定的校验属性来决定返回什么结果 if ((bool)value) { return InvalidMessage(parameter.ToString()); } else { return parameter.ToString(); } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } private string InvalidMessage(string propertyName) { switch (propertyName) { case "Age": return "年龄应该介于0到200之间!"; case "Name": return "请输入您的姓名!"; case "Sex": return "请输入Male或Female!"; default: return propertyName; } } #endregion } } 至此,应用程序如下图: 接下来的工作就是在修改我们的用户界面代码Page.xaml代码如下: Code <UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SLApplicationDataTest.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:myscreen="clr-namespace:SLApplicationDataTest" xmlns:myconvert="clr-namespace:SLApplicationDataTest" Width="600" Height="300"> <UserControl.Resources> <myconvert:InvalidToBrushConverter x:Key="myBrushConvert"/> <myconvert:ErrMsgConverter x:Key="myErrMsgConvert"/> </UserControl.Resources> <Canvas x:Name="LayoutRoot" Width="600" Height="300" Background="Wheat"> <StackPanel x:Name="stackPeopleCenter" Height="300" Width="600" Background="White"> <StackPanel Orientation="Horizontal"> <Button x:Name="addButton" Content="Add" Margin="10"/> <Button x:Name="deleteButton" Content="Delete" Margin="10"/> </StackPanel> <data:DataGrid x:Name="dgPeople" AutoGenerateColumns="False" > &nbs, p;<data:DataGrid.Columns> <data:DataGridTemplateColumn Header="Name"> <data:DataGridTemplateColumn.CellTemplate > <DataTemplate> <Border Background="{Binding Validator.InvalidName, Converter={StaticResource myBrushConvert }}" ToolTipService.ToolTip="{Binding Validator.InvalidName, Converter={StaticResource myErrMsgConvert},,ConverterParameter=Name}"> <TextBlock Text="{Binding Name}" ></TextBlock> </Border> </DataTemplate> </data:DataGridTemplateColumn.CellTemplate> <data:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding Name , Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Background="{Binding Validator.InvalidName, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator.InvalidName, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Name}"> </TextBox> </DataTemplate> </data:DataGridTemplateColumn.CellEditingTemplate> </data:DataGridTemplateColumn> <data:DataGridTemplateColumn Header="Sex"> <data:DataGridTemplateColumn.CellTemplate > <DataTemplate> <Border Background="{Binding Validator.InvalidSex, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator.InvalidSex, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Sex}"> <TextBlock Text="{Binding Sex}"></TextBlock> </Border> </DataTemplate> </data:DataGridTemplateColumn.CellTemplate> <data:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding Sex , Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Background="{Binding Validator.InvalidSex, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator.InvalidSex, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Sex}"> </TextBox> </DataTemplate> </data:DataGridTemplateColumn.CellEditingTemplate> </data:DataGridTemplateColumn> <data:DataGridTemplateColumn Header="Age"> <data:DataGridTemplateColumn.CellTemplate > <DataTemplate> <Border Background="{Binding Validator.InvalidAge, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator.InvalidAge, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Age}"> <TextBlock Text="{Binding Age}"></TextBlock> </Border> </DataTemplate> </data:DataGridTemplateColumn.CellTemplate> <data:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding Age , Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" BindingValidationError="TextBox_BindingValidationError" Background="{Binding Validator.InvalidAge, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator.InvalidAge, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Age}"></TextBox> </DataTemplate> </data:DataGridTemplateColumn.CellEditingTemplate> </data:DataGridTemplateColumn> <data:DataGridTextColumn Header="Address" Binding="{Binding Address}" /> </data:DataGrid.Columns> </data:DataGrid> </StackPanel> <myscreen:PleaseWaitValidate x:Name="myWaitingSexValidateScreen" Visibility="Collapsed" Margin="8,2,2,2" Width="300" Height="150"> </myscreen:PleaseWaitValidate> </Canvas> </UserControl> 在界面方面,我们做如下工作: 1、引入我们定义的类对象,引入代码: xmlns:myscreen="clr-namespace:SLApplicationDataTest" xmlns:myconvert="clr-namespace:SLApplicationDataTest" 2、添加控件级资源 <UserControl.Resources> <myconvert:InvalidToBrushConverter x:Key="myBrushConvert"/> <myconvert:ErrMsgConverter x:Key="myErrMsgConvert"/> </UserControl.Resources> 3、校验结果的数据绑定(颜色表现的绑定Background,与提示信息的绑定 ToolTipService.ToolTip) <Border Background="{Binding Validator.InvalidName, Converter={StaticResource myBrushConvert }}" ToolTipService.ToolTip="{Binding Validator.InvalidName, Converter={StaticResource myErrMsgConvert},,ConverterParameter=Name}"> <TextBlock Text="{Binding Name}" ></TextBlock> </Border> 二、对上述创建的校验类进一步完善,用数据字典管理校验结果 接下来,我们引入Dictionary,由它来集中管理各属性的校验结果。当有某个字段数据校验没有通过,则由PersonValidator类向Dictionary中添加字段名以及出错提示信息。如果此校验错误得到了更正,则由PersonValidator类负责从Dictionary中移除对应的键值对,如果Dictionary中为空,则表示某个Person类对象各属性值的校验都正确。 下面进行我们的修改。 1、PersonValidator类全部代码如下: using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using SLApplicationDataTest.SexServiceReference; //因为要使用WCF服务 using System.Windows.Browser; //因为要用到HtmlPage.Window.Alert using System.ComponentModel; //因为要用到INotifyPropertyChanged using System.Collections.Generic; namespace SLApplicationDataTest { public class PersonValidator { 相关常量定义#region 相关常量定义 public const string PROPERTY_NAME_ISVALID = "IsValid"; public const string PROPERTY_NAME_INVALID = "Invalid"; public const string PROPERTY_NAME_NAME = "Name"; public const string PROPERTY_NAME_AGE = "Age"; public const string PROPERTY_NAME_SEX = "Sex"; private Dictionary<string, string> _errors; //定义一个数据字典用于集中管理校验结果 #endregion 年龄属性及年龄校验#region 年龄属性及年龄校验 // private bool _invalidAge; public bool InvalidAge { get { //return _invalidAge; return _errors.ContainsKey(PROPERTY_NAME_AGE); } set { //if (_invalidAge == value) // return; //_invalidAge = value; //OnPropertyChanged("InvalidAge"); if (value) { RegisterError(PROPERTY_NAME_AGE, "年龄必须介于0到200之间!"); } else { ClearError(PROPERTY_NAME_AGE); } } } public void ValidateAge(int newValue) { InvalidAge = (newValue < 0 || newValue > 200);//年龄应该介于0到200之间,如果超过逻辑范围,则返回true,表示校验没通过! } #endregion 姓名属性及姓名校验#region 姓名属性及姓名校验 // private bool _invalidName; public bool InvalidName { get { //return _invalidName; return _errors.ContainsKey(PROPERTY_NAME_NAME); } set { //if (_invalidName == value) return; //_invalidName = value; //OnPropertyChanged("InvalidName"); if (value) { RegisterError(PROPERTY_NAME_NAME, "请输入您的姓名!"); } else { ClearError(PROPERTY_NAME_NAME); } } } public void ValidateName(string newValue) { if (string.IsNullOrEmpty(newValue)) //姓名输入不能为空 { InvalidName = true; //如果为空,则返回true return; } InvalidName = (0 == newValue.Trim().Length); } #endregion 性别属性及性别校验#region 性别属性及性别校验 //private bool _invalidSex; public bool InvalidSex { get { // return _invalidSex; return _errors.ContainsKey(PROPERTY_NAME_SEX); } set { //if (_invalidSex == value) return; //_invalidSex = value; //OnPropertyChanged("InvalidSex"); if (value) { RegisterError(PROPERTY_NAME_SEX, "请输入正确的性别!"); } else { ClearError(PROPERTY_NAME_SEX); } } } 性别输入校验程序:通过调用WCF服务来完成校验工作#region 性别输入校验程序:通过调用WCF服务来完成校验工作 private void ValidateSex(string SexStr) { if (string.IsNullOrEmpty(SexStr)) { InvalidSex = true; &nbs, p; return; } SexWCFClient sexCs = new SexWCFClient(); sexCs.SexValidationCompleted += new EventHandler<SexValidationCompletedEventArgs>(sexCs_SexValidationCompleted); ((Page)Application.Current.RootVisual).StartWait("请稍候,正在校验性别输入!"); //打开等待提示界面,提示用户指定的提示信息 sexCs.SexValidationAsync(SexStr, SexStr); //其中第二个SexStr为传入的UserState参数,将在sexCs_SexValidationCompleted中使用 } public void sexCs_SexValidationCompleted(object sender, SexValidationCompletedEventArgs e) { ((Page)Application.Current.RootVisual).EndWait(null); //关闭等待提示界面 InvalidSex = !e.Result; } #endregion #endregion 整体校验属性#region 整体校验属性 // private bool _isValid; public bool IsValid { get { //return _isValid; return (0 == _errors.Keys.Count); //当字典中没有任何error记录时,返回true,表示整体校验通过 } set { //if (_isValid == value) return; //_isValid = value; //OnPropertyChanged("IsValid"); OnPropertyChanged(PROPERTY_NAME_ISVALID); _data.RaisePropertyChanged("Validator"); } } #endregion Person _data; //定义一个Person类对象(此对象将会被关联到需要校验的实际Person对象) PersonValidator构造函数(把要校验的Person类对象做为参数传递给PersonValidator类的构造函数以建立相互之间的关联)#region PersonValidator构造函数(把要校验的Person类对象做为参数传递给PersonValidator类的构造函数以建立相互之间的关联) 构造函数一#region 构造函数一 public PersonValidator(Person data) { _data = data; //当它所关联的Person类对象实例的任何属性发生变更时则会激活_data_PropertyChanged事件处理 OLD#region OLD //_data_PropertyChanged事件处理实际就是对变更的属性值进行数据校验 // default valid // _isValid = true; #endregion _data.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_data_PropertyChanged); _errors = new Dictionary<string, string>(); } #endregion 构造函数二#region 构造函数二 public PersonValidator(Person data, bool defaultInvalid) : this(data) { if (defaultInvalid) { OLD#region OLD //_invalidAge = true; //_invalidName = true; //_invalidSex = true; //_isValid = false; #endregion _errors.Add(PROPERTY_NAME_AGE, PROPERTY_NAME_AGE); _errors.Add(PROPERTY_NAME_SEX, PROPERTY_NAME_SEX); _errors.Add(PROPERTY_NAME_NAME, PROPERTY_NAME_NAME); } } #endregion 当Person类对象的任一属性发生改变时重新进行校验#region 当Person类对象的任一属性发生改变时重新进行校验 void _data_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "IsValid") { return; } switch (e.PropertyName) { OLD#region OLD //case "Age": // ValidateAge(_data.Age); // break; //case "Name": // ValidateName(_data.Name); // break; //case "Sex": // ValidateSex(_data.Sex); // break; #endregion case PROPERTY_NAME_AGE: ValidateAge(_data.Age); break; case PROPERTY_NAME_NAME: ValidateName(_data.Name); break; case PROPERTY_NAME_SEX: ValidateSex(_data.Sex); break; } } #endregion #endregion PersonValidator类的Validate方法#region PersonValidator类的Validate方法 public void Validate() { ValidateAge(_data.Age); ValidateName(_data.Name); ValidateSex(_data.Sex); } #endregion INotifyPropertyChanged Members#region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion 获取对应字段的校验出错提示信息#region 获取对应字段的校验出错提示信息 public string this[string propertyName] { get { if (_errors.ContainsKey(propertyName)) { return _errors[propertyName]; } else { return null;// propertyName; } } } #endregion error Dictionary 字典管理#region error Dictionary 字典管理 添加与更新Dictionary记录#region 添加与更新Dictionary记录 public void RegisterError(string propertyName, string message) { if (_errors.ContainsKey(propertyName)) { _errors[propertyName] = message; //更新对应的error信息对 } else { _errors.Add(propertyName, message); //添加新的error信息对 } OnPropertyChanged(PROPERTY_NAME_INVALID + propertyName); IsValid = false; } #endregion 移除Dictionary记录#region 移除Dictionary记录 public void ClearError(string propertyName) { if (_errors.ContainsKey(propertyName)) { _errors.Remove(propertyName); //删除指定的error信息对 } OnPropertyChanged(PROPERTY_NAME_INVALID + propertyName); IsValid = true; } #endregion #endregion } } 代码说明: 我们对此类进行了大量修改,引入了Dictionary,创建了针对Dictionary进行管理的函数(键值对的添加、更新与删除操作),还对各属性定义进行了修改, 属性值的变化直接反映到Dictionary记录内容的变量(取代了存放各属性值的私有变量)。 2、Person类全部代码如下: using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ComponentModel; //因为要用到INotifyPropertyChanged接口 using SLApplicationDataTest.SexServiceReference; //因为要使用WCF服务 using System.Windows.Browser; //因为要使用HtmlPage.Window.Alert namespace SLApplicationDataTest { public class Person : INotifyPropertyChanged { private PersonValidator _personValidator; //定义一个PersonValidator类对象 private string _name; private string _sex; private int _age; private string _address; Person类对象的构造函数#region Person类对象的构造函数 构造函数一#region 构造函数一 public Person() { //在新创建一个Person类对象的同时,创建一个PersonValidator对象,并将新创建的Person类对象传递给PersonValidator对象 //从而在Person类对象和PersonValidator之间建立了关联 _personValidator = new PersonValidator(this); } #endregion 构造函数二#region 构造函数二 public Person(string NameStr, string SexStr, int AgeInt, string AddressStr):this() { this._name = NameStr; this._sex = SexStr; this._age = AgeInt; this._address = AddressStr; } #endregion #endregion Person类对象的属性#region Person类对象的属性 姓名属性#region 姓名属性 public string Name { get { return _name; } set { if (value == _name) return; _name = value; OnPropertyChanged("Name"); } } #endregion 性别属性#region 性别属性 public string Sex { get { return _sex; } set { if (value == _sex) return; _sex = value; OnPropertyChanged("Sex"); } } #endregion 年龄属性#region 年龄属性 public int Age { get { return _age; } set { if (value == _age) return; 年龄校验 OLD#region 年龄校验 OLD /**/如果输入的不是整数,则抛出异常 //try //{ // Convert.ToInt32(value); //} //catch(Exception ex) //{ // throw new Exception(ex.ToString()); //} /**/如果输入的整数不在合理范围同,则也抛出异常 //if (value < 0 || value > 200) //{ // throw new Exception("Age must be between 0 and 200"); //} #endregion _age = value; OnPropertyChanged("Age"); } } #endregion 地址属性#region 地址属性 public string Address { get { return _address; } set { if (value == _address) return; _address = value; &nb, sp; OnPropertyChanged("Address"); } } #endregion Validator属性#region Validator属性 public PersonValidator Validator { get { return this._personValidator; } } #endregion #endregion INotifyPropertyChanged 接口实现#region INotifyPropertyChanged 接口实现 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion RaisePropertyChanged方法#region RaisePropertyChanged方法 internal void RaisePropertyChanged(string name) { OnPropertyChanged(name); } #endregion } } 代码说明:主要加入了RaisePropertyChanged方法,此方法主要用在PersonValidator类中的Person类对象整体校验属性功能中。 3、ErrMsgConverter类全部代码如下: using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Data;//因为要用到IValueConverter接口 namespace SLApplicationDataTest { public class ErrMsgConverter : IValueConverter { IValueConverter Members#region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { /**/根据绑定的校验属性来决定返回什么结果 // if ((bool)value) // { // return InvalidMessage(parameter.ToString()); // } // else // { // return parameter.ToString(); // } PersonValidator validator = value as PersonValidator; string propertyName = parameter.ToString(); if (null != validator) { string message = validator[propertyName]; if (string.IsNullOrEmpty(message)) { return propertyName; } else { return message; } } return value; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } //private string InvalidMessage(string propertyName) //{ // switch (propertyName) // { // case "Age": return "年龄应该介于0到200之间!"; // case "Name": return "请输入您的姓名!"; // case "Sex": return "请输入Male或Female!"; // default: return propertyName; // } //} #endregion } } 代码说明:我们在这时主要对返回出错信息的方法进行了修改,出错信息的提取主要来自PersonValidator类对象中的Dictionary记录内容。 4、界面内容的修改,Page.xaml全部代码如下: Code <UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="SLApplicationDataTest.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:myscreen="clr-namespace:SLApplicationDataTest" xmlns:myconvert="clr-namespace:SLApplicationDataTest" Width="600" Height="300"> <UserControl.Resources> <myconvert:InvalidToBrushConverter x:Key="myBrushConvert"/> <myconvert:ErrMsgConverter x:Key="myErrMsgConvert"/> </UserControl.Resources> <Canvas x:Name="LayoutRoot" Width="600" Height="300" Background="Wheat"> <StackPanel x:Name="stackPeopleCenter" Height="300" Width="600"&n, bsp;Background="White"> <StackPanel Orientation="Horizontal"> <Button x:Name="addButton" Content="Add" Margin="10"/> <Button x:Name="deleteButton" Content="Delete" Margin="10"/> </StackPanel> <data:DataGrid x:Name="dgPeople" AutoGenerateColumns="False" BindingValidationError="dgPeople_BindingValidationError"> <data:DataGrid.Columns> <data:DataGridTemplateColumn Header="Name"> <data:DataGridTemplateColumn.CellTemplate > <DataTemplate> <Border Background="{Binding Validator.InvalidName, Converter={StaticResource myBrushConvert }}" ToolTipService.ToolTip="{Binding Validator, Converter={StaticResource myErrMsgConvert},,ConverterParameter=Name}"> <TextBlock Text="{Binding Name}" ></TextBlock> </Border> </DataTemplate> </data:DataGridTemplateColumn.CellTemplate> <data:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding Name , Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Background="{Binding Validator.InvalidName, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Name}" Tag="Name"> </TextBox> </DataTemplate> </data:DataGridTemplateColumn.CellEditingTemplate> </data:DataGridTemplateColumn> <data:DataGridTemplateColumn Header="Sex"> <data:DataGridTemplateColumn.CellTemplate > <DataTemplate> <Border Background="{Binding Validator.InvalidSex, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Sex}"> <TextBlock Text="{Binding Sex}"></TextBlock> </Border> </DataTemplate> </data:DataGridTemplateColumn.CellTemplate> <data:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding Sex , Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Background="{Binding Validator.InvalidSex, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Sex}" Tag="Sex"> </TextBox> </DataTemplate> </data:DataGridTemplateColumn.CellEditingTemplate> </data:DataGridTemplateColumn> <data:DataGridTemplateColumn Header="Age"> <data:DataGridTemplateColumn.CellTemplate > <DataTemplate> <Border Background="{Binding Validator.InvalidAge, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Age}"> <TextBlock Text="{Binding Age}"></TextBlock> </Border> </DataTemplate> </data:DataGridTemplateColumn.CellTemplate> <data:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding Age , Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Background="{Binding Validator.InvalidAge, Converter={StaticResource myBrushConvert}}" ToolTipService.ToolTip="{Binding Path=Validator, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Age}" Tag="Age"></TextBox> </DataTemplate> </data:DataGridTemplateColumn.CellEditingTemplate> </data:DataGridTemplateColumn> <data:DataGridTextColumn Header="Address" Binding="{Binding Address}" /> </data:DataGrid.Columns> </data:DataGrid> </StackPanel> <myscreen:PleaseWaitValidate x:Name="myWaitingSexValidateScreen" Visibility="Collapsed" Margin="8,2,2,2" Width="300" Height="150"> </myscreen:PleaseWaitValidate> </Canvas> </UserControl> 代码说明: i、首先是数据绑定的内容发生了变化,例如: ToolTipService.ToolTip="{Binding Path=Validator, Converter={StaticResource myErrMsgConvert}, ConverterParameter=Age}" ii、校验事件的处理移交到DataGrid层面。 BindingValidationError="dgPeople_BindingValidationError",其后台代码如下: Code private void dgPeople_BindingValidationError(object sender, ValidationErrorEventArgs e) { if (e.Action == ValidationErrorEventAction.Added) { Person person = ((Control)e.OriginalSource).DataContext as Person; if (null != person && null != ((Control)e.OriginalSource).Tag) { person.Validator.RegisterError(((Control)e.OriginalSource).Tag.ToString(), e.Error.Exception.Message); } //((Control)e.Source).Background = new SolidColorBrush(Colors.Red); //((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, e.Error.Exception.Message); } else if (e.Action == ValidationErrorEventAction.Removed) { Person person = ((Control)e.OriginalSource).DataContext as Person; if (null != person && null != ((Control)e.OriginalSource).Tag) { person.Validator.ClearError(((Control)e.OriginalSource).Tag.ToString()); } //((Control)e.Source).Background = new SolidColorBrush(Colors.White); //((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, null); } } Page.xaml.cs全部代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Browser; //因为要使用HtmlPage.Window.Alert(message)); namespace SLApplicationDataTest { public partial class Page : UserControl { People mypeople; public Page() { InitializeComponent(); this.addButton.Click += new RoutedEventHandler(addButton_Click); this.deleteButton.Click += new RoutedEventHandler(deleteButton_Click); this.dgPeople.KeyDown += new KeyEventHandler(peopleDataGrid_KeyDown); Loaded += new RoutedEventHandler(Page_Loaded); } private void Page_Loaded(object sender, RoutedEventArgs e) { 取得数据源数据并绑定到DataGrid控件上#region 取得数据源数据并绑定到DataGrid控件上 mypeople = People.GetTestData(); this.dgPeople.ItemsSource = mypeople; #endregion } 通过按钮添加新记录行#region 通过按钮添加新记录行 void addButton_Click(object sender, RoutedEventArgs e) { mypeople.Add(new Person()); } #endregion 通过按钮删除记录#region 通过按钮删除记录 void deleteButton_Click(object sender, RoutedEventArgs e) { DeletePerson(); } #endregion 删除记录子程序#region 删除记录子程序 private void DeletePerson() { if (null == this.dgPeople.SelectedItem) { return; } Person person = this.dgPeople.SelectedItem as Person; if (null == person) { return; } mypeople.Remove(person); } #endregion 处理键盘响应事件#region 处理键盘响应事件 void peopleDataGrid_KeyDown(object sender, KeyEventArgs e) { 如果是Insert键,则做插入新行操作#region 如果是Insert键,则做插入新行操作 if (Key.Insert == e.Key) { mypeople.Add(new Person()); } #endregion 如果是Delete键,则做删除操作#region 如果是Delete键,则做删除操作 if (Key.Delete == e.Key) { DeletePerson(); } #endregion } #endregion 校验错误处理程序#region 校验错误处理程序 private void TextBox_BindingValidationError(object sender, ValidationErrorEventArgs e) { if (e.Action == ValidationErrorEventAction.Added) { //如果校验出错,则抛出错误提示窗口 ((Control)e.OriginalSource).Background = new SolidColorBrush(Colors.Red); ((Control)e.OriginalSource).SetValue(ToolTipService.ToolTipProperty, e.Error.Exception.Message); ((Control)e.OriginalSource).Focus(); this.Dispatcher.BeginInvoke(() => HtmlPage.Window.Alert(e.Error.Exception.Message)); } else if (e.Action == ValidationErrorEventAction.Removed) { //如果校验通过,则做如下处理 ((Control)e.OriginalSource).Background = new SolidColorBrush(Colors.White); ((Control)e.OriginalSource).SetValue(ToolTipService.ToolTipProperty, null); } } #endregion 打开与关闭“等待校验窗口”#region 打开与关闭“等待校验窗口” public void StartWait(string message) { this.myWaitingSexValidateScreen.DataContext = message; this.myWaitingSexValidateScreen.StartWait(); } public void EndWait(string message) { this.myWaitingSexValidateScreen.DataContext = message; this.myWaitingSexValidateScreen.StopWait(); } #endregion private void dgPeople_BindingValidationError(object sender, ValidationErrorEventArgs e) { if (e.Action == ValidationErrorEventAction.Added) { Person person = ((Control)e.OriginalSource).DataContext as Person; if (null != person && null != ((Control)e.OriginalSource).Tag) { person.Validator.RegisterError(((Control)e.OriginalSource).Tag.ToString(), e.Error.Exception.Message); } //((Control)e.Source).Background = new SolidColorBrush(Colors.Red); //((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, e.Error.Exception.Message); } else if (e.Action == ValidationErrorEventAction.Removed) { Person person = ((Control)e.OriginalSource).DataContext as Person; if (null != person && null != ((Control)e.OriginalSource).Tag) { person.Validator.ClearError(((Control)e.OriginalSource).Tag.ToString()); } //((Control)e.Source).Background = new SolidColorBrush(Colors.White); //((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, null); } } } } 生成项目并F5运行,效果如下 : 教程下载