It's been over a week since my last post on Composite UI App Block, but don't start thinking I have given up on it already. I still want to finish the PetShop sample and at the same time to cover all important parts of CAB on this blog.
Last time I have covered some basics on using Commands and UI Elements and demonstrated how I implemented commands in my sample application. But as I said before, out of the box CAB only has UIElementAdapters for ToolStrips and MenuStrips. Just to get started I put all my commands on the main menu, but the original PetShop was a web app so this were all links. So it got me thinking what it takes to implement my own UIElementAdapter. For most people support for menus and toolstrip could be enough, but some of us use third-party controls or wants to invoke commands from other controls like trees, or Outlook style navigation bars. I think knowing how to extend CAB to handle such situation can be helpful.
(On the other hand, I heard on ARCast that Infragistics committed to release CAB wrappers for their controls so those of you who use NetAdvantage don't need to read any further).
First thing was to create the control that will display my links and fire events when one is clicked. For this I have extended the standard LinkLabel adding collection of my own LinkListItems. These items are similar to a menu items having Text, Enabled, and Visible properties, and a Click event. The control automatically updates itself based on these settings. Under the hood it constructs a single line from the texts of all visible items inserting bars ( | ) as separators, and it positions a LinkArea on each enabled item. Then upon receiving LinkClicked event it propagates it back to corresponding item and fires its Click event. It took ~200 lines of code but there is nothing fancy so I won't bore you with it here.
Now having the control ready I need to plug it into the CAB framework. There are three additional elements that I need to provide: IUIElementAdapter, IUIElementAdapterFactory, and CommandAdapter.
As explained in last post, UIElement adapter is responsible for properly showing items of given UIElement type. This interface has only two methods: Add and Remove that are called each time items are added or removed from extension site. In my case these methods simply add or remove items from the LinkList items collection:
public class LinkListUIAdapter : UIElementAdapter
{
LinkList linkList;
public LinkListUIAdapter(LinkList linkList)
{
Guard.ArgumentNotNull(linkList, "linkList");
this.linkList = linkList;
}
protected override LinkListItem Add(LinkListItem uiElement)
{
if (linkList == null)
throw new InvalidOperationException();
linkList.Items.Add(uiElement);
return uiElement;
}
protected override void Remove(LinkListItem uiElement)
{
if (uiElement.Owner != null)
uiElement.Owner.Items.Remove(uiElement);
}
}
The role of IUIElementAdapterFactory is to provide appropriate UIElementAdapter instances for given UIElement's type. The Supports method should indicate if particular factory supports the given UI Elements type, and then GetAdapter methods should create adapter for that UI Element.
public class LinkListItemAdapterFactory : IUIElementAdapterFactory
{
public IUIElementAdapter GetAdapter(object uiElement)
{
if (uiElement is LinkList)
return new LinkListUIAdapter((LinkList)uiElement);
throw new ArgumentException("uiElement");
}
public bool Supports(object uiElement)
{
return (uiElement is LinkList);
}
}
If we want to be able to attach command to the UI Elements we also need to provide a CommandAdapter. It's purpose is to provide logic that is needed for UIElement to invoke a Command, and also to synchronize changes to Command with the UI Element. Luckily for me the first part is handled by the base class EventCommandAdapter, so I only had to make sure that changes to Command status will be applied to the corresponding link:
public class LinkListItemCommandAdapter : EventCommandAdapter
{
public LinkListItemCommandAdapter()
: base()
{ }
public LinkListItemCommandAdapter(LinkListItem item, string eventName)
: base(item, eventName)
{ }
protected override void OnCommandChanged(Command command)
{
base.OnCommandChanged(command);
foreach (KeyValuePairstring>> pair in Invokers)
{
pair.Key.Enabled = (command.Status == CommandStatus.Enabled);
pair.Key.Visible = (command.Status != CommandStatus.Unavailable);
}
}
}
Having all these three pieces we can now register them in CAB. It should be done in the AfterShellCreated method of the ShellApplication class:
protected override void AfterShellCreated()
{
base.AfterShellCreated();
ICommandAdapterMapService mapService =
RootWorkItem.Services.Get();
mapService.Register(typeof(LinkListItem), typeof(LinkListItemCommandAdapter));
IUIElementAdapterFactoryCatalog catalog =
RootWorkItem.Services.Get();
catalog.RegisterFactory(new LinkListItemAdapterFactory());
}Finally we have all set up and we are ready to use the new UI Elements. But the best thing it was, that the only change I had to make was to replace references to
ToolStripMenuItem with my own
LinkListItems like this:
LinkListItem signInItem = new LinkListItem("SIGN IN");
UIExtensionSites["NavigationBar"].Add(signInItem);
Commands["SignIn"].AddInvoker(signInItem, "Click");
All the other code that handles commands and updates their status remains unchanged. And thats the real advantage of CAB!