Infosys Microsoft Alliance and Solutions blog

« Importance of Application.DoEvents | Main | Working with MSMQ Journal Queues »

Custom User Input Validation in WPF

Building any application that works with User Input cannot be complete usually without having some sort of validations for the input values. WPF is no exception. There is an interesting discussion around WPF validation on Paul Stovell's blog

Martin Bennedik has written a WPF Validation Block over Microsoft's Enterprise Library 3.0. WPF 3.5 also brings in additional validation support via the IDataEffortInfo Interface.

However, all said and done, these techniques typically work with data binding. One can always work with Data binding in WPF, but what if you don't have a need for that? What if there are say only one or two fields you want to validate? Also the WPF validation does provide feedback to the user when validation fails by using the ErrorTemplate, but to mark some field as mandatory, one typically still requires to be put the most used red asterix (*) near the mandatory fields.

Look at the screen shot of an application below. It has two text boxes for user's first name and last name. Both are mandatory. However instead of the asterix approach, I thought of why not the text box itself show a red border when it is empty and also a tooltip along with it to show what is required. Once the user enter's a valid value, the border can change back to the default border color. Finally, the text boxes also should be queriable for valid state so that when the form/dialog/window is submitted, I can loop through them and see if I can submit or not.

ValidationApp.jpg

First, to see how to have the TextBox check and provide feedback on it being valid. For this I used the new Extension Method functionality in C# 3.0. For a deep dive into this feature, check here. The following is the method I wrote where I just check to see if the TextBox control has some string entered or not and return true/false accordingly. Needless to say that the TextBox in the parameter below is from the System.Windows.Controls namespace, that is used in WPF.

    public static class CustomExtensions

    {

        public static bool IsValid(this TextBox obj)

        {

            if (obj.Text.Length > 0)

                return true;

 

            return false;

        }

    }

Next, to display the appropriate borders, I needed two brush resources - one, the default border brush that the TextBox uses and, two, a similar gradient brush that fills to red color to be used when the TextBox is invalid. Creating these with Expression Blend is very trivial. I saved these both as resources in the XAML file.

    <Window.Resources>

        <LinearGradientBrush x:Key="FaultyBorderBrush" EndPoint="0,20" StartPoint="0,0" MappingMode="Absolute">

            <GradientStop Color="#FFABADB3" Offset="0.05"/>

            <GradientStop Color="#FFE2E3EA" Offset="0.07"/>

            <GradientStop Color="#FFFF0000" Offset="1"/>

        </LinearGradientBrush>

        <LinearGradientBrush x:Key="DefaultBorderBrush" EndPoint="0,20" StartPoint="0,0" MappingMode="Absolute">

            <GradientStop Color="#FFABADB3" Offset="0.05"/>

            <GradientStop Color="#FFE2E3EA" Offset="0.07"/>

            <GradientStop Color="#FFE3E9EF" Offset="1"/>

        </LinearGradientBrush>

    </Window.Resources>

Finally, comes the logic to invoke the IsValid method at appropriate times so that the necessary feedback can be given to the user. The initial setting was done in the constructor of the Window so that i could initially show the TextBoxs with red border and the tooltip (as shown in the figure above). I also handled the TextChange event of the TextBoxes so that I could invoke the IsValid method to see if the TextBox is valid or not and change the border color appropriately. I also handled the Button's click event just to show that while submitting the form, I can invoke the same method and allow/prevent submission of the form accordingly. The following code shows the complete code behind for the Window.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

 

namespace GenericWPFApp

{

    /// <summary>

    /// Interaction logic for MainPane.xaml

    /// </summary>

    public partial class MainPane : Window

    {

        const string _firstNameMessage = "FirstName cannot be left blank";

        //following is used with the different extension method which supports min and max lengths

        const string _lastNameMessage = "LastName should be between 0 and 100 characters";

 

        public MainPane()

        {

            InitializeComponent();

 

            //since the text boxes are initially empty, they are invalid. Initialize them accordingly

            SetTextBoxProperties(txtFirstName, false, _firstNameMessage);

            SetTextBoxProperties(txtLastName, false, _lastNameMessage);

        }

 

        private void txtFirstName_TextChanged(object sender, TextChangedEventArgs e)

        {

            TextBox box = sender as TextBox;

            if (box != null)

                SetTextBoxProperties(box, box.IsValid(), _firstNameMessage );

        }

 

        private void txtLastName_TextChanged(object sender, TextChangedEventArgs e)

        {

            TextBox box = sender as TextBox;

            if (box != null)

                SetTextBoxProperties(box, box.IsValid(0, 100), _lastNameMessage);

        }

 

        private void SetTextBoxProperties(TextBox ctrl, bool valid, string tip)

        {

            if (valid)

            {

                ctrl.BorderBrush = (Brush)TryFindResource("DefaultBorderBrush");

                ctrl.ToolTip = null;

            }

            else

            {

                ctrl.BorderBrush = (Brush)TryFindResource("FaultyBorderBrush");

                ctrl.ToolTip = tip;

            }

        }

 

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            bool valid = false;

            valid = txtFirstName.IsValid();

            if (!valid)

            {

                message.Text = "Form is invalid";

                return;

            }

 

            valid = txtLastName.IsValid(0, 100);

            if (!valid)

            {

                message.Text = "Form is invalid";

                return;

            }

 

            message.Text = "Form is valid";

        }

    }

}

Disclaimer: The code isn't optimized and you may find better ways to implement the same logic. I just wanted to show that we can get some of the validation functionality without necessarily going the DataBinding way. Another caveat is that since the extension method is written for the TextBox, it will be available for all the TextBoxes used in the application. While this is good, the issue could be that you may want different logic for different TextBoxes. You can always write different implementations for the same. One such method, which works with minimum and maxium length is shown below. The challenge however will be to invoke the right method for the appropriate TextBoxes.

        public static bool IsValid(this TextBox obj, int minLen, int maxLen)

        {

            int length = obj.Text.Length;

            if ((minLen < length)  && (maxLen > length))

                return true;

 

            return false;

        }

Since extension methods can be written for any control/class, you can write similar logic for any WPF control to provide your own validation mechanisms. Comments welcome !  

 

 

 

 

Comments

Nice entry. :) Very informative. I am currently trying to build a Error Provider for a custom set of controls, which is not implemented on the Data Access Layer and Extensions looks like the way to go.
Just a question: Is there a good way to implement extensions so that a programmer can easily implement a control and put in the parameters to validate against all in the XAML?

@Serene, feel free to drop me a mail at atulg AT infosys.com and we can discuss further.

Nice work . Informative post

How can we create the style with validation Rules inside the style so that any input control can get it automatically?

Ashish, my take will be that it will not work that way for

1. Validation rule is set on a binding and if you set binding in a global style, it will result in same buiding for all controls, which you will not want

2. Validation rule would possibly differ based on actual values being assigned to controls and hence having a common validation rule may not work out.

However with the extension method approach i showed above, you can possibly still achieve this, since extension method will apply to all controls of that type.

Can you make the code available for download?

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Please key in the two words you see in the box to validate your identity as an authentic user and reduce spam.

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter