This is a very small MVVM tutorial. I used the MVVM Light (Galasoft) framework. I like this framework because, it brings a lot of very useful features.
The idea behind MVVM is to seperate the UI from the code. The most things I like are that your application is testable, “blendable” and you don’t have code in the code-behind file.
MVVM in short:
Example:
First I create a base class for all models, which implements the interfaces INotifyPropertyChanged and INotifyDataErrorInfo.
I like to keep the Models clean (POCO). Sometimes it’s very difficult. You can implement the INotifyPropertyChanged interface in the ViewModel and wrap the model class. But this will lead to code duplication. That’s why I decided to implement the interfaces in the model in this example.
The base class for all models.
public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo { #region Property changed public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string propertyName, Actionmessage) { if (this.PropertyChanged != null) { // property changed this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); // send app message (mvvm light toolkit) if(message != null) message(this.IsValid); } } #endregion #region Notify data error private Dictionary > _errors = new Dictionary >(); public event EventHandler ErrorsChanged; // get errors by property public IEnumerable GetErrors(string propertyName) { if (this._errors.ContainsKey(propertyName)) return this._errors[propertyName]; return null; } // has errors public bool HasErrors { get { return (this._errors.Count > 0); } } // object is valid public bool IsValid { get { return !this.HasErrors; } } public void AddError(string propertyName, string error) { // Add error to list this._errors[propertyName] = new List () { error }; this.NotifyErrorsChanged(propertyName); } public void RemoveError(string propertyName) { // remove error if (this._errors.ContainsKey(propertyName)) this._errors.Remove(propertyName); this.NotifyErrorsChanged(propertyName); } public void NotifyErrorsChanged(string propertyName) { // Notify if (this.ErrorsChanged != null) this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); } #endregion }
And here my model.
public class Customer : ModelBase { private string _CustomerId; public string CustomerId { get { return this._CustomerId; } set { if (this._CustomerId != value) { if (value == "abc") base.AddError("CustomerId", "abc not allowed"); else base.RemoveError("CustomerId"); this._CustomerId = value; base.NotifyPropertyChanged("CustomerId", new Action((valid) => { AppMessages.CustomerIsValid.Send(valid); })); } } } public string Name { get; set; } }
And here is my ViewModel which represents a customer. This class inherits the base class ViewModelBase of the MVVM Light Toolkit. Beside the base class you will find in the following code snippet a second cool feature of MVVM Light framework.
The constructor registers a message. A message can be sent from everywhere and can be registered everywhere. This is very useful, when you don’t want to couple two classes. For instance class A sends a message and the class B can register the message without knowing the sender. In this example, I use this feature for validation. My Models are sending messages. For instance the customer sends the message “CustomerIsValid”. My ViewModel registers this message and executes the RaisePropertyChanged(“IsValid”) method.
Later you will see, that the “IsValid” property is used in the view. You see know that my Model raises the PropertyChanged event and my ViewModel tool. It is possible to do this only from my Model… or only from my ViewModel. But lets say you have a second Model called “Address” which you want to expose in the same ViewModel. And this Model has to be validated too. In my opinion, it’s easier to send message from the Model and register them in the ViewModel. The ViewModel executes the RaisePropertyChanged method and in the View you have only to bind the IsValid property, which returns true when customer and address are valid.
public class MainViewModel : ViewModelBase { public MainViewModel() { if (IsInDesignMode) { // Code runs in Blend --> create design time data. } else { // Code runs "for real" // Register message. (just run the PropertyChanged function. "arg" isn't important here.) AppMessages.CustomerIsValid.Register(this, new Action((arg) => { this.RaisePropertyChanged("IsValid"); })); this.Customer = new Customer(); } } private Customer _customer; public Customer Customer { get { return _customer; } set { _customer = value; } } public bool IsValid { get { return this.Customer.IsValid; } } }
(Sending/registering message is very easy..)
public class AppMessages { public static class CustomerIsValid { public static void Send(bool argument) { Messenger.Default.Send(argument); } public static void Register(object recipient, Action action) { Messenger.Default.Register(recipient, action); } } }
And last but no least.. the view:
Nothing special here. The DataContext “Locator” is another feature of MVVM Light. It is the entry point for bindings.
This is it!
Here are two printscreens.
Form is valid:
Form is not valid (error popup visible and save button is disabled):
Download VS.NET Solution: Click here