65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中的 Regex 验证

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (37投票s)

2006 年 9 月 17 日

CPOL

8分钟阅读

viewsIcon

205751

downloadIcon

4137

演示如何在 Windows Presentation Foundation 应用程序中使用正则表达式执行输入验证。

引言

本文演示如何在 Windows Presentation Foundation (WPF) 应用程序中使用正则表达式验证用户输入。本文介绍的技术为开发人员提供了两种通过正则表达式验证 TextBox 文本的方法:显式地将派生自 ValidationRule 的对象添加到 BindingValidationRules 中,或者简单地使用静态服务提供程序类的附加属性。

此代码已针对 .NET Framework v3.0 的 2006 年 6 月 CTP 版进行编译和测试,但在更高版本的框架中也应能正常工作。

背景

WPF 提供了一种与 WinForms 和 ASP.NET 中执行验证方式截然不同的输入验证方法。本文假设读者熟悉 WPF 验证的基础知识。如果您还不熟悉 WPF 中的验证,我强烈推荐 Paul Stovell 关于该主题的优秀文章SDK 中的此页面也信息量很大。

WPF 中输入验证的基本思想涉及抽象的 ValidationRule 类。派生自 ValidationRule 的类必须重写 Validate 方法以执行自定义验证逻辑,并返回一个指示输入值是否有效的值。ValidationRule 派生类的实例可以添加到 BindingValidationRules 集合中。当需要验证绑定值时,将查询所有 ValidationRule 派生对象以查看输入值是否有效。

RegexValidationRule

我的目标是创建一种通过正则表达式验证用户输入的可重用方法。我通过创建 RegexValidationRule 类来实现这一点,该类派生自 ValidationRule,并在其 Validate 重写中执行 RegexRegexValidationRule 旨在验证 TextBoxText 属性。

下面的 XAML 展示了如何使用此类别

<TextBox Name="txtProductCode">  
  <TextBox.Text>
    <Binding Path="ProductCode">
      <Binding.ValidationRules>
        <jas:RegexValidationRule
          RegexText="^[A-Z]{3}\.[0-9]{3}$"
          ErrorMessage="Invalid product code"
          RegexOptions="IgnoreCase"
          />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

上面在 XAML 中创建的 BindingRegexValidationRule 可以在代码隐藏中创建,如下所示

Binding binding = new Binding();
binding.Path = new PropertyPath( "ProductCode" );

RegexValidationRule rule = new RegexValidationRule();
rule.RegexText = @"^[A-Z]{3}\.[0-9]{3}$";
rule.ErrorMessage = "Invalid product code.";
rule.RegexOptions = RegexOptions.IgnoreCase;

binding.ValidationRules.Add( rule );
txtProductCode.SetBinding( TextBox.TextProperty, binding );

RegexValidationRule 类有三个公共属性,可用于调整其行为

  • RegexText - 存储验证期间使用的正则表达式。
  • ErrorMessage - 存储验证失败时使用/显示的文本(例如工具提示中的消息)。
  • RegexOptions - 允许您修改用于验证输入值的 Regex 对象的 RegexOptions 属性。

在 XAML 文件中编写正则表达式时,请务必记住 XAML 要求某些字符必须转义,例如“&”号 (& = &) 和撇号 (' = &apos;)。有关这些必要麻烦的更多信息,请参阅 SDK 中的此页面

这都很好,但是仅仅向 Text 属性的关联 Binding 在 XAML 中添加一个 RegexValidationRule 实例似乎需要付出很多努力。如果不需要创建多个嵌套元素,只需向 BindingValidationRules 集合添加一个规则,那将更加方便。本文的下一节将展示一种更容易使用 RegexValidationRule 类的方法。

RegexValidator

RegexValidator 类提供了一种流线型的方式,使用正则表达式来验证 TextBox 的文本。通过附加属性的魔力,可以避免上一节中看到的 XAML 中的多个嵌套元素,只需将 RegexValidationRule 添加到 BindingValidationRules 中。

RegexValidator 是一个静态类,其唯一工作是创建 RegexValidationRule 对象并将它们添加到相应的 BindingValidationRules 中。它通过附加属性公开其功能,因此您无需创建 RegexValidator 实例即可使用它提供的服务。事实上,您无法实例化 RegexValidator 类,因为它是一个静态类。

下面是 RegexValidator 的使用示例

 <TextBox Name="txtProductCode"
  Text="{Binding Path=ProductCode}" 
  jas:RegexValidator.RegexText="^[a-zA-Z]{3}\.[0-9]{3}$"
  jas:RegexValidator.ErrorMessage="Invalid product code." 
  />

上面看到的 XAML 可以用 C# 表示,如下所示

// Establish the Text property's binding.
Binding binding = new Binding();
binding.Path = new PropertyPath( "ProductCode" );
this.txtProductCode.SetBinding( TextBox.TextProperty, binding );

// Assign the regular expression.
RegexValidator.SetRegexText( 
 this.txtProductCode, @"^[a-zA-Z]{3}\.[0-9]{3}$" );

// Assign the error message.
RegexValidator.SetErrorMessage(
 this.txtProductCode, "Invalid product code." );

正如您所看到的,RegexValidatorRegexValidationRule 更易于使用,尤其是在 XAML 中使用时。您只需指定附加属性的值,其余工作将由系统为您处理。

细心的观察者会注意到,这是上一节中使用的示例的修改版本,但它没有设置 RegexOptions 属性。我决定从 RegexValidator 中省略 RegexOptions 属性,因为我怀疑该属性不会经常使用(我可能错了!)。省略 RegexValidator 中的该属性的好处是,该类的 API 保持非常小巧和简单。如果您需要设置 RegexOptions 属性,您将需要像本文上一节中那样显式创建 RegexValidationRule

工作原理

本文的其余部分解释了 RegexValidationRuleRegexValidator 的工作原理。您不需要阅读本节即可在您的应用程序中使用这些类。

RegexValidationRule 非常简单。下面是该类的精简版本

public class RegexValidationRule : ValidationRule
{
 // All other members were omitted for clarity.

 /// <summary>
 /// Validates the 'value' argument using the regular expression and 
 /// RegexOptions associated with this object.
 /// </summary>
 public override ValidationResult Validate( object value, 
   CultureInfo cultureInfo )
 {
 ValidationResult result = ValidationResult.ValidResult;

 // If there is no regular expression to evaluate,
 // then the data is considered to be valid.
 if( ! String.IsNullOrEmpty( this.RegexText ) )
 {
  // Cast the input value to a string (null becomes empty string).
  string text = value as string ?? String.Empty;

  // If the string does not match the regex, return a value
  // which indicates failure and provide an error mesasge.
  if( ! Regex.IsMatch( text, this.RegexText, this.RegexOptions ) )
   result = new ValidationResult( false, this.ErrorMessage );
 }

 return result; 
}

Validate 方法(继承自 ValidationRule)只是调用 Regex 类的静态 IsMatch 方法来执行验证。如果输入值与存储在 RegexText 属性中的正则表达式不匹配,则返回一个 ValidationResult,指示该值无效并提供错误消息。错误消息从 ErrorMessage 属性中检索。如果输入值有效,则返回 ValidResult 属性。该属性返回 ValidationResult 的单例实例,因此使用它来指示成功可以避免用所有表达相同内容(“该值有效”)的冗余 ValidationResult 实例来碎片化托管堆。

RegexValidator 类更为复杂。首先,让我们看看该类是如何声明的

public static class RegexValidator
{
 // There's more to come...
}

由于它是一个静态类,因此永远无法实例化,并且其所有成员都必须是静态的。这是有道理的,因为这个类是一个服务提供者,它的所有功能都通过附加属性公开。没有理由实例化该类。

接下来,我们将检查其附加属性的实现

/// <summary>
/// Identifies the RegexValidator's ErrorMessage attached property.  
/// This field is read-only.
/// </summary>
public static readonly DependencyProperty ErrorMessageProperty;

/// <summary>
/// Returns the error message used when validation fails for the
/// specified TextBox.
/// </summary>
/// <param name="textBox">The TextBox whose error message is returned.</param>
public static string GetErrorMessage( TextBox textBox )
{
 return textBox.GetValue( ErrorMessageProperty ) as string;
}

/// <summary>
/// Sets the error message used when validation fails for the 
/// specified TextBox.
/// </summary>
/// <param name="textBox">The TextBox being validated.</param>
/// <param name="value">The error message.</param>
public static void SetErrorMessage( TextBox textBox, string value )
{
 textBox.SetValue( ErrorMessageProperty, value );
}

/// <summary>
/// Identifies the RegexValidator's RegexText attached property.  
/// This field is read-only.
/// </summary>
public static readonly DependencyProperty RegexTextProperty;

/// <summary>
/// Returns the regular expression used to validate the specified TextBox.
/// </summary>
/// <param name="textBox">
/// The TextBox whose regular expression is returned.
/// </param>
public static string GetRegexText( TextBox textBox )
{
 return textBox.GetValue( RegexTextProperty ) as string;
}

/// <summary>
/// Sets the regular expression used to validate the 
/// specified TextBox.
/// </summary>
/// <param name="textBox">The TextBox being validated.</param>
/// <param name="value">The regular expression.</param>
public static void SetRegexText( TextBox textBox, string value )
{
 textBox.SetValue( RegexTextProperty, value );
}

这些附加属性遵循了公共静态 Get/Set 方法的约定,以将属性值与 DependencyObject 相关联(在本例中,它只适用于 TextBox 类)。附加属性在静态构造函数中注册

static RegexValidator()
{
 RegexTextProperty = DependencyProperty.RegisterAttached(
  "RegexText",
  typeof( string ),
  typeof( RegexValidator ),
  new UIPropertyMetadata( null, OnAttachedPropertyChanged ) );

 ErrorMessageProperty = DependencyProperty.RegisterAttached(
  "ErrorMessage",
  typeof( string ),
  typeof( RegexValidator ),
  new UIPropertyMetadata( null, OnAttachedPropertyChanged ) );
}

请注意,注册属性时,在 UIPropertyMetadata 参数中提供了一个回调方法。当 TextBoxRegexValidator 附加属性之一的值被修改时,会调用该回调。该回调方法的实现如下

/// <summary>
/// Invoked whenever an attached property of the 
/// RegexValidator is modified for a TextBox.
/// </summary>
/// <param name="depObj">A TextBox.</param>
/// <param name="e"></param>
static void OnAttachedPropertyChanged( DependencyObject depObj, 
  DependencyPropertyChangedEventArgs e )
{
 TextBox textBox = depObj as TextBox;
 if( textBox == null )
  throw new InvalidOperationException(
   "The RegexValidator can only be used with a TextBox." );

 VerifyRegexValidationRule( textBox );
}

您可能想知道,为什么我费心使用回调来调用 VerifyRegexValidationRule 方法,而不是直接从为附加属性创建的静态 Set 方法中调用该方法。这种方法可能看起来可以正常工作,并且更简单

// This is not how it works!
public static void SetErrorMessage( TextBox textBox, string value )
{
 textBox.SetValue( ErrorMessageProperty, value );
 VerifyRegexValidationRule( textBox );
} 

如果 ErrorMessage 属性的值始终通过为 TextBox 调用 RegexValidator.SetErrorMessage 来设置,那么这种方法就会奏效。然而,完全有可能通过直接调用 TextBox 本身的 SetValue 来为 TextBox 设置 ErrorMessage 附加属性(或任何其他附加属性),就像 SetErrorMessage 方法所做的那样。在这种情况下,对 VerifyRegexValidationRule 的调用将不会发生,并且 TextBoxText 属性绑定将永远不会向其 ValidationRules 集合中添加 RegexValidationRule。通过使用注册附加属性时提供的回调方法,我们可以确保每当 TextBox 的附加属性值被修改时,我们的回调都会调用 VerifyRegexValidationRule

现在,是时候检查 VerifyRegexValidationRule 方法的作用了。请记住,只要 RegexValidator 附加属性的值被修改,就会调用此方法。

/// <summary>
/// Creates or modifies the RegexValidationRule in the TextBox's 
/// Text property binding to use the current values of the attached 
/// properties exposed by this class.
/// </summary>
/// <param name="textBox">The TextBox being validated.</param>
static void VerifyRegexValidationRule( TextBox textBox )
{
 RegexValidationRule regexRule = GetRegexValidationRuleForTextBox( textBox );
 if( regexRule != null )
 {
  regexRule.RegexText = 
    textBox.GetValue( RegexValidator.RegexTextProperty ) as string;

  regexRule.ErrorMessage = 
    textBox.GetValue( RegexValidator.ErrorMessageProperty ) as string;
 }
}

此方法尝试检索一个 RegexValidationRule,用于指定 TextBoxText 属性绑定。如果它获得了对该对象的引用,则会将属性值传输到该对象。请注意,RegexValidationRule 属性的实际值由 TextBox 作为附加属性存储。在处理附加属性时,务必记住这些属性的值由它们所应用的对象存储,而不是由公开附加属性的类/对象存储。

此时,有必要检查 GetRegexValidationRuleForTextBox 方法的工作原理。此方法负责为给定的 TextBox 创建或检索 RegexValidationRule 对象。

/// <summary>
/// Returns a RegexValidationRule to be used for validating the specified
/// TextBox.  If the TextBox is not yet initialized, this method returns null.
/// </summary>
static RegexValidationRule GetRegexValidationRuleForTextBox( TextBox textBox )
{
 if( ! textBox.IsInitialized )
 {
  // If the TextBox.Text property is bound, but the TextBox is not yet
  // initialized, the property's binding can be null.  In that situation,
  // hook its Initialized event and verify the validation rule again when 
  // that event fires.  At that point in time, the Text property's binding
  // will be non-null.
  EventHandler callback = null;
  callback = delegate
  {
    textBox.Initialized -= callback;
    VerifyRegexValidationRule( textBox );
  };
  textBox.Initialized += callback;
  return null;
 }

 // Get the binding expression associated with the TextBox's Text property.
 BindingExpression expression = 
   textBox.GetBindingExpression( TextBox.TextProperty );
 if( expression == null )
  throw new InvalidOperationException(
   "The TextBox's Text property must be bound for the RegexValidator to " + 
   "validate it." );

 // Get the binding which owns the binding expression.
 Binding binding = expression.ParentBinding;
 if( binding == null )
  throw new ApplicationException(
   "Unexpected situation: the TextBox.Text binding expression has no " + 
   "parent binding." );

 // Look for an existing instance of the RegexValidationRule class in the
 // binding.  If there is more than one instance in the ValidationRules
 // then throw an exception because we don't know which one to modify.
 RegexValidationRule regexRule = null;
 foreach( ValidationRule rule in binding.ValidationRules )
 {
  if( rule is RegexValidationRule )
  {
   if( regexRule == null )
    regexRule = rule as RegexValidationRule;
   else
    throw new InvalidOperationException(
     "There should not be more than one RegexValidationRule in a Binding's " +
     "ValidationRules." );
  }
 }

 // If the TextBox.Text property's binding does not yet have an 
 // instance of RegexValidationRule in its ValidationRules,
 // add an instance now and return it.
 if( regexRule == null )
 {
  regexRule = new RegexValidationRule();
  binding.ValidationRules.Add( regexRule );
 }

 return regexRule;
}

该方法获取与 TextBoxText 属性关联的 BindingExpression 的引用。然后,它检索拥有该表达式的 Binding 的引用,并遍历 BindingValidationRules 集合以查找 RegexValidationRule。如果找不到,它将创建一个新实例并将其添加到 ValidationRules 集合中。如果找到多个 RegexValidationRule,它将抛出异常,因为无法知道应该返回哪一个。

结论

RegexValidationRuleRegexValidator 支持在 WPF 中使用正则表达式验证 TextBox

您可以创建 RegexValidationRule 并显式将其添加到 Text 属性的 Binding 中,或者直接使用 RegexValidator 的附加属性,它将为您处理其余工作。

如果您需要为 RegexValidationRule 指定单个 RegexOptions 值,您可以在 XAML 中使用该类的 RegexOptions 属性。

在 XAML 文件中创建正则表达式时,请记住 XAML 要求某些字符必须进行转义(例如和号和撇号)。

完整的源代码和演示应用程序可从本文顶部下载。

修订历史

  • 2006 年 9 月 17 日 - 创建文章。
  • 2006 年 11 月 2 日 - 最初,文章指出位操作不能在 XAML 中执行(即 XAML 解析器不支持它们)。我提到“事实”是关于组合 RegexOptions 枚举的多个值。事实证明我错了;通过用逗号分隔,可以组合带标志的枚举值。我从文章中删除了该错误信息。以下是揭示 XAML 中组合带标志枚举真相的 WPF 论坛帖子:Microsoft 论坛
© . All rights reserved.