WPF Validation for the whole form

validation, wpf

Solution

A WPF application should disable the button to submit a form iff the entered data is not valid. You can achieve this by implementing the IDataErrorInfo interface on your business object, using Bindings with `ValidatesOnDataErrors``=true`. For customizing the look of individual controls in the case of errors, set a `Validation.ErrorTemplate`.

XAML:

<Window x:Class="Example.CustomerWindow" ...>
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Save"
                        CanExecute="SaveCanExecute"
                        Executed="SaveExecuted" />
    </Window.CommandBindings>
    <StackPanel>
        <TextBox Text="{Binding FirstName, ValidatesOnDataErrors=true, UpdateSourceTrigger=PropertyChanged}" />
        <TextBox Text="{Binding LastName, ValidatesOnDataErrors=true, UpdateSourceTrigger=PropertyChanged}" />
        <Button Command="ApplicationCommands.Save" IsDefault="True">Save</Button>
        <TextBlock Text="{Binding Error}"/>
    </StackPanel>
</Window>

This creates a `Window` with two `TextBox`es where you can edit the first and last name of a customer. The "Save" button is only enabled if no validation errors have occurred. The `TextBlock` beneath the button shows the current errors, so the user knows what's up.

The default `ErrorTemplate` is a thin red border around the erroneous Control. If that doesn't fit into you visual concept, look at Validation in Windows Presentation Foundation article on CodeProject for an in-depth look into what can be done about that.

To get the window to actually work, there has to be a bit infrastructure in the Window and the Customer.

Code Behind

// The CustomerWindow class receives the Customer to display
// and manages the Save command
public class CustomerWindow : Window
{
    private Customer CurrentCustomer;
    public CustomerWindow(Customer c) 
    {
        // store the customer for the bindings
        DataContext = CurrentCustomer = c;
        InitializeComponent();
    }

    private void SaveCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = ValidationEngine.Validate(CurrentCustomer);
    }

    private void SaveExecuted(object sender, ExecutedRoutedEventArgs e) 
    {
        CurrentCustomer.Save();
    }
}

public class Customer : IDataErrorInfo, INotifyPropertyChanged
{
    // holds the actual value of FirstName
    private string FirstNameBackingStore;
    // the accessor for FirstName. Only accepts valid values.
    public string FirstName {
        get { return FirstNameBackingStore; }
        set {
            FirstNameBackingStore = value;
            ValidationEngine.Validate(this);
            OnPropertyChanged("FirstName");
        }
    }
    // similar for LastName        

    string IDataErrorInfo.Error {
        get { return String.Join("\n", BrokenRules.Values); }
    }

    string IDataErrorInfo.this[string columnName]
    {
        get { return BrokenRules[columnName]; }
    }
}

An obvious improvement would be to move the `IDataErrorInfo` implementation up the class hierarchy, since it only depends on the `ValidationEngine`, but not the business object.

While this is indeed more code than the simple example you provided, it also has quite a bit more of functionality than only checking for validity. This gives you fine grained, and automatically updated indications to the user about validation problems and automatically disables the "Save" button as long as the user tries to enter invalid data.

Problem

I have been seriously disappointed with WPF validation system. Anyway! How can I validate the complete form by clicking the "button"? For some reason everything in WPF is soo complicated! I can do the validation in 1 line of code in ASP.NET which requires like 10-20 lines of code in WPF!! I can do this using my own ValidationEngine framework: ``` Customer customer = new Customer(); customer.FirstName = "John"; customer.LastName = String.Empty; ValidationEngine.Validate(customer); if (customer.BrokenRules.Count > 0) { // do something display the broken rules! } ```

Original source