我在这里介绍一种自定义控件方式的正则验证方法。业务背景不做详细说明,这里以技术背景为主。
思路:创建一个控件,使其具有文本框的特性外,还能就行正则验证;
依据这个思路,来讲解实现。
第一步:创建自定义控件
因为要保留TextBox所有特性,所以就继承于TextBox控件即可
public class RegexTextBox : TextBox { static RegexTextBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(RegexTextBox), new FrameworkPropertyMetadata(typeof(RegexTextBox))); } }
第二步:需要添加正则验证的信息
那就需要在类中添加2个依赖属性:正则表达式(用于验证输入),错误消息(当验证不通过时显示的错误内容,不是必须的)
public class RegexTextBox : TextBox { …… public static readonly DependencyProperty RegextProperty; public static readonly DependencyProperty ErrorMessageProperty; }
第三步:使控件能触发验证
触发验证的时机一般是:窗体加载后和内容改变后
“窗体加载后”不够准确,因为正则验证的表达式可能是从server传过来,控件初始化后,查找并应用控件模板,而WPF中对于输入错误都采用了Trigger来触发显示,例如:
<Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors).CurrentItem.ErrorContent}" /> </Trigger> </Style.Triggers>
比较好的方式是在应用模板后先验证一次默认值。应用模板是OnApplyTemplate。
public class RegexTextBox : TextBox { …… //应用模板时验证一次默认值 public override void OnApplyTemplate() { base.OnApplyTemplate(); ValidateInput(true); } //内容更改时再次验证 protected override void OnTextChanged(TextChangedEventArgs e) { base.OnTextChanged(e); ValidateInput(); } }
第四步:定义一个正则验证规则(RegexValidationRule),使其在文本框内容的绑定上运用此验证规则
public class RegexValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { …… } }
第五步:定义控件模板
模板就像HTML的CSS一样,可以定义控件的外观、特效等。
其中ListBoxChrome是装饰器,关于装饰器请参见:
http://blog.csdn.net/qing2005/article/details/6830860
http://blog.csdn.net/qing2005/article/details/6914327
在例子,我还增加一个装饰器,喜欢的朋友也能看看。
<Style x:Key="{x:Type local:RegexTextBox}" TargetType="{x:Type local:RegexTextBox}" > <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Padding" Value="1"/> <Setter Property="AllowDrop" Value="true"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:RegexTextBox}"> <theme:ListBoxChrome x:Name="Bd" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" SnapsToDevicePixels="true"> <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </theme:ListBoxChrome> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors).CurrentItem.ErrorContent}" /> </Trigger> </Style.Triggers> </Style>
接下来是测试项目内容:
1.主界面,就用用户注册来演示,要求:用户名至少5个字符(不能有空格),年龄必须在0-150之间
注意RegexTextBox控件,增加了Regex和ErrorMessage属性。
<Window ……> <Window.DataContext> <vm:MainWindowViewModel /> </Window.DataContext> <Window.Resources> …… </Window.Resources> <Grid> …… <TextBlock Text="Name" Grid.Row="0" /> <lib:RegexTextBox Grid.Column="1" Regex="\w{5,}" Text="{Binding Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ErrorMessage="At least 5 characters"/> <TextBlock Text="Age" Grid.Row="1" /> <lib:RegexTextBox Grid.Row="1" Grid.Column="1" Regex="150|((1[0-1]|\d)?\d)" Text="{Binding Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ErrorMessage="Input number"/> <Button Grid.Row="2" Grid.Column="1" Content="Submit" /> </Grid> </Window>
2.ViewModel内容,使用了通知属性
public class MainWindowViewModel : NotificationObject { private string _name; ///<summary> /// Name ///</summary> public string Name { get { return _name; } set { if (_name != value) { _name = value; RaisePropertyChanged(() => this.Name); } } } private int _age; /// <summary> /// _age /// </summary> public int Age { get { return _age; } set { if (_age != value) { _age = value; RaisePropertyChanged(() => this.Age); } } } }
测试方式:
1.初始状态:名字为空,验证不通过,在名字文本框的提示内容中能显示错误消息
2.年龄输入错误数字
3.年龄输入其他字符
如果在验证错误触发器使用(Validation.Errors)[0].ErrorContent时,当输入正确时会在输出窗口显示内部错误System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0)[0].ErrorContent; DataItem='RegexTextBox' (Name=''); target element is 'RegexTextBox' (Name=''); target property is 'ToolTip' (type 'Object') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: 指定的参数已超出有效值的范围。
但是可以使用(Validation.Errors).CurrentItem.ErrorContent,就不能出现异常。
结束语:
有人问:花这么大的精力写一个正则文本框有意义吗?不是直接可以在xaml中使用正则验证吗?
回答:1.一般正则就是在文本框中,对随意输的内容进行验证;
2.如果采用静态xaml的话,需要的窗体初始化后给控件的binding属性添加验证规则;
3.用xaml写的正则验证规则,比此复杂一点点。
当然,如果是开发一个小软件,就不需要这么花精力写自定义控件;如果是一个企业级项目,以后控件一直要复用,并且可能还会增加很多特殊需求的话,还是建议使用自定义扩展控件。
源代码下载:http://download.csdn.net/detail/qing2005/4345058