Other Articles In This Series:
Introduction
In the first article in this series, I looked at some of the issues I was having with the implementation of MVVM in a WPF application, and suggested some improvements to existing frameworks.
In this article, I will begin a walkthrough creating an MVVM application (albeit a small one) using the techniques I touched on last time.
In the third article, I'll add sufficient meat to the bones created in article 2 to give us a running application, albeit one that doesn't do too much.
In the fourth article, I'll finish off the application to show a (small) but functioning application demonstrating some of the functions available.
We'll just create the generic pre-requisites here, the 'framework' if you like. Next time, we'll continue with the actual project.
Just because one has to give these things a name, I thought I'd call this MVVM#. mainly because MVMVDCV looked too much like a roman date " src="http://www.codeproject.com/script/Forums/Images/smiley_wink.gif" />
Pre-requisites and Caveats
I'm building this using VS2010 and C#, .NET 4.0. It should be translatable to VB.NET.
I'm NOT using TDD here. Although one of the advantages of MVVM is the enhanced testability of your application, I'm steering clear of both to keep the article short, and to avoid revealing my lack of knowledge on the subject of TDD!
I'm assuming familiarity with VS2010 and C# - so no 'Click this, drop down that" however, I do try not to make assumptions, so even novice users should be able to follow along - and the source of the completed application is available for download.
Specification
I wanted this to be fairly realistic within the constraints of an article - so here's the spec:
- We want the user to be able to see a list of Customers.
- They need to be able to filter the list by State.
- They need to select a Customer and edit their details.
- They need to be able to save their changes.
Well, that's the sort of spec I'm sure many of you are used to! Obviously, we'll have to make lots of assumptions, and design decisions ourselves - but that's all good; we're going to be agile and show the users where we are, frequently, and re-engineer as necessary.
Let's Get Started!
Create a new WPF C# application. I'm calling mine "CustomerMaintenance
". We don't want the default MainWindow
- so delete it.
Now, we'll create all of the projects we're going to need. Because each of the things we're dealing with should be independent of one another, I like to have each in its own project - that way cross-contamination is harder, easily documented by the 'references' in each project, and I can split development amongst multiple developers more easily if I need to. So, create new Class Library projects as below:
- Controllers
- Messengers
- Models
- Services
- ViewModels
We also want to create a project for our Views. This should not be a Class Library, but a WPF User Control Library.
You can either delete the default classes created, or just rename them when we get around to creating them.
Your VS Solution explorer should look like this.
Solution Explorer
Foundation Classes
We're going to be using messaging to communicate around our application. We could use events (so long as we're very careful to remove handlers when classes are disposed). In this implementation, I'm using a Messenger
class derived from the MVVM Foundation classes. The Messenger
class source code is part of the download for this article. It's a utility class that would be used in all my MVVM projects as-is.
The Messenger
class is in its own project (Messengers
) so I can keep it independent.
There's a real problem with the implementation of some Messenger
systems that can cause unexpected behaviour, and be really difficult to track down. to describe it, imagine this scenario:
ViewModels
VMA and VMB are both instantiated. VMA sends messages to which VMB subscribe, but the two know nothing about one another. When VMB handles the message, it performs some function (writes to the database, for example).
VMA sends the message, VMB receives it, writes to the DB and all is good. Now the user closed VMB (if it's in a window, maybe they close the window) so the system removes all 'Strong' references to VMB. There is still a weak reference to it - in the Messenger
- but that won't stop it being Garbage Collected, so all is good. Of course, because VMA knows nothing about what's happening with VMB it continues to send the message - after all, other ViewModel
s may subscribe to the message too...
Now, if you recall Garbage Collection 101, the Garbage Collector doesn't necessarily remove items from memory immediately they are free of all hard references - the Garbage Collector runs when it goddamn feels like it. so if you have plenty of memory available, especially if VMB is small, it's still sitting there. Then VMA sends the message. You can see where this is going, can't you? Yep - VMB STILL RECEIVES THE MESSAGE even though there's no references to it! And it still writes merrily away to the database! Even though the user closed it!
The answer to this is for VMB to unsubscribe from the message before it is closed. But wait, wasn't part of the reason for using weak references that we wouldn't have to worry about the memory leaks associated with forgetting to remove event handlers? (yes it was!) But now we still have to remember to remove message handlers or we risk unexpected behaviour which may actually be harder to track down than memory leaks!
What I have implemented to try to overcome this issue, is a Deregister
method, that takes a ViewModel
as a parameter, and removes all Message subscriptions to it. I then call this method from my Base ViewModel
class in a CloseViewModel
method.
I've also added two enumerations to the source, MessageHandledStatus
and NotificationResult
.
The first, MessageHandledStatus
, was added to allow the message handlers (our View Models) to communicate back to the Messenger system.
The default value (NotHandled
) tells the system that the ViewModel
hasn't handled the message (so the Messenger should keep sending it to any other ViewModel
registered as a recipient).
If a ViewModel
sets the value to HandledContinue
, this tells the Messenger to continue sending out the message, but on completion it will know that something has handled the message.
The HandledCompleted
value tells the Messenger not to send the message out to any further recipients as it has been handled
Finally, the NotHandledAbort
message tells the Messenger that although the message has not been handled, it should not send it to further recipients.
The NotificationResult
enumeration is used to return a some information to the ViewModel
that sends the message. This can be used, for example, to instantiate a new ViewModel
to handle some event if there are currently no handlers registered to handle it.
My version of the messenger class also uses a class, Message
, which is carried around with the message. The class looks like this...
Message.cs
namespace Messengers
{
public class Message
{
#region Public Properties
public MessageHandledStatus HandledStatus
{
get;
set;
}
private MessageTypes messageType;
public MessageTypes MessageType
{
get
{
return messageType;
}
}
public object Payload
{
get;
set;
}
#endregion
#region Constructor
public Message(MessageTypes messageType)
{
this.messageType = messageType;
}
#endregion
}
}
This Message
object is passed around with every message - so every message handler has the opportunity to look at, or modify, the HandledStatus
and look at the MessageType
. This, for example, allows a single message handler to cater for many different message types, and to set the HandledStatus
appropriately.
The Message
object also contains a 'Payload
'. This is some object you want passed around with that message, so when we have saved a Customer
, for example, we send a message using:
Messenger.NotifyColleagues(MessageTypes.MSG_CUSTOMER_SAVED, data);
where the data passed is the CustomerEditViewData
, which contains all the information just updated - so if some ViewModel
somewhere wants to act, it already has the information at its fingertips, so to speak.
We're also going to be using two other classes from the MVVMFoundation
project - namely ObservableObject
and RelayCommand
. Both of these I create in the ViewModels
project in a folder called BaseClasses
, as all ViewModel
and ViewData
classes derive from ObservableObject
, and ViewModels
are the RelayCommand
handlers.
You will also need to add a reference in the ViewModels
project, to PresentationCore
.
To complete this section, we should add our enumeration for the messages. So add a new file to the Messengers
project, called Messages
...
MessageTypes.cs
namespace Messengers
{
public enum MessageTypes
{
MSG_CUSTOMER_SELECTED_FOR_EDIT, MSG_CUSTOMER_SAVED };
}
These are the only two messages our application is going to handle - so adding them now is no problem. In a larger application, we'd be adding new messages as the functionality is specified - it's a good place to go to ensure the functionality provided matches the requirements.
This, if you like, is the basic 'framework' for my MVVM# application. You can, of course, use any implementation you like of the Mediator pattern (our Messenger
class). The ObservableObject
and RelayCommand
classes can also be replaced with some other version providing similar functionality.
ViewModel
The other bit-players in our scenario can now get created too.
We're using a Controller to manage the application - so let's create its interface. Again, this can go in the BaseClasses folder in the ViewModels
project. You can see that the interface for the base controller just specifies that it has a Messenger
property.
IController.cs
using Messengers;
namespace ViewModel
{
public interface IController
{
Messenger Messenger
{
get;
}
}
}
Now, we also need base classes for our ViewData
and ViewModel
classes. Here, some controversy creeps into my implementation. We're going to declare an IView
interface for use in our ViewModel
.
What!!! I can hear the gasps! Our ViewModels
shouldn't know anything about our Views! Well, I live in the real world. I need to be able to tell the Views to activate themselves and to close themselves. More accurately, I need to be able to tell a View when its ViewModel
is closing, or when its ViewModel
is activating - so giving the View the option to handle these events.
You'll see that is all the IView
interface is - definitions of a couple of methods to allow the views to hook into events raised by the ViewModel
. It additionally specifies the DataContext
property - which every view, as a descendent of UserControl
, will have anyway. You'll see this property used in the ViewModel
constructor.
IView.cs
namespace ViewModel
{
public interface IView
{
void ViewModelClosingHandler(bool? dialogResult);
void ViewModelActivatingHandler();
object DataContext{get;set;}
}
}
The BaseViewdata
is about as simple a class as you could want...
BaseViewData.cs
namespace ViewModels
{
public abstract class BaseViewData : ObservableObject
{
}
}
The BaseViewModel
source also declares the two delegates that use the methods defined in the IView
interface. We also keep, in the BaseViewModel
, a collection of Child BaseViewModels
. Keeping this list allows each ViewModel
to ensure that each of its children unsubscribe from all of their messages (and release any other resources) when the 'parent' is being 'closed'. Incidentally, rather than use the name "Parent
" for variables I have used "daddy" - to avoid any possible confusion with other uses of the Parent name. Those of a feminist bent, feel free to rename this 'mummy'.
The BaseViewModel
contains a BaseViewData
property. The BaseViewData
is business data bound to the View
, while any other properties of the ViewModel
that may be bound by the View
provide functionality rather than just data.
In the constructor, a ViewModel
is passed an IController
and IView
reference. This is logical - every ViewModel
is going to require a Controller
to service it, and a View
to provide a GUI. You can see that the constructor is where the methods defined in the IView
are wired up to the Event Handlers defined in the BaseViewModel
- and you can take note that the ViewModel
does not retain any other reference to the View.
Finally two methods, CloseViewModel
and ActivateViewModel
, are provided.
BaseViewModel.cs
using System.Collections.Generic;
namespace ViewModel
{
public delegate void ViewModelClosingEventHandler(bool? dialogResult);
public delegate void ViewModelActivatingEventHandler();
public abstract class BaseViewModel : ObservableObject
{
public event ViewModelClosingEventHandler ViewModelClosing;
public event ViewModelActivatingEventHandler ViewModelActivating;
private List<BaseViewModel> childViewModels = new List<BaseViewModel>();
public List<BaseViewModel> ChildViewModels
{
get { return childViewModels; }
}
#region Bindable Properties
#region ViewData
private BaseViewData viewData;
public BaseViewData ViewData
{
get
{
return viewData;
}
set
{
if (value != viewData)
{
viewData = value;
base.RaisePropertyChanged("ViewData");
}
}
}
#endregion
#endregion
#region Controller
protected IController Controller
{
get;
set;
}
#endregion
#region Constructor
public BaseViewModel()
{
}
public BaseViewModel(IController controller)
{
Controller = controller;
}
public BaseViewModel(IController controller, IView view)
: this(controller)
{
if (view != null)
{
view.DataContext = this;
ViewModelClosing += view.ViewModelClosingHandler;
ViewModelActivating += view.ViewModelActivatingHandler;
}
}
#endregion
#region public methods
public void CloseViewModel(bool? dialogResult)
{
Controller.Messenger.DeRegister(this);
if (ViewModelClosing != null)
{
ViewModelClosing(dialogResult);
}
foreach (var childViewModel in childViewModels)
{
childViewModel.CloseViewModel(dialogResult);
}
}
public void ActivateViewModel()
{
if (ViewModelActivating != null)
{
ViewModelActivating();
}
}
#endregion
}
}
Controller
Because our Controllers will have some common functionality, we'll use a BaseController
class from which our controller(s) will inherit. In this case, the only common functionality, in fact, is a reference to the singleton instance of our Messenger
class, as defined in the IController
interface.
So we just need to create a new class in the Controllers
project, called BaseController
. As usual, I create it in a folder called Base Classes.
BaseController.cs
using Messengers;
using ViewModel;
namespace Controllers
{
public abstract class BaseController : IController
{
public Messenger Messenger
{
get
{
return Messenger.Instance;
}
}
}
}
View
In our Views project, we need to create two items. Firstly, we're going to create a Window. This window will be used by our Views when we want to show them in a window - as you'll see later.
Because we're creating base classes (well, the Window
isn't actually a base class, but it sort of fits the idea) I create a folder called Base Classes, then create within it a new Window
called ViewWindow
.
Because when we display our views, we need to put them on some surface, we'll add a DockPanel
to the window. This is the surface on which all of our views will be placed when shown in a window. It must be named WindowDockPanel
. Note that you can 'pretty up' your window as much as you like - just so long as it has a WindowDockPanel
. (and you can change that functionality if you want, by changing the code that puts Views onto the window - it's all defined in Base classes, so the implementation can be changed to suit your preferences.)
Next we create the BaseView
class.
BaseView.cs
using System;
using System.Windows;
using System.Windows.Controls;
using ViewModels;
namespace Views
{
public delegate
void OnWindowClose(
Object sender, EventArgs e);
public partial class BaseView : UserControl, IDisposable, IView
{
private ViewWindow viewWindow;
private OnWindowClose onWindowClosed = null;
#region Closing
public void ViewClosed()
{
if (DataContext != null)
{
((BaseViewModel)DataContext).ViewModelClosing -=
ViewModelClosingHandler;
((BaseViewModel)DataContext).ViewModelActivating -=
ViewModelActivatingHandler;
this.DataContext = null; }
}
void ViewsWindow_Closed(object sender, EventArgs e)
{
if (onWindowClosed != null)
{
onWindowClosed(sender, e);
}
((BaseViewModel)DataContext).CloseViewModel(false);
}
#endregion
#region IView implementations
public void ViewModelClosingHandler(bool? dialogResult)
{
if (viewWindow == null)
{
System.Windows.Controls.Panel panel =
this.Parent as System.Windows.Controls.Panel;
if (panel != null)
{
panel.Children.Remove(this);
}
}
else
{
viewWindow.Closed -= ViewsWindow_Closed;
if (viewWindow.IsDialogWindow)
{
if (viewWindow.IsActive)
{
viewWindow.DialogResult =
dialogResult;
}
}
else
{
viewWindow.Close();
}
viewWindow = null;
}
ViewClosed();
}
public void ViewModelActivatingHandler()
{
if (viewWindow != null)
{
viewWindow.Activate();
}
}
#endregion
#region Constructor
public BaseView()
{
}
#endregion
#region Window
private ViewWindow ViewWindow
{
get
{
if (viewWindow == null)
{
viewWindow = new ViewWindow();
viewWindow.Closed += ViewsWindow_Closed;
}
return viewWindow;
}
}
#endregion
#region Showing methods
public void ShowInWindow(bool modal, string windowTitle)
{
ShowInWindow(modal, windowTitle, 0, 0, Dock.Top, null);
}
public void ShowInWindow(bool modal, ViewWindow window)
{
ShowInWindow(modal, window, window.Title,
window.Width, window.Height,
Dock.Top, null);
}
public void ShowInWindow(
bool modal, ViewWindow window,
string windowTitle, double windowWidth,
double windowHeight,
Dock dock, OnWindowClose onWindowClose)
{
this.onWindowClosed = onWindowClose;
viewWindow = window;
viewWindow.Title = windowTitle;
DockPanel.SetDock(this, dock);
viewWindow.WindowDockPanel.Children.Add(this);
if (windowWidth == 0 && windowHeight == 0)
{
viewWindow.SizeToContent =
SizeToContent.WidthAndHeight;
}
else
{
viewWindow.SizeToContent = SizeToContent.Manual;
viewWindow.Width = windowWidth;
viewWindow.Height = windowHeight;
}
if (modal)
{
viewWindow.ShowDialog();
}
else
{
viewWindow.Show();
}
}
public void ShowInWindow(
bool modal, string windowTitle,
double windowWidth, double windowHeight,
Dock dock, OnWindowClose onWindowClose)
{
ShowInWindow(modal, ViewWindow, windowTitle,
windowWidth, windowHeight, dock, onWindowClose);
}
#endregion
#region IDisposable Members
void IDisposable.Dispose()
{
if (viewWindow != null)
{
viewWindow.Closed -= this.ViewsWindow_Closed;
}
}
#endregion
}
}
You'll need to add a reference to the ViewModel
project in order for this to compile.
This is a reasonably basic BaseView
- there's a few different methods that can be used for showing our View, but the list is not complete by any means. I've tried to give the basic requirements in this version. It is certainly open to expansion. You will also see the implementation of our event handlers for when the ViewModel
closes, and when it is Activated.
End of the Second Part
We've now completed the project up to the point where we need to start creating application specific code. In other words, we've created our framework - but I really don't want to use that word - I don't see this as a framework, but just as a bunch of classes, put together to allow me to develop a WPF MVVM# application.
The application should build - if you're typing it in rather than downloading it, check your namespaces, as VS does tend to add folder names to namespaces just to annoy me.
Next time, we'll start developing the application proper - and finally have something to run.