Wednesday, April 21, 2010

Ensuring your Prism Modules can load multiple instances in your UI

In many Prism examples I have seen, such as the StockTrader Reference Implementation, it's not unusual to see a module register its View with the region it wants it loaded into. For example, the StockTrader's MarketModule has code like this:
public void Initialize()
{
...
this.regionManager.RegisterViewWithRegion
(RegionNames.ResearchRegion, () =>
this.container.Resolve
<ITrendLinePresentationModel>().View);
}

This has never really sat all that well with me, and it wasn't until recently that I realised why. An upcoming project has the requirement to not only load multiple instances of a Module's Views, but to load them in different Regions that could be user defined. Code like that above simply won't work in a situation like this. Unfortunately, it's also the way I've been writing my applications, and now I wish I hadn't.


So, what's a better way to have Views loaded into regions? I think the first thing to do is to never make a Module responsible for registering its Views with Regions. Instead, have a Module perform it's initialisation (such as registering all its types with Unity) and then sit there and wait for instructions on what to do next. A good idea could be to have it listen for a composite event that tells it when to load a View, and the name of the Region into which it should be loaded. By taking responsibility for determining when and where a View should be loaded away from the Module, it becomes so much more re-usable.


So what component should tell the Module what to do next? A reasonable choice would be the Shell itself, after all, the Shell is the component that defines the Regions, so it is therefore a good candidate for knowing what they should be used for.


Inside the Module itself, it will listen for load requests and respond accordingly. The code snippet below shows a method inside the Module Controller that when called will load the Modules Views into the Regions specified inside the Composite Event's payload:

/// <summary>
/// Listens for requests to load the View into a Region.
/// </summary>
/// <param name="payload">The regions to load into</param>
public void LoadViewsEventHandler(LoadViewsEventArgs payload)
{
// Create the Views using Unity
Views.IDetailsViewModel detailsViewModel =
_container.Resolve<Views.IDetailsViewModel>();

// Inject them into the given Regions
_regionManager.AddToRegion(payload.DetailsRegion,
detailsViewModel.View);
}


And lastly, the code snippet below shows the code inside the Shell's ViewModel that is called after all the Modules have been initialised. It loads three new instances of the Module's Views into different Regions inside the Shell:

public void LoadViews()
{
// Load all the views we want to show in the Shell
_eventAggregator.GetEvent<LoadViewsEvent>().Publish(
new LoadViewsEventArgs()
{
DetailsRegion = "DetailsRegion1",
});

_eventAggregator.GetEvent<LoadViewsEvent>().Publish(
new LoadViewsEventArgs()
{
DetailsRegion = "DetailsRegion2",
});

_eventAggregator.GetEvent<LoadViewsEvent>().Publish(
new LoadViewsEventArgs()
{
DetailsRegion = "DetailsRegion3",
});
}

I've included a sample project that demonstrates the concepts described here.

2 comments:

  1. This is a brilliant an elegant solution to this problem. I have been looking for a nice way of doing this and I wasn't able to find anything. So glad I came accross this blog! Thanks.

    ReplyDelete
  2. Thanks I been looking for someone who had the same idea. A lot of other developer who are using Prism has no idea what I was talking about.

    It doesn't make sense to me that the module has to know the name of the Region. It should be handed to the module in my opinion.

    By passing in the region name it makes it more flexible on where the module should load the views.

    ReplyDelete