If you’ve been following this blog long enough, you should know I’m an avid prism fan. Most of my projects I am making use of Prism or Composite Application Guidance. This is not a post for learning Prism, but to provide a solution, not so straight-forward for a rather straight-forward problem.
I must add that that rarely I had had been pushed to extreme solutions such as I show you here. For most scenarios you are pretty much good with the default behaviors.
In my scenario I’ve been using a control as a region. This control requires that when adding views to it, it must be done with RegisterViewWithRegion extension method. The concept is simple in contrast to the “regular” AddToRegion method. RegisterViewWithRegion doesn’t requires the region to be initialized, yet. When the region becomes available than those views who had been registered will be called via delegates and added to the now ready region.
A regular usage of RegisterViewWithRegion is as follows:
public void AddView(object view)
{
RegionManager.RegisterViewWithRegion(RegionNames.MainRegion, () => view);
}
A common scenario is to allow clients to "readd” a view, but instead of duplicating it, just put into focus, or in Prism concepts, activate the view. This is were things get messy. For an example let’s simplify it a bit and for the following wrong code snippet let’s just do nothing when the view already exist.
public void AddView(object view)
{
if (!RegionManager.Regions[RegionNames.MainRegion].Views.Contains(view))
{
RegionManager.RegisterViewWithRegion(RegionNames.MainRegion, () => view);
}
}
Why is this code bad? if have a reason to use RegisterViewWithRegion, most likely it fail to check if the view exist if the region is not ready yet. So we need to move this check into the action delegate, to be called only when the region is ready.
public void AddView(object view)
{
RegionManager.RegisterViewWithRegion(RegionNames.MainRegion,
() =>
{
if (!RegionManager.Regions[RegionNames.MainRegion].Views.Contains(view))
{
return view;
}
return null;
});
}
Last code looks formidable but there is a catch, when we returning null, it is actually adding a null view to the region. In my scenario it’s really an undesired side effect of the whole situation. It is worth mentioning that a RegionAdapter doesn’t solve this issue completely, when you react to a view being added you can decide to not do anything but than the underlying control is out of sync with the views collection as the views collection will keep those nulls as views. While it might serve for some purposes, I haven’t thought it is clean enough. Also it kept me checking for nulls every time I iterate over the region views.
So luckily, the prism source code is open for grab, though I wanted to avoid altering the source code by all means, I’ve followed the key players involved when you register a view with RegisterViewWithRegion.
To make a long story short one need to dwell into Region Behaviors, in specific responsible for the scenario above is the AutoPopulateRegionBehavior. Soon enough I could identify the culprit:
public virtual void OnViewRegistered(object sender, ViewRegisteredEventArgs e)
{
if (e == null) throw new System.ArgumentNullException("e");
if (e.RegionName == this.Region.Name)
{
AddViewIntoRegion(e.GetView());
}
}
When we call e.GetView() it will call our delegate, if GetView returns null than it will add a null, no brainer. Fortunately the Prism team did a good job, and provided us a mean to change those behaviors on many levels, by making this virtual I could’ve created my own version of AutoPopulateRegionBehavior that avoided adding nulls. I inherited from it and overridden this method.
Last piece of the puzzle is the making the configuration of the behavior factory to use my version instead of the one coming from the box. This is done actually rather easily by overriding the bootstrapper ConfigureDefaultRegionBehaviors method.
What I ended up doing but didn’t like so much is copy pasting the original implementation of ConfigureDefaultRegionBehaviors and changing it to use my own AutoPopulateRegionBehavior because the Factory didn’t give me a way to replace an existing behavior. Perhaps the Prism team can make this available in the next version.
Ariel