Era da parecchio tempo che avevo in draft questo post ed è stata una recente domanda sull'argomento a ricordarmi che forse era giunta l'ora di completarlo e pubblicarlo.
L'argomento è: validatione in WPF, un area dove a parer mio WPF è un pò carente e ancor peggio è Silverlight anche se in quest' ultima ci saranno interessanti novita' al MIX 09.
Una delle problematiche con le quali ci si scontra nel mondo reale è quella di validare piu' informazioni e, solo quando tutte sono congruenti, permettere l'esecuzione di un operazione, che tradotto in parole povere significa il verificare che tutti i campi siano stati completati prima di abilitare il tasto Send eventualmente fornendo, come avviene in ASP.NET un riassunto di quello che non va.
L'esempio, basato ovviamente su Model-View-ViewModel, è composto da una window dove l'utente deve inserire i dati in un determinato formato prima che il tasto Save venga abilitato.
Tutta la funzionalità ruota attorno all'evento Validation.AddErrorHandler() il quale permette di invocare un delegate quando un elemento cambia il proprio stato in base alle regole di validazione.
Partendo da questo ho creato un attached property che consente di specificare un metodo del ViewModel associato da invocare quando lo stato di validazione cambia.
L' utilizzo e' il seguente:
<TextBox my:Extensions.Validation="ProcessError"
Text="{Binding Name, ValidatesOnExceptions=true, NotifyOnValidationError=true}" />
in questo esempio il metodo da invocare si chiama ProcessError e va detto che affiche' il delegate passato al metodo AddErrorHandler() venga invocato è necessario impostare la proprieta' NotifyOnValidationError del binding a true.
Il ViewModel associato alla window eredita, come da manuale, da una classe ViewModelBase definita in questo modo:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<ValidationErrorInfo> validationErrors =
new ObservableCollection<ValidationErrorInfo>();
public ObservableCollection<ValidationErrorInfo> ValidationErrors
{
get { return validationErrors; }
}
public void ProcessError (DependencyObject key, Exception error)
{
if (error != null)
this.ValidationErrors.Add(new ValidationErrorInfo() { Key = key, Exception = error });
else
{
ValidationErrorInfo errorToRemove = this.ValidationErrors.Where(err => err.Key == key).FirstOrDefault();
if (errorToRemove != null)
{
this.ValidationErrors.Remove(errorToRemove);
}
else
{
throw new ArgumentException("Provided object has no associated validation errors, can't remove.");
}
}
this.OnPropertyChanged("ValidationErrors");
}
...
}
Dal codice potete vedere come il codice in ProcessError non faccia altro che aggiungere/rimuovere un oggetto ValidationErrorInfo (una banale tupla) a una collezione esposta a sua volta dalla proprietà ValidationErrors affinchè nello xaml sia possibile scrivere:
<ListBox ItemsSource="{Binding ValidationErrors}"
DisplayMemberPath="Exception.Message"
Foreground="Red" />
Tutto il resto è racchiuso nell’ attached property che potete analizzare scaricando il codice di esempio.
Enjoy