Click here to Skip to main content
Click here to Skip to main content

Catel - Part 3 of n: The MVVM Framework

, 28 Jan 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
This article explains the MVVM framework that ships with Catel.

Catel is a brand new framework (or enterprise library, just use whatever you like) with data handling, diagnostics, logging, WPF controls, and an MVVM Framework. So, Catel is more than "just" another MVVM Framework or some nice Extension Methods that can be used. It's more like a library that you want to include in all the (WPF) applications you are going to develop in the near future.

This article explains the MVVM Framework.

Article Browser

Table of Contents

  1. Introduction
  2. Models
  3. View Models
  4. 4. Views & Controls
  5. 5. Additional Information
  6. History

1. Introduction

Welcome to Part 3 of this article series about Catel. This article explains the MVVM framework that ships with Catel.

If you haven't read the previous article(s) of Catel yet, it is recommended that you do. They are numbered, so finding them shouldn't be too hard.

If you are not yet familiar with MVVM, please read: WPF Apps With the Model-View-ViewModel Design Pattern by Josh Smith.

This article will explain all the parts of MVVM, in the order in which we think they must be built. First of all, the Models, which are the closest to the business. Then, the View Models which define what part of the Models should be visible to the user in a specific situation. This also includes validation that is specific for the functionality that the View Model represents. Last, but not least, the View itself, which is the final representation of the View Model to the end-user.

At the end of this article, we will also take a look at the specific controls that Catel ships to make it much easier to work with View Models. For example, Catel ships with a solution for the “nested controls” problem in MVVM. With Catel, you can dynamically create sub-level View Models for user controls.

During this article, I will not go into the technical details on how the framework actually works. If there is enough interest, I can write a separate article about the technique behind the framework. Just let me know if you are interested, and if the count is high enough, I will write a separate article.

