Validation of user input is a pretty essential component of almost any application. Ideally, it is a quick and unobtrusive way to give the user feedback and help guide them through the application.
Instead of preventing the user from entering invalid data, I’ve found actually allowing invalid states to give a much better user experience. Don’t preventing the user from moving the cursor out of a text box with an invalid input, just prevent the user from doing the higher-level OK/Save command. If a user is entering contact information into an address book, you shouldn’t lock the the cursor in the phone number field if it’s invalid. You should instead prevent them from hitting “OK” or “Save”. This is a less annoying behavior (“Why can’t I move my cursor? This program is broken!”). Example I’ve created a simple example that shows how to do input validation in an MVVM-way using IDataErrorInfo, an ErrorTemplate, and a RelayCommand. This code assumes you have a ViewModelBase that implements INotifyPropertyChanged and RelayCommand. Both are included in most MVVM toolkits and are very easy to write. View Model: This code is unfortunately extremely verbose. I’ve been investigating ways to reduce the amount of code that MVVM and things like properties and validation take. Some simple code generation/rewriting combined with naming conventions should really eliminate almost all of this code.
publicclassContactViewModel:ViewModelBase,IDataErrorInfo{privatestringm_FirstName,m_LastName;privatestringm_PhoneNumber;privatereadonlyRelayCommandm_SaveCommand;publicContactViewModel(){m_SaveCommand=newRelayCommand(SaveCommand_Execute,SaveCommand_CanExecute);}publicICommandSaveCommand{get{returnm_SaveCommand;}}privatevoidSaveCommand_Execute(){//Save to database or something here}privateboolSaveCommand_CanExecute(){returnIsFirstNameValid&&IsLastNameValid&&IsPhoneNumberValid;}publicstringFirstName{get{returnm_FirstName;}set{if(m_FirstName!=value){m_FirstName=value;OnPropertyChanged("FirstName");}}}publicboolIsFirstNameValid{get{return!string.IsNullOrWhiteSpace(FirstName);}}publicstringLastName{get{returnm_LastName;}set{if(m_LastName!=value){m_LastName=value;OnPropertyChanged("LastName");}}}publicboolIsLastNameValid{get{return!string.IsNullOrWhiteSpace(LastName);}}publicstringPhoneNumber{get{returnm_PhoneNumber;}set{if(m_PhoneNumber!=value){m_PhoneNumber=value;OnPropertyChanged("PhoneNumber");}}}publicboolIsPhoneNumberValid{get{return!string.IsNullOrWhiteSpace(PhoneNumber)&&Regex.IsMatch(PhoneNumber,@"^[0-9() -]{3,}$");}}publicstringError{get{if(!IsFirstNameValid){returnthis["FirstName"];}if(!IsLastNameValid){returnthis["LastName"];}if(!IsPhoneNumberValid){returnthis["PhoneNumber"];}returnstring.Empty;}}publicstringthis[stringcolumnName]{get{if(columnName=="FirstName"){if(!IsFirstNameValid)return"First Name must not be empty.";elsereturnstring.Empty;}if(columnName=="LastName"){if(!IsLastNameValid)return"Last Name must not be empty.";elsereturnstring.Empty;}if(columnName=="PhoneNumber"){if(!IsPhoneNumberValid)return"Phone Number contains invalid characters.";elsereturnstring.Empty;}returnstring.Empty;}}}Design-timeViewModel:publicclassDesignTimeContactViewModel:ContactViewModel{publicDesignTimeContactViewModel(){FirstName="John";LastName="Smith";PhoneNumber="(555) 123-4567asdf";}}