WPF自定义正则验证控件

我在这里介绍一种自定义控件方式的正则验证方法。业务背景不做详细说明,这里以技术背景为主。
思路:创建一个控件,使其具有文本框的特性外,还能就行正则验证;

WPF自定义正则验证控件_第1张图片
WPF自定义正则验证控件_第2张图片
WPF自定义正则验证控件_第3张图片

依据这个思路,来讲解实现。

第一步:创建自定义控件
因为要保留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

你可能感兴趣的:(Class,regex,WPF,setter,textbox,binding)