views:

23

answers:

1

My understanding is that Silverlight does not support DataTemplates with a DataType attribute.

How then would you accomplish the following in SL (author is Josh Smith, full link below). In a nutshell, he's saying that if you bind a TabControl's tab pages to a collection of ViewModels, WPF will figure out how to display each one on the fly by looking for a DataTemplate that has the appropriate (matching) DataType set. Way cool, but I'm wondering how you would (could?) do this in Silverlight.

Applying a View to a ViewModel

MainWindowViewModel indirectly adds and removes Workspace­ViewModel objects to and from the main window's Tab­Control. By relying on data binding, the Content property of a TabItem receives a ViewModelBase-derived object to display. ViewModelBase is not a UI element, so it has no inherent support for rendering itself. By default, in WPF a non-visual object is rendered by displaying the results of a call to its ToString method in a TextBlock. That clearly is not what you need, unless your users have a burning desire to see the type name of our ViewModel classes!

You can easily tell WPF how to render a ViewModel object by using typed DataTemplates. A typed DataTemplate does not have an x:Key value assigned to it, but it does have its DataType property set to an instance of the Type class. If WPF tries to render one of your ViewModel objects, it will check to see if the resource system has a typed DataTemplate in scope whose DataType is the same as (or a base class of) the type of your ViewModel object. If it finds one, it uses that template to render the ViewModel object referenced by the tab item's Content property.

The MainWindowResources.xaml file has a Resource­Dictionary. That dictionary is added to the main window's resource hierarchy, which means that the resources it contains are in the window's resource scope. When a tab item's content is set to a ViewModel object, a typed DataTemplate from this dictionary supplies a view (that is, a user control) to render it, as shown in Figure 10.in Figure 10.

http://msdn.microsoft.com/en-us/magazine/dd419663.aspx in Figure 10.

+1  A: 

Here is ONE way you can do it. I have used a technique like this in the past, and had great success with it.

Consider a very simple container that will create the view for you like this:

public class ViewMapper : ContentControl
{
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);

        if (e.Property.Name == "DataContext")
            WhenDataContextChanges();
    }

    private void WhenDataContextChanges()
    {
        if (DataContext == null)
            Content = null;
        else
            Content = ViewFactory.GetView(DataContext.GetType());
    }
}

EDIT

So, you can use this control to do the mapping for you:

<Border DataContext="{Binding MyViewModel}">
    <ViewMapper />
</Border>

END EDIT

Note that ViewMapper simply waits for the data context to change, looks up the appropriate view for the data type, and creates a new one. It relies on ViewFactory, which is a very simple static lookup that maps types to views:

public class ViewFactory
{
    private static readonly Dictionary<string, Func<UIElement>> _registry = new Dictionary<string, Func<UIElement>>();

    private static string Key(Type viewModelType)
    {
        return viewModelType.FullName;
    }

    public static void RegisterView(Type viewModelType, Func<UIElement> createView)
    {
        _registry.Add(Key(viewModelType), createView);
    }

    public static UIElement GetView(Type viewModelType)
    {
        var key = Key(viewModelType);
        if (!_registry.ContainsKey(key))
            return null;

        return _registry[key]();
    }
}

Then, you simply need to register the view mappings some place:

ViewFactory.RegisterView(typeof(SomeViewModel), () => new SomeView());

Note that ViewFactory could just as easily use Activator.CreateInstance instead of using the Func mechanism. Take that one step further, and you can use an IoC container... You could always decide to map via a string Name property on the ViewModel instead of a type... the possibilities are endless and powerful here.

Brian Genisio
That's really cool. Am I correct in assuming that this works with user controls, and that when you create your user control, you have to have it inherit from ViewMapper so it'll know what to do when its DataContext changes? That's cool in its own right, but the article above allows you to bind a TabControl's (or whatever) item Source to a list of ViewModels, and each knows how to display based on *typed* data templates. I was just wondering if that was possible in SL, but maybe I'm misunderstanding your post
Adam
Note my edit. If you want to use the `ViewMapper`, just put it in place where you want the mapping to happen. Notice how I put a `ViewMapper` inside a `Border`. You could just as easily put it inside of your tab and get the data mapping. I actually prefer a method like this instead of DataTyped DataTemplates because it gets the mapping out of the view, and leaves it up to configuration.
Brian Genisio