Deferred input validation using WPF

My WPF application has the following script:

  • The user enters a value in the text box.
  • I run a background thread to check its value (via a web service call).
    (98% of the time when the entry is valid.)
  • The user leaves the text box and head to work on the next text box.
  • The return of the background thread and the results show that the input is invalid.
    This allows the view model to learn about this by triggering an event. (I use Prism events and modules.)

I need a way for this event so that the TextBox knows that there was a validation error with the input it entered. (Remember that focus is no longer in this control.)

I can come up with all kinds of ways to “roll my own check” and do it. But I would like to work within the existing validation framework in WPF.

Note. I’m not sure if this is relevant, but I will need to maintain a list of “Required Validations” that everyone needs to go through before I turn on the “Save” button.

+3
source share
1 answer

Here are a few methods using IDataErrorInfo that you can try. They both rely on the fact that raising the INotifyPropertyChanged notification will re-evaluate the binding check.

. , - . , - .

Xaml

<Window.DataContext>
    <WpfValidationUpdate:ViewModel/>
</Window.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <TextBox Margin="5" Grid.Row="0" Text="{Binding Text1, IsAsync=True, ValidatesOnDataErrors=True}"/>
    <TextBox Margin="5" Grid.Row="1" Text="{Binding Text2, ValidatesOnDataErrors=True}"/>
</Grid>

public class ViewModel : IDataErrorInfo, INotifyPropertyChanged
{
    private string _text1;
    private string _text2;
    private bool _text1ValidationError;
    private bool _text2ValidationError;

    #region Implementation of IDataErrorInfo

    public string this[string columnName]
    {
        get
        {
            if(columnName == "Text1" && _text1ValidationError)
            {
                return "error";
            }

            if(columnName == "Text2" && _text2ValidationError)
            {
                return "error";
            }

            return string.Empty;
        }
    }

    public string Error
    {
        get
        {
            return string.Empty;
        }
    }

    public string Text1
    {
        get { return _text1; }
        set
        {
            _text1 = value;
            OnPropertyChanged("Text1");

            // Simulate web service synchronously taking a long time
            // Relies on async binding
            Thread.Sleep(2000);
            _text1ValidationError = true;

            OnPropertyChanged("Text1");
        }
    }

    public string Text2
    {
        get { return _text2; }
        set
        {
            _text2 = value;
            OnPropertyChanged("Text2");

            // Simulate web service asynchronously taking a long time
            // Doesn't rely on async binding
            Task.Factory.StartNew(ValidateText2);
        }
    }

    private void ValidateText2()
    {

        Thread.Sleep(2000);

        _text2ValidationError = true;
        OnPropertyChanged("Text2");
    }

    #endregion

    #region Implementation of INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if(handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion
}
+6

All Articles