The Influence of MVC on my WPF

I was introduced to MVC as a pattern for Web development through Castle MonoRail. From there I studied a bit of Ruby on Rails (not enough) as well as the general philosophy of 37signals.

As I mentioned before, I thought I was moving away from Web development. However, the dive into ASP.NET MVC that Silver Arcade brought about has roped me back in.

My work during the day (and some nights too) is still WPF though. I’ve discovered that MVC thinking has seeped into my WPF development.

Convention Over Configuration

A characteristic of most MVC frameworks is Convention Over Configuration. In short, it means that your “environment” makes assumptions about what you want and that you only need to be explicit about the exceptions.

A practical example of this is the way that the View for an Action is inferred from the name of the Controller and Action. After creating your Action, you create a folder for the controller and and view that matches the name of the action. It just works. If you need to make exception, you can.

An Example in WPF

In our WPF development we employ a number of Separated Presentation patterns. One of the more common patterns we use is MVP (in particular, the flavor of MVP implemented in Caliburn, which is really Supervising Controller).

Imagine a simple contact manager application. We can add new contacts, view a list of all contacts, and open individual contacts for viewing or editing. We can open as many individual contacts as we like.

mvc

Let’s say that there are buttons to open up the “Add New” view and “All Contacts” view. We can open an existing contact by double-clicking on them in the “All Contacts” view.

my imaginary solutionMy solution setup for this imaginary (and highly contrived) application might look like this.

ApplicationPresenter would be the root object of my UI. It would have an ObservableCollection of IPresenter that would be bound to a TabControl in Shell.xaml. Likewise, ApplicationPresenter will have a CurrentPresenter of IPresenter that will be bound to the SelectedItem on the TabControl.

Shell.xaml is my only window in this example. All of the other views are user controls. I named it “shell” because I think of it as the outermost level of the views. Shell’s data context is set to ApplicationPresenter.

I’m not going to dive too much deeper into this, because I want to get to my real point.

My presenters don’t know anything about their views. It’s up to data binding to render each presenter with the correct view. This means that I need to create data templates for each of the presenters. Not such a big deal in a small application, but it can get tedious as the application grows.

Hmmm… This structure is beginning to look a lot like an MVC app. Perhaps I can have it automatically infer my data templates for me?

Some Code

Yes we can. Here’s a very naiveté bit of code that will do just that. It makes some big assumptions:

  • all of your presenters implement IPresenter
  • all of your presenters are in a namespace containing “.Presenters”
  • all of your views are in an analogous namespace containing in “.Views”

Again, this is not production ready code, just my spike to prove the idea works. Please feel free to make suggestions.

public static class MvpConfiguration
{
    /// <summary>
    /// Creates a set of data templates pair to presenter classes based on naming conventions.
    /// </summary>
    /// <remarks>If a data template is already present in the resources, then it is skipped.</remarks>
    /// <param name="resources">The ResourceDictionary that the data templates for the views will be added to.</param>
    public static void InferViewsFromPresenters(ResourceDictionary resources)
    {
        var lookup = MatchPresentersToViews();

        foreach (var pair in lookup)
        {
            var template = CreateDataTemplate(pair.Value, pair.Key);
            if (resources.Contains(template.DataTemplateKey)) continue;
            resources.Add(template.DataTemplateKey, template);
        }
    }

    private static Dictionary<Type, Type> MatchPresentersToViews()
    {
        var presenters = from type in Assembly.GetExecutingAssembly().GetTypes()
                         where typeof (IPresenter).IsAssignableFrom(type)
                         select type;

        var first = presenters.FirstOrDefault();
        if (first == null) throw new Exception("I expected to find at least one presenters!");

        string viewNamespace = first.Namespace
            .Replace(".Presenters", ".Views");
        viewNamespace = viewNamespace.Substring(0, viewNamespace.IndexOf(".Views")+6);
        //yeah, that was hackish

        var views = from view in Assembly.GetExecutingAssembly().GetTypes()
                    where !string.IsNullOrEmpty(view.Namespace)
                          && view.Namespace.StartsWith(viewNamespace)
                    select view;

        var table = new Dictionary<Type, Type>();

        foreach (var view in views)
        {
            var presenter = GetPresenterForView(view, presenters);
            if(presenter == null) continue;

            table.Add(presenter,view);
        }

        return table;
    }

    private static Type GetPresenterForView(Type type, IEnumerable<Type> presenters)
    {
        string name = type.Name.Replace("View", "Presenter");
        var presenter =
            presenters.Where(p => p.Name == name).FirstOrDefault();
        return presenter;
    }

    private static DataTemplate CreateDataTemplate(Type viewType, Type dataType)
    {
        var template = new DataTemplate(dataType);

        var factory = new FrameworkElementFactory(viewType);
        template.VisualTree = factory;
        return template;
    }
}

To use this, in your App.xaml.cs within OnStartup() add:

MvpConfiguration.InferViewsFromPresenters(Resources);

What do you think? Am I totally nuts?

Oh, and I should also point out that FrameworkElementFactory is actually deprecated. :-( Caveat emptor.


Posted 05-06-2009 3:48 PM by Christopher Bennage

[Advertisement]

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)