Click here to Skip to main content
11,706,142 members (61,495 online)
Click here to Skip to main content

Layout Manager for Prism v2

, 24 Apr 2009 CPOL 48.5K 1.2K 55
Rate this:
Please Sign up or sign in to vote.
Provides layout management for Composite WPF (Prism) applications
diagram.jpg

Introduction

One of the issues you may encounter when working on a Prism project is the management of regions and views within your application. While the RegionManager does an adequate job of managing regions, the orchestration of views and regions is pretty much left up to the developer.

A common approach is to define string constants in a common infrastructure assembly and injecting views into regions using these constants. This gets the job done, but adds rigidity to your application. For applications which require multiple layouts, coordinating regions and views can be a bit tedious.

One common approach I would not recommend is injecting your views in your module's Initialize method.

public void Initialize()
{
var view = new MyView();
_Container.RegisterInstance<IMyView>(view);
_RegionManager.Regions[RegionNames.Shell].Add(view);
}

This violates the encapsulation of the module, restricting the reuse of the module.

Background

On one project, we opted to create a "layout module." The sole purpose of this module was to load a layout UserControl into the Shell region of the main application window, and injecting the views into its own defined regions. Definitely a step in the right direction by decoupling the module views from the regions. The layout module was defined and loaded like any other module, but had to be the last module loaded due to its dependencies. One drawback to this approach was the increasing number of dependencies. The layout module had to reference all the infrastructure assemblies of the views it was required to manage.

Still this solution felt a bit too purpose-built. And other issues quickly arose, such as multiple layout support.

Ideally we were looking for a complete decoupling of regions and views with the ability to dynamically load layouts as required.

We quite liked the idea of using "layout views", views whose sole purpose was to define regions, and providing no business or UI logic. But, the source and introduction of these views needed to be dynamic and flexible.

Using the Code

The LayoutManager is my first attempt at tackling this issue. Its purpose is to dynamically manage one or more layout configurations for a Prism application.

To compile and run the LayoutManager you will need Visual Studio 2008 SP1 and the latest version of the Composite Application Guidance for WPF and Silverlight - February 2009.

The solution is fairly standard Prism solution, consisting of an Infrastructure, Shell and Modules projects. For the sake of simplicity, I've only included a single Modules project, where normally there would be more.

solution.jpg

classDiagram.jpg

The LayoutManager maintains a collection of Layout objects, which define layout controls, along with the views that will reside in the layout.

Configuration

The LayoutManager is configured by a LayoutProvider specified in your app.config file.

<section name="layoutProvider" type="Composite.Layout.Configuration.LayoutProviderSection, Composite.Layout"/>

Currently, two providers are available: ConfigLayoutProvider and XamlLayoutProvider. Custom providers can be used by inheriting from LayoutProviderBase.

ConfigLayoutProvider

Defines the LayoutManager in the app.config file as shown below:

<layoutProvider name="ConfigLayoutProvider" 
    type="Composite.Layout.Configuration.ConfigLayoutProvider, Composite.Layout">
    <layoutManager shellName="Shell" >

      <layouts>
        <layout name="FirstLayout" 
              filename="Layouts\FirstLayout.xaml" 
              fullname="First Layout" 
              isDefault="True"
              description="This is the default layout" 
              thumbnailSource="pack://application:,,,/LayoutManager.Infrastructure;
              component/Resources/Images/layout1.png">

          <views>
            <view typeName="LayoutManager.Infrastructure.IViewA,
                LayoutManager.Infrastructure" regionName="Left"  />
            <view typeName="LayoutManager.Infrastructure.IViewB,
                LayoutManager.Infrastructure" regionName="Right" />

            <viewModel typeName="LayoutManager.Infrastructure.IMenuViewModel,
                LayoutManager.Infrastructure" regionName="Menu"  viewProperty="View"/>
          </views>
        </layout>

        ...
          </layouts>
    </layoutManager>
  </layoutProvider>
XamlLayoutProvider

Defines the LayoutManager in Xaml

<layoutProvider name="XamlLayoutProvider"

           type="Composite.Layout.Configuration.XamlLayoutProvider, Composite.Layout"
           filename="Layouts\LayoutConfiguration.xaml"/>

The source of the Xaml can be specified by type or by filename.

<Layout:LayoutManager xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                      xmlns:Layout=
                          "clr-namespace:Composite.Layout;assembly=Composite.Layout"
                      xmlns:Infrastructure=
    "clr-namespace:LayoutManager.Infrastructure;assembly=LayoutManager.Infrastructure"
                      ShellName="Shell">
    <Layout:LayoutManager.Layouts>
        <Layout:Layout x:Name="FirstLayout"

                       Fullname="First Layout"
                       Filename="Layouts\FirstLayout.xaml"
                       Description="This is the default layout"
                       ThumbnailSource=
"pack://application:,,,/LayoutManager.Infrastructure;component/Resources/Images/layout1.png"
                       IsDefault="True">

            <Layout:Layout.Views>
                <Layout:ViewModel RegionName="Menu"
                                  Type="{x:Type Infrastructure:IMenuViewModel}"
                                  ViewProperty="View" />

                <Layout:View RegionName="Left"
                             Type="{x:Type Infrastructure:IViewA}" />
                <Layout:View RegionName="Right"
                             Type="{x:Type Infrastructure:IViewB}" />

            </Layout:Layout.Views>
        </Layout:Layout>
	...
    </Layout:LayoutManager.Layouts>
</Layout:LayoutManager>

Each Layout contains a Views collection. The views collection accommodates both Views and ViewModels. The View specifies what view control is to be loaded and what region it is to be placed in. You can also set the visibility for the view. Use the ViewProperty of the ViewModel to specify the name of the property on your ViewModel which holds the View.

The LayoutManager is loaded after all of the modules have initialized. In the Bootstrapper.cs:

protected override void InitializeModules()
{
    base.InitializeModules();
    InitializeLayoutManager();
}

private void InitializeLayoutManager()
{
    var layoutManager = LayoutConfigurationManager.LayoutManager;
    layoutManager.Initialize(Container);
    Container.RegisterInstance(layoutManager,
        new ContainerControlledLifetimeManager());
    //parameterless LoadLayout loads the default Layout into the Shell
    layoutManager.LoadLayout();
}

The LayoutManager requires use of the Container. Once your layouts have been loaded, call the Initialize method passing in the container.

Once that is done, you can register the LayoutManager in the container making it accessible to other modules.

Loading a Layout

Layouts are loaded by calling the LoadLayout method of the LayoutManager.

  • LoadLayout() - loads the default layout in the Shell
  • LoadLayout(string layoutName) - loads the named layout in the Shell

The MenuViewModel.cs illustrates the use of LoadLayout:

private void LayoutCommandExecute(ILayout layout)
{
var layoutManager = _Container.Resolve<ILayoutManager>(); 
layoutManager.LoadLayout(layout.Name);
}

The basic sequence of loading a layout is:

  1. If there is a current layout, remove it from the RegionManager.
  2. Clear out any controls that were bound to any regions. This step is necessary otherwise you will get an InvalidOperationException ("This control is being associated with a region, but the control is already bound to something else.") when you try to reload it in the future. Currently, the LayoutManager only supports ItemsControls, ContentControls and Panels using the RegionManager.RegionName attached property.
  3. Add the new Layout Control to the RegionManager.
  4. Register any Regions contained within the Layout Control.
  5. Load any views associated with the new layout.

Events

There are several events raised by the LayoutManager:

  • LayoutManagerInitializedEvent - raised at the end of Initialize (see MenuViewModel.cs for an example of subscribing to this event)
  • LayoutLoadingEvent - raised at the beginning of LoadLayout
  • LayoutLoadedEvent - raised at the end of LoadLayout
  • LayoutUnloadingEvent - raised before the current layout is about to be unloaded
  • LayoutUnloadedEvent - raised after the current layout has been unloaded

All of these events are published through the EventAggregator.

Limitations

Currently there are several limitations with the LayoutManager, these are:

LayoutManager currently only supports UserControls as layout controls. There is also the basic assumption that your application main window has a single region defined, where layout controls are injected. Regions must be defined in XAML using the RegionManager.RegionName attached property.

Other Considerations

While the LayoutManager does decouple the regions from the views, it does not entirely do away with string-based region names. Dynamic manipulation of regions and views in code will still rely on region names (see the AddCommandExecute method in MenuViewModel.cs on how to programmatically add layouts). And region name attributes must match actual region names in the Layout control.

A possible approach to addressing this dependency may be to introduce a RegionType enumeration such as Top, Bottom, Left, Right, Center, StatusBar, Menu, Toolbar, etc. In which case, the LayoutManager could resolve these regions regardless of string names.

I have not tested the LayoutManager in all possible scenarios, such as nested layouts and custom RegionAdapters, or with Silverlight.

I am interested in feedback/comments/suggestions from the Prism community.

Points of Interest

For a good introduction to using Prism and WPF check out:

History

  • 13th April, 2009: Initial post
  • 23rd April, 2009: Refactored the Composite Layout assembly, including change of namespace. LayoutManager, Layout, View and ViewModel are now DependencyObjects. Decoupled layout configuration from the app.config file, allowing for flexible configuration. Added ConfigLayoutProvider and XamlLayoutProvider. Add token views and custom menus.

License

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

Share

About the Author

Ron Gramann
Software Developer (Senior)
United Kingdom United Kingdom
I'm a software developer living in Northampton, UK, and enjoy working with the latest .Net technologies.

You may also be interested in...

Comments and Discussions

 
QuestionQuestion Pin
MikeyW10-Jul-13 9:14
memberMikeyW10-Jul-13 9:14 
AnswerRe: Question Pin
Ron Gramann10-Jul-13 10:42
memberRon Gramann10-Jul-13 10:42 
Generalquestion Pin
shayderi14-Jul-10 3:57
membershayderi14-Jul-10 3:57 
GeneralRe: question Pin
Ron Gramann16-Jul-10 22:31
memberRon Gramann16-Jul-10 22:31 
GeneralWindow Manager Pin
ChristianR192-May-10 8:08
memberChristianR192-May-10 8:08 
GeneralRe: Window Manager Pin
Ron Gramann4-May-10 4:27
memberRon Gramann4-May-10 4:27 
GeneralRe: Window Manager Pin
Mohammed Abed24-May-10 21:23
memberMohammed Abed24-May-10 21:23 
GeneralRe: Window Manager Pin
Ron Gramann24-May-10 23:01
memberRon Gramann24-May-10 23:01 
GeneralRe: Window Manager Pin
Craig Petersen13-Jul-14 7:41
memberCraig Petersen13-Jul-14 7:41 
GeneralThanks for the helpful sample Pin
kfrosty12-Mar-10 3:43
memberkfrosty12-Mar-10 3:43 
GeneralRe: Thanks for the helpful sample Pin
Ron Gramann17-Mar-10 9:51
memberRon Gramann17-Mar-10 9:51 
GeneralUI Controller Pin
dakash28-Apr-09 3:50
memberdakash28-Apr-09 3:50 
GeneralRe: UI Controller Pin
Ron Gramann28-Apr-09 4:24
memberRon Gramann28-Apr-09 4:24 
GeneralRe: UI Controller [modified] Pin
dakash28-Apr-09 5:14
memberdakash28-Apr-09 5:14 
GeneralRe: UI Controller Pin
Ron Gramann28-Apr-09 7:52
memberRon Gramann28-Apr-09 7:52 
GeneralRe: UI Controller Pin
dakash29-Apr-09 5:55
memberdakash29-Apr-09 5:55 
GeneralRe: UI Controller Pin
Ron Gramann29-Apr-09 7:47
memberRon Gramann29-Apr-09 7:47 
GeneralRe: UI Controller Pin
dakash29-Apr-09 11:04
memberdakash29-Apr-09 11:04 
GeneralRe: UI Controller Pin
Ron Gramann30-Apr-09 0:23
memberRon Gramann30-Apr-09 0:23 
GeneralSome feedback / Questions Pin
cbertolasio20-Apr-09 17:46
membercbertolasio20-Apr-09 17:46 
I have read your article pretty thoroughly. I feel your pain in that managing views in regions is a bit tedious.

I have some questions as I am not sure what you are trying to do and what problem it solves.

- what is a layout supposed to be and why do you need a layout?
- have you considered Scoped Regions? So Module A has a ViewA that defines Regions inside of ViewA and all of the subordinate views of Module A are injected into the scoped regions inside of View A? Module A still depends on infrastructure but all views inside of module A can still communicate outside of module A using infrastructure.
- your view is decoupled from the module, but coupled to a layout, that is now defined in config. youre still coupled to where in the UI the view will reside, views are put into layouts now, and layouts are put into regions. what have you accomplished? you have moved the problem to some other code.
- if you dont beleive that a module should be responsible for loading views into regions, I beleive that this is where a supervising controller comes into play? module loads controller, controller does the work and determines where to place the view.
- are your customers supposed to build new xml configuration files for new layouts? why and when does your customer need a new layout?
- if you have common commands, events, regions, services, and DTOs in your infrastructure and each module depends on infrastructure why would your modules be coupled and dependent upon one another? Module A publishes event, module B just happens to be listening for the event. The event is defined in infrastructure. How much more de-coupled can that be?
- suppose that you publish your regions, commands, events, services, toolbar / menubar views etc that live in your infrastructure module, and other module developers come along and create modules for use in your application, and now you want a new "skin / laout", dont you just define a new shell and load NewShell instead of the old shell, and poof, as long as you have not broken your published API, then the module developers module should work in your new shell.

I do appreciate your thoughts on the topic, but it feels like you may be creating a generalization that may never be used or may never solve a specifc problem, or maybe I dont see the specific problem you are trying to solve. Possibly consider creating a specialization that meets the customers needs and works, and then if needed, consider generalizations.

Another question you might ask is "what would your solution look like if you didnt have this problem?"

if you are curious to chat outside of codeProject I would be more than happy to kick around some ideas... cbertolasio AT gmail DOT com.

Regards
GeneralRe: Some feedback / Questions Pin
Ron Gramann21-Apr-09 8:02
memberRon Gramann21-Apr-09 8:02 
GeneralRe: Some feedback / Questions Pin
cbertolasio21-Apr-09 19:12
membercbertolasio21-Apr-09 19:12 
GeneralRe: Some feedback / Questions [modified] Pin
Ron Gramann21-Apr-09 23:51
memberRon Gramann21-Apr-09 23:51 

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 | Terms of Use | Mobile
Web03 | 2.8.150819.1 | Last Updated 24 Apr 2009
Article Copyright 2009 by Ron Gramann
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid