The ToolStrip is a wonderful thing. In a CAB application it is a shared site – a UIExtension site – that any WorkItem from any Module can manipulate. This is a powerful feature of the CAB infrastructure, but it does come with a cost: maintenance of the ToolStrip can be a headache in a complex CAB application.

Figure 1 shows a typical CAB application with multiple DeckWorkspaces in play. It is not uncommon to have a nested hierarchy of DeckWorkspaces showing various views, some of which contain DeckWorkspaces of their own. This is the natural product of a modular design where WorkItems are managing their own environments and hosting their views in the nearest available DeckWorkspace. By maintaining this sort of hierarchical design, lower level WorkItems never need to know about higher level DeckWorkspaces that may or may not exist. It becomes one less dependency to worry about.
The disadvantage to this scheme is that the ToolStrip is a shared site, and as such can be manipulated by anyone at any time. This can be problematic when trying to show/hide or disable/enable certain ToolStrip items based on the view currently being shown. This becomes even more troublesome when MenuItems are showing/hiding views from different Modules. The problem is one of detection: when is the view being displayed, and who should be responsible for managing the ToolStrip state when that happens?
The solution I’ve employed has two parts: A Service to manage the buttons that will appear on the ToolStrip and a Strategy for notifying DeckWorkspaces of the currently active view. That strategy is the Chain of Responsibility Pattern, and here we’re using it in a hierarchical manner.
Note: I will use the term DeckView to refer to a View with a DeckWorkspace on it and nothing more.
IUIElementExtensionService
The first thing I did was create a class that was responsible for holding references to the buttons that would be shown on the ToolStrip itself. I implemented it as an IDictionary<string, ToolStripMenuItem>. What’s great about this solution is that it uses a string already associated with the button: namely, the CommandHandler commandName.
Every button that does something on the ToolStrip requires a CommandHandler. And if your’e like me, you probably store the commandName strings in a class somewhere as constants, that way you can do compile-time checking and avoid runtime errors due to misspellings. So you have a bunch of const strings already available for use.
The class that implements the IDictionary I call the IUIElementExtensionService. Once the IUIElementExtensionService is available in the service collection we can make use of its capability to store buttons. Since it’s a service, any WorkItem in the chain (and any object that can utilize the ObjectBuilder’s Dependency Injection framework) can use it.
// Fetch reference to the ToolStrip UIExtensionSite
UIExtensionSite toolStrip = RootWorkItem.UIExtensionSites[UIExtensionSiteNames.ShellToolStrip];
// Fetch service
IUIElementExtensionService service = Services.Get<IUIElementExtensionService>();
// Create a button
// I actually do this with a factory class so I can
// standardize things like padding, margins, etc.
ToolStripMenuItem button = new ToolStripMenuItem();
button.Name = CommandNames.SaveEmployee;
button.Text = "Save";
button.Image = Resources.Save;
// Add button to the service using the same CommandName
service.Add(CommandNames.SaveEmployee, button);
So now you have a very simple service that stores your ToolStrip buttons and makes them available to any WorkItem or object that needs to manipulate them. The joy here is that we never have to recreate the buttons every time we need to show them. We just fetch the service and add them to the ToolStrip after clearing it.
Note: Typically what I do is create a UIManagerWorkItem that is responsible soley for creating the buttons when the RootWorkItem for a Module (ModuleController for all you SCSF folks) loads. It’s a one-and-done WorkItem, and it keeps all that creation code out of my other WorkItems (and when you have a lot of WorkItems creating a lot of ToolStrip buttons it can get cluttered and scattered quickly). Clean, neat, and simple.
With the Service out of the way, our next task is to delegate the management of the ToolStrip to the objects that make the most sense. This is where the Chain of Responsibility pattern comes in.
ToolStrip Management Strategy: Chain of Responsibility
The real difficulty with managing the ToolStrip is when you need to reshow a DeckView that is already showing the child SmartPart you really want to see. How do you detect a SmartPart that is already essentially active, just nested on a DeckView somewhere down the chain?
Fortunately, the DeckWorkspaces have a nice Event for this: SmartPartActivated. Utilizing this event your DeckWorkspaces can tell you what SmartPart is active, and you can use that information to propogate a message down the hierarchy informing child DeckViews of the currently active SmartPart above them. Events get chained together until they reach the leaf-node: a SmartPart that is not a DeckView. That’s when the final DeckView in the chain handles the responsibility and manipulates the ToolStrip.

We start at the top: the Shell (Leaf nodes are SmartParts that are not DeckViews, and are green). In Visual Studio, right-click the event properties for the DeckWorkspace and wire-up the SmartPartActivated event.
private void ShellDeckWorkspace_SmartPartActivated(object sender, WorkspaceEventArgs e)
{
// Do something here
}
The next step is to do something with the event. What we want to do is propogate a chain of WorkspaceEventArgs down the hierarchy so that the next level of DeckViews can respond to it and manipulate the ToolStrip if necessary. So, we use the EventBroker and declare a global event (blue event dots on the diagram):
[EventPublication(ShellEventTopicNames.SmartPartActivated, PublicationScope.Global)]
public event EventHandler SmartPartActivated;
Then we call the EventBroker Event, passing along our WorkspaceEventArgs:
private void ShellDeckWorkspace_SmartPartActivated(object sender, WorkspaceEventArgs e)
{
if (SmartPartActivated != null)
SmartPartActivated(this, e);
}
Somewhere down the chain we have a Module with a WorkItem that contains a DeckView that is showing a SmartPart. The SmartPart itself may be another DeckView, or just a regular UserControl. What the DeckView wants to know is: When is one of my children the currently active view, so that I can manipulate the ToolStrip? To know that, we subscribe to the higher level EventBroker Event.
// This happens in the Presenter for the View IMyDeckView which
// contains the DeckWorkspace.
[EventSubscription(EventTopicNames.SmartPartActivated, ThreadOption.UserInterface)]
public void SmartPartActivated(object sender, WorkspaceEventArgs e)
{
if(e.SmartPart != null && e.SmartPart is IMyDeckView)
{
Control activeSmartPart = view.GetActiveSmartPart();
if (activeSmartPart != null)
ExtendToolStrip(activeSmartPart);
}
}
There are a couple things going on here with this event subscription. First, we’re checking to see if the argument, e.SmartPart, is the same type as the View (IMyDeckView) of the Presenter that is catching this event. If it is, then we’re going to manipulate the ToolStrip. Otherwise we’re going to ignore it because it’s not us, and we’re just going to let someone else on the same tier handle it.
Once we know this is our DeckView that is being shown, the Presenter asks the DeckView to fetch the ActiveSmartPart. That’s a property of the DeckWorkspace: DeckWorkspace.ActiveSmartPart.
Finally, we’re making a call to ExtendToolStrip, which is a local method that will use the IUIElementExtensionService to show/enable/disable the ToolStrip buttons for this view. It will do something like this:
public void ExtendToolStrip(object smartPart)
{
UIExtensionSite toolStrip = RootWorkItem.UIExtensionSites[UIExtensionSiteNames.ToolStrip];
toolStrip.Clear();
ToolStripItem button;
if (smartPart is IView1)
{
button = uiService[CommandNames.Button1];
toolStrip.Add(uiService[CommandNames.Button1]);
ToolStripSeparator separator = new ToolStripSeparator();
toolStrip.Add(separator);
button = uiService[CommandNames.Button2];
toolStrip.Add(button);
}
else if (smartpart is IView2)
{
etc...
}
Thus, based on the individual View we are showing in our DeckView (and there may be several children, so the statement above becomes an if-else or switch), we clear the ToolStrip first and then add various buttons and separators, etc.
At the end of the ExtendToolStrip method we make sure to broadcast a new EventBroker Event, similiar to the one the Shell published. This is where the Chain of Responsibility comes in. It is possible that one of our children is also a DeckView, and we want them to handle the ToolStrip if they are showing the leaf node SmartPart.
This new event is for the second tier of DeckViews. They don’t subscribe to the Shell’s event because they are on a level below and none of their SmartParts will match with what the Shell’s DeckWorkspace reports. So, we fire off a new event and pass along the ActiveSmartPart as the argument (red event dots on diagram):
// Payroll is a Module in my CAB application. This is a 1st-level
// DeckWorkspace propogating its event down the hiearchy.
if (PayrollSmartPartActivated != null)
PayrollSmartPartActivated(this, new WorkspaceEventArgs(smartPart));
The EventBroker publication of PayrollSmartPartActivated looks just like the Shell’s publication.
So now you have a Chain of Responsibility. Each level down the hierarchy you publish a new event and pass along the ActiveSmartPart. Eventually you will reach a DeckView that is showing the leaf-node SmartPart, the end of the chain, and that DeckView will be responsible for manipulating the ToolStrip to match the needs of the view it is showing.
Note: You could delegate the responsibility of the ToolStrip manipulation to the actual SmartPart (or preferably its Presenter) that is the leaf node. However, I have found that having the ToolStrip manipulation code in the DeckView’s Presenter is a good idea because it is a logical place for the responsibility to lie since the SmartPart Presenter is often more concerned with handling the Model and View, and the traffic between the two. The management of the ToolStrip seems to be beyond its intended scope. Plus, it keeps my SmartPart Presenters thin and focused; there’s a clear responsibility. Finally, the DeckViews do nothing anyway, so giving them some logical UI responsibility seems reasonable.
Conclusion
I hope this helps some folks who might have been having trouble trying to manage the ToolStrip. I think the ToolStrip is a great thing to utilize in a GUI application; many users are accustomed to it and since the buttons can utilize icons it creates a better visual experience. But managing it in a CAB application can be troublesome. This sort of approach should help. It’s working wonders for me.