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.

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.

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.

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.

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:

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.

Add the new Layout Control to the RegionManager.

Register any Regions contained within the Layout Control.

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.

Comments and Discussions

Hi there,
seems like a nice solution..
My question is:
What if you have two view's of the same type that supposed
to be located at the same region (as tab items for instance)??
I mean that you can specify a view name instead of view type
(at the app.config file..) and at the application level you need to set
every view with a name (Not a big disadvantage for my opinion)...

Good question. I've never tried that scenario. If it is an all or nothing proposition, I suppose you could write an extension method which returns the "name" if one is supplied or type if not? This would be easy enough to verify, just register two views, one by name, the other by type and see what happens.