Dailycode.info

Short solution for short problems

Required field validation in WPF using MVVM

 

This article will describe simple validation handling for WPF and MVVM.

I first implemented input validation myself. This was OK, but the only down side was that the CanSave method of the relay command was not updated when I entered a value, only when I the field lost focus the update came and the Save button was enabled. This makes it hard to work if the Save button is the next action after filling in the value.

So I will discuss the implementation of the IDataErrorInfo interface. This actually will reflect in your Model, ViewModel and View. We will tell the View that ErrorHandling is done and that the property changed event will be the trigger to do the validation. Also, some styling is applied to visualize the error s. Then the Model will have to implement the actual validation sets telling the viewmodel whether the model is valid or not. Last the view model will have handle the actual save command and check the validation.

So let’s start from the bottom and work our way up. The model:

For the demo I used a simple popup screen that adds a record to the audio type list. The record consists of a name. This field we will use to implement the validation. An empty field is not allowed.

First implement the IDataErrorInfo interface to your class.

public class AudioTypeEnt: IDataErrorInfo

 

This  will expose 2 methods which we will use to handle the validation.

#region IDataErrorInfo Members

 

string IDataErrorInfo.Error { get { return null; } }
 
string IDataErrorInfo.this[string propertyName]
{
    get { return this.GetValidationError(propertyName); }
}
 
#endregion // IDataErrorInfo Members

 

Then we will need to explain what fields need to be validated and how:

string GetValidationError(string propertyName)

{

    if (Array.IndexOf(ValidatedProperties, propertyName) < 0)

        return null;

 

    string error = null;

 

    switch (propertyName)

    {

        case "Name":

            error = this.ValidateName();

            break;

 

        default:

            Debug.Fail("Unexpected property being validated on Audio Type: " + propertyName);

            break;

    }

 

    return error;

}

 

string ValidateName()

{

    if (IsStringMissing(this.Name))

    {

        return Strings.AudioTypeEnt_MissingName;

    }

    return null;

}

 

 

static bool IsStringMissing(string value)

{

    return

        String.IsNullOrEmpty(value) ||

        value.Trim() == String.Empty;

}

 

So here we checked if the Name property is not null or empty or has some spaces. If any of these, we will return the error string coming from a resource file telling that the name field is still missing entry.

Last what we need to implement in the model is the IsValid method. This will be called be the viewmodel to check the validity of the record.

/// <summary> 

/// Returns true if this object has no validation errors. 

/// </summary> 

public bool IsValid

{

    get

    {

        foreach (string property in ValidatedProperties)

            if (GetValidationError(property) != null)

                return false;

 

        return true;

    }

}

 

static readonly string[] ValidatedProperties =

{

    "Name"

};

 

So let’s move on to The view model. There we simply check the data and trigger a requery so the relaycommands can reflect this.  Also here the view model will have to implement the IDataErrorInfo

 interface:  public class AddAudioTypeViewModel : WorkspaceViewModel, IDataErrorInfo

 

#region Error Handling

 

public string Error

{

    get { return (AudioTypeRecord as IDataErrorInfo).Error; }

}

 

public string this[string propertyName]

{

    get

    {

        string error = null;

 

 

        error = (AudioTypeRecord as IDataErrorInfo)[propertyName];

 

        // Dirty the commands registered with CommandManager,

        // such as our Save command, so that they are queried

        // to see if they can execute now.

        CommandManager.InvalidateRequerySuggested();

 

        return error;

    }

}

 

#endregion

 

We implement the interface methods. You can do some extra validation here, but we just pass on the error and trigger the Requery by calling the CommandManager.InvalidateRequerySuggeted method.

Then we can tell the CanSave method what to check. And when a save is triggered we will again check if the record is valid, if so, just save it.

 

/// <summary>

/// Saves the audio to the repository.  This method is invoked by the SaveCommand.

/// </summary>

public void Save()

{

    if (!AudioTypeRecord.IsValid)

        throw new InvalidOperationException(Strings.AudioTypeEnt_MissingName);

    else

    {

/// Saves the audio to the repository

        GenericSingleton<RepositoryHold>.GetInstance().AudioRep.AddAudioTypeRec(new AudioTypeEnt() { Name = Name, Valid = true });

        base.OnPropertyChanged("DisplayName");

        RequestClose();

    }

}

 

/// <summary>

/// Returns true if the audio type is valid and can be saved.

/// </summary>

bool CanSave

{

    get { return AudioTypeRecord.IsValid; }

}

 

Last we have to tell the view that which field needs to be checked and what will trigger the validation check. This has to be done in the binding:

<TextBox Text="{Binding Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"

 

If you like, you can add a content presenter to show the errors that come from the view model:

<ContentPresenter

      Grid.Row="1" Grid.Column="1"

      Content="{Binding ElementName=txtName, Path=(Validation.Errors).CurrentItem}" Margin="10,0" />

 

Finally we define a template for the validation errors in the view:

<DataTemplate DataType="{x:Type ValidationError}">

                <TextBlock

          FontStyle="Italic"

          Foreground="Red"

          HorizontalAlignment="Left"

          Margin="0,1"

          Text="{Binding Path=ErrorContent}"

          />

 

And that should do it!

 

Nothing filled in:

After the first character is entered the save button is enabled: