WPF Simplified Part 15: Data Validation

Say we want to validate user input in a TextBox,

<Grid>        
    <!-- Validate this TextBox -->
    <TextBox Height="30" Width="80" Text="{Binding FirstName}" />
</Grid>

where, the TextBox is bound to an instance of the Person class,

public class Person { public string FirstName { get; set; } }

The way to do this is to associate ValidationRules with the Binding (the Text-FirstName binding). These rules validate the update of the binding source property (FirstName property) and if the update causes an error, the binding is marked as invalid.

Say we want that the TextBox’s Text (and hence the Person’s FirstName property) cannot be empty. Let’s change the Person class to throw an exception if the FirstName is null or empty,

public class Person {
    private string firstname;
    public string Firstame {
        get { return this.firstname; }
        set {
            if (string.IsNullOrEmpty(value)) {
                throw new Exception("First name cannot be null or empty");
            }
            this.firstname = value;
        }
    } 
}

Now we need a rule to associate with the binding that says, if there is an exception thrown during the update of the source property, the binding is invalid. Luckily, there is already a built-in rule for that called ExceptionValidationRule. All we need to do is associate this rule with the binding, like this,

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <ExceptionValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

The XAML above associates a ExceptionValidationRule with the binding causing the binding to be invalidated if an exception is thrown (in our case, when the FirstName is null or empty). The validation occurs when the value of a target (TextBox’s Text dependency property) is transferred to the binding source property (Person’s FirstName property). What causes a source update depends on the value of the UpdateSourceTrigger property – “PropertyChanged” means the binding engine updates the source value on every keystroke, which means it also checks every rule in the ValidationRules collection on every keystroke. UpdateSourceTrigger could also have the value “LostFocus”, which updates the source when the target has lost focus.

We can also write our own ValidationRule,

public class CustomValidationRule : ValidationRule {
    public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
        // Say the first name can't contain a space,
        if (value.ToString().Contains(' ')) {
            return new ValidationResult(false, "No spaces allowed in FirstName");
        }
    
        return ValidationResult.ValidResult;
    }
}

and apply it to the TextBox,

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <ExceptionValidationRule />
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
 

There are two type of built-in ValidationRules,

1. ExceptionValidationRule: Represents a rule that checks for exceptions that are thrown during the update of the binding source property.

2. DataValidationRule: Represents a rule that checks for errors that are raised by the IDataErrorInfo implementation of the source object.

Implementing IDataErrorInfo is easy enough, we implement the Person class like this,

public class Person : IDataErrorInfo {
    public string FirstName { get; set; }

    public string this[string propertyName] {
        get {
            string result = null;
            if (propertyName == "FirstName") {
                if (string.IsNullOrEmpty(this.FirstName))
                {
                    result = "Cannot be null or empty";
                }
            }
            return result;
        }
    }
 
    // Not using this
    public string Error {
        get { return null; }
    }

}

And the TextBox XAML is similar to above,

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <DataErrorValidationRule />
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

An alternate syntax for the above is to use the ValidatesOnDataErrors (instead of adding the DataErrorValidationRule explicitly) and ValidateOnExceptions (instead of adding the ExceptionValidationRule explicitly),

    <TextBox Height="30" Width="80">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" 
                ValidatesOnDataErrors="True" ValidatesOnExceptions="True">

                <Binding.ValidationRules>
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

We can also provide visual feedback to the user by setting the Validation.ErrorTemplate attached property of the TextBox to a custom ControlTemplate.

    <TextBox Height="30" Width="80" ToolTip="{Binding RelativeSource={RelativeSource Self}, 
                                              Path=(Validation.Errors)[0].ErrorContent}">
        <TextBox.Text>
            <Binding Path="FirstName" UpdateSourceTrigger="PropertyChanged" 
                     ValidatesOnDataErrors="True" ValidatesOnExceptions="True">
                <Binding.ValidationRules>
                    <local:CustomValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
        <Validation.ErrorTemplate>
            <ControlTemplate>
                <DockPanel>
                    <AdornedElementPlaceholder Name="MyAdorner"/>
                    <TextBlock Foreground="Red" Text="{Binding ElementName=MyAdorner, 
                        Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
                </DockPanel>
            </ControlTemplate>
        </Validation.ErrorTemplate>
    </TextBox>

 

Digg This
Advertisements

About soumya chattopadhyay
I live and work in Seattle, WA. I work with Microsoft technologies, and I'm especially interested in C#.

4 Responses to WPF Simplified Part 15: Data Validation

  1. Pingback: WPF Simplified Series « I.Net

  2. Ascariz says:

    as you can remove a rule from a combovox?

  3. Catherine says:

    Love the title and love the blog. Good job!

  4. Paras Sharma says:

    Hi, I want to know that whether INotifyDatErrorInfo can be used in WPF or not?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: