Validating user input in WPF is supposed to be simple. Wpf comes with a lot of built in validation functionality. But things stop being simple as soon as you want to validate data somewhere other than a tab-out of a textbox.
If you've arrived here you've probably been googling for 8 hours trying to get a basic use case working:
Validate a group of controls in WPF, all at once, on a button click.
That's it! If you can do it with Wpf's out of the box validation facilities, your google-fu is a lot better than mine. I can't seem to make WPF validation do anything meangful outside of the tabbed-out-of-textbox scenario.
In short, I wanted something that looked like this:

Here's a shortcut I came up with that doesn't involve any
ValidatesOnDataErrors,
ValidatesOnExceptions,
ValidationRules,
NotifyOnValidationError, or
IDataErrorInfo insanity. Anyone who doesn't get a headache just trying to remember all those is nuts.
In any case, none of these worked for me, and I don't have a lot of time to dive into the depths of nightmares like
Binding.NotifyOnValidationError:
<Grid>
<Grid.BindingGroup>
<BindingGroup>
<BindingGroup.NotifyOnValidationError>
</BindingGroup.NotifyOnValidationError>
</BindingGroup>
</Grid.BindingGroup>
</Grid>
Yikes! Run for your life! Not writing a lot of code is important for both my for sanity and maintainability purposes. The code above fails in both cases.
Here's a much simpler idea that embraces MVVM + Databinding but doesn't get involved with the IDataErrorInfo insanity:
Expose a public boolean for "InputInvalid" in your ViewModel. Views will bind to this property and update their display with Style.Triggers.
That's it. Here's how it looks.
First, the xaml:
<Window x:Class="RootSilver.WpfMvvm.Window1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<-- Define a style for your textboxes when there's invalid data.
The DataTrigger is the InvalidInput boolean in the ViewModel -->
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InvalidInput,
UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="2"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=InvalidInput,
UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<-- The rest is standard Wpf Mvvm: binding textboxes to properties and an ICommand for the button click -->
<Grid Height="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="79*"></ColumnDefinition>
<ColumnDefinition Width="199*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" VerticalAlignment="Center">
User Name</TextBlock>
<TextBox Grid.Column="1" Grid.Row="0" Width="150" Height="20"
VerticalAlignment="Center" Text="{Binding Path=UserName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Column="0" Grid.Row="1" VerticalAlignment="Center">
User Name</TextBlock>
<TextBox Grid.Column="1" Grid.Row="1" Width="150" Height="20"
VerticalAlignment="Center" Text="{Binding Path=Password, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1" Grid.Row="2" Command="{Binding Path=LoginCommand}" >Submit</Button>
</Grid>
</Window>
Next, the ViewModel. Notice that the only thing happening here is that we have a standard login routine with some logic. If it fails, we let the view know by changing
InvalidInput to false and raising a
PropertyChanged event. We won't go ahead with the login, and we'll leave it up to the view to interpret this information as it chooses.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel; //INotifyPropertyChanged
using System.Windows.Input; //ICommand
namespace RootSilver.WpfMvvm
{
public class Window1ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate{};
public string UserName { get; set; }
public string Password { get; set; }
public bool InvalidInput { get; private set; }
private ICommand loginCommand;
public virtual ICommand LoginCommand
{
get
{
return this.loginCommand ??
(this.loginCommand = new DelegateCommand(() =>
{
this.InvalidInput = (this.UserName != "Admin" && this.Password != "password");
PropertyChanged(this, new PropertyChangedEventArgs("InvalidInput"));
}));
}
}
}
}
The
DelegateCommand is Microsoft's MVVM/Prism command. Similar to Josh Smith's
RelayCommand.