Monday, January 3, 2011

Using Nested classes to encapsulate View Models in User Controls

I have been working on developing some more complex user controls that will serve as the base of a lot of applications we develop. These controls will exist in a separate library of common controls. Although I am not using Prism, this is following in line with the composite concept the present, having an application being composed of many different reusable user controls.

I like the MVVM pattern and have made extensive use of View Models within the control.  However, I do not want to really show all those "internal" view models to the users of the control.  Unfortunately, traditional hiding techniques, such as internal classes are not allowed by WPF.  To solve this problem, I employed a few tricks.

1.  Declare the constructor as internal on the public class.  This prevents the applications from initializing their own instances of View Model and protects against others using your classes.  This means you can more freely upgrade and modify your class as the internal needs of your control change without worrying about breaking someone else's code.

2.  "Hide", or at least partially hide all the "guts" of the control by putting them inside the user control class as embedded classes. When doing this, I like to have each View Model in its own file.  I learned long ago that one class per file is a good rule to follow.  To do this, I use the handy partial class feature.

Now, these two methods don't always work.  If you directly reference the View Model from the XAML, you have to make the class public. You can see the requirements for classes used within XAML here.   However, in my case many of my view models are only instantiated in code and returned via properties (like lists of items, etc).  In fact, only one of my View Models needed to be made public.

Here is an example of a control's View Model. It can't be instantiated outside the control's assembly (aka, the app using it) and the class is at least somewhat hidden from view. For example, Intellisense won't show all the class names unless someone actually types out "MyUserControl.".

public partial class MyUserControl
  public class MyViewModel
     internal MyViewModel() { }