As you will notice (or maybe you don't), this framework has a lot in common with other MVVM frameworks out there. This is, of course, normal because all of the frameworks are trying to implement the same pattern, which isn't that hard to understand if you think about it long enough. Before we started writing the MVVM framework, we first investigated other frameworks because there already are enough, probably too many. However, even the better (or best), such as Cinch, still took too much time to use, and there was too much code we had to write to create a View Model. That's the point where we decided to write our own framework that contains lots of luxury for lazy developers such as me.

When you want to be able to create View Models quickly, code snippets are the way to go. I can't say this enough, but you should really use code snippets; it saves you tons of type work.

In this article, the word “convenience” will be used a lot, maybe too much in your opinion. If so, I am sorry about that, but it is really important to understand that Catel is all about convenience. It's like Steve Ballmer says: “Convenience! Convenience! Convenience!”

2. Models

This article isn't really about Models, but we are discussing MVVM, and the Models part is one of the three major parts of MVVM. Therefore, I want to tell you a bit about what kind of Models we use to store our data. Basically, you can use all types of objects as Models, as long as the Models implement the most commonly used interfaces required by WPF.

For MVVM, it is very important that the following interfaces are implemented:

  • INotifyPropertyChanged

    If this interface is not implemented, changes will not be reflected to the UI via bindings. In other words, your Model and View Model will be useless in an MVVM setting.

    Finally, it is strongly recommended to have your Models implement the following interfaces as well:

  • IDataErrorInfo

    If this interface is not implemented, errors cannot be shown to the user.

  • IEditableObject

    If this interface is not implemented, a Model cannot work with “states”. This means that a user cannot start editing an object and finally cancel it (because there is no stored “state” that can be used to restore the values).

2.1. Database Models

The most commonly used setting is an application that communicates with a database. There are tons of ORM tools out there, like the following:

I personally have a very good experience with LLBLGen Pro, but that's where I want to stop advertising. The most important thing is that the ORM mapper supports the interfaces that are required for WPF.

2.2. File Models

File models are really easy, assuming they are being used by a single user. Especially when you use the DataObjectBase (or extended SavableDataObjectBase) classes that also ship with Catel. A tree of objects can consist of objects. When this approach is used, it is very simple to take out a separate part and pass that as a Model to a View Model. Normally, you don't have to worry about multi-user environments like you have to when you use databases, so you can simply lock the file and you don't have to worry about concurrent updates.

3. View Models

The View Models in Catel are very easy to write, and give the end-user a great flexibility in how to approach the Models. This part of the article will explain the classes that make it possible to easily create View Models.

3.1. ViewModelBase

The ViewModelBase class is the most important class of all in the MVVM Framework of Catel. Of course, it can't do anything useful without the other classes, but all the View Models that are created using Catel derive of this class. ViewModelBase is based on the DataObjectBase class that ships with Catel. Thanks to the existence of that class, the MVVM framework was set up very quickly (although “very quickly” is relative). Below is a class diagram that shows the class tree:

The class diagram above shows how many default interfaces of the .NET Framework are supported in the DataObjectBase class. Since most of these interfaces are used by WPF as well, the ViewModelBase class itself can take huge advantage of the implementation of DataObjectBase.

Because ViewModelBase derives from DataObjectBase, you can declare properties exactly the same way. Even better, you can simply use DataObjectBase (or the extended SavableDataObjectBase) to create (and save) your Models, and use ViewModelBase as the base for all the View Models.

To declare a View Model, use the following code snippet:

  • vm

    Defines a new View Model.

When using the vm code snippet, this is the result:

/// <summary>
/// BasicViewModel view model.
/// </summary>
public class BasicViewModel : ViewModelBase
{
    #region Constructor & destructor
    /// <summary>
    /// Initializes a new instance of the <see cref="BasicViewModel"/> class.
    /// </summary>
    public BasicViewModel()
        : base()
    {
    }
    #endregion

    #region Properties

    /// <summary>
    /// Gets the title of the view model.
    /// </summary>
    /// <value>The title.</value>

    public override string Title { get { return "View model title"; } }
    #region Models
    // TODO: Register models with the vmpropmodel codesnippet
    #endregion

    #region View model

    // TODO: Register view model properties with
    // the vmprop or vmpropviewmodeltomodel codesnippets
    #endregion

    #endregion

    #region Commands

    // TODO: Register commands with the vmcommand
    // or vmcommandwithcanexecute codesnippets

    #endregion

    #region Methods
    /// <summary>
    /// Initializes the object by setting default values.
    /// </summary>     
    protected override void Initialize()
    {
        // TODO: Implement logic to initialize the view model
    }
    /// <summary>
    /// Validates the fields.
    /// </summary>
    protected override void ValidateFields()
    {
        // TODO: Implement any field validation of this object.
        // Simply set any error by using the SetFieldError method
    }
    /// <summary>
    /// Validates the business rules.
    /// </summary>
    protected override void ValidateBusinessRules()
    {
        // TODO: Implement any business rules of this object.
        // Simply set any error by using the SetBusinessRuleError method
    }
    /// <summary>
    /// Saves the data.
    /// </summary>
    /// <returns>
    ///    <c>true</c> if successful; otherwise <c>false</c>.
    /// </returns>     
    protected override bool Save()
    {
        // TODO: Implement logic when the view model is saved
        return true;
    }
    #endregion
}

It looks like a lot of code, but this is generated for you by the code snippet. You now have a full skeleton that can easily be extended by using the right code snippets, just like the comments tell you to.

3.2. Defining Properties

There are several code snippets available to create View Model properties:

  • vmprop

    Defines a simple View Model property.

  • vmpropmodel

    Defines a View Model property with ModelAttribute. The property is also made private by default.

  • vmpropviewmodeltomodel

    Defines a View Model property with ViewModelToModelAttribute.

When using the vmprop code snippet, this is the result:

/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name
{
    get { return GetValue<string>(NameProperty); }
    set { SetValue(NameProperty, value); }
}
/// <summary>
/// Register the Name property so it is known in the class.
/// </summary>
public static readonly PropertyData NameProperty = 
                       RegisterProperty("Name", typeof(string));

In the View, it is now possible to bind to the Name property of the View Model, as long as DataContext is set to an instance of the View Model.

3.3. Defining Commands

There are several code snippets available to create View Model commands:

  • vmcommand

    Defines a command that is always executable.

  • vmcommandwithcanexecute

    Defines a command that implements a CanExecute method to determine whether the command can be invoked on the View Model in its current state.

When using the vmcommandwithcanexecute code snippet, this is the result:

/// <summary>
/// Gets the Add command.
/// </summary>
public Command<object, object> Add { get; private set; }
// TODO: Move code below to constructor
Add = new Command<object, object>(Add_Execute, Add_CanExecute);
// TODO: Move code above to constructor
/// <summary>
/// Method to check whether the Add command can be executed.
/// </summary>
/// <param name="parameter">The parameter of the command.</param>
private bool Add_CanExecute(object parameter)
{
    return true;
}
/// <summary>
/// Method to invoke when the Add command is executed.
/// </summary>
/// <param name="parameter">The parameter of the command.</param>
private void Add_Execute(object parameter)
{
    // TODO: Handle command logic here
}

The only thing left to do now is to move the creation of the command to the constructor (as the comments already instructs you to).

In the View, it is now possible to bind any Command property (such as the Command property of a Button) to the Add property of the View Model, as long as DataContext is set to an instance of the View Model.

3.4. Services

Services are very important in MVVM. They define a way to interact with the user without using fixed controls such as MessageBox or SaveFileDialog. The interfaces defined in Catel only define generic functionality of what to expect from a specific service. Then, the actual implementation determines what interface implementation will be returned when a service uses the following call:

var messageService = GetService<IMessageService>();

This call inside a ViewModelBase implementation returns the actual implemented class that is registered as the IMessageService implementation. For example, in Windows applications, the underlying implementation would use the MessageBox class. But, during unit tests, you don't want to be bothered by a MessageBox since there is no one that confirms it. Therefore, you can mock the IMessageService interface during unit testing.

3.4.1. IMessageService

IMessageService can be used to show message boxes. It supports the standard messagebox functionality as known in the .NET Framework, but also provides some convenience methods (such as ShowWarning, where the developer only has to pass the warning message).

The service can be used as shown below:

var messageService = GetService<IMessageService>();
messageService.ShowError("An error has occurred!");

3.4.2. IOpenFileService

IOpenFileService can be used to let the user pick a file to open it.

The service can be used as shown below:

var openFileService = GetService<IOpenFileService>();
openFileService.Filter = "*.dob|Data Object Base files (*.dob)";



if (openFileService.DetermineFile())
{
    // Selected file is in openFileService.FileName
}

3.4.3. ISaveFileService

ISaveFileService can be used to let the user pick a file to save it.

The service can be used as shown below:

var saveFileService = GetService<ISaveFileService>();

saveFileService.Filter = "*.dob|Data Object Base files (*.dob)";
if (saveFileService.DetermineFile())
{
    // Selected file is in saveFileService.FileName
}

3.4.4. IPleaseWaitService

IPleaseWaitService can be used to control PleaseWaitWindow that ships with Catel. PleaseWaitWindow (and its helper class PleaseWaitHelper) are very useful to give the user feedback while the user must wait for an action to finish.

The service can be used as shown below:

var pleaseWaitService = GetService<IPleaseWaitService>();
pleaseWaitService.Show(() => Thread.Sleep(3000), "Saving data");

3.4.5. IProcessService

IProcessService is a service that allows a developer to start processes from a View Model. Most MVVM developers just use Process.Start to start an application, but this breaks the MVVM because you can no longer test your View Models (or at last that part) via simple unit tests.

The use of the service is very simple, and looks a lot like the Process class. The service provides three method overloads:

  • void StartProcess(string fileName);
  • void StartProcess(string fileName, string arguments);
  • void StartProcess(string fileName, string arguments, ProcessCompletedDelegate processCompletedCallback);

Via the last overload, it is possible to implement an asynchronous wait for the process to complete. ProcessCompletedDelegate provides the exit code of the application.

Thanks to this abstraction of starting processes, it is now possible to fully control what happens during unit tests. You can now test several process results without actually having to mock or run actual processes.

3.4.6. IUIVisualizationService

IUIVisualizationService is the most complex service of all. It handles the problem that all MVVM developers face: showing popup windows. The window is based on the implementation as it can be found in Cinch. The cool thing about the implementation in Catel is that it does much more automatically. For example, all non-MVVM windows must be registered by calling the Register method. However, Catel searches the whole application domain for an implementation of DataWindow<TViewModel>. If you want to use the full power of Catel, it is recommended that all windows derive from the DataWindow<TViewModel> class. Because Catel automatically registers the windows, you don't have to take care of this yourself. This means that if you have created an MVVM window based on DataWindow<TViewModel>, you can simply use the service like this:

// Create view model
var viewModel = new PersonViewModel();
// Get service & show dialog
var uiVisualizerService = GetService<IUIVisualizerService>();
uiVisualizerService.ShowDialog(viewModel);

As you can see, you don't have to actually be aware of the UI any longer. You simple create a View Model instance and pass it to IUIVisualizerService. It will know what window to show, and it will automatically set the View Model. Because it knows the mapping, it will try to inject the View Model instance passed to the ShowDialog method into the window. If that is not possible, it will use the default (no arguments) constructor of the window and set the data context of the window manually.

Catel offers a lot of power out-of-the-box, but some people just want to use a part of it. That's why it is also possible to support non-MVVM windows with Catel. The downside of this approach is that the window will have to be registered manually like this:

var uiVisualizerService = GetService<IUIVisualizerService>();
uiVisualizerService.Register("personWindow", typeof(PersonWindow));

Once registered, the window can be used like this:

var uiVisualizerService = GetService<IUIVisualizerService>();
uiVisualizerService.ShowDialog("personWindow", dataContext);

The dataContext parameter shown in the example above will be set as the data context for the PersonWindow registered earlier with a call to the Register method.

3.5. Validation

Because the ViewModelBase class derives from DataObjectBase, it provides the same power of validation that the DataObjectBase class has to offer. DataObjectBase (and thus ViewModelBase) offers the following types of validation:

  • Field warnings;
  • Business warnings;
  • Field errors;
  • Business errors.

ViewModelBase uses smart validation. This means that if the object is already validated, the object is not validated again to make sure that the View Models don't hit too much on the performance. Only when a property on the View Model changes, validation will be invoked. Of course, if required, it is still possible to force validation when the View Model must be validated, even when no properties have changed.

To implement field or business rule validation, you only have to override ValidateFields and/or the ValidateBusinessRules method:

///<summary>
/// Validates the fields.
///</summary>
protected override void ValidateFields()
{
    // Set field warning
    SetFieldWarning(MyProperty, "Warning description");
    // Set field error
    SetFieldError(MyProperty, "Error description");
}
///<summary>
/// Validates the business rules.
///</summary>
protected override void ValidateBusinessRules()
{
    // Set business rule warning
    SetBusinessRuleWarning("Business rule description");
    // Set business rule error
    SetBusinessRuleError("Business error description");
}

To find more information on how the validation in DataObjectBase works, please refer to the first article of this article series.

3.6. Interaction with Models

One of the most important reasons why a View Model is created is because it serves as the glue between a View and the Model. The communication between the View and the View Model is fully taken care of by WPF in the form of Bindings. The problem is that most of the time, a View Model is used to show a subset of a Model (which is, for example, a database entity).

Most MVVM frameworks (actually, I haven't seen anyone not requiring manual updating) require manual updating, which brings us back to the stone age (remember the WinForms time setting the controls at startup, and reading the values at the end?). Catel solves this issue by providing convenience attributes that take care of this dumb getting/setting story between the View Model and the Model. Catel fully supports getting/setting the values from/to the Model, but believe me: you will love the attributes that are described next.

3.6.1. ModelAttribute

To be able to map values from/to a Model, it is important to know the actual Model. So, to let the View Model know what property represents the Model, ModelAttribute can be used like shown below:

/// <summary>
/// Gets or sets the person.
/// </summary>
[Model]
public Person Person
{
    get { return GetValue<Person>(PersonProperty); }
    private set { SetValue(PersonProperty, value); }
}
/// <summary>
/// Register the Person property so it is known in the class.
/// </summary>
public static readonly PropertyData PersonProperty = 
              RegisterProperty("Person", typeof(Person));

A Model setter is normally written as private (you normally don't want a UI to be able to change a Model), but the getter is public because you might want to read info from it.

Note: you should use the vmpropmodel code snippet to create Model properties.

Models in Catel are handled as very, very special objects. This means that as soon as a Model is set, Catel tries to call the IEditableObject.BeginEdit method. Then, as soon as the Model is changed without being saved, or if the View Model is cancelled, the Model is correctly cancelled via IEditableObject.CancelEdit. If the Model is saved, the active Models will be committed via IEditableObject.EndEdit. I will leave the rest of the magic out of this article, but if you have any questions about it, don't hesitate to contact me!

3.6.2. ViewModelToModelAttribute

Now that we know how to declare a property has a Model, it is time to learn how we can communicate with it. Normally, you would have to watch the Model to make sure it is synchronized correctly when the Model is updated. With Catel, this is not necessary any longer. Simply use ViewModelToModelAttribute, and you will get the following advantages:

  • Models are automatically being watched for changes, thus if a mapped property changes, the View Model is updated accordingly;
  • When a View Model is changed, this property is automatically mapped to the Model;
  • When the Model changes, the View Model is initialized automatically with the values of the new Model;
  • When a Model has an error or warning (business or field), the warnings are mapped to the View Model so you can “re-use” the validation of the Model inside your View Model.

So, you get all of this for free? No, you will have to decorate your property with ViewModelToModelAttribute, like shown below:

/// <summary>
/// Gets or sets the first name.
/// </summary>
[ViewModelToModel("Person")]
public string FirstName
{
    get { return GetValue<string>(FirstNameProperty); }
    set { SetValue(FirstNameProperty, value); }
}
/// <summary>
/// Register the FirstName property so it is known in the class.
/// </summary>
public static readonly PropertyData FirstNameProperty = 
              RegisterProperty("FirstName", typeof(string));

The code example is the easiest usage of the attribute that is available. It only provides the name of the Model property. This is required because it is possible (but not likely) to have multiple Models. But what if the property on your Model has a different name than your View Model? No problem, use the overload of the attribute as shown below:

[ViewModelToModel("Person", "RealFirstName")]
public string FirstName
///... (remaining code left out for the sake of simplicity)

The code above will map the FirstName property of the View Model to the RealFirstName property of the Person model.

There are some other overloads to enable support for LLBLGen Pro, but I don't want to get into the details on that one during this article.

3.7. Interaction with Other View Models

Now that we've seen how easy it is to communicate between the View Model and the Model, you want more, right? I know how it is: “You let 'em have one finger, they take your whole hand”. No worries, you can have my right hand, as long as I can keep my left one. Anyway, the developers of Catel are prepared for this. So, let's talk about the interaction with other View Models.

Say, you have a multiple document interface (MDI as it was called in the old days). If you are following MVVM principles, every document (or tab) has its own View Model. Then, you want to be aware of updates of a single type of View Model. Say, for example, that there is a View Model representing a family called FamilyViewModel. This View Model is probably interested in changes in the PersonViewModel.

3.7.1. View Model Manager

Let's start with the basics. As we have learned earlier in this article, all View Models created with the help of Catel derive from the ViewModelBase class. One of the things that this class does is that it registers itself with the ViewModelManager class when it is being created, and it unregisters itself again when it is closed. So, simply said, ViewModelManager is a class that holds a reference to all existing View Models at the moment.

3.7.2. IntestedInAttribute

Now that we know about the ViewModelManager class, and know that there is a repository that holds all of the live instances of all View Model classes, it should be fairly easy to communicate with other View Models. It actually is; you just have to decorate a View Model with InterestedInAttribute, as shown below:

[InterestedIn(typeof(FamilyViewModel))]



public class PersonViewModel : ViewModelBase

A View Model can have multiple InterestedInAttribute instances, so it is possible to subscribe to multiple View Model types at the same time. Once a View Model is decorated with InterestedInAttribute, the View Model will receive all changes (and of course, the View Model that caused the change) via the OnViewModelPropertyChanged method, as shown below:

/// <summary>
/// Called when a property has changed for a view model
/// type that the current view model is interested in. This can
/// be accomplished by decorating the view model
/// with the <see cref="InterestedInAttribute"/>.
/// </summary>
/// <param name="viewModel">The view model.</param>
/// <param name="propertyName">Name of the property.</param>
protected override void OnViewModelPropertyChanged(IViewModel viewModel, 
                        string propertyName)
{
    // You can now do something with the changed property
}

3.8. Saving

The last thing (which is actually quite important) is the saving of the View Model. You have successfully created a View Model, and you are able to use it inside a View. Now the only thing left is to save the changes that are made in the View Model. Depending on how you use the View Model, this can either be almost nothing, or quite a lot. Let's start with the “almost nothing” option.

3.8.1. Using ModelAttribute Decorations

When you use the ModelAttribute described earlier in this article, you only have to save the Model since the changes are already reflected to the Model. The code below shows an example, assuming that the Save method on the Model returns a boolean:

protected override bool Save()
{
    // Save model
    return Person.Save();
}

3.8.2. Manual Mapping and Saving

If you have chosen not to go with the convenience attributes, you will have to map all the properties to the Model and then save the Model:

protected override bool Save()
{
    // Map all properties
    Person.FirstName = FirstName;
    // Save model
    return Person.Save();
}

In the example above, it might seem a little dull that I keep telling you to use the ModelAttribute in combination with the ViewModelToModelAttribute, but imagine a View Model with 10 properties. It's much easier to decorate them with an attribute than to get all the properties on initialization, and map all the properties back to the Model on saving.

4. Views & Controls

Views are what the user actually sees on the screen. The Views communicate with the View Models. The MVVM framework that ships with Catel can be used on its own. However, the framework requires some conventions that you, as the end-user of Catel, should not be worrying about. Therefore, Catel ships with both a UserControl and a Window that fully support the MVVM framework. Those can both be used as base classes for all controls and windows developed in an application.

4.1. DataWindow<TViewModel>

The easiest object to use with the MVVM framework is the DataWindow<TViewModel> class. This window class derives from the well-known DataWindow of Catel, so it offers the same functionality. The additional features that DataWindow<TViewModel> offers in advantage of the regular DataWindow class is that it fully takes care of the construction of the View Models and the validation of the View Models.

The usage of the DataWindow<TViewModel> class is very simple once you know how to do it. First of all, you will have to specify the base class in the XAML file like shown below:

<Windows:DataWindow 
   x:Class="Catel.Articles._03___MVVM.Examples.DataWindow.PersonWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:BasicViewModel="clr-namespace:Catel.Articles._03___MVVM.Examples.BasicViewModel"
   xmlns:Windows="clr-namespace:Catel.Windows;assembly=Catel.Windows"
   x:TypeArguments="BasicViewModel:PersonViewModel">
      <!-- Content left out for the sake of simplicity -->
</Windows:DataWindow>

As you can see, two things have changed in regard to a “normal” window definition:

  1. The type definition has changed from Window to Windows:DataWindow;
  2. An attribute called x:TypeArguments is added that specifies the type of the View Model supported by the window.

The code-behind is even simpler:

/// <summary>
/// Interaction logic for PersonWindow.xaml
/// </summary>
public partial class PersonWindow : DataWindow<PersonViewModel>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PersonWindow"/> class.
    /// </summary>
    /// <param name="viewModel">The view model.</param>
    public PersonWindow(PersonViewModel viewModel)
        : base(viewModel)
    {
        // Initialize component
        InitializeComponent();
    }
}

The code above is everything you will need when using the MVVM framework of Catel.

4.1.1. Construction of View Models

There are multiple ways to construct a window with a View Model. There are three options that you have to construct a View Model:

  • Constructor with View Model

    This is the best option you can use. This way, it is possible to inject View Models into the data window.

  • Constructor with Model

    It is possible to save a developer from creating a View Model manually by accepting a Model as input. Then, the data window will have to construct the View Model manually and pass it through to its base constructor.

  • Empty constructor

    If you use an empty constructor, the developer will have to set the data context manually. This is something you really should avoid. But hey, it's all up to you.

4.1.2. Automatic Validation

The cool thing about DataWindow<TViewModel> is that it automatically wraps the content that a developer defines into an InfoBarMessageControl. This way, errors and warnings are shown at the top of the window. Another feature of DataWindow<TViewModel> is that it automatically creates a WarningAndErrorValidator control and sets the View Model as the source. This way, all the warnings of the View Model are also shown in InfoBarMessageControl. In other words: you don't have to do anything to implement validation, except for actually setting the warnings and errors in your View Model. And if the validation takes place in the Model, you can use ViewModelToModelAttribute, so you don't have to worry about that either.

4.2. UserControl<TViewModel>

UserControl<TViewModel> is a very interesting class of Catel, and fully shows the power of the MVVM framework that ships with Catel. The user control is able to fully integrate MVVM on a user control level, and solves the “nested user control” problem, which is explained in detail a bit further in this article.

4.2.1. Automatic Construction Without Parameter

The simplest thing to do is to create a View Model that has an empty constructor (thus without parameters). If UserControl<TViewModel> is added to the visual tree, the View Model is instantly constructed and available for usage. A View Model that is used inside a UserControl<TViewModel> implementation is exactly the same as the DataWindow<TViewModel> implementation. This way, developers don't have to worry about whether they can currently write a View Model that is meant for a window or a control.

4.2.2. Automatic Construction with Parameter

A bit harder (it's still very easy, don't worry), but much more powerful is the construction with a parameter. This way, a control is forced to use the data context to create the View Model. If there is no valid data context that can be used to construct the View Model, no View Model will be constructed. This sounds a little abstract, but let's take a look at a more meaningful example.

Say, you want to write an application to manage company trees. The top-level of the data consists of a collection of Company objects (Models). You want to display the companies inside an ItemsControl, which is a very good way to represent the companies. But, how are you going to display the company details? You can simply create a template, but I wouldn't recommend that because the company representation can become very complex (and dynamic), because it consists of Person objects that can have children (employees), and the children are Person objects as well, that can have children, etc. You might think that this is a very simple scenario, which it actually is to make sure that all readers understand it correctly. But, there can be a lot of complex tree scenarios. For example, for a client, I had to write a complete treatment overview of a patient, which consists of a lot of different objects, which all have a child collection of other object types. Then, you can save yourself with writing a simple and generic data template. Below is a graphical form of the example:

Now comes the real power of UserControl<TViewModel> in to play. For example, to show the company and its managers, one has to write an ItemsControl that contains the companies and then a user control containing the details of the company. For the sake of simplicity, I will leave the employees out for now. The usage might seem a bit complex, but once you get the hang of it, it's actually quite simple. First of all, create a View Model that has a constructor of the Model that you want to accept; in our case, the Company class of which we will show the details:

/// <summary>
/// Initializes a new instance of the <see cref="CompanyViewModel"/> class.
/// </summary>
/// <param name="company">The company.</param>
public CompanyViewModel(Models.Company company)
    : base()
{
    // Store values
    Company = company;
}

As you can see, the View Model can only be constructed by passing a company model. This is quite normal, because how can we show the details of a non-existing (null) company? Now that we have a View Model, we can create our user control:

<Controls:UserControl 
   x:Class="Catel.Articles._03___MVVM.Examples.UserControlWithParameter.Company"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Windows"
   xmlns:UserControlWithParameter=
     "clr-namespace:Catel.Articles._03___MVVM.Examples.UserControlWithParameter"
   x:TypeArguments="UserControlWithParameter:CompanyViewModel">
       <!-- For the sake of simplicity, content is left out -->
</Controls:UserControl>

Note that the class definition is now Controls:UserControl instead of UserControl, and that an additional attribute TypeArguments is added defining the type argument.

The code-behind is even simpler:

/// <summary>
/// Interaction logic for Company.xaml
/// </summary>
public partial class Company : UserControl<CompanyViewModel>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="Company"/> class.
    /// </summary>
    public Company()
    {
        // Initialize component
        InitializeComponent();
    }
}

Now that the control is created (I don't want to focus on the actual control content here, since it's not important), we can use the user control in our main window that has a collection of companies. The View Model also has a SelectedCompany property representing the selected company inside the listbox. Then, we use the Company control and bind the data context to the SelectedCompany property:

<!-- Items control of companies -->
<ListBox Grid.Column="0" ItemsSource="{Binding CompanyCollection}" 
               SelectedItem="{Binding SelectedCompany}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <Label Content="{Binding Name}" />
                <Label Content="{Binding CEO.FullName}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

<!-- Company details -->

<UserControlWithParameter:Company Grid.Column="1" 
              DataContext="{Binding SelectedCompany}" />

As the code shows, there is a listbox containing all the companies. The data context of the user control is bound to the SelectedCompany. The cool thing is that as soon as a company is selected, the user control will create an instance of CompanyViewModel because it accepts a Company instance in the constructor. The screenshot of the example application will (hopefully) give more insight into what change is causing the exact View Model creation:

In the image above, you see two controls. The first one is an ItemsControl that binds to CompaniesViewModel because the window represents a list of windows. The second one is CompanyControl, which dynamically constructs CompanyViewModel as soon as a company is selected on the left. This means that for every company selection, a new View Model is constructed. This way, you can handle the saving, cancelling, and closing of the View Model before the next View Model is constructed.

The best thing about this is that you can actually start re-using user controls throughout your whole application. Instead of having the main View Model have to define all the properties of (sub) controls, now each control has its own View Model, and you don't have to worry about the implementation in the parent of a control. Simply set the data context of the user control to the right type instance, and the user control will handle the rest.

Now that you've learned how easy it is to create user controls that create View Model on-the-fly, let's take a look at the “nested user controls” problem which can be solved with the technique you've just learned about.

4.2.3. Nested User Controls

One of the issues most users of MVVM face is the “nested user controls” problem. The problem is that most (actually, all that we've seen) MVVM frameworks only support one View Model for a window (or if you're lucky, a user control). However, the “nested user controls” problem raises lots of questions:

  • What if the requirements are to build a dynamic UI where the nested user controls are loaded dynamically when they are required?
  • What about validation in the nested user controls?
  • When should the nested user control View Models be saved?

Most MVVM developers just answer: “Put all the properties of the nested user controls on the main View Model”. Say that again? Are you kidding me? That's not a real world solution for a real world problem. So, we, as developers of Catel, offer you a real world solution for the “nested user controls” problem in the form of UserControl<TViewModel>.

The real power of the UserControl<TViewModel> class lays in the fact that it is able to construct View Models dynamically based on its data context. So, the only thing developers have to take care of is to set the right data context. Below is a graphical presentation of the “nested user controls” problem:

Regular MVVM frameworks

Catel MVVM framework

As the images above show, the method that Catel uses to solve the problem is much more professional. Below are a few reasons:

  • Separation of concerns (each control has a View Model only containing the information for itself, not for children);
  • User controls are built so they can be re-used. Without the user controls being able to have their own View Models, how should one actually use user controls with MVVM?

The idea behind the user control is pretty complex, especially because WPF isn't very good at runtime data context type changing. However, with a few workarounds (very well described in the source code of UserControl<TViewModel>), it is possible to dynamically construct View Models. The user control constructs the View Model with or without a constructor, as described earlier in this article. When the View Model is constructed, the user control tries to find a (logical or visual) parent that implements the IViewModelContainer interface. Thanks to this interface, a View Model can subscribe itself to a parent View Model, and the validation chain is created as shown below:

As the image above shows, all children in the chain are validated, and when the last child is validated, the View Model reports the result of its children and itself back to its parent. This way, it is still possible to disable a command when one of the nested user control View Models has an error.

Saving a chain of nested View Models works exactly the same as validation. First, the View Model saves all the children, then itself, and finally reports back its result to the parent.

Now, let's go to some “real-life” example. I don't want to make it too complex, but not too easy as well, but don't want to put the focus on the content of the data, but on the user control and View Model creation. Therefore, I have chosen for the data model below:

The image shows that we have a house. In that house, we have multiple rooms. In each room, there can be several tables with chairs and beds. This shows a “complex” UI tree with lots of different user controls (each object has its own representation and thus user control). Now, our goal is to create user controls that can be used in the window that shows the full house, but also in “sub-parts”, and we want to be fully independent of HouseWindowViewModel (which is the only View Model that would be created in a regular MVVM framework).

The example below shows only the Room control and the corresponding View Model. The full source code of this article is provided in the source code repository of Catel, so the whole example is available if you are interested or need a more complete example.

First, we start with a simple Model. For the Model, we use the DataObjectBase class that is explained in the first article of Catel. By using the provided code snippets, this Model is setup within a minute:

/// <summary>
/// Bed Data object class which fully supports serialization,
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>

[Serializable]
public class Room : DataObjectBase<Room>
{
    #region Constructor & destructor
    /// <summary>
    /// Initializes a new object from scratch.
    /// </summary>
    public Room()
        : this(NameProperty.GetDefaultValue<string>()) { }
    /// <summary>
    /// Initializes a new instance of the <see cref="Room"/> class.
    /// </summary>
    /// <param name="name">The name.</param>

    public Room(string name)
    {
        // Create collections
        Tables = new ObservableCollection<Table>();
        Beds = new ObservableCollection<Bed>();
        // Store values
        Name = name;
    }
    /// <summary>
    /// Initializes a new object based on <see cref="SerializationInfo"/>.
    /// </summary>
    /// <param name="info"><see cref="SerializationInfo"/>
    /// that contains the information.</param>
    /// <param name="context"><see cref="StreamingContext"/>.</param>
    protected Room(SerializationInfo info, StreamingContext context)
        : base(info, context) { }

    #endregion
    #region Properties

    /// <summary>
    /// Gets or sets the name.
    /// </summary>
    public string Name
    {
        get { return GetValue<string>(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
    /// <summary>
    /// Register the Name property so it is known in the class.
    /// </summary>
    public static readonly PropertyData NameProperty = 
           RegisterProperty("Name", typeof(string), "Room");
    /// <summary>
    /// Gets or sets the table collection.
    /// </summary>
    public ObservableCollection<Table> Tables
    {
        get { return GetValue<ObservableCollection<Table>>(TablesProperty); }
        set { SetValue(TablesProperty, value); }
    }
    /// <summary>
    /// Register the Tables property so it is known in the class.
    /// </summary>
    public static readonly PropertyData TablesProperty = 
           RegisterProperty("Tables", typeof(ObservableCollection<Table>));
    /// <summary>
    /// Gets or sets the bed collection.
    /// </summary>
    public ObservableCollection<Bed> Beds
    {
        get { return GetValue<ObservableCollection<Bed>>(BedsProperty); }
        set { SetValue(BedsProperty, value); }
    }
    /// <summary>
    /// Register the Beds property so it is known in the class.
    /// </summary>
    public static readonly PropertyData BedsProperty = 
           RegisterProperty("Beds", typeof(ObservableCollection<Bed>));
    #endregion
}

Next, we are going to create the View Model. Again, by the use of code snippets explained earlier in this article, the View Model is set up within a few minutes:

/// <summary>
/// Room view model.
/// </summary>
public class RoomViewModel : ViewModelBase
{
    #region Variables
    private int _bedIndex = 1;
    private int _tableIndex = 1;
    #endregion
    #region Constructor & destructor
    /// <summary>
    /// Initializes a new instance of the <see cref="RoomViewModel"/> class.
    /// </summary>
    public RoomViewModel(Models.Room room)
    {
        // Store values
        Room = room;
        // Create commands
        AddTable = new Command<object>(AddTable_Execute);
        AddBed = new Command<object>(AddBed_Execute);
    }
    #endregion
    #region Properties

    /// <summary>
    /// Gets the title of the view model.
    /// </summary>
    /// <value>The title.</value>

    public override string Title { get { return "Room"; } }
    #region Models

    /// <summary>
    /// Gets or sets the room.
    /// </summary>
    [Model]
    public Models.Room Room
    {
        get { return GetValue<Models.Room>(RoomProperty); }
        private set { SetValue(RoomProperty, value); }
    }
    /// <summary>
    /// Register the Room property so it is known in the class.
    /// </summary>
    public static readonly PropertyData RoomProperty = 
                  RegisterProperty("Room", typeof(Models.Room));

    #endregion
    #region View model

    /// <summary>
    /// Gets or sets the name.
    /// </summary>
    [ViewModelToModel("Room")]
    public string Name
    {
        get { return GetValue<string>(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
    /// <summary>
    /// Register the Name property so it is known in the class.
    /// </summary>
    public static readonly PropertyData NameProperty = 
                  RegisterProperty("Name", typeof(string));
    /// <summary>
    /// Gets or sets the table collection.
    /// </summary>
    [ViewModelToModel("Room")]
    public ObservableCollection<Models.Table> Tables
    {
        get { return GetValue<ObservableCollection<Models.Table>>(TablesProperty); }
        set { SetValue(TablesProperty, value); }
    }
    /// <summary>
    /// Register the Tables property so it is known in the class.
    /// </summary>
    public static readonly PropertyData TablesProperty = 
      RegisterProperty("Tables", typeof(ObservableCollection<Models.Table>));
    /// <summary>
    /// Gets or sets the bed collection.
    /// </summary>
    [ViewModelToModel("Room")]
    public ObservableCollection<Models.Bed> Beds
    {
        get { return GetValue<ObservableCollection<Models.Bed>>(BedsProperty); }
        set { SetValue(BedsProperty, value); }
    }
    /// <summary>
    /// Register the Beds property so it is known in the class.
    /// </summary>
    public static readonly PropertyData BedsProperty = 
      RegisterProperty("Beds", typeof(ObservableCollection<Models.Bed>));

    #endregion

    #endregion
    #region Commands
    /// <summary>
    /// Gets the AddTable command.
    /// </summary>
    public Command<object> AddTable { get; private set; }
    /// <summary>
    /// Method to invoke when the AddTable command is executed.
    /// </summary>
    /// <param name="parameter">The parameter of the command.</param>
    private void AddTable_Execute(object parameter)
    {
        Tables.Add(new Models.Table(string.Format("Table {0}", _tableIndex++)));
    }
    /// <summary>
    /// Gets the AddBed command.
    /// </summary>
    public Command<object> AddBed { get; private set; }
    /// <summary>
    /// Method to invoke when the AddBed command is executed.
    /// </summary>
    /// <param name="parameter">The parameter of the command.</param>
    private void AddBed_Execute(object parameter)
    {
        Beds.Add(new Models.Bed(string.Format("Bed {0}", _bedIndex++)));
    }
    #endregion
}

As you can see, the View Model can only be constructed by passing a Room Model object. It is very important to be aware of this construction. The reason that there is no empty constructor is because there is no support for Views that do not represent a Room Model.

In the View Model, the properties of the Room Model are mapped by the use of the Model attribute and the ViewModelToModel attribute. Last but not least, commands are defined to be able to add new tables and beds to the Room Model.

Now the Model and the View Model are fully set up; the last thing to do is to create the actual View. To accomplish this, add a new WPF user control to the project. The first thing to do is to implement the code-behind, since that is the easiest to do:

/// <summary>
/// Interaction logic for Room.xaml
/// </summary>
public partial class Room : UserControl<RoomViewModel>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="Room"/> class.
    /// </summary>
    public Room()
    {
        // Initialize component
        InitializeComponent();
    }
}

The only thing we changed from the default user control template is that the user control now derives from the generic Catel.Windows.Controls.UserControl<TViewModel> control instead of the default System.Windows.Controls.UserControl control. Then, the View Model that the user control should use is provided as a generic argument. This is it for the code-behind, let's move up to the View.

The last thing to do now is the actual XAML view. For the sake of simplicity, the actual content is left out (it's just a grid with a TextBox and ItemsControls for the children):

<Controls:UserControl 
  x:Class="Catel.Articles._03___MVVM.Examples.NestedUserControls.Room"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:Controls="clr-namespace:Catel.Windows.Controls;assembly=Catel.Windows"
  xmlns:NestedUserControls=
     "clr-namespace:Catel.Articles._03___MVVM.Examples.NestedUserControls"
  x:TypeArguments="NestedUserControls:RoomViewModel">

    <!-- For the sake of simplicity, the content is left out -->

</Controls:UserControl>

A few things are very important to notice in the XAML code shown above. The first thing to notice is that (like the code-behind), the base class is now Controls:UserControl instead of UserControl. Then, it is also required to set the type arguments of the base class. This can be done by using the x:TypeArguments attribute. The type argument is the same View Model that is used in the code-behind.

That's all that can be learned about solving the “nested user control” problem. We have set up the Model, View Model, and finally the View. Now, let's take a look at how it looks in a screenshot (and notice the construction time of the View Model, they are really constructed on-demand):

The red border is the control that we just created. It shows the name of the room, the View Model construction time, and the child objects (inside expanders).

4.2.4. Mapping Properties from/to a View Model

When developing custom user controls, you still want to use the power of MVVM, right? With Catel, all of this is possible. All other frameworks require a developer to manually set the data context on a user control. Or, what about mapping user control properties from/to the View Model?

To map a property of a custom user control to a View Model and back, the only thing a developer has to do is to decorate the Dependency Property of the control with ControlToViewModelAttribute. Normally, a developer has to build logic that subscribes to property changes of both the View Model and the control, and then synchronize all the differences. Thanks to ControlToViewModelAttribute, the UserControl<TViewModel> that ships with Catel takes care of this. The usage of the attribute looks as follows:

[ControlToViewModel]
public bool MyDependencyProperty
{
    get { return (bool)GetValue(MyDependencyPropertyProperty); }
    set { SetValue(MyDependencyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store
// for MyDependencyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyDependencyPropertyProperty =
    DependencyProperty.Register("MyDependencyProperty", 
    typeof(bool), typeof(MyControl), new UIPropertyMetadata(true));

By default, the attribute assumes that the name of the property on the View Model is the same as that of the property on the user control. To specify a different name, use the overload of the attribute constructor, as shown in the following example:

[ControlToViewModel("MyViewModelProperty")]
public bool MyDependencyProperty

//... (remaining code left out for the sake of simplicity)

In the first place, all of this looks fine enough. However, what should happen when the current View Model of the control is replaced by another instance? Or, what if the developer only wants to map values from the control to the View Model, but not back? By default, the View Model will take the lead when this attribute is used. This means that as soon as the View Model is changed, the values of the control will be overwritten by the values of the View Model. If another behavior is required, the MappingType property of the attribute should be used:

[ControlToViewModel("MyViewModelProperty", 
    MappingType = ControlViewModelModelMappingType.TwoWayControlWins)]
public bool MyDependencyProperty
//... (remaining code left out for the sake of simplicity)

The table below explains the options in detail:

Enum value

Description

TwoWayDoNothing

Two way, which means that either the control or the View Model will update the values of the other party as soon as they are updated.

When this value is used, nothing happens when the View Model of the user control changes. This way, it might be possible that the values of the control and the View Model are different. The first one to update next will update the other.

TwoWayControlWins

Two way, which means that either the control or the View Model will update the values of the other party as soon as they are updated.

When this value is used, the value of the control is used when the View Model of the user control is changed, and is directly transferred to the View Model value.

TwoWayViewModelWins

Two way, which means that either the control or the View Model will update the values of the other party as soon as they are updated.

When this value is used, the value of the View Model is used when the View Model of the user control is changed, and is directly transferred to the control value.

ControlToViewModel

The mapping is from the control to the View Model only.

ViewModelToControl

The mapping is from the View Model to the control only.

5. Additional Information

5.1. Validation in Model or View Model

I have had a lot of discussion on whether the validation should take place in the Model or the View Model. Some people think that the validation should always occur inside the Model because you don't want to persist invalid Models to the persistence store. Others say that Models themselves don't need validation, but the state the View Model is in requires validation. I think both are true, and I will tell you why.

First of all, you don't want invalid Models in your persistence store. Thus, the most basic checks such as type, ranges, and required fields should be validated in the Model. But sometimes, it is required to restrict the user more than the Model does, and that's where validation in the View Model comes in handy. Another reason why you want to implement (a part of) the validation in the View Model is the state of the Model inside a workflow. If you have a workflow that updates the Model step by step, the Model isn't valid after the first step in the workflow. However, you already want to persist the Model because the user might decide to execute the following steps at a later time. You don't want to implement the state logic of a workflow in your Model (and if you did that, get rid of it, as soon as possible). This is another feature where View Model validation comes in handy.

The good news is that, with Catel, it doesn't matter what you want, because it's all possible. If you want your Model to do all the validation, then this is possible using the Model and ViewModelToModel attributes which map the values of the properties and the errors directly to the Model so the View Model acts as a proxy between the View and the Model. If you want to do all of the validation inside the View Model, then you can implement the ValidateFields and ValidateBusinessRules methods in the View Model. And, if you want the best of both worlds, such as me, then you can use a combination of the techniques described above.

5.2. Support for LLBLGen Pro

ViewModelBase fully supports entities generated by LLBLGen Pro. This means that the fields of the entities in a View Model are saved upon initialization (or Model changes, in case the entity is defined as a Model). When a View Model is cancelled, the entity fields are rolled back; otherwise the changes are committed.

An important thing is that we only tested the support for LLBLGen Pro using Self Servicing mode.

5.3. What to Expect in the Future?

We are currently thinking about creating generic interfaces to support other ORM mappers in ViewModelBase. However, we need to compare the several ORM mappers that are currently available to be able to create a generic interface.

6. History

  • 28 January, 2011: Add the IProcessService explanation
  • 25 November, 2010: Added article browser and brief introduction summary
  • 23 November, 2010: Some small textual changes
  • 22 November, 2010: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Geert van Horrik
Software Developer CatenaLogic
Netherlands Netherlands
I am Geert van Horrik, and I have studied Computer Science in the Netherlands.
 
I love to write software using .NET (especially the combination of WPF and C#). I am also the lead developer of Catel, an open-source application development framework for WPF, Silverlight, WP7 and WinRT with the focus on MVVM.
 
I have my own company since January 1st 2007, called CatenaLogic. This company develops commercial and non-commercial software.
 
To download (or buy) applications I have written, visit my website: http://www.catenalogic.com
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberHelmut Obertanner24-Apr-12 8:57 
QuestionNested ViewModels Pinmemberjens_weller9-Feb-12 20:55 
AnswerRe: Nested ViewModels PinmemberGeert van Horrik9-Feb-12 23:30 
QuestionRe: Nested ViewModels Pinmemberjens_weller10-Feb-12 3:17 
AnswerRe: Nested ViewModels PinmemberGeert van Horrik10-Feb-12 3:26 
SuggestionRe: Nested ViewModels Pinmemberjens_weller16-Feb-12 22:27 
GeneralRe: Nested ViewModels PinmemberGeert van Horrik16-Feb-12 22:36 
QuestionRe: Nested ViewModels Pinmemberjens_weller16-Feb-12 23:00 
AnswerRe: Nested ViewModels PinmemberGeert van Horrik17-Feb-12 1:06 
QuestionExample Pinmemberedychandra15-Sep-11 18:49 
Can you provide full project example like simple mvvm toolkit, with a project, view, viewmodel, viewmodel locator and some simple data. So we can easy understand your project structure and your code definition.
AnswerRe: Example PinmemberGeert van Horrik15-Sep-11 22:43 
GeneralRe: Example Pinmemberedychandra15-Sep-11 22:51 
GeneralRe: Example PinmemberGeert van Horrik15-Sep-11 22:52 
QuestionControls:UserControl does not exist for Silverlight ? PinmemberJasper4C#12-May-11 3:40 
AnswerRe: Controls:UserControl does not exist for Silverlight ? PinmemberGeert van Horrik12-May-11 4:12 
GeneralFor the sake of simplicity, the content is left out :( PinmemberGoldrian Wingril4-Feb-11 7:26 
GeneralRe: For the sake of simplicity, the content is left out :( PinmemberGeert van Horrik4-Feb-11 7:28 
GeneralMy vote of 5 PinmemberRaviRanjankr2-Feb-11 1:56 
GeneralArticle suggestion PinmemberRyanEK24-Nov-10 11:34 
GeneralRe: Article suggestion PinmemberGeert van Horrik25-Nov-10 0:06 
GeneralSection typo Pinmemberdrobosson22-Nov-10 20:56 
GeneralRe: Section typo PinmemberGeert van Horrik22-Nov-10 23:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 28 Jan 2011
Article Copyright 2010 by Geert van Horrik
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid