David Foderick's Blog - OnMaterialize()

August 2006 - Posts

Workaround for Injecting menu items into CAB Shell MenuStrip

Anyone who has worked with CAB for a while has probably been bitten by a limitation of the UIExtensionSite when adding menu items into the shell from a module. The limitiation is that the UIExtensionSite only supports the Add method to add additional items to a UI element. Translated to a MenuStrip example, that means that you can only add menu items to the shell's menu. For example, in your shell you define the File menu with an Exit item. Registering the File menu as an extension site, you can now add menu items to the File menu and they will be added after the Exit item. The results are probably not what you want. What can you do if you want to insert new items before the Exit item? Read on.

 The key to working around the problem is to realize that you have a great deal of flexibility in which UI elements you expose as extension sites. For a MenuStrip in the shell there are three basic choices for what to expose. I will call them Top level, Detail level and Individual item.

Expose the Top level menu items if you want to add more top level menu items from a module. For example if you have File and Edit menu itmes and you want to add a top level menu called "My Menu" then register the top level like this:

RootWorkItem.UIExtensionSites.RegisterSite(UIExtensionSiteNames.MainMenu, this.Shell.MainMenuStrip.Items);

In your module, add the new top level menu item.

UIExtensionSites[UIExtensionSiteNames.MainMenu].Add(new ToolStripMenuItem("My Menu"));

"My Menu"  will appear as the rightmost top level menu.

Expose a detail level menu collection to add additional menu items after any items that are already defined in the shell. For example if you have a File menu with an Exit item then registering it like this

ToolStripMenuItem fileItem = (ToolStripMenuItem)Shell.MainMenuStrip.Items[0];
RootWorkItem.UIExtensionSites.RegisterSite(UIExtensionSiteNames.FileMenu, fileItem.DropDownItems);

and adding a menu item like this

UIExtensionSites[UIExtensionSiteNames.FileMenu].Add(new ToolStripMenuItem("New"));

will yield a File menu with Exit then New.

So it seems you can only add items to the end of the list! What if you wanted that New menu item to be placed before the Exit item? Ok, here the trick. The third option is to register an invisible individual menu item as the first menu item in the File menu. When you register this individual item and Add to it, items will appear after it in the list. So the registration code looks like this:

             //need a placeholder menu item to add items after
            ToolStripSeparator placeHolderTool = new ToolStripSeparator();
            placeHolderTool.Visible = false;
            fileItem.DropDownItems.Insert(0,placeHolderTool);
            RootWorkItem.UIExtensionSites.RegisterSite(UIExtensionSiteNames.FileMenu, placeHolderTool);

Now when you add the New menu item

UIExtensionSites[UIExtensionSiteNames.FileMenu].Add(new ToolStripMenuItem("New"));

You get a File menu with with New then Exit as a result. 

The little magic that makes this possible is in the ToolStripUIAdapterFactory. It looks at what kind of ui element you are exposing and creates the correct adapter for it. The adapter knows how to handle each situation and performs the correct implementation for you.

    public class ToolStripUIAdapterFactory : IUIElementAdapterFactory
    {
        /// <summary>
        /// See <see cref="IUIElementAdapterFactory.GetAdapter"/> for more information.
        /// </summary>
        public IUIElementAdapter GetAdapter(object uiElement)
        {
            if (uiElement is ToolStrip)
                return new ToolStripItemCollectionUIAdapter(((ToolStrip)uiElement).Items);

            if (uiElement is ToolStripItem)
                return new ToolStripItemOwnerCollectionUIAdapter((ToolStripItem)uiElement);

            if (uiElement is ToolStripItemCollection)
                return new ToolStripItemCollectionUIAdapter((ToolStripItemCollection)uiElement);

            throw new ArgumentException("uiElement");
        }
...

What is a WorkItemExtension?

The Composite UI Application Block (CAB) is all about writing loosely coupled plug-in modules for Smart Client apps. What happens when someone has written a module and you want to enhance its behavior without modifying the original code? You write a WorkItemExtension.

First a little background. A WorkItem represents a particular use case, like editing a customer, for example. As a container for the use case, it contains a view and a presenter. In the bank teller sample, the CustomerWorkItem displays a CustomerView that allows the user to edit customer data. Now someone else comes along and they want to display additional information to extend the original use case. Of course, subclassing the CustomerWorkItem would not work because there would be no way to tell the original caller of the work item (probably a ModuleInit) to run your subclass instead of the parent. Instead, they would write a CustomerWorkItemExtension to extend the CustomerWorkItem without modifying original CustomerWorkItem code. The WorkItemExtension hooks into all the events of the extended WorkItem (Initialized, Activated, Deactivated, Terminated). The code looks like this:

     [WorkItemExtension(typeof(CustomerWorkItem))]
    public class CustomerWorkItemExtension : WorkItemExtension
    {
        protected override void OnActivated()
        {
                WorkItem.Workspaces[workspace].Show(aditionalView);
        }
    }

Notice that the attribute defines which workitem we are extending and that the WorkItem member variable in this code snippet is the extended WorkItem (CustomerWorkItem in our example).

Another cool trick is to extend your work item base class and include logging or performance metrics for all your work items. The Smart Client Software Factory does this for infrastucture testing.This is a simple example of Aspect Oriented Programming.
 

Entity SQL Quick Reference Guide: Get your copy
After you install the ADO.NET vNext August 2006 CTP, check out the eSQL quick reference guide in C:\Program Files\Microsoft SDKs\ADO.NET vNext CTP\Docs. It lists some of the more common keywords for building entity queries.
Fixing Smart Tags after installing LINQ/Entity Framework preview
If you're going to play around with the new Entity Framework August 2006 CPT, you'll have to install the May CTP of LINQ. A nasty side affect of that installation is that it wipes out some of your smart tags. To get your VS 2005 environment back in order, you should see the list of known issues and this blog entry from Harmut.

One additional snag that you might encounter is getting a Visual Studio error message when running the devenv /setup /resetuserdata /resetsettings command.The reset does not complete and smart tags are still stunted. In my case, the error was caused by an unruly addin (TestDriven .NET). Uninstalling the addin before resetting Visual Studio should fix it.