Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Blendability Part II – Design Time Support for Prism

5.00/5 (4 votes)
19 Jan 2011CPOL3 min read 19.2K  
Blendability Part II – Design time support for Prism

In my previous post, I’ve presented the Blendability concept and explained how to leverage Blend’s sample-data generation in order to support view-model at design time.

In this post, I would like to continue with this concept and reveal a tiny research I’ve done related to design time support of Prism modules.

If you’ve ever developed a WPF Composite Application using Prism, you may be aware of a frustrating problem while trying to work with the Shell at design time. It always ends with something like this:

image

As you can see, the Shell is always left blank, and there is no way to design it properly since no module was loaded at design time and you don’t have the perspective of a whole picture.

To fix this problem, all you have to do is force Blend or Visual Studio designers to load the missing modules.

A good start point would be the RegionManager class. As you may notice, we are using the RegionManager to “mark” a WPF control as region by setting the RegionManager.RegionName attached property.

Digging inside RegionManager, you can see the following lines of code:

C#
public static readonly DependencyProperty RegionNameProperty =
  DependencyProperty.RegisterAttached(
    "RegionName",
    typeof(string),
    typeof(RegionManager),
    new PropertyMetadata(OnSetRegionNameCallback));
private static void OnSetRegionNameCallback(
   DependencyObject element,
   DependencyPropertyChangedEventArgs args)
{
    if (!IsInDesignMode(element))
    {
        CreateRegion(element);
    }
}

Looking at the definition of the RegionNameProperty attached property, there is a OnSetRegionNameCallback property changed callback. This method actually does the job. But as you can see, in case you’re at design-time, it simply does nothing. Removing this condition yields an exception at design-time, caused by a missing service locator.

So now we are dealing with how to force the creation of a service locator, and so many other services such as the IEventAggregator.

Herein, let me introduce the Designtime Bootstrapper. This guy is a special Prism bootstrapper we will use for design time-only modules, easily created from XAML.

Now before I shall begin, let’s have few words about the differences between module’s run-time and design-time activities.

Working at design-time, it’s not reasonable to load a module as it was at run-time. There may be special module configuration and run-time activities such as server calls, which are not appropriate at design-time. Here, you should be considering developing modules for design-time only. But this one is just my friendly suggestion, not a mandatory solution.

Now let’s continue with the Designtime Bootstrapper. Starting with Prism 4, there are two options to load modules: Unity and MEF. In this post, I’ll concentrate in one of these two options, which is MEF of course.

Loading modules with MEF, you should create a MefBootstrapper.

Here is my design-time MEF bootstrapper:

C#
[ContentProperty("Catalog")]
public class DesigntimeMefBootstrapper :
    MefBootstrapper, ISupportInitialize
{
    /// <summary>
    /// Gets or sets the design-time catalog.
    /// </summary>
    public DesigntimeCatalog Catalog
    {
        get;
        set;
    }
 
    /// <summary>
    /// An empty stub to attach the bootstrapper from XAML.
    /// </summary>
    public static void SetBootstrapper(
        Application application,
        DesigntimeMefBootstrapper bootstrapper)
    {
    }        
 
    /// <summary>
    /// There is no need for Shell at design-time.
    /// </summary>
    protected override DependencyObject CreateShell()
    {
        return null;
    }
 
    /// <summary>
    /// Use the Catalog added at design time.
    /// </summary>
    protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();
 
        if (Catalog != null)
        {
            AggregateCatalog.Catalogs.Add(Catalog);
        }
    }
        
    protected override IModuleCatalog CreateModuleCatalog()
    {
        return new ConfigurationModuleCatalog();
    }
 
    /// <summary>
    /// Register the container instance so it can be imported later.
    /// </summary>
    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
            
        Container.ComposeExportedValue<CompositionContainer>(Container);
    }
 
    void ISupportInitialize.BeginInit()
    {            
    }
 
    void ISupportInitialize.EndInit()
    {
        if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
        {
            Run();
        }
    }
}

This design-time bootstrapper provides the following:

  1. Special Catalog property of type DesigntimeCatalog. This is an adapter for the MEF catalog so we can create design-time catalogs directly from XAML.
  2. Implementation of the ISupportInitialize interface to ensure that all properties are initialized from XAML before acting.
  3. Stub method for creating a design-time bootstrapper directly from Application’s XAML as an attached property.

And here is one of my MEF catalog adapters for design-time:

C#
public class DesigntimeAssemblyCatalog : 
	DesigntimeCatalogAdapter<AssemblyCatalog>
{
    protected override AssemblyCatalog CreateInnerCatalog()
    {
        var assembly = Assembly.Load(AssemblyName);
        return new AssemblyCatalog(assembly);
    }
 
    public string AssemblyName { get; set; }             
}

Having these, let's jump into the Application’s XAML file to see how we can use them.

XML
<Application x:Class="Blendability.Prism.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ts="http://blogs.microsoft.co.il/blogs/tomershamam">
 
    <ts:DesigntimeMefBootstrapper.Bootstrapper>
        <ts:DesigntimeMefBootstrapper>
            <ts:DesigntimeAggregateCatalog>
                <ts:DesigntimeAssemblyCatalog AssemblyName=
				"Blendability.Modules.Camera2dT1" />
                <ts:DesigntimeAssemblyCatalog AssemblyName=
				"Blendability.Modules.DesigntimeModule" />
            </ts:DesigntimeAggregateCatalog>
        </ts:DesigntimeMefBootstrapper>
    </ts:DesigntimeMefBootstrapper.Bootstrapper>
 
    <Application.Resources>
 
    </Application.Resources>
 
</Application>

As you can see, I’ve created an instance of type DesigntimeMefBootstrapper directly from the Application’s XAML file, configured it to load two modules: a regular module, and another one created for design-time only.

Now that everything is ready, let’s go back to where we started. We had to remove the design-time condition from the RegionManager, OnSetRegionNameCallback method.

Since Prism 4 is an open source, we can open the RegionManager source code and change it, but if you ask me, I’d rather have an adapter than change the original code since who knows what will be brought with the next version of Prism.

So here is my adapter:

C#
public class RegionManager
{        
    public static readonly DependencyProperty RegionNameProperty =
        DependencyProperty.RegisterAttached(
            "RegionName",
            typeof(string),
            typeof(RegionManager),
            new PropertyMetadata(OnSetRegionNameCallback));
        
    public static void SetRegionName(
        DependencyObject regionTarget,
        string regionName)
    {
        if (regionTarget == null)
            throw new ArgumentNullException("regionTarget");
        regionTarget.SetValue(RegionNameProperty, regionName);
    }
        
    public static string GetRegionName(DependencyObject regionTarget)
    {
        if (regionTarget == null)
            throw new ArgumentNullException("regionTarget");
        return regionTarget.GetValue(RegionNameProperty) as string;
    }
 
    private static void OnSetRegionNameCallback(
        DependencyObject element,
        DependencyPropertyChangedEventArgs args)
    {
        Microsoft.Practices.Prism.Regions.RegionManager.SetRegionName(
            element, (string)args.NewValue);
 
        if (DesignerProperties.GetIsInDesignMode(element))
        {
            CreateRegion(element);
        }            
    }
 
    private static void CreateRegion(DependencyObject element)
    {
        IServiceLocator locator = ServiceLocator.Current;
        DelayedRegionCreationBehavior regionCreationBehavior =
            locator.GetInstance<DelayedRegionCreationBehavior>();
        regionCreationBehavior.TargetElement = element;
        regionCreationBehavior.Attach();
    }
}

Ok, now let’s change the shell to work with the region-adapter and see what happens at design-time.

imageimage

This is it guys, we are done! Now we are able to watch the whole story at design-time.

Update #1: Changed RegionManager to RegionAdapter to prevent confusion.

Here is the code.

In the next post, I’ll talk about a replacement for the view-model locator using MEF, providing a nice and clean solution for design-time.

Happy new year to you all.

Image 4Image 5

License

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