DCSIMG
A Validated TextBox as a WPF UserControl - David Sackstein's Blog

A Validated TextBox as a WPF UserControl

In this post I will be describing a WPF UserControl that I wrote to handle the validation of a text box.

The complete source code can be downloaded here.

Requirements

I would like to be able to use the control like this:

      <local:ValidatedTextBox x:Name="tbAnInteger">

         <local:ValidatedTextBox.Text>

            <Binding Path="AnInteger" Mode="TwoWay"

                     UpdateSourceTrigger="PropertyChanged">

               <Binding.ValidationRules>

                  <local:RangeValidationRule MinValue="3" MaxValue="50" />

                  <local:DivisibleValidationRule Divisor="4"/>

               </Binding.ValidationRules>

            </Binding>

         </local:ValidatedTextBox.Text>

      </local:ValidatedTextBox>

and have the control handle the display of any error message as a label next to the textbox and as a tooltip.

I would like to be able to add any number of validation rules (though usually one would be enough) and be able to ask explicitly in code whether the control validates.

As the XAML shows, the validation rules themselves are not part of the control, but can be arbitrarily built and used by the client code.

Implementation

The implementation is fairly straightforward (like everything in WPF – it looks really easy).

Here is the XAML:

<UserControl x:Class="ValidationSample.ValidatedTextBox"

            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

            xmlns:local="clr-namespace:ValidationSample">

   <Validation.ErrorTemplate>

      <ControlTemplate>

         <DockPanel LastChildFill="True"

                   Background="Transparent"

                   ToolTipService.InitialShowDelay="0"

                   ToolTipService.ShowDuration="2000"

                   ToolTip="{Binding

                             ElementName=myAdorner,

                             Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">

            <TextBlock DockPanel.Dock="Right"

                      VerticalAlignment="Center"

                      Text="{Binding

                             ElementName=myAdorner,

                             Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">

            </TextBlock>

            <AdornedElementPlaceholder Name="myAdorner"/>

         </DockPanel>

      </ControlTemplate>

   </Validation.ErrorTemplate>

   <TextBox Text="{Binding

                 RelativeSource={

                     RelativeSource FindAncestor,

                     AncestorType={x:Type local:ValidatedTextBox}

                 },

                 Path=Text,

                 UpdateSourceTrigger=PropertyChanged}">

   </TextBox>

</UserControl>

The control consists only of a TextBox and an ErrorTemplate (which is an attached property of the Validation class).

The Text property of the TextBox is bound to the Text (dependency) property of the ValidatedTextBox. The ErrorTemplate kicks in when there are one or more validation errors. Then it displays text and a tooltip describing the error.

This is the code behind implementation of the ValidatedTextBox.

    public partial class ValidatedTextBox : UserControl

    {

        public ValidatedTextBox()

        {

            InitializeComponent();

        }

 

        public string Text

        {

            get { return (string)GetValue(TextProperty); }

            set { SetValue(TextProperty, value); }

        }

 

        public static readonly DependencyProperty TextProperty =

            DependencyProperty.Register("Text", typeof(string),

                typeof(ValidatedTextBox), new UIPropertyMetadata(""));

 

    }

As you can see, I only exposed a Text property (as a Dependency Property). Of course, you could expose other properties too, or even expose the internal TextBox itself.

To test the control, I wrote the following validation rules:

    public class RangeValidationRule : ValidationRule

    {

        public int MinValue { get; set; }

        public int MaxValue { get; set; }

 

        public override ValidationResult Validate(

            object value, System.Globalization.CultureInfo cultureInfo)

        {

            int intValue;

 

            string text = String.Format ("Must be between {0} and {1}",

                                          MinValue, MaxValue);

            if (! Int32.TryParse(value.ToString(), out intValue))

                return new ValidationResult(false, "Not an integer");

            if (intValue < MinValue)

                return new ValidationResult(false, "To small. " + text);

            if (intValue > MaxValue)

                return new ValidationResult(false, "To large. " + text);

            return ValidationResult.ValidResult;

        }

    }

 

    public class DivisibleValidationRule : ValidationRule

    {

        public int Divisor { get; set; }

        public override ValidationResult Validate(

            object value, System.Globalization.CultureInfo cultureInfo)

        {

            int intValue;

 

            if (!Int32.TryParse(value.ToString(), out intValue))

                return new ValidationResult(false, "Not an integer");

            if (intValue % Divisor != 0)

                return new ValidationResult(false,

                                      "Not divisible by " + Divisor);

            return ValidationResult.ValidResult;

        }

    }

These rules are just examples.

I added an instance of each rule to the ValidationRules collection of the Binding of the Text property of the ValidatedTextBox.

Note, that if both validation rules fail, only the first error (in the order that the rules appear in the collection) will be displayed.

Check Validity in Code

Let’s say you have an OK button which you would like enabled only if a ValidatedTextBox validates.

I decided to use some Command Binding for that.

Here is my XAML for the Main Window:

<Window x:Class="ValidationSample.MainWindow"

       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

       xmlns:local="clr-namespace:ValidationSample"

       Title="Main Window"

       Height="300" Width="300"

       Loaded="Window_Loaded">

   <Window.CommandBindings>

      <CommandBinding Command="Save"

                     CanExecute="CommandBinding_CanExecute"

                     Executed="CommandBinding_Executed" />

   </Window.CommandBindings>

   <Window.Resources>

   </Window.Resources>

   <StackPanel>

      <local:ValidatedTextBox x:Name="tbAnInteger">

         <local:ValidatedTextBox.Text>

            <Binding Path="AnInteger"

                     Mode="TwoWay"

                     UpdateSourceTrigger="PropertyChanged">

               <Binding.ValidationRules>

                  <local:RangeValidationRule MinValue="3" MaxValue="50" />

                  <local:DivisibleValidationRule Divisor="4"/>

               </Binding.ValidationRules>

            </Binding>

         </local:ValidatedTextBox.Text>

      </local:ValidatedTextBox>

      <Button Command="Save">Text</Button>

   </StackPanel>

</Window>

I bound the application command “Save” adding handlers for CanExecute and Executed and associated the Save button with the Save ApplicationCommand.

The code behind for these handlers is:

        private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)

        {

            e.CanExecute = ! Validation.GetHasError(tbAnInteger);

        }

 

        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)

        {

            Close();

        }

As you can see, Validation.GetHasErrors allows me to check if any of the validation rules fails.

So, when there are any validation errors, the Save button is not enabled.

The complete source code can be downloaded here.

Comments are welcome!

Published Monday, April 27, 2009 12:52 AM by David Sackstein

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems