Szymon Kobalczyk's Blog

A Developer's Notebook

  Home  |   Contact  |   Syndication    |   Login
  82 Posts | 5 Stories | 151 Comments | 380 Trackbacks

News

View Szymon Kobalczyk's profile on LinkedIn

Twitter












Article Categories

Archives

Post Categories

Image Galleries

Blogs I Read

Tools I Use

In most applications certain user interface areas are shared among various modules. Examples are all kinds of menus, tool bars, status bars, Outlook style sidebars and others. What is common for all of them is that they serve as containers for collections items (menu items, toolbar buttons, etc.) that user can activate to trigger some actions in the application. For example, the File > Open menu item normally would invoke the action of opening a file. However same action could be also accessible using a toolbar button. So it is desirable that we could decouple the user interface elements from the actions that they trigger. CAB provides solution for this in the form of UIElements and Commands

UIExtensionSites are the shared containers that can contain your UIElements. Out of the box CAB provides support for MenuStrips and ToolBarStrips but you can easily add support for other elements by implementing your own UIElementAdapter. After you add a menu or toolbar to your SmartPart you need to register it as an extension site, by calling the RegisterSite method on the WorkItem.UIExtensionsSites collection and providing unique identifier for that site. Usually extension sites are registered in the RootWorkItem by the developer of shell the application. Later when module developer needs to add his own items to that site he only need to create these items as usual and than call the Add method of the specific UIExtensionSite. The corresponding UIElementAdapter is responsible for properly showing items for given UIElement type.

Next step is to attach Commands to your UIElements. Commands are used to assign easily the same code to multiple UIElements. To declare command you need to create event handler that implements your action. Then you add the CommandHandler attribute to the method declaration specifying command name. To assign Command with a UIElement you can call the AddInvoker method on specific Commands collection passing reference to the UIElement and specifying name of its event that will trigger the action.

All pages in the .NET PetShop sample share a common NavigationBar that contains links to following pages: Sign In, Sign Out, Edit Account, View Shopping Cart and the Search TextBox. So we can consider them as UIElements that invoke Commands in different modules. For example, SignIn command is handled in the Account module, and ViewShoppingCart is handled in the Orders module. So it would be nice if each module could register the commands it provides and the Shell doesn't need to know anything besides providing an extension site for them.

Because out of the box CAB only supports UIElementAdapters for toolbars and menus for now I will implement the navigation bar in form of the main menu to see how this works. Later I can implement a custom UIElementAdapter that would closely match the web style.

First I need to add a MenuStrip to the the ShellForm and make it publicly accessible. Then I can register it as UIExtensionSite by adding following line in ShellApplication's AfterShellCreated method:
  RootWorkItem.UIExtensionSites.RegisterSite("NavBar", Shell.menuStrip);
Basically that is all that shell developer needs to do -- the rest belongs to module developers. So I quickly switch hats, and go to the account management module. Let's start by adding the SignIn button. In the OnRunStarted method of AccountWorkItem I add following lines:
  ToolStripMenuItem signInItem = new ToolStripMenuItem("SIGN IN");
  UIExtensionSites["NavBar"].Add(signInItem);
As you can see, first I create a normal ToolStripMenuItem. But then, instead of adding it directly to a MenuStrip, I add it to the UIExtendsionSite defined earlier. Internally CAB finds the MenuStrip defined earlier in ShellForm and passes it together with the menu item to the correct UIElementAdapter with knows how to handle them both correctly.

Now, when I run the application I should see my first menu item and I can add items for other two commands (SignOut and EditAccount) the same way. However, clicking all these menu items does nothing so I need to attach handlers for appropriate events on these items. I could to this the usual way by directly attaching instance method to the Click event but in CAB we should use Commands to handle such things. Following line will register the Click event of the SignIn menu item as invoker for the SignInCommand:
 Commands["SignInCommand"].AddInvoker(signInItem, "Click");
To handle this command I need to declare the event handler and mark it with CommandHandler attribute:
  [CommandHandler("SignInCommand")]
  public void SignInHandler(object sender, EventArgs e)
  {
    SignIn();
  }
What is important is that command handlers and UI elements that invoke them doesn't need to be defined in the same class or in other way directly accessible. For example, the command could be invoked in one WorkItem and handled in another WorkItem, SmartPart or Presenter. And I guess that there can be multiple handlers as well as many invokers attached to single command but I haven't tried this yet.

In many cases command's availability depends of the application's state. For example, toolbar's undo button would be disabled if user hasn't made any modifications. In my case the SignIn button should be visible only if no user has authenticated and SignOut and My Account buttons should be visible otherwise. Normally I would control this by setting the Visible property on MenuItems but again in CAB I can do this indirectly using Commands. This ensures that if there are multiple UIElements for the same Command then all of them would get updated accordingly. Commands define the Status property that can have one of three values: Enabled, Disabled and Unavailable. When value of this property changes, the adapters linked to particular command can apply these changes to the corresponding UIElements. So in my case when user signs in or signs out I execute following code:
  if (userAuthenticated) 
    Commands["CommandSignIn"].Status = CommandStatus.Unavailable; 
  else
    Commands["CommandSignIn"].Status = CommandStatus.Enabled;
posted on Tuesday, January 24, 2006 6:28 PM

Feedback

# re: Understanding Composite UI Application Block, Part V 1/26/2006 12:23 PM vincent
what is also important is that the menuitems are created by your own code, like you do. i don't think this will work if you have your menuitems created by the designer of vs.net. or am i wrong?
all examples i find create the menuitems from a config.

btw: it would be great if you let us take a look at the petshop app.

# re: Understanding Composite UI Application Block, Part V 1/26/2006 12:58 PM vincent
ok, never mind. the following code did the trick. i is possible to 'search' for a menu item and set the command to handle it later.

RootWorkItem.UIExtensionSites.RegisterSite("MainMenu", Shell.MainMenuStrip);
ToolStripMenuItem helpItem = (ToolStripMenuItem)Shell.MainMenuStrip.Items["helpToolStripMenuItem"];

if (helpItem != null && helpItem.DropDownItems != null)
{
ToolStripItem aboutItem = helpItem.DropDownItems["aboutToolStripMenuItem"];

if (aboutItem != null && aboutItem.Tag != null)
{
RootWorkItem.Commands[aboutItem.Tag.ToString()].AddInvoker(aboutItem, "Click");
}
}



# re: Understanding Composite UI Application Block, Part V 1/26/2006 3:05 PM Szymon
Vincent,
As you found already, there is no problem in registering MenuItems created with designer as Command invokers. To check this I added menuitem to the mainmenu on my ShellForm. Then in ShellApplication's AfterShellCreated() method I registered it with:

RootWorkItem.Commands[Constants.Commands.SignIn].AddInvoker(Shell.MySignInToolStripMenuItem, "Click");

However this approach adds havy dependency between Shell and modules. The concept of UIExtensionSites is designed for situations when Shell doesn't need to know anything of modules that it hosts. On the other hand modules can assume that shall provides well known extension sites where thay can put own UIElements.

# re: Understanding Composite UI Application Block, Part V 4/2/2006 6:38 PM Johnny DK
Nice artical, Im working with CAB ona large scale application i DK, I have trouble manage the eventargs that is send by the command. Isn't possible to customize the eventargsobject that the commandhandler gets?

offen it's enough to ask on status == WorkItem.Active but i need til invoke a WorkItem that is not Active. I would prefer to ask on WorkItem.Id instead.

Is there a way to do so?

# re: Understanding Composite UI Application Block, Part V 8/18/2006 9:42 AM mace
I DON'T REALLY understand this, where am I supposed to add the menu so that it shows up on the form the only thing I get is a exceptions that says that it has a null value or that its not set to an object reference

# re: Understanding Composite UI Application Block, Part V 9/13/2006 6:45 AM ksu
I have the same problem. I got "System.ArgumentNullException: Value can not be null." when I try to register menu.

# Problems with adding menus 10/26/2006 2:41 PM Nigel Aston
In answer to the problems from mace and ksu on adding menus, the example menu is added by your shell. E.g. in your shellApplication code...

public class AShellApplication : FormShellApplication<AShellWorkItem, AShellForm>
{
...
protected override void AfterShellCreated()
{
base.AfterShellCreated();
RootWorkItem.UIExtensionSites.RegisterSite(SystemService.Definitions.MenuBar, Shell.MainMenuStrip);

//MainMenuStrip is a property containing the main menu strip for the form. This should be set to the name of the menu strip that you dragged on to the form from the toolbox.
}

}

# re: Understanding Composite UI Application Block, Part V 11/1/2006 8:45 PM Prakash
Hi,
Can we get the sample applicaiton that u have created?

# re: Understanding Composite UI Application Block, Part V 11/1/2006 8:50 PM Szymon
Hi,
The sample should be still available for download from http://projectdistributor.net/Projects/Project.aspx?projectId=192

Look for the Release box on the right.

# re: Understanding Composite UI Application Block, Part V 11/6/2006 8:47 PM Brian
I see how easy it is to add menu items to a menustrip and wire them up. What is the best way to setup and wire up a context menu? For example, I have a view that has a listview in it and I want to have a menustrip with a menu option to set the view style(Large Icon, Small Icon, blah, blah...) and also have a context menu to present the same options. So I want the user to able to set the view style from either a main menu or the right click context menu on the listview. I read where a ToolStripMenuItem cannot be associated with more than 1 ToolStripMenu. I would think it would be easier if I could create my ToolStripMenuItem menus and then attach them were I needed (contextMenus, ToolStripMenu and else where) and I would not need to create multiple ToolStripMenuItems to present the same menu.

Thanks in advance for any advice.
PS: I am using the MVP pattern.

# CAB isn't so easy to learn... 3/9/2007 5:14 PM Guy Burstein's Blog
After spending a few hours trying to figure out the responsibilities of all the elements of CAB, I found

# re: Understanding Composite UI Application Block, Part V 6/1/2007 7:50 AM Neal
While there are lots of examples of adding very basic menu items. Do you know how to handle over menu and toolstrip items. For example set the checked state of a menu item when the command is executed? I have searched and searched again for an example, but there do not appear to be any axamples of how to do this anywhare.

Regards
Neal

# re: Understanding Composite UI Application Block, Part V 5/23/2008 8:46 AM Dogge
I wish it would explain how you register a third party toolbar (for example DotNetBar“s RibbonControl) as a UI extension site! I wanna use it the same way as a menubar/maintoolbar and be able to add buttons to it from a module. Good article tho, got me some more meat on the bone

Post Feedback

Title:
Name:
Email: (never displayed)
Url:
Comments: 
Please add 8 and 1 and type the answer here: