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.