Dynamic View creation

Topics: Conventions, Extensibility
Mar 9, 2013 at 1:03 PM
Edited Mar 9, 2013 at 1:03 PM
I have a need to create a specific view dynamically - meaning I won't know which view to create until runtime. The view class to use is determined based on several criteria - I have a factory class that has this knowledge. I use MEF to bring in additional installed views.

I was looking into the ViewLocator - from the name I gathered that would be a logical place to start, but that is a static class (as well as other related classes - all static). So I see no way to override or replace that functionality.

Is it possible to inject custom view creation logic into CM? The only way out of this I see now, is to have a 'View' property on the ViewModel...

Thanx,
Marc
Jun 17, 2013 at 11:35 PM
Hmm... I think the ability to have custom View creation logic is something that I need to.

I wonder how I could possibly achieve that using CM.
Jun 20, 2013 at 2:13 PM
Edited Jun 20, 2013 at 3:21 PM
Override / replace the ViewLocator.LocateForModelType value.
Like ViewLocator.LocateForModelType = ...

We are also using that. See below:
var namedViewLocator = kernel.Get<INamedViewLocator>();
namedViewLocator.Initialize(ViewLocator.LocateForModelType);
ViewLocator.LocateForModelType = namedViewLocator.LocateForModelType;
Where as the implementation of INamedViewLocator looks like that:
using System;
using System.Windows;
using Caliburn.Micro;
using Ninject;
using Ninject.Syntax;

internal class NamedViewLocator : INamedViewLocator
{
    private readonly IResolutionRoot resolutionRoot;

    public NamedViewLocator(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    private Func<Type, DependencyObject, object, UIElement> FallbackLocateForModelType { get; set; }

    public void Initialize(Func<Type, DependencyObject, object, UIElement> fallbackLocateForModelType)
    {
        this.FallbackLocateForModelType = fallbackLocateForModelType;
    }

    public UIElement LocateForModelType(Type modelType, DependencyObject displayLocation, object context)
    {
        var name = context as string;
        if (name != null)
        {
            var view = this.resolutionRoot.TryGet<UIElement>(name);
            if (view != null)
            {
                ViewLocator.InitializeComponent(view);
                return view;
            }
        }

        return this.FallbackLocateForModelType(modelType, displayLocation, context);
    }
}
XAML:
<ContentControl 
                cal:View.Context="{Binding PalletType,
                                            Mode=OneWay}"
                cal:View.Model="{Binding Pallet}"/>
So basically what we are doing is taking the Default ViewLocator.LocateForModelType Func and wrapping it in another func. Then Setting ViewLocator.LocateForModelType to that func. This is the way you "override" the view instancation behavior.
The func points to the method of an object (INamedViewLocator) created by our Dependency Injection Container (Ninject).

We have only that one special case where we need to modify the view instantiation behavior. If you have more cases, you should probably opt to make that LocateForModelType-Wrapper more generic, like being able inject multiple factories into it, or what ever.
Jul 4, 2013 at 10:52 AM
@BLJ: IC. Thanks for the idea.
The only difference is that I only need to redefine the view type. Thus, I redefined the ViewLocator.LocateTypeForModelType logic instead of ViewLocator.LocateForModelType one.