Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Blendability Part II – Design time support for Prism

, 19 Jan 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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 that 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:

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

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 can we use them.

<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:

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.

License

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

Share

About the Author

Tomer Shamam
Architect CodeValue
Israel Israel
Tomer Shamam is a Software Architect and a UI Expert at CodeValue, the home of software experts, based in Israel (http://codevalue.net). Tomer is a speaker in Microsoft conferences and user groups, and in-house courses. Tomer has years of experience in software development, he holds a B.A degree in computer science, and his writings appear regularly in the Israeli MSDN Pulse, and other popular developer web sites such as CodePlex and The Code Project. About his thoughts and ideas you can read in his blog (http://blogs.microsoft.co.il/blogs/tomershamam).
Follow on   Twitter

Comments and Discussions

 
GeneralGood stuff Tomer PinmvpPete O'Hanlon24-Jan-11 1:20 
GeneralRe: Good stuff Tomer PinmemberTomer Shamam24-Jan-11 1:30 
GeneralRe: Good stuff Tomer PinmvpPete O'Hanlon24-Jan-11 1:55 
GeneralRe: Good stuff Tomer PinmemberTomer Shamam24-Jan-11 1:59 
GeneralRe: Good stuff Tomer PinmvpPete O'Hanlon24-Jan-11 2:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 20 Jan 2011
Article Copyright 2011 by Tomer Shamam
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid