Condividi tramite


Template/ View Selection with MEF in Silverlight

One of my favourite features in WPF is the ability to have it automagically wire up a databound object (often a ViewModel) to a particular DataTemplate. A lot of people really miss this feature when using Silverlight as it's still missing from the latest version (Silverlight 4 at the time of writing).

Folk have blogged a number of bespoke solutions, including one by my colleague Rob Garfoot: Flexible Data Template Support in Silverlight.

I was recently working on a project where I was keen to leverage similar functionality to help me use a ViewModel to manage navigation. It's a relatively common approach in my experience; we have a daddy ViewModel, maybe called MainViewModel that has a property of type object (or maybe BaseViewModel depending how you roll):

public classMainViewModel : INotifyPropertyChanged
{
// simplified, needs INPC implementation
public objectCurrentViewModel { get; set; }
}

The MainViewModel can then act as a 'Controller' as it swaps out different CurrentViewModel etc. I'm sure you can see how you would vary this particular implementation to do a bunch of different things.

With MEF being my new muse I wanted to explore how I could leverage MEF to help me do this in an elegant way. There's a lot of talk of Convention over Configuration lately and in particular, it's application to the MVVM pattern in how View's get hooked up to ViewModel's. The typical convention being WibbleView would automagically get mapped to WibbleViewModel, for example.

Call me old fashioned but I'm just not comfortable with this approach yet. I've always preferred stuff to be explicit and for me, this is a keen violation of that trend. Having said that, I also prefer stuff that's simple so a huge XML file containing aliases and a mapping table doesn't appeal to me either.

Anyway, with MEF rushing to my rescue, here's the experimental solution I came up with.

1. Create a new control based on the ContentControl. 2. Bind the ContentControl to the CurrentViewModel property. 3. Override the OnContentChangedmethod inside the content control to 'intercept' the content and, where appropriate, choose an appropriate View for this content. 4. Decorate my views with some instruction that indicates which ViewModel it should apply to. 5. Discover these views at runtime.

Points 4 and 5 are where MEF really helps out. I often think of MEF being an unusual technology as he doesn't really change the design pattern, he just makes the implementation so much easier!

This implementation uses some of the more advanced features of MEF including the ExportFactorytype and custom MetadataAttributes - I won't go into the nittydetails here, just share the key ideas and the code with you.

What we end up with...

This is the resulting usage pattern.

In the 'main' view we bind to the MainViewModel using our new ViewModelContentControl which is where most of the magic happens:

<tjoc:ViewModelContentControl
Content="{Binding CurrentViewModel}" />

Then each ViewModelis attributed with a ViewModelTemplate attribute in the code-behind. So the 'OneView' would look like this:

[ViewModelTemplate(typeof(OneViewModel))]
public partial classOneView : UserControl
{
publicOneView()
{
InitializeComponent();
}
}

And the associated OneViewModel ViewModel would be a simple, err, ViewModel. No need for anything funky there (which feels right to me).

Job Done. To preview what this ridiculously simple usage might look like, head to the original blog post here: https://www.thejoyofcode.com/Template_View_selection_with_MEF_in_Silverlight.aspx

 

Most of the magic happens inside that ViewModelContentControl. It exposes an Import property that's looking for a list of FrameworkElements (the views).

[ImportMany(typeof(FrameworkElement))]
publicObservableCollection<ExportFactory<FrameworkElement, IViewModelTemplateMetadata>> ElementFactories { get; set; }

At runtime, called from inside OnContentChanged we scan these Imports looking for a matching template:

private objectFindContainer(object content)
{
Type contentType =content.GetType();

if(ElementFactories == null|| ElementFactories.Count == 0)
{
return content;
}

var factory =ElementFactories.SingleOrDefault(ef => ef.Metadata.ViewModelType == contentType);

if (factory == null)
{
return content;
}

var fe =factory.CreateExport().Value;
fe.DataContext = content;
returnfe;
}

If we find a matching template, we set the content (the ViewModel) to be the DataContext of the newly created View. Cool.

I also added some animation when the change happens just because I could.

You likey? Get the source below. As always, this is demoware and your mileage may vary. It's just for fun.

There's a bunch of problems that aren't considered in this simple demonstration; for example, what if multiple views use a single ViewModel (of course, a further layer of abstraction would see this problem off no problem).

 

Originally posted by Josh Twist on 19th May 2010 here: https://www.thejoyofcode.com/Template_View_selection_with_MEF_in_Silverlight.aspx