I have the following scenario in my WPF application:
- The user enters a value in a text box.
- I start a background thread to go validate that value (Via a Web Service call).
(98% of the time the input is valid.)
- The user leaves the text box and heads on to work on the next text box(s).
- The background thread returns and the results indicate that the input was invalid.
It lets the view model know this via the firing of an event. (I am using Prism events and modules.)
I need a way for that event to let the TextBox know that there was a Validation error with the input it was given. (Remember that the focus is no longer in that control.)
I can think of all kinds of ways to "roll my own validation" stuff and get this done. But I would like to work within an existing validation framework in WPF.
Note: I am not sure if it is relevant, but I will need to be maintaining a list of "NeededValidations" that will all have to pass before I enable the Save button.
Here are a couple of methods using IDataErrorInfo that you can try. They both rely on the fact that raising INotifyPropertyChanged notification will cause re-evaluation of binding validation.
There are two text boxes. One uses an async binding and assumes the web service call is synchronous. The second uses a synchronous binding and assumes the web service call is async.
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>
View model
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
}