I have seen some demos of the Model-View-ViewModel (MVVM) pattern in the past. I was supposed to start another WPF project, but instead, given its importance in designing and coding WPF applications, I took time out to learn more about this pattern. Diving into the details does take some effort, but in this post I will simply outline the most rudimentary aspects of the pattern and go from there. This is by no means an exhaustive look at this pattern. I am just documenting my learning process and if anyone is interested, can follow along. What I will be doing in this post is to give a generalization of the pattern, without any of the WPF Commands and tests portions. Basically, it is to show how the subsections hook up to each other.
Download the Project
MVVM is a more WPF specific form of the Model-View-Presenter (MVP) pattern, by taking advantage of some of the features of the platform, such as data binding, data templates, resources, commands and routed events, to name a few. Of course you can dig out the details of these features from MSDN. In addition, there are some specific MSDN Magazine sections you can refer to:
- MVVM on CodePlex
- WPF Apps With The Model-View-ViewModel Design Pattern [MSDN Magazine, September 2008]
- Customize Data Display with Data Binding and WPF [MSDN Magazine, July 2008]
- Understanding Routed Events and Commands In WPF [MSDN Magazine, September 2008]
The whole idea behind this pattern is the separation of concerns. That is, you can completely separate the presentation logic from the business logic of the application. This alone demonstrates the flexibility of the pattern in that you can have one group dedicated to the front end look/feel aspect of the app, whereas another team can tackle the more technical aspects of the project.
Let's take a brief look at each subsection of the MVVM pattern as shown in Fig. 1.
Model
The Model is the section responsible for retrieving and exposing data to WPF in a way that it can understand. This data can come from a database, XML, RSS, or custom object. For example, given a XML file, you can create a custom object with public properties to model the elements of that file.
ViewModel
The task of this section is to abstract away some of the interaction logic from the View (UI) so that it remains purely just that - UI. In addition, since it stands between the View and the Model, the View never directly communicated with the Model. Instead, the View delegates the commands of the user to the ViewModel, which in turn performs the necessary logic to fulfill the user request. The ViewModel basically hold the presentation logic and state that is shared by the View(s). For example, imagine you have a custom class (our Model in this case) that exposes three public properties. However, the ViewModel only exposes two of those properties to all the Views of the application. Since the Views never have direct interaction with the Model, all it can do is interact with the two properties that the ViewModel exposes. I will go into more detail about it soon.
The ViewModel is also where you can implement the INotifyPropertyChanged and/or INotifyCollectionChanged since WPF does not provide automatic change notifications (the ObservableCollection class does support change notifications though). Since the ViewModel is data bound to its View, then any property that changes will be automatically propagated to the View. The ViewModel interacts with the View using the WPF Commands infrastructure. Also, each View will have its own corresponding ViewModel.
View
The View is the graphical front end of the application. It is basically a set of controls that is bound to a corresponding ViewModel object and should be entirely coded in XAML. No other code should be put in the accompanying code behind other than what was generated by Visual Studio. The View contains a reference to the ViewModel via its DataContext property. In order to render the ViewModel data to the user, the View uses Data Templates
Example
I am using Visual Studio 2008 SP1 + .NET 3.5 SP1
Time for an example. If you download the MVVM files from CodePlex, it comes with a Visual Studio template to create a MVVM WPF project as in Fig. 2. The MVVM project will provide you with some additional boilerplate code, such as the DelegateCommand class and a test project (this is optional). In addition, the MVVM project has folders to separate the sections of the pattern as shown in Fig. 3.
For this example, you can use the CodePlex template or create a WPF project and add the Models, ViewModels and Views folders shown in Fig. 3. Since I have the template installed, I will just use that to generate the folders and for now, I will ignore the Commands and Tests parts of the solution.
The Model
I will start off with the Model by creating a super simple class called Person.cs and putting it in the Models folder. It will construct a Person object using firstName and lastName as arguments and expose two public properties. Note that this class is just an ordinary class and it has nothing to do with the MVVM pattern per say. You can rip this out and replace it if need be.
Since I am not pulling in any data from a file or database, I have added a method to this class to create some data points to test the example out with. This is shown in Listing 1.
1: using System;
2: using System.Collections.Generic;
3:
4: namespace MvvmDemo01.Models
5: {
6: public class Person
7: {
8: // Private members
9: private static List<Person> persons = new List<Person>();
10:
11: // Public constructor to create an instance
12: // of the class
13: // This is the general Model of the data I will
14: // be using which exposes the FirstName
15: // and LastName properties
16:
17: // The PersonViewModel class in the ViewModels
18: // folder
19: public Person(String firstName, String lastName)
20: {
21: this.FirstName = firstName;
22: this.LastName = lastName;
23: }
24: // Public properties
25: public String FirstName { get; set; }
26: public String LastName { get; set; }
27:
28: // Method to create a List of Person objects
29: // NOTE: Since I am not getting my data from
30: // any other sources (XML, etc), I am just making
31: // some up
32: public static List<Person> GeneratePersons()
33: {
34: persons.Add(new Person("Bugs", "Bunny"));
35: persons.Add(new Person("Daffy", "Duck"));
36: return persons;
37: }
38: }
39: }
This is basically all that is needed for the Model portion.
The View
This is a very straightforward section as well. The MVVM Visual Studio template will create an initial view called MainView.xaml for you. This is where you will put the Views that is responsible for rendering your data. The pattern basically states that you should create UserControls to generate the necessary views. In simple cases you can probably just use the View that was automatically created.
For now, follow the pattern and in the Views folder, create a new UserControl called PersonView.xaml as shown in Listing 2. This control will be used to set the DataTemplate property of a ListBox control in the MainView.xaml file. The code in the PersonView UserControl is actually the code needed to create a template to represent the data. If you want you can delete this file and copy its contents to the data template section of the ListBox.ItemTemplate property in the MainView.xaml file as shown in Listing 3 and still get the same result.
1: <UserControl x:Class="MvvmDemo01.Views.PersonView"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
4: <StackPanel Orientation="Horizontal">
5: <Border CornerRadius="3" BorderBrush="Blue"
6: BorderThickness="1" Margin="5">
7: <StackPanel Orientation="Horizontal">
8: <TextBlock Text="First Name: "
9: Margin="5" FontStyle="Italic"/>
10: <TextBlock Text="{Binding Path=FirstName}"
11: Margin="5" Foreground="Red"/>
12: </StackPanel>
13: </Border>
14: </StackPanel>
15: </UserControl>
1: <Window x:Class="MvvmDemo01.Views.MainView"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:v="clr-namespace:MvvmDemo01.Views"
5: Title="Characters" Width="200" Height="220">
6: <StackPanel>
7: <TextBlock Text="List of Characters" Margin="4" />
8: <Border Margin="3" BorderBrush="Black"
9: BorderThickness="2" CornerRadius="3">
10: <!-- use the PersonView UserControl to generate
11: the template-->
12: <ListBox ItemsSource="{Binding Persons}"
13: Margin="3">
14: <ListBox.ItemTemplate>
15: <DataTemplate>
16: <v:PersonView />
17: </DataTemplate>
18: </ListBox.ItemTemplate>
19: </ListBox>
20: </Border>
21:
22: <!-- OR just create the template inline-->
23: <Border Margin="3" BorderBrush="Black"
24: BorderThickness="2" CornerRadius="3">
25: <!-- use the PersonView UserControl to generate
26: the template-->
27: <ListBox ItemsSource="{Binding Persons}"
28: Margin="3">
29: <ListBox.ItemTemplate>
30: <DataTemplate>
31: <StackPanel Orientation="Horizontal">
32: <Border CornerRadius="3" BorderBrush="Blue"
33: BorderThickness="1" Margin="5">
34: <StackPanel Orientation="Horizontal">
35: <TextBlock Text="First Name: "
36: Margin="5" FontStyle="Italic"/>
37: <TextBlock Text="{Binding Path=FirstName}"
38: Margin="5" Foreground="Red"/>
39: </StackPanel>
40: </Border>
41: </StackPanel>
42: </DataTemplate>
43: </ListBox.ItemTemplate>
44: </ListBox>
45: </Border>
46: </StackPanel>
47: </Window>
The ViewModel
This is where the majority of the fun stuff happens. Again, if you are using the template from CodePlex, there will be at least two ViewModel files generated for you - ViewModelBase.cs which is an abstract class that implements the INotifyPropertyChanged interface and the MainViewModel.cs which derives from ViewModelBase and is the View Model that is associated with the corresponding MainView.xaml View file.
In this example, I am creating a PersonViewModel.cs file in the ViewModels folder which will associate with the Person.cs Model. Basically, I am using this class (PersonViewModel.cs) to expose properties from my data class. Notice that in the Person class, I have two properties defined - FirstName and LastName. However, in the PersonViewModel class, even though I am creating Person objects, I am only exposing the FirstName property. This means that Views that are attached to the PersonViewModel will only see the FirstName property, which it exposes and not both properties as exposed by the original Model (Person.cs). This is to show that the View does not directly interact with the Model.
My PersonViewModel simply creates a Person object and exposes only a single property of the Person class. This is shown in Listing 4.
1: using System;
2: using MvvmDemo01.Models; // to access Person
3:
4: namespace MvvmDemo01.ViewModels
5: {
6: // This class will expose the
7: // necessary fields of the Person
8: // model. Even though Person exposes
9: // two public fields, as an example,
10: // I will only expose the FirstName here
11: public class PersonViewModel
12: {
13: // Get a reference to the Person object
14: private Person _person;
15:
16: // Constructor to create a Person object
17: public PersonViewModel(Person person)
18: {
19: this._person = person;
20: }
21:
22: // Expose only one property from the Person object
23: // Make it readonly since I will not be adding
24: // anything back to the data model
25: public String FirstName
26: {
27: get { return (String)_person.FirstName; }
28: }
29: }
30: }
The MainViewModel class basically sets up a property that will be used to set the DataContext property of the View that binds to this ViewModel. What this means is that in this class, I will create a collection of PersonViewModel objects which in turn represents a Person (Model) object. This is an ObservableCollection which supports change notifications.
NOTE: The way I did this is not necessary here. I only used an ObservableCollection because I am trying to be as close to the MVVM pattern as possible. Since in this project I am not too concerend with changes in the collection, instead, I could have simply created a normal list of Person objects (List<Person>) and expose a property of the same type rather than exposing a property whose type is ObservableCollection<PersonViewModel> as shown in Listing 5.
The Persons property exposed by the MainViewModel class will be used to set the DataContext of the MainView which in turn has elements that data bind to whatever properties are exposed by each object of the collection.
1: using System.Collections.Generic;
2: using System.Collections.ObjectModel;
3: using MvvmDemo01.Models;
4:
5: namespace MvvmDemo01.ViewModels
6: {
7: public class MainViewModel : ViewModelBase
8: {
9: // Create ObservableCollection from the
10: // System.Collections.ObjectModel namespace to
11: // hold a collection of PersonViewModel
12: // objects
13: private ObservableCollection<PersonViewModel> _persons =
14: new ObservableCollection<PersonViewModel>();
15:
16: public MainViewModel() { }
17:
18: // Method to "load" and create the collection of
19: // PersonViewModel
20: public void LoadPersons()
21: {
22: // call the static GeneratePersons method to
23: // fill the list
24: List<Person> _person = Person.GeneratePersons();
25: foreach (var person in _person)
26: {
27: this._persons.Add(new PersonViewModel(person));
28: }
29: }
30:
31: // After the data has been loaded, expose the collection
32: // as a public property so that the View can bind to it
33: // i.e. the View used to display the data will set its
34: // DataContext property to this collection. A data template
35: // in the view will configure each object for display
36: public ObservableCollection<PersonViewModel> Persons
37: {
38: get { return this._persons; }
39: }
40: }
41: }
The setting of the View's DataContex is done in the App.xaml file as shown in Listing 6.
1: using System.Windows;
2: using MvvmDemo01.ViewModels; // to acceess MainViewModel
3:
4: namespace MvvmDemo01
5: {
6: public partial class App : Application
7: {
8: private void OnStartup(object sender, StartupEventArgs e)
9: {
10:
11: Views.MainView view = new Views.MainView();
12:
13: // create an instance of the MainViewModel
14: // and 'load' the data into a collection
15: MainViewModel mvm = new MainViewModel();
16: mvm.LoadPersons();
17:
18: // set the data context of MainView to the
19: // loaded collection
20: view.DataContext = mvm;
21:
22: view.Show(); // display main window
23: }
24: }
25: }
A subset of the Class Diagram for the project is given in Fig. 4.
Now all you have to do is compile and run the application and you will get something like that in Fig. 5.
2 comments:
Nice introduction to MVVM. it is helpful. First of all thanks for the time you spend in order to help others. From here, what happen if i want to new views like Add/Edit Person or show person view? I try many times to catch that by reading PeriodicTable which help me as well about MVVM and XML. But i cannot understand or deduce how to navigate from on usercontrol to another via a button in WPF. Thanks
I am glad you learned something. Without going into too much detail, to, I think what you are looking for is a Delegate Command. Commands in WPF work along the Visual Tree so in order to communicate beyond that, you will need the commanding structure of MVVM. If you get the toolkit and documentation from codeplex (http://wpf.codeplex.com/wikipage?title=WPF%20Model-View-ViewModel%20Toolkit), it goes into commands. I do apologize as I don't recall using it in the Periodic Table example.
In the sample from codeplex, they provide you with the DelegateCommand class that does most of the heavy lifting for you. In addition, you can take a peek at Prism (Composite Application Guidance or something of the sort) as they have an implementation of a delegate command as well - either one will work for you. I hope this helps and feel free to ask any other questions. If I don't know the answer, maybe I can point you in the right direction of the answers.
Post a Comment