Click here to Skip to main content
15,887,302 members
Articles / Desktop Programming / WPF

Bare Metal MVVM - Where the Code Meets the Road - Part 3

Rate me:
Please Sign up or sign in to vote.
4.87/5 (16 votes)
23 Feb 2017CPOL13 min read 16.5K   130   16   12
Templates and how we can use them to support developing MVVM applications
This series of articles covers MVVM from the absolute beginning; no frameworks and no helpers, we're going to learn MVVM from the nuts and bolts.

Introduction

There have been many, many articles about MVVM so you may wonder why I am writing more articles on it. The simple reason is, there's a lot of misinformation about MVVM out there and I hope that this series of articles helps to dispel some of the myths about what is a really, really straightforward pattern.

Now, you are probably aware that MVVM started off life as a pattern for WPF applications, and that it has spread out from its humble beginnings into other languages, frameworks and platforms. However, because these different implementations all rely on features that aren't visibly exposed to us, the developer, we tend to miss out on just what it is that makes MVVM such a useful tool in our development toolbox, and what features we need to start off with MVVM. So, for this first article, we're going to ditch all those fancy platforms, and build an MVVM application from the ground up as a Console application. Yes, that's right, we're going to recreate some of what goes on behind the scenes to show some of the "magic" that makes MVVM possible.

In this article, we're going to start looking at templates and how we can use them to support developing MVVM applications, including a discussion of View first and ViewModel first development. As templating is such a powerful feature and as it has so much going on "under the hood", we're going to tackle templates over a few articles so, by the end of this article, we should have an understanding of what templates are and how to create our own, as well as the basic infrastructure we are going to need to provide support for registering and finding templates.

Series

Development Environment

I have developed this application in VS 2015 and made liberal use of C#6 features. If you don't have VS2015 installed, I hope you will at least be able to follow along with the concepts as I will explain the entire codebase here.

Throwing It All Away

Those who have carefully read my first couple of articles in this series will probably be surprised to hear that we are going to throw away the code we have written so far and start again. Wait! What was that? Throwing code away. Yup, we're going to throw the code we wrote away; not because it was wrong or because it couldn't be used. The simple reason for throwing the code away (for the moment) is because we want to focus on using templates to control what we see on the screen, so we are going to put something together that allows us to focus purely on the template aspects at first - don't worry, we'll be re-adding a super-sized form of the binding functionality we have already encountered.

Why Templates?

If we Google template, we see that one definition is "something that serves as a model for others to copy"; that's what we're going to use. By putting template support into our application, we're going to build in mechanisms to control what the user sees using simple building blocks. For those who are used to programming in something like WPF, templates are all over the place and we often don't notice them, but templates provide us with a handy little facility that we're going to recreate (and which will show why we removed the content from the previous articles).

View vs ViewModel - Which Comes First, Was It the Chicken or the Egg?

Something you may hear talked about is whether we should go View first or ViewModel first when we create our applications. This refers to how we get our UI onto the screen; with View first, we typically create a View and then set the DataContext for it explicitly in the View. In other words, we can think of this as controlling the lifetime of the ViewModel inside our View, and it is our responsibility for explicitly doing something to ensure the View is displayed. This, pretty much, is what we did with the first couple of articles - we let the View control the DataContext and the application responded to that.

MYTH BUSTER #3. You don't have to create a UserControl to do something View first. It's entirely possible to set a DataContext from a template.

Well, View first seems fairly straightforward so what's this ViewModel first thing then? Cunningly enough, this inverts the thinking of View first and we render the DataContext to the UI, letting the underlying application framework (be it WPF, Silverlight or whatever) take care of choosing the most appropriate view to show for that DataContext. I know, this sounds confusing but it's really straightforward and it's what we are going to build here. As we have already seen that this article is about templates, it should come as no surprise that the underlying mechanism for displaying an appropriate view is by registering a template for that view and let the framework pick it up when we set the DataContext on the view.

Let's start by creating a simple base template class that all templates will derive from. This class will expose a TargetType that we will use to set the type that we want to use when we are resolving the ViewModel against the template, and a Render method that we will use to render out our template. We're going to make the dataContext in this method dynamic, a handy time saver when we want to work with properties from a ViewModel later on. Notice that we are passing in the DataContext to this method, rather than the way we were used to from the first couple of articles where we set the DataContext explicitly against the template. We are doing this because we are going to return the same template instance each time we "see" the appropriate type (there's another reason for doing this that we'll come to when we start to beef up the templating system later on, where we are going to break the UI out into a tree like structure - this is something we'll cover later on).

C#
public abstract class TemplateBase
{
  public Type TargetType { get; set; }

  public abstract void Render(dynamic dataContext);
}

One of the features of ViewModel first resolution is that there will always be something displayed on the screen. If we display a ViewModel that we don't have a template registered against, the underlying framework walks back through the ViewModel hierarchy until it finds something registered that it can display - and there is always something that can be displayed because the base type for any class is object, and we will have a template that prints out the ToString() on the ViewModel. This sounds more complicated than it really is. First, let's define our DefaultTemplate.

C#
public class DefaultTemplate : TemplateBase
{
  public DefaultTemplate()
  {
    TargetType = typeof (object);
  }

  public override void Render(dynamic dataContext)
  {
    Console.WriteLine($"{dataContext}");
  }
}

That's it, our default template simply writes out the context to the console whenever we need to render out a type of object.

The Engine Room

It's all very well creating templates but we need some way to hook these templates up, and to find the appropriate template given a ViewModel. This means that we need some common infrastructure, a single point of access to the templates if you like, to bring them all together. We're going to create a Singleton class (I know, yuck, but it's a convenient way to bring everything into one easy to access location) that we will register our templates in, and which we will use to find the most appropriate template to render out to the UI. To start with, we need to provide the single instance of this class.

C#
public class TemplateEngine
{
  public static TemplateEngine Instance { get; } = new TemplateEngine();

  static TemplateEngine()
  {
  }

  private TemplateEngine()
  {
  }
}

With this in place, all classes in our application can make use of the one instance of TemplateEngine. We now need to provide a mechanism to add templates.

C#
public List<TemplateBase> Templates { get; } = new List<TemplateBase> { new DefaultTemplate() };

public void Add(TemplateBase template)
{
  Templates.Add(template);
}

The eagle eyed reader will see that we initialize the list of templates with an instance of DefaultTemplate. With this in place, we will always have a template to render out if no matching template is registered, regardless of what ViewModel we throw at our application. This leads us neatly to the last piece of our TemplateEngine puzzle, namely how to find the appropriate template.

C#
public TemplateBase FindTemplate<T>(T instance)
{
  Type targetType = instance.GetType();
  while (true)
  {
    TemplateBase template = Templates.FirstOrDefault(x => x.TargetType == targetType);
    if (template != null)
    {
      return template;
    }
    targetType = targetType?.BaseType;
  }
}

FindTemplate simply walks the object hierarchy until it finds a matching template; it does this by comparing the TargetType to the current object hierarchy type. Ultimately, this would fall back to DefaultTemplate if we haven't registered an appropriate template. Putting this all together, this is our TemplateEngine as it stands right now.

C#
public class TemplateEngine
{
  public static TemplateEngine Instance { get; } = new TemplateEngine();

  static TemplateEngine()
  {
  }

  private TemplateEngine()
  {
  }

  public List<TemplateBase> Templates { get; } = 
                            new List<TemplateBase> { new DefaultTemplate() };

  public void Add(TemplateBase template)
  {
    Templates.Add(template);
  }

  public TemplateBase FindTemplate<T>(T instance)
  {
    Type targetType = instance.GetType();
    while (true)
    {
      TemplateBase template = Templates.FirstOrDefault(x => x.TargetType == targetType);
      if (template != null)
      {
        return template;
      }
      targetType = targetType?.BaseType;
    }
  }
}

The Application

Our application needs a hook-point that is going to serve as the entry point for our running code. We are going to create a class called Application that will serve as the "heart" of our code, giving us places to hook our templates in. Before we look at this class, let's create a ViewModel that will serve as the basis of the data we want to see in the console window while our application is running.

C#
public class PersonViewModel
{
  public string Name { get; } = "Bobby Tables";
  public DateTime DateOfBirth { get; } = new DateTime(1980, 03, 24);
}

This is a very lightweight ViewModel (and notice that it doesn't implement INotifyPropertyChanged - remember our earlier myth buster, ViewModels don't have to implement INotifyPropertyChanged if there's nothing changing). Right, with that in place, let's create our Application class. The first thing we're going to do is introduce a DataContext that we will hook our PersonViewModel into and a Run method to trigger the application running state.

C#
public object DataContext { get; set; }

public void Run()
{
  TemplateBase template = TemplateEngine.Instance.FindTemplate(this);
  template.Render(DataContext);
}

As in all good executables, we need a Main method that will actually trigger the running of our application.

C#
private static void Main()
{
  Application application = new Application { DataContext = new PersonViewModel() };
  TemplateEngine.Instance.Add(new ApplicationTemplate());
  application.Run();

  Console.ReadKey();
}

As we can see in this code, we're instantiating our Application class and setting the DataContext to be an instance of PersonViewModel. Ultimately, we're calling the Run method to actually trigger the rendering of our application template. While all this is (possibly) very interesting, it misses out on the joy that is that middle line - the one where we are registering a new template. If we didn't have that line in, when we ran the application, all we would see is the result of the ToString() method; remember from our code above, we work our way back up the object hierarchy until we find a template of an appropriate type, which would be the default template in this case. By registering this ApplicationTemplate, we have something appropriate to display. Let's create our template; all it's going to do is set the target type to Application and, when the Render method is called, write out an appropriate message that the application has started, and then find and render out the appropriate template for the DataContext.

C#
public class ApplicationTemplate : TemplateBase
{
  public ApplicationTemplate()
  {
    TargetType = typeof (Application);
  }

  public override void Render(dynamic dataContext)
  {
    Console.WriteLine("Inside Application");
    TemplateEngine.Instance.FindTemplate(dataContext).Render(dataContext);
  }
}

The beauty about this code is how simple our templates really are.

At this point, it's got to be appropriate for us to run the application and see the application and default templates in action.

The application running for the first time with both template types

There we clearly see that our application template and default template are being called. We want more though. We want the information in our ViewModel writing to the screen. Let's put together our template for that.

C#
public class PersonTemplate : TemplateBase
{
  public PersonTemplate()
  {
    TargetType = typeof (PersonViewModel);
  }

  public override void Render(dynamic dataContext)
  {
    Console.WriteLine(dataContext.Name);
    Console.ForegroundColor = ConsoleColor.DarkYellow;
    Console.WriteLine($"Was born on {dataContext.DateOfBirth,0:dddd, MMMM d, yyyy}");
  }
}

This is, obviously, a more complex template. Before we can use it, we need to register it, so we're going to add it back in the Application class like so:

C#
private static void Main()
{
  Application application = new Application { DataContext = new PersonViewModel() };
  TemplateEngine.Instance.Add(new ApplicationTemplate());
  TemplateEngine.Instance.Add(new PersonTemplate());
  application.Run();

  Console.ReadKey();
}

Now when we run our application, we're going to see something like this:

The application running for the first time with the person template type added

Wrapping Up

As we have seen, with a minimal amount of code, we have managed to build a fairly straightforward template system. We have deliberately shied away from making the registrations here too XAML like as our aim is to understand MVVM without getting bogged down too much in framework specific implementations. There is a question, of course, that we haven't addressed yet so let's tackle it now.

Which Is Better, View First or ViewModel First?

The answer is neither and both. The reality is, they both have their strengths and weaknesses and the wise MVVM developer uses both techniques as they need. Some things to bear in mind that should help to show which technique we should consider using and when:

With View first, it is trivial to see which ViewModel is in use at any point because it is closely tied to the View. Thus (and I can't believe I got to use thus in an article) it is a simple case of just looking at the view for the context. The fact that we have the ViewModel and the view tied together also means that we can easily see what properties we have available for our view - this can be a great aid to designers in more graphical frameworks. Something I despair about reading is the misunderstandings that we have to have the View and the ViewModel in the same assembly when we develop View first. This is complete tosh! As long as we have something we can resolve to the DataContext of the View at runtime, we could just work against an interface - Dependency Injection or even Service Location would let us work with our concrete ViewModel in a completely different assembly. Another inaccuracy I have seen peddled about View first is that it is less DRY. If there was a shakes head sadly while banging it off the wall smiley, I would be using it right now.

MYTH BUSTER #4. Contrary to what you might read, View first doesn't necessarily mean that the ViewModel has to live in the same project as the View.

MYTH BUSTER #5. View first doesn't mean that your code needs to be any less DRY.

With ViewModel first, we get to leverage the full power of templates and oh how that is a powerful weapon in our arsenal. In this article, we have just scratched at the very surface of what templates provide so it's appropriate now to talk about what we are going to add over the next few articles. As we are using templates, we can use template triggers to automatically respond to binding changes (which could mean more than just changes in properties in our ViewModels). This means that we can leave a lot of work to triggers, which lets us build incredibly complex interfaces with ease.

As we discussed earlier, the use of ViewModel first allows us to fall back through the object hierarchy until we find an appropriate type to display. This is something I have leveraged many times to provide specialist ViewModels that drop to a single View. This technique has been a real time saver when I have developed applications that use a list to display items for editing, but which have specialist requirements in each ViewModel. Basically, I use this technique to provide a common base ViewModel and then use specialised versions for each type. As an example of this, I used this technique with an image editing application I was working on to provide the ability to edit the attributes of different types of files (certain RAW files have very specialist ways of getting to the image metadata). This would have been very, very difficult to achieve using View first as a development technique.

What's Up Next?

We have kept the template development here very self contained. There's a lot to take in and I wanted this code to be as complete as possible to show how to render out templates and to understand how this relates to ViewModel first. That doesn't cover everything that templates provide us though. In the next article, we'll reintroduce binding support and then see how we can add triggers to our templates to react to changes in information. I hope we have shattered some more of the myths of MVVM and given some food for thought.

History

  • 23rd February, 2017: Initial version

License

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


Written By
CEO
United Kingdom United Kingdom
A developer for over 30 years, I've been lucky enough to write articles and applications for Code Project as well as the Intel Ultimate Coder - Going Perceptual challenge. I live in the North East of England with 2 wonderful daughters and a wonderful wife.

I am not the Stig, but I do wish I had Lotus Tuned Suspension.

Comments and Discussions

 
QuestionGreat tutorial Pin
bakunet19-Apr-18 9:24
bakunet19-Apr-18 9:24 
QuestionWPF version Pin
Terppe7-Mar-17 23:06
Terppe7-Mar-17 23:06 
AnswerRe: WPF version Pin
Pete O'Hanlon7-Mar-17 23:20
mvePete O'Hanlon7-Mar-17 23:20 
QuestionConsole Pin
Terppe7-Mar-17 2:16
Terppe7-Mar-17 2:16 
AnswerRe: Console Pin
Pete O'Hanlon7-Mar-17 2:26
mvePete O'Hanlon7-Mar-17 2:26 
QuestionAttention: Contributors to this site. DEFINE YOUR BLOODY ACRONYMS Pin
OldBikerPete24-Feb-17 14:02
OldBikerPete24-Feb-17 14:02 
AnswerRe: Attention: Contributors to this site. DEFINE YOUR BLOODY ACRONYMS Pin
Pete O'Hanlon24-Feb-17 21:22
mvePete O'Hanlon24-Feb-17 21:22 
GeneralRe: Attention: Contributors to this site. DEFINE YOUR BLOODY ACRONYMS Pin
Nelek2-Mar-17 22:03
protectorNelek2-Mar-17 22:03 
GeneralMy vote of 5 Pin
Daniel Vaughan24-Feb-17 0:46
Daniel Vaughan24-Feb-17 0:46 
GeneralRe: My vote of 5 Pin
Pete O'Hanlon24-Feb-17 1:02
mvePete O'Hanlon24-Feb-17 1:02 
Questionsource code download links are broken. Pin
Tridip Bhattacharjee23-Feb-17 21:47
professionalTridip Bhattacharjee23-Feb-17 21:47 
AnswerRe: source code download links are broken. Pin
Pete O'Hanlon23-Feb-17 21:57
mvePete O'Hanlon23-Feb-17 21:57 

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

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