A Regex Attached Property

As is always in a project you come across some requirement that can be achieved in many different ways and the art is to choose the correct one. This situation arose when in my current project I had to provide field level validation in a Textbox based on a specified pattern. Now, all of you would immediately think Regex as I did, however, the question then arose User Control or something similar. The answer was simply NO! The correct WPF or Silverlight answer came from the book “WPF Control Development Unleashed” by Podila and Hoffman. Here the authors present 5 steps in deciding what to correctly do in given the requirement above. When considering whether to build a custom control or not:

  1. Consider if it possible to use an existing framework control or one from a third party.
  2. If not then consider using a group of existing controls
  3. If not then consider using templates and converters
  4. If not then consider using attached properties
  5. If none of the above then it is likely that a custom control is needed.

After careful consideration of the process I decided that the correct implementation was via an Attached Property.

Attached Properties

A quick review: An Attached Property can be used to add behaviour to an existing control. These new properties are not defined on the control being extended, usually via inheritance, but via a separate class or object. Hence the attached property acts as a source of new functionality that is attached to the current target. When the attached property is set on the object via XAML a property changed event is fired on the target allowing the attached property to register itself on the source. Upon the firing of specific source event, those that the attached property associated itself with, the source is passed information pertaining to the target and associated old and new values.

The Regex Attached Property

Now a quick explanation on the main points of the Regex Attached Property:

  1. The Attached Property only activates itself when it is attached to a TextBox element (OnRegexEntryChanged).
  2. The Attached Property links into the PreviewTextInput event and compares the given input with the RegEx expression in TextBoxPreviewTextInput. If the match is made then the string is displayed.

Here is the full code for the Regex Attached Property:

using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Interactors
{
    /// <summary>
    /// Definition of the <see cref=”RegexInteractor”/>  class.
    /// </summary>
    public class RegexInteractor : DependencyObject
    {
        /// <summary>
        /// Using a DependencyProperty as the backing store for RegexEntry.  This enables animation, styling, binding, etc…
        /// </summary>
        public static readonly DependencyProperty RegexEntryProperty =
            DependencyProperty.RegisterAttached(“RegexEntry”typeof(bool), typeof(RegexInteractor), new UIPropertyMetadata(false, OnRegexEntryChanged));

        /// <summary>
        /// Runs test for get Regex.
        /// </summary>
        /// <param name=”obj”>
        /// The obj parameter.
        /// </param>
        /// <returns>
        /// The get Regex entry.
        /// </returns>
        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        [Description(“Enable or disable Regex checking for this control.”)]
        [DisplayNameAttribute(@”Is Regex Enabled”)]
        [CategoryAttribute(“Interactor”)]
        public static bool GetRegexEntry(DependencyObject obj)
        {
            return (bool)obj.GetValue(RegexEntryProperty);
        }

        /// <summary>
        /// Runs test for set Regex.
        /// </summary>
        /// <param name=”obj”>
        /// The obj parameter.
        /// </param>
        /// <param name=”value”>
        /// If set to <c>true</c> if value.
        /// </param>
        public static void SetRegexEntry(DependencyObject obj, bool value)
        {
            obj.SetValue(RegexEntryProperty, value);
        }

        /// <summary>
        /// Runs test for on Regex entry.
        /// </summary>
        /// <param name=”regexEntry”>
        /// The RegexEntry parameter.
        /// </param>
        /// <param name=”e”>
        /// The <see cref=”System.Windows.DependencyPropertyChangedEventArgs”/> instance containing the event data.
        /// </param>
        private static void OnRegexEntryChanged(DependencyObject regexEntry, DependencyPropertyChangedEventArgs e)
        {
            if (!(regexEntry is TextBox))
            {
                return// support only Regex entry in a TextBox
            }

            var textBox = regexEntry as TextBox;

            // add the event handles for key press
            if ((bool)e.NewValue)
            {
                textBox.PreviewTextInput += TextBoxPreviewTextInput;
            }
            else
            {
                textBox.PreviewTextInput -= TextBoxPreviewTextInput;
            }
        }

        /// <summary>
        /// The validation Regex.
        /// </summary>
        public static readonly DependencyProperty ExpressionProperty = DependencyProperty
            .RegisterAttached(
                “Expression”,
                typeof(string),
                typeof(RegexInteractor),
                new PropertyMetadata(string.Empty));

        /// <summary>
        /// Sets the expression.
        /// </summary>
        /// <param name=”obj”>The obj parameter.</param>
        /// <param name=”value”>The value parameter.</param>
        public static void SetExpression(DependencyObject obj, string value)
        {
            obj.SetValue(ExpressionProperty, value);
        }

        /// <summary>
        /// Gets the expression.
        /// </summary>
        /// <param name=”obj”>The obj parameter.</param>
        /// <returns>
        /// Returns a value of <see cref=”System.String”/>.
        /// </returns>
        [AttachedPropertyBrowsableForType(typeof(TextBox))]
        [DisplayNameAttribute(@”Regex expression”)]
        [Description(“The Regex expression to use to validate the textbox entry.”)]
        [CategoryAttribute(“Interactor”)]
        public static string GetExpression(DependencyObject obj)
        {
            return (string)obj.GetValue(ExpressionProperty);
        }

        /// <summary>
        /// Handles the PreviewTextInput event of the textBox control.
        /// </summary>
        /// <param name=”sender”>The source of the event.</param>
        /// <param name=”e”>The <see cref=”System.Windows.Input.TextCompositionEventArgs”/> instance containing the event data.</param>
        private static void TextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            if (! (sender is TextBox))
            {
                return;
            }

            var tb = sender as TextBox;
            var text = (sender as TextBox).Text + e.Text;
            var prevInfo = tb.GetValue(ExpressionProperty) as string;
            if (prevInfo == null)
            {
                return;
            }

            var regex = new Regex(prevInfo);
            e.Handled = !regex.IsMatch(text);
        }
    }
}

 
 

The XAML Markup

<TextBox x:Name="maxVal"
    Text="{Binding MaxValue}"
    ctrl:RegexInteractor.RegexEntry="True"
    ctrl:RegexInteractor.Expression="^[1-9]+[0-9]{0,3}$"
 />
 

That’s all folks…

Advertisements

~ by Intelligence4 on January 3, 2011.

 
%d bloggers like this: