In this article I will discuss about the WPF MVVM(Model-View-View Model) design pattern. I will create a simple data driven live application in WPF which will use MVVM pattern. I have used C# language and Visual Studio 2015 Community to develop the application. I have also used Entity Framework to interact with data base. First I will discuss about the MVVM and its uses. Then I develop a simple CRUD application using the MVVM concepts.
What is MVVM
MVVM is a simple design pattern which keeps the logical layers of application separate from each other.
Coming to the various parts of the WPF MVVM lets discuss them one by one here:
View –
- View does not contain any code behind apart from animations.
- The code behind can contain direct reference to other controls.
- View is the UI with which the end user interacts with. It is generally a .xaml(window), user control or data template. It contains the styling of the controls, animations and other UI specific functionalities.
- There are some scenarios which are difficult to handle in .xaml which we can handle in View’s code behind like animations.
- The view contains a reference of View Model using DataContext property. It interacts with View Model using Command and Data Bindings
- The view can use IValueConvertor to convert the format and display of data from View Model to View
View Model-
- View Model is just model for the view.It attaches the required data from the model to view model
- View Model does not contain any visual logic.
- Should not derive from any WPF visual class
- Encapsulates the presentation logic for a use case or user task in the application.
- It is easily testable even without UI.
- it contains the properties and commands which can bind to the UI controls.
- It may implement additional properties which can be helpful to display the Model data to the view.
- It can implement the InotifyPropertyChanged and INotifyCollectionChanged
Model
- Model is non visual class. It encapsulates the applications data and business logic.
- It does not refer the View or View Model class and has no dependency on how they are implemented.
- It generally provides the property and collection changes events through INotifyPropertyChanged and INotifyCollectionChanged.
- It provided data validation and error reporting using IDataErrorInfo
- It is used with service or repository that encapsulates data access and caching.
Practical Application using WPF MVVM
In this example I will create a simple application which can be used to display, add, update or delete data. The UI for the application is as shown below.
The project solution looks like as shown below.
I will discuss all these parts here one by one . I have used Entity Framework to talk to database. You can visit my two articles to get starting with EF.
View
My MainWindow.xaml.cs is the main container of the sub View that is the PersonCollection.xaml. The code for the MainWindow.xaml is as shown below
<Window x:Class="MVVMApplication.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MVVMApplication" xmlns:VM="clr-namespace:MVVMApplication.ViewModel" xmlns:View="clr-namespace:MVVMApplication.View" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <VM:MainWindowViewModel></VM:MainWindowViewModel> </Window.DataContext> <Grid> <View:PersonCollection></View:PersonCollection> </Grid> </Window>
In the above code you can see at row number 11 that I am assigning data context for this view. The data context is the MainWindowViewModel.cs class, which is discussed in the View Model section.
and the code behind is
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); (this.DataContext as MainWindowViewModel).ShowMessageBox += delegate (object sender, EventArgs args) { MessageBox.Show(((MessageEventArgs)args).Message); }; } }
As you can see there is not much code in the code behind file. Apart from the handler assignment to an event named ShowMessageBox which I will discuss later.
The sub view is the PersonCollection.xaml. The code is as shown below figure.
In the figure I have marked all the data bindings with black arrow. Since I have set the DataContext of the main windows as the instance of MainWindowViewModel.cs class. For PersonCollection.xaml the view model is the same instance. This view model contains the properties named PersonCollection and SelectedPerson. It also contains the Add, Save and Delete commands. But as TextBox are bound to the FirstName, LastName, CityOfResidence and Profession properties, which are properties of Person class. I have set the datacontext of the Grid as SelectedPerson which contains all these properties. SelectedPerson property is of type Person class.
View Model
The view model class is as shown below.
public class MainWindowViewModel:NotificationClass { Business _business; private Person _person; public EventHandler ShowMessageBox = delegate { }; public MainWindowViewModel() { _business = new Business(); PersonCollection = new ObservableCollection<Person>(_business.Get()); } private ObservableCollection<Person> personCollection; public ObservableCollection<Person> PersonCollection { get { return personCollection; } set { personCollection = value; OnProprtyChanged(); } } public Person SelectedPerson { get { return _person; } set { _person = value; OnProprtyChanged(); } } public RelayCommand Add { get { return new RelayCommand(AddPerson, true); } } private void AddPerson() { try { SelectedPerson = new Person(); } catch (Exception ex) { ShowMessageBox(this, new MessageEventArgs() { Message = ex.Message }); } } public RelayCommand Save { get { return new RelayCommand(SavePerson, true); } } private void SavePerson() { try { _business.Update(SelectedPerson); ShowMessageBox(this, new MessageEventArgs() { Message = "Changes are saved !" }); } catch (Exception ex) { ShowMessageBox(this, new MessageEventArgs() { Message = ex.Message }); } } public RelayCommand Delete { get { return new RelayCommand(DeletePerson, true); } } private void DeletePerson() { _business.Delete(SelectedPerson); } }
It contains instance of the Business class as well as the Person(Model) class. All the properties bound to the View are present here. Commands are also present in the Model class which are of type RelayCommand which I have discussed later in the article.This class contains a reference to the Business class and a property of type Person (model) named as SelectedPerson. Business class allows us to interact with data source and contains all the business logic.
Model
Person is the model class. Business class has the logic to populate the Model class and interact with View Model. PersonDB is the class which contains logic to interact with the Entity Framework. The code for all of them is as follow.
public class Business { PersonDB _dbContext = null; public Business() { AppDomain.CurrentDomain.SetData("DataDirectory", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)); _dbContext = new PersonDB(); } internal IEnumerable<Person> Get() { return _dbContext.Person.ToList(); } internal void Delete(Person person) { _dbContext.Person.Remove(person); } internal void Update(Person updatedPerson) { CheckValidations(updatedPerson); if (updatedPerson.Id > 0) { Person selectedPerson = _dbContext.Person.First(p => p.Id == updatedPerson.Id); selectedPerson.FirstName = updatedPerson.FirstName; selectedPerson.LastName = updatedPerson.LastName; selectedPerson.CityOfResidence = updatedPerson.CityOfResidence; selectedPerson.Profession = updatedPerson.Profession; } else { _dbContext.Person.Add(updatedPerson); } _dbContext.SaveChanges(); } private void CheckValidations(Person person) { if(person == null) { throw new ArgumentNullException("Person", "Please select record from Grid or Add New"); } if (string.IsNullOrEmpty(person.FirstName)) { throw new ArgumentNullException("First Name", "Please enter FirstName"); } else if (string.IsNullOrEmpty(person.LastName)) { throw new ArgumentNullException("Last Name", "Please enter LastName"); } else if ((int)person.Profession == -1) { throw new ArgumentNullException("Profession", "Please enter Profession"); } } }
public class Person:NotificationClass { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string CityOfResidence { get; set; } public Profession Profession { get; set; } }
public class PersonDB:DbContext { public PersonDB():base("name=DefaultConnection") { } public DbSet<Person> Person { get; set; } }
There is not much business logic in this application. But I have put the validation of checking for null of emptiness of the fields as business logic.
Infrastructure
For infrastructure I have three classes as shown below
public class NotificationClass : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged = delegate { }; public void OnProprtyChanged([CallerMemberName]string propertyName = null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
public class RelayCommand : ICommand { private Action localAction; private bool _localCanExecute; public RelayCommand(Action action, bool canExecute) { localAction = action; _localCanExecute = canExecute; } public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return _localCanExecute; } public void Execute(object parameter) { localAction(); } }
public enum Profession { Default = -1, Doctor, SoftwareEngineer, Student, SportsPerson, Other } public class MessageEventArgs:EventArgs { public string Message { get; set; } }
These are the classes which are building blocks of various functionalities. Notification class can be derived to any class which wants to use the INotifyPropertyChanged functionality.
RelayCommand class contains the logic to create and instance of ICommand interface. It binds to the Command of the controls in the View.
If I run the application and click on update button without doing any other action. I will get the pop up as shown below
The popup is called using an event which is present in the View model. The handler for the event is present in the MainWindow.cs code behind.
If you want to Add New record, Click Add New Button > Enter All the details > Click Update.
If you want to Update a record. Select record in Grid > Update Details in TextBoxes > Click Update Button
If you want to delete record. Select record in Grid > Click On Delete.
All the three layers are based on the principles which we have discussed earlier in the article. If you do any of the operations you will be able to see the result of the same in the application UI.
Though this application can be made more robust and intuitive. But for demo purpose I have added only basic functionalities.
Conclusion
In this article I have described about all the three components of WPF MVVM. The article also contains a working example of the CRUD application using WPF MVVM design principles.