Creating multiple shells is the correct idea. You just need to take care of the details appropriately.
When and how to create a new Shell
The Prism way is, of course, to have a DelegateCommand
handle the creation of a new shell. Considering that this command does not strictly belong to any particular ViewModel (I 'd say it has an application-wide scope), it feels better to me to have a public static class ApplicationWideCommands
with a CreateNewShellCommand
static property. You can then either bind to it from XAML with {x:Static}
or execute it from code-behind as needed.
This command would need to take care of two things:
- Create the new
Window
(actually, a Shell
)
- Instantiate a new
IRegionManager
for the new shell, so that there is no conflict in region names between the regions in the existing shell and those in the new shell
- Instruct the regions in the new shell that they belong to the new
IRegionManager
I 'll tackle this last-to-first, because it's easier to explain.
Giving your new Shell a new RegionManager
When declaring a region in Prism you can declare the region manager to use in addition to the region name. Normally you don't need to do this, but here we need to choose which RegionManager
to use because region names must be unique in the scope of a single region manager. Since the region names are hardcoded inside the XAML of the Views, and it would be a major pain to assign them another way, we need to change the other half of the equation: the region manager instance used by each shell. So inside Shell.xaml
there might be something like this:
<ContentControl
regions:RegionManager.RegionManager="{Binding RegionManager}"
regions:RegionManager.RegionName="ExampleRegion"
/>
This will instruct the "WorkspaceRegion" in each shell that it belongs to the IRegionManager
provided by the binding. Since the shell usually has no DataContext
, we can declare the RegionManager
property in the shell class itself:
public partial class Shell : Window
{
public Shell(IRegionManager regionManager)
{
this.RegionManager = regionManager;
InitializeComponent();
}
public IRegionManager RegionManager { get; private set; }
}
So now we just need to make sure that each Shell
instance gets its own RegionManager
. For the "first" shell, this will be done by the BootStrapper
. (The code below uses the DI container to resolve objects, and the examples use the UnityContainer
. If you use MEF for dependency injection just mentally translate to the equivalent code.)
protected override DependencyObject CreateShell()
{
// I am assuming you have a reference to the DI container
var regionManager = this.Container.Resolve<IRegionManager>();
return new Shell(regionManager);
}
For the other shells, it will be done by the CreateNewShellCommand
:
private static ExecuteCreateNewShellCommand()
{
// I am assuming you have a reference to the DI container
var regionManager = this.Container.Resolve<IRegionManager>();
ver newRegionManager = regionManager.CreateRegionManager();
var shell = new Shell(newRegionManager);
// The rest is easy, for example:
shell.Show();
}
There is an important caveat here: The RegionManager
is registered into the container as a singleton. This means that whenever you resolve IRegionManager
you will be getting back the same instance. For this reason, we create a new instance by calling the IRegionManager.CreateRegionManager
method (this applies Prism v4; I 'm not sure about v2).
At this point, you know how to create any number of new Shell
instances and wire up the regions accordingly.
UI composition details
The final detail you need to take care of is that all regions hosted in each shell, no matter how deep in its visual tree, have to bind to the same RegionManager
.
This means that you have to explicitly set the region manager to use like we did in the ContentControl
example above for all regions in all Views in your application. Fortunately, this is done quite easily because:
- All Views will end up being descendants of a
Shell
in the visual tree
- The
Shell
already exposes the correct RegionManager
as a property, so we can bind to that
You would do so like this:
<ItemsControl
regions:RegionManager.RegionManager="{Binding RegionManager, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Shell}}}"
regions:RegionManager.RegionName="AnotherRegion"
/>
All set!
You now should be ready to go.