相信很多人都听说过这句名言:garbage in ,garbage out ! 数据录入不规范(或错误)就象一颗定时炸弹,迟早会给系统带来麻烦,所以在数据录入时做好验证是很有必要的。
相对传统asp.net开发而言,SL4中的数据验证要轻松很多(主要得益于Xaml的Binding特性),步骤如下:
1、定义业务Model类时,在需要验证的属性setter中,写好业务逻辑,对于不合规范的value,要
抛出异常!
同时切记Model类要实现INotifyPropertyChanged接口,同时每个setter方法的最后,要显示调用OnPropertyChanged方法
比如,我们要做一个会员注册填写资料的Form,需要一个UserModel类,为了方便,先定义一个通用的基类BusinessBaseObject.cs
using System.ComponentModel; namespace BusinessObject { public class BusinessBaseObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// 属性改变时触发事件 /// </summary> /// <param name="propertyName">Property that changed.</param> protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (null != handler) { handler.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } }
再来定义UserModel.cs
#define DEV //#undef DEV using System; using Common.Silverlight; namespace BusinessObject { public class UserModel : BusinessBaseObject { private string _userName; public string UserName { get { return _userName; } set { #if DEV if (string.IsNullOrEmpty(value)) { throw new Exception("用户名不能为空"); } if (!(value.IsEmail() || value.IsUserName())) { throw new Exception("格式错误,正确示例如:abc007"); } #endif _userName = value; OnPropertyChanged("UserName"); } } private bool? _sex = null; public bool? Sex { get { return _sex; } set { #if DEV if (!_sex.HasValue) { throw new Exception("请选择性别"); } #endif _sex = value; OnPropertyChanged("UserName"); } } private string _password; public string Password { get { return _password; } set { if (string.IsNullOrEmpty(value)) { throw new Exception("密码不能为空!"); } #if DEV if (value.Length < 6) { throw new Exception("密码长度不得低于6位"); } #endif _password = value; OnPropertyChanged("Password"); } } private string _password2; public string Password2 { get { return _password2; } set { if (string.IsNullOrEmpty(value)) { throw new Exception("密码不能为空"); } #if DEV if (value!=this._password) { throw new Exception("重复密码必须与密码输入一致"); } #endif _password2 = value; OnPropertyChanged("Password2"); } } private DateTime _birthday; public DateTime Birthday { get { return _birthday; } set { #if DEV if (value <= DateTime.Now.AddYears(-100)) { throw new Exception("我们不赞同百岁以上的老人上网"); } #endif _birthday = value; OnPropertyChanged("Birthday"); } } private string _email; public string Email { get { return _email; } set { #if DEV if (!string.IsNullOrEmpty(value) && !value.IsEmail()) { throw new Exception("格式错误,正确示例如:yjmyzz@126.com"); } #endif _email = value; OnPropertyChanged("Email"); } } private string _telephone; public string Telephone { get { return _telephone; } set { #if DEV if (!value.IsTel()) { throw new Exception("格式错误,正确示例如:021-38889088或021-36559079-023"); } #endif _telephone = value; OnPropertyChanged("Telephone"); } } private string _mobile = ""; public string Mobile { get { return _mobile; } set { #if DEV if (!value.IsMobile()) { throw new Exception("格式错误,正确示例如:13916752109"); } #endif this._mobile = value; OnPropertyChanged("Mobile"); } } private bool _agree = false; public bool Agree { get { return _agree; } set { #if DEV if (value==false) { throw new Exception("您必须同意注册条款!"); } #endif this._agree = value; OnPropertyChanged("Agree"); } } private int _internetAge = 0; public int InternetAge { get { return _internetAge; } set { #if DEV if (_internetAge<0) { throw new Exception("网龄不能为负数"); } #endif _internetAge = value; OnPropertyChanged("InternetAge"); } } private TimeRange _internetHours = new TimeRange(); public TimeRange InternetHours { get { return _internetHours; } set { _internetHours = value; OnPropertyChanged("InternetHours"); } } } public class TimeRange : BusinessBaseObject { private TimeSpan start = DateTime.Now.TimeOfDay + TimeSpan.FromMinutes(5); private TimeSpan end = DateTime.Now.TimeOfDay + TimeSpan.FromHours(1); private const int MaximumRangeSpan = 6; public TimeSpan Start { get { return this.start; } set { if (value < DateTime.Now.TimeOfDay) { throw new Exception("上网时段起始值必须在当前时间5分钟以后");//注:这个限定只是为了演示数据验证,并无实际意义 } if (End - value > TimeSpan.FromHours(MaximumRangeSpan)) { ThrowOutOfRangeException(); } this.start = value; OnPropertyChanged("Start"); } } public TimeSpan End { get { return this.end; } set { if (value < DateTime.Now.TimeOfDay) { throw new Exception("上网时段截止值不能早于当前时间");//注:这个限定只是为了演示数据验证,并无实际意义 } if (value - Start > TimeSpan.FromHours(MaximumRangeSpan)) { ThrowOutOfRangeException(); } this.end = value; OnPropertyChanged("End"); } } private static void ThrowOutOfRangeException() { string message = string.Format("上网时间不能大于 {0} 小时", MaximumRangeSpan); throw new Exception(message); } } }
2、xaml界面部分,用Binding将各控件与Model实例的属性关联,对于指定长度和指定输入字符集的字段(比如:18位身份证号,手机号之类),最适合用RadMaskedTextBox,示例如下:
<UserControl xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:validation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:Telerik_Windows_Controls_MaskedTextBox="clr-namespace:Telerik.Windows.Controls.MaskedTextBox;assembly=Telerik.Windows.Controls.Input" xmlns:Telerik_Windows_Controls_Chromes="clr-namespace:Telerik.Windows.Controls.Chromes;assembly=Telerik.Windows.Controls" x:Class="Telerik.Sample.Validataion" mc:Ignorable="d" d:DesignHeight="600" d:DesignWidth="600"> <UserControl.Resources> <Style x:Key="PasswordBoxToRadMaskTextBoxStyle" TargetType="PasswordBox"> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Background" Value="#FFFFFFFF"/> <Setter Property="Foreground" Value="#FF000000"/> <Setter Property="Padding" Value="2"/> <Setter Property="BorderBrush" Value="#FF848484" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="PasswordBox"> <Grid x:Name="RootElement"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation Duration="0" To="#FFFFC92B" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="Border"/> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Focused"> <Storyboard> <ColorAnimation Duration="0" To="#FFFFC92B" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="Border"/> </Storyboard> </VisualState> <VisualState x:Name="Unfocused"> <Storyboard> <ColorAnimation Duration="0" To="{TemplateBinding BorderBrush}" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="Border"/> </Storyboard> </VisualState> </VisualStateGroup> <VisualStateGroup x:Name="ValidationStates"> <VisualState x:Name="Valid"/> <VisualState x:Name="InvalidUnfocused"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="InvalidFocused"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" Storyboard.TargetName="validationTooltip"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <System:Boolean>True</System:Boolean> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Border x:Name="Border" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="1" Opacity="1" BorderBrush="{TemplateBinding BorderBrush}"> <Border x:Name="ContentElement" Margin="{TemplateBinding Padding}"/> </Border> <Border x:Name="DisabledVisualElement" BorderBrush="#A5F7F7F7" BorderThickness="{TemplateBinding BorderThickness}" Background="#A5F7F7F7" IsHitTestVisible="False" Opacity="0"/> <Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Visibility="Collapsed"> <ToolTipService.ToolTip> <ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ValidationToolTipTemplate}"> <ToolTip.Triggers> <EventTrigger RoutedEvent="Canvas.Loaded"> <BeginStoryboard> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip"> <DiscreteObjectKeyFrame KeyTime="0"> <DiscreteObjectKeyFrame.Value> <System:Boolean>true</System:Boolean> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </ToolTip.Triggers> </ToolTip> </ToolTipService.ToolTip> <Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12"> <Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0"/> <Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0"/> </Grid> </Border> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <Grid VerticalAlignment="Center" HorizontalAlignment="Center" Width="500"> <Grid.RowDefinitions> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="30" /> <RowDefinition Height="50" /> <RowDefinition Height="30" /> <RowDefinition Height="Auto"/> <RowDefinition Height="30" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100" /> <ColumnDefinition Width="400" MinWidth="200"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="用户名:"/></TextBlock> <telerik:RadMaskedTextBox Grid.Column="1" Grid.Row="0" x:Name="txtUserName" VerticalAlignment="Center" GotFocus="RadMaskedTextBox_GotFocus" Value="{Binding UserName, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" EmptyContent="格式:2-50位字母、数字、下线线组合" Width="250" MaskType="None" HorizontalAlignment="Left" /> <TextBlock Grid.Row="1" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="密码:"/></TextBlock> <PasswordBox Grid.Row="1" Grid.Column="1" Width="250" MaxLength="32" HorizontalAlignment="Left" VerticalAlignment="Center" Password="{Binding Password, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" x:Name="txtPwd" Style="{StaticResource PasswordBoxToRadMaskTextBoxStyle}" /> <TextBlock Grid.Row="2" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="重复密码:"/></TextBlock> <PasswordBox Grid.Row="2" Grid.Column="1" Width="250" HorizontalAlignment="Left" VerticalAlignment="Center" Password="{Binding Password2, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" x:Name="txtPwd2" Style="{StaticResource PasswordBoxToRadMaskTextBoxStyle}"/> <TextBlock Grid.Row="3" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="性别:"/></TextBlock> <StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal" > <telerik:RadComboBox x:Name="rcboSex" Width="250" SelectedIndex="2"> <telerik:RadComboBoxItem Content="男" /> <telerik:RadComboBoxItem Content="女" /> <telerik:RadComboBoxItem Content="保密" /> </telerik:RadComboBox> </StackPanel> <TextBlock Grid.Row="4" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="电话号码:"/></TextBlock> <telerik:RadMaskedTextBox Grid.Column="1" Grid.Row="4" MaskType="None" ValueChanging="txtTel_ValueChanging" x:Name="txtTel" VerticalAlignment="Center" GotFocus="RadMaskedTextBox_GotFocus" Value="{Binding Telephone, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" EmptyContent="格式:区号-电话号码-分机号(可选)" Width="250" HorizontalAlignment="Left" /> <TextBlock Grid.Row="5" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="手机号码:"/></TextBlock> <telerik:RadMaskedTextBox Grid.Column="1" Grid.Row="5" Mask="###########" x:Name="txtMobile" VerticalAlignment="Center" GotFocus="RadMaskedTextBox_GotFocus" Value="{Binding Mobile, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" EmptyContent="比如:13916752109" Width="250" HorizontalAlignment="Left" /> <TextBlock Grid.Row="6" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="电子邮箱:"/></TextBlock> <telerik:RadMaskedTextBox Grid.Column="1" Grid.Row="6" MaskType="None" x:Name="txtEmail" VerticalAlignment="Center" GotFocus="RadMaskedTextBox_GotFocus" Value="{Binding Email, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" EmptyContent="比如:youname@sample.com" Width="250" HorizontalAlignment="Left" /> <TextBlock Grid.Row="7" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="出生日期:"/></TextBlock> <telerik:RadDatePicker Grid.Column="1" Grid.Row="7" SelectedDate="{Binding Birthday, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Width="250" Height="22" HorizontalAlignment="Left" x:Name="rdp_Birthday" /> <TextBlock Grid.Row="8" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="网龄:"/></TextBlock> <telerik:RadNumericUpDown Grid.Row="8" Grid.Column="1" Minimum="0" Maximum="30" Value="{Binding InternetAge, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" Loaded="btnInternetAge_Loaded" SmallChange="1" HorizontalAlignment="Left" VerticalAlignment="Center" Width="250" x:Name="btnInternetAge" /> <TextBlock Grid.Row="9" Grid.Column="0" TextAlignment="Right" VerticalAlignment="Center"><Run Text="经常上网时段:"/></TextBlock> <StackPanel Grid.Row="9" Grid.Column="1"> <telerik:RadSlider IsSelectionRangeEnabled="True" Style="{StaticResource RadSliderStyle}" Minimum="0" Maximum="1380" TickFrequency="240" SelectionStart="{Binding InternetHours.Start, Converter={StaticResource TimeConverter}, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" SelectionEnd="{Binding InternetHours.End, Converter={StaticResource TimeConverter}, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" TickPlacement="TopLeft" Width="250" HorizontalAlignment="Left"> <telerik:RadSlider.TickTemplate> <DataTemplate> <Grid> <TextBlock FontSize="8" UseLayoutRounding="True" Text="{Binding Converter={StaticResource TickValueConverter}}" /> </Grid> </DataTemplate> </telerik:RadSlider.TickTemplate> </telerik:RadSlider> <StackPanel Orientation="Horizontal" Width="200" UseLayoutRounding="True" HorizontalAlignment="Left" Margin="25,0,0,0"> <TextBlock FontSize="8" Text="{Binding InternetHours.Start, Converter={StaticResource TimeToTextConverter}}" /> <TextBlock FontSize="8" Text=" - " /> <TextBlock FontSize="8" Text="{Binding InternetHours.End, Converter={StaticResource TimeToTextConverter}}" /> </StackPanel> </StackPanel> <telerik:RadToggleButton Grid.Row="10" Width="250" HorizontalAlignment="Left" Grid.Column="1" Content="我同意注册条款" VerticalAlignment="Center" IsChecked="{Binding Agree, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" x:Name="btnAgree" /> <validation:ValidationSummary Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="2" AllowDrop="True" Header="提示" x:Name="ValidationSummary1"/> <telerik:RadButton Content="提交" Grid.Row="12" Grid.Column="1" Height="22" Width="65" HorizontalAlignment="Left" Click="btnSubmit_Click" x:Name="btnSubmit" Margin="190,0,0,0"/> </Grid> </Grid> </UserControl>
3、Xaml.cs后端部分
using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System; using BusinessObject; using Telerik.Windows.Controls; using System.Text.RegularExpressions; namespace Telerik.Sample { public partial class Validataion : UserControl { UserModel model = new UserModel(); public Validataion() { InitializeComponent(); this.Loaded += new RoutedEventHandler(Validataion_Loaded); } void Validataion_Loaded(object sender, RoutedEventArgs e) { this.DataContext = model; #region //model.Mobile = "13916752109"; //model.UserName = "yjmyzz"; //model.Password = "123456"; //model.InternetAge = 10; //model.Birthday = new DateTime(1979, 6, 5); //model.Telephone = "021-36559079"; //model.Email = "yjmyzz@126.com"; //model.Password2 = model.Password; #endregion #region 有错误时,不允许提交(必须配合输入框获取焦点时,自动激活验证) Binding binding = new Binding("HasErrors"); binding.Source = ValidationSummary1; binding.Converter = new HasErrorsToIsEnabledConverter(); this.btnSubmit.SetBinding(Button.IsEnabledProperty, binding); #endregion } private void RadMaskedTextBox_GotFocus(object sender, RoutedEventArgs e) { //文本框获得焦点时,自动激活实时验证 UpdateBindingExpression(sender as DependencyObject, RadMaskedTextBox.ValueProperty); } private void UpdateBindingExpression(DependencyObject obj, DependencyProperty prop) { BindingExpression b = obj.ReadLocalValue(prop) as BindingExpression; b.UpdateSource(); } private void btnSubmit_Click(object sender, RoutedEventArgs e) { //显式验证必填项 UpdateBindingExpression(txtUserName, RadMaskedTextBox.ValueProperty); UpdateBindingExpression(txtPwd, PasswordBox.PasswordProperty); UpdateBindingExpression(txtPwd2, PasswordBox.PasswordProperty); UpdateBindingExpression(this.btnAgree, RadToggleButton.IsCheckedProperty); } private void btnInternetAge_Loaded(object sender, RoutedEventArgs e) { //去掉小数位 (sender as RadNumericUpDown).NumberFormatInfo.NumberDecimalDigits = 0; } private void txtTel_ValueChanging(object sender, RadMaskedTextBoxValueChangingEventArgs e) { //只能输入"0-9"和下划线 e.Handled = new Regex(@"^[\d-]{0,}$").IsMatch(e.NewMaskedText) == false; } } }
运行截图:
在线演示地址: