?

Log in

Previous 10

Sep. 30th, 2008

DevLynx

Further Thoughts on CAB

I've been meaning to write an entry for a couple of weeks and finally found a bit of time. We have been very busy at the "day job" moving from Delphi to C#. We made the decision 3 or 4 months ago not to use the Microsoft Composite Application Block. I was somewhat disappointed since I had done so much work with the framework.

Our first C# application at work is relatively simple compared to some of our other applications. This makes it a great candidate to be our vanguard into the .NET and C# world. Because of the research that I did into the CAB framework one of my initial requirements was a dependency injection container. I chose Ninject and and I'm very happy with it. It is simple, does not require a complex configuration files, and is easy to use.

As soon as I implemented Ninject, I needed a publisher/subscriber tool. I found a lightweight EventBroker that worked very similar to the CAB event broker. It wasn't until after this was in place that I realized there is a message broker in Ninject - not documented and not easily discoverable. As I have mentioned before, one of the biggest problems with open source is the lack of documentation. Developing software is my passion, but it's also how I make my living. I generally don't have time to pour through the source code to discover how something works - I want documentation. That's why when I started my investigation into CAB I documented everything that I was attempting to accomplish. So far I'm going to stick with the lightweight EventBroker that I originally found.

I added a state manager, workspaces, a variation of smart parts, and pretty soon, I had created a lightweight version of the CAB just like Jeremy D. Miller said could be done. I just didn't believe it would be easy until I did it.

So, where do I go with the CAB framework? I don't have an answer for that yet. There are still things that are available in the CAB that my framework at the "day job" does not do; I'm sure that those will be added as soon as they are needed.

In the mean time I'm working on another Open Source project which I will announce here as soon as it's ready for beta testing. More to follow...

dlx
Tags:

Jul. 30th, 2008

DevLynx

Ribbon Application Menu Bottom Pane

The Microsoft Office 2007 Ribbon has buttons located on a panel at the bottom of the application menu. In Office 2007 this panel contains two buttons: program options and exit the application. In fact, the application that I am using to compose this journal entry (Help & Manual 5) uses a ribbon control following this standard. The CAB DevExpress Extension Kit needs to support this standard. After finishing the Ribbon demo I decided to create a UIElementAdapter to fulfill this need.

Once again, there is a complication with the Developer Express controls which must be avoided in our quest for decoupling. DevExpress designed the container for the bottom pane to hold any control. DevExpress cannot anticipate how we will use this container. Therefore, DevExpress leaves the application menu active until the developer explicitly closes the menu. A major problem surfaces when a button launches a modal dialog prior to closing the application menu (see Q106244 and AB11484).

Our issue is that any module should be able to add a button to the bottom pane of the application menu. A dependency on the application menu or ribbon will be required since the developer may need to close the application menu. This violates our goal of decoupling.

One option is to add an additional event handler to the button when it is added to the UIElementAdapter with the call UIExtensionSites[extensionSite].Add(button). This allows the developer to use the standard CAB commands and assign a Click event via AddInvoker. This would allow us to call the event handler to close the application menu and call the commands Invoker event.  However, the code

    UIExtensionSites[extensionSite].Add(button);
    Commands[CommandNames.MyCommand].AddInvoker(button, "Click");

could just as easily be written as

    Commands[CommandNames.MyCommand].AddInvoker(button, "Click");
    UIExtensionSites[extensionSite].Add(button);

In the latter case, the command will be fired before our event handler and the application menu is not closed at the right time. Furthermore, I can find no documentation that the events are guaranteed to fire in any specific order. So this approach was abandoned.

Another option would be to have the developer fire an event from the command handler to close the application menu. But this means that the developer requires knowledge of where this specific command is located in the interface or call this event from all command handlers. This option hold no appeal.

So I decided to go back to the same method that is used for both the GalleryItem and the dynamic UI elements. The button Tag property is used to hold an event name. The UI element adapter handles the click event. When any button in the bottom pane is clicked the UI element adapter closes the application menu and then fires the event. Again, I think that this is a compromise between decoupling which hides details from the developer and how the DevExpress control functionality.

You will find the code for the RibbonAppMenuBottomPaneSimpleButtonUIAdapter in the latest source code change set on the CAB DevExpress Extension Kit web page.

The last Ribbon item I am planning to add to the CAB DevExpress Extension Kit is creating an ApplicationMenu Right Pane workspace. However, this is not a high priority so it may be a few months before this is complete. If there are any other things that the Ribbon is missing please let me know and I'll see about getting to them in the future.

dlx

Jul. 19th, 2008

DevLynx

Creating Dynamic UIElements

There is some controversy in the CAB development community about the usefulness of Commands and UIExtensionSites. You can see some of the discussions here, here, and here (and in comments from this journal here). I am still in the "commands and UIExtensionSites are good" camp; but I am willing to bypass the CAB philosophy if I find a requirement which I cannot implement using these CAB tools. One of the suggestions in the aforementioned discussions was to only use commands for menu items and toolstrip items. This seems to make a lot of sense to me. However, this still means that we need to handle galleries, menus and toolbars — some of which could be created dynamically.

I am using "dynamic" in this discussion to mean a set of items where the number of items is not known at compile time. Furthermore, the items within this dynamic set will call the same method; individual dynamic items may pass different parameters to the method. Two examples of this are:
  • Developer Express skins: New skins are added on occasion and we don't want to recode for each new skin. The dynamic information passed is the skin name.
  • Fonts: We cannot anticipate how many or which fonts will be on the end user machine. The dynamic information passed can be either the font name or an instance of the font itself.
How can we accomplish this dynamic creation and still keep somewhat close to the CAB philosophy?

To implement dynamic UIElements, we have to abandon CAB commands and embrace CAB Events. CAB commands are intentionally designed to hide all information about which UIElement was pressed. Events allow us to pass additional information with the event, a requirement since multiple UIElements will call the same method to accomplish their work.

I have created a DynamicCommandEventLink class which is used to pass the necessary information. This class includes the event topic name that this particular dynamic item will fire. It also contains an object for the dynamic data to be passed to the event. An instance of this class is passed in the Tag property of the menu item, gallery item, etc. I then created special extension sites which handle the DynamicCommandEventLink. These include RibbonGalleryDynamicUIAdapter and RibbonGalleryGroupDynamicUIAdapter working in tandem; and the BarLinksCollectionDynamicUIAdapter which handles menus and toolbars.

I imagine that some will say that all of this can be accomplished via directly instantiating a menu or gallery bypassing the CAB framework. This is true for most situations. But if someone directly instantiates a menu to which a module later needs to add items, we have lost the decoupling that the CAB provides. The dynamic UIElements gives the developer a decoupling choice.

You can see the code within the CAB DevExpress Extension Kit. Examples of how to use them are supplied in the BankTeller demo application where both the skins ribbon gallery and an optional skins menu use dynamic UI adapters.

dlx
DevLynx

CAB DevExpress Extension Kit Ribbon Demo

Since I have been working on the CAB DevExpress Extension Kit I have added a number of UIElementAdaptors for the Ribbon. However, there was no good example of the Ribbon control in the Extension Kit. So this past week I modified the Extension Kit demo to include the Ribbon control. I did this using a conditional compilation symbol which I would never use in this way outside of a demo. However, in this case it does a few things.
  • This method demonstrates exactly where changes need to be made within the code. You can search for "UseRibbonForm" and see both the Ribbon code and the menu code side by side. I hope that this is a good way to see how the Ribbon code is different than code for the menu.
  • It reduces the maintenance costs. When a new feature is added to the demo it will be easy to get it to work in both the menu and the Ribbon.
  • Last, it sped up the development greatly; it would have taken me a much longer time to rewrite the BankTeller demo from scratch.
Even with these advantages I wouldn't use this technique in this way in a shipping product. To compile the Ribbon, you must add "UseRibbonForm" to the conditional compilation symbol property of the BankShell and the BankTellerModule. To compile to the menu just remove this symbol.

There were a couple of changes to the BankTeller application to get the Ribbons to work. First, the Ribbon control does not work well without button images. So I had to add glyphs to the application. I added them to both the Ribbon and to the original menus. The glyphs were made using Axialis IconWorkshop and their Image Objects. Since the supplied glyphs were constructed with Axialis IconWorkshop I can distribute them in the Extension Kit. You are free to use these images in any project if you so desire. I also included in the project the icons from which the glyphs were created.I extended the MenuItemElement ConfigurationElement to handle the glyphs in the configuration file. I separated the guts of the Windows menu into it's own class so that it could be easily used by both the menu and Ribbon based forms. I then added support for dynamic creation of UIElement items. This allows me to loop through the available skins while still adhering more closely to the CAB philosophy. This dynamic UIElement creation will be my next post.

I have compiled this against DevExpress 2008v2 beta so I will check in the code when 2008v2 is officially released. Until then, you can get a copy from my site at tetzel.com.

dlx
Tags:

Jun. 11th, 2008

DevLynx

Ribbon Galleries (redux)

I have completed a first version of Ribbon Gallery support for the CAB DevExpress Extension Kit. My solution is different than the other UIAdapters in the extension kit because of the way Developer Express chose to implement galleries. Including gallery support in an application involves adding a Gallery to a Ribbon Page Group, then adding a Gallery Group to the Gallery and finally, adding a Gallery Item into the Gallery Group. It is in this final step that the major difference is revealed; the Tag property of the GalleryItem must be set to the CommandName to be run.

There are two reasons that the GalleryItem Tag property is required to be the CommandName. First, the Developer Express gallery implementation has the 'click' events in the RibbonGalleryBarItem not in the GalleryItem. That means that the individual GalleryItem cannot handle it's own click event. This leads to the second reason for placing the CommandName in the Tag. The CAB has, purposefully, highly decoupled the UIElement from the command handler. Within the command handler, you cannot obtain any information about the UIElement that called the command handler.

We end up with a conundrum; Developer Express does not allow us to link a GalleryItem directly to the click event and the CAB does not allow us to find information about which GalleryItem was clicked. My solution is to supply the CommandName within the GalleryItem Tag property. At one point I considered creating a GalleryItem descendant which would add a CommandName property. But since the CAB prevents information about UIElement from being passed to a command handler the Tag property could not be used for anything else. I determined that a GalleryItem descendant was overkill. I'm not entirely happy with this solution but I feel that it is an adequate compromise.

The other difference is that we need access to the WorkItem which added the command to be fired. This is passed into the RibbonGalleryUIAdapter during construction. The first version of the adapter stated that the RootWorkItem was to be passed. However, Karel Kral was kind enough to tell me that this wouldn't work. While the EventBroker stores all of it's events in the root work item, commands are stored in, and can only be accessed from, the work item that added the command. This actually didn't change any of the code flow, but I made sure that the parameter and property names were modified as well as the documentation. Thanks Karel!

The base solution contains two files RibbonGalleryUIAdapter.cs and RibbonGalleryGroupUIAdapter.cs.

RibbonGalleryUIAdapter.cs

using System;

using DevExpress.XtraBars;

using DevExpress.XtraBars.Ribbon;

using Microsoft.Practices.CompositeUI.UIElements;

using Microsoft.Practices.CompositeUI.Utility;

using Microsoft.Practices.CompositeUI;

using Microsoft.Practices.CompositeUI.Commands;

 

namespace CABDevExpress.UIElements

{

    /// <summary>

    /// An adapter that wraps a <see cref="RibbonGalleryBarItem"/> for use

    /// as an <see cref="IUIElementAdapter"/>.

    /// </summary>

    public class RibbonGalleryUIAdapter : UIElementAdapter<GalleryItemGroup>

    {

        private readonly RibbonGalleryBarItem ribbonGallery;

 

        /// <summary>

        /// Initializes a new instance of the

        /// <see cref="RibbonApplicationMenuUIAdapter"/> class.

        /// </summary>

        /// <param name="ribbonGallery">The application menu.</param>

        /// <param name="workItem">The work item which added the command.

        /// We need to access the Commands property of the work item to

        /// fire the Command associated with the GalleryItem.</param>

        public RibbonGalleryUIAdapter(RibbonGalleryBarItem ribbonGallery,

            WorkItem workItem)

        {

            Guard.ArgumentNotNull(ribbonGallery, "RibbonGalleryBarItem");

            Guard.ArgumentNotNull(workItem, "workItem");

 

            this.ribbonGallery = ribbonGallery;

            this.workItem = workItem;

            // Since Developer Express does not support click events for

            // GalleryItems we need to handle the ItemClick event here.

            this.ribbonGallery.Gallery.ItemClick +=

                new GalleryItemClickEventHandler(ItemClick);

        }

 

        private readonly WorkItem workItem;

        /// <summary>

        /// Gets the work item which was passed in at creation.

        /// </summary>

        /// <value>The work item.</value>

        protected WorkItem WorkItem

        {

            get { return workItem; }

        }

 

        /// <summary>

        /// Fired when any <see cref="GalleryItem"/> contained in this

        /// <see cref="RibbonGalleryBarItem"/> is clicked. If the

        /// GalleryItem.Tag property contains an active CommandName string,

        /// that command is fired.

        /// </summary>

        /// <param name="sender">The sender.</param>

        /// <param name="e">The <see cref="GalleryItemClickEventArgs"/>

        /// instance containing the event data.</param>

        protected virtual void ItemClick(object sender,

            GalleryItemClickEventArgs e)

        {

            if (e.Item.Tag is string

                && !String.IsNullOrEmpty(e.Item.Tag as string))

            {

                Command command = WorkItem.Commands.Get<Command>

                    (e.Item.Tag as string);

                if (command != null)

                    command.Execute();

            }

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Add(TUIElement)"/>

        /// for more information.

        /// </summary>

        protected override GalleryItemGroup Add(GalleryItemGroup uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "GalleryItemGroup");

 

            ribbonGallery.Gallery.Groups.Add(uiElement);

            return uiElement;

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Remove(TUIElement)"/>

        /// for more information.

        /// </summary>

        protected override void Remove(GalleryItemGroup uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "GalleryItemGroup");

 

            ribbonGallery.Gallery.Groups.Remove(uiElement);

        }

    }

}


RibbonGalleryGroupUIAdapter.cs

using System;

using Microsoft.Practices.CompositeUI.UIElements;

using DevExpress.XtraBars.Ribbon;

using Microsoft.Practices.CompositeUI.Utility;

 

namespace CABDevExpress.UIElements

{

    /// <summary>

    /// An adapter that wraps a <see cref="GalleryItemGroup"/> for use

    /// as an <see cref="IUIElementAdapter"/>.

    /// </summary>

    public class RibbonGalleryGroupUIAdapter : UIElementAdapter<GalleryItem>

    {

        private readonly GalleryItemGroup ribbonGalleryGroup;

 

        /// <summary>

        /// Initializes a new instance of the

        /// <see cref="RibbonGalleryGroupUIAdapter"/> class.

        /// </summary>

        /// <param name="ribbonGalleryGroup"></param>

        public RibbonGalleryGroupUIAdapter(GalleryItemGroup ribbonGalleryGroup)

        {

            Guard.ArgumentNotNull(ribbonGalleryGroup, "GalleryItemGroup");

            this.ribbonGalleryGroup = ribbonGalleryGroup;

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Add(TUIElement)"/> for

        /// more information.

        /// </summary>

        protected override GalleryItem Add(GalleryItem uiElement)

        {

            ValidateUiElement(uiElement);

 

            ribbonGalleryGroup.Items.Add(uiElement);

            return uiElement;

        }

 

        /// <summary>

        /// Validates a UIElement to ensure that the GalleryItem.Tag property

        /// contains a non-empty string value. This value is assumed to be a

        /// CommandName associated with an instantiated CAB Command.

        /// </summary>

        /// <param name="uiElement">The UIElement to be validated.</param>

        protected virtual void ValidateUiElement(GalleryItem uiElement)

        {

            // not using Guard here so that we can supply the additional

            // information in the message.

            if (uiElement == null)

                throw new ArgumentNullException("uiElement.Tag cannot null."

                    + " It must contain the CommandName to be fired.");

            if (uiElement.Tag is string)

            {

                if (String.IsNullOrEmpty(uiElement.Tag as string))

                    throw new ArgumentException("uiElement.Tag cannot be empty."

                        + " It must contain the CommandName to be fired.");

            }

            else

                throw new ArgumentException("uiElement.Tag must be a string and"

                    + " must contain the CommandName to be fired.");

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Remove(TUIElement)"/> for

        /// more information.

        /// </summary>

        protected override void Remove(GalleryItem uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "GalleryItem");

            ribbonGalleryGroup.Items.Remove(uiElement);

        }

    }

}


An example of how galleries are created follows. Note that this example takes shortcuts with UIExtension site names; you will want to follow all of the standard conventions regarding UIExtension site names as constants.

// First we have to create the Ribbon Page and Group.

string pageName = "Galleries";

RibbonPage ribbonPage = new RibbonPage(pageName);

WorkItem.UIExtensionSites[UIExtensionSiteNames.RibbonControl].

    Add<RibbonPage>(ribbonPage);

WorkItem.UIExtensionSites.RegisterSite(pageName, ribbonPage);

string pageGroupName = "My Gallery Page Group";

RibbonPageGroup ribbonGroup = new RibbonPageGroup(pageGroupName);

ribbonGroup.ShowCaptionButton = false;

WorkItem.UIExtensionSites[pageName].Add<RibbonPageGroup>(ribbonGroup);

WorkItem.UIExtensionSites.RegisterSite(pageGroupName, ribbonGroup);

 

// Next we create the Gallery within the Ribbon Group

string galleryName = "Gallery";

RibbonGalleryBarItem ribbonGallery = new RibbonGalleryBarItem();

ribbonGallery.Caption = galleryName;

ribbonGallery.Gallery.AllowHoverImages = true;

ribbonGallery.Gallery.ColumnCount = 3;

ribbonGallery.Gallery.ImageSize = Resources.Contact24.Size;

ribbonGallery.Gallery.HoverImageSize = Resources.Contact64.Size;

WorkItem.UIExtensionSites[pageGroupName].Add<BarItem>(ribbonGallery);

// Pass the WorkItem which added the command into the

// RibbonGalleryUIAdapter. This allows the command to be fired.

WorkItem.UIExtensionSites.RegisterSite(galleryName,

    new RibbonGalleryUIAdapter(ribbonGallery, WorkItem));

 

// Then create the group within the gallery

string galleryGroupName = "Gallery Group";

GalleryItemGroup galleryGroup = new GalleryItemGroup();

galleryGroup.Caption = galleryGroupName;

WorkItem.UIExtensionSites[galleryName].Add<GalleryItemGroup>(galleryGroup);

WorkItem.UIExtensionSites.RegisterSite(galleryGroupName,

    new RibbonGalleryGroupUIAdapter(galleryGroup));

 

// Finally create the gallery item and add it to the gallery group.

GalleryItem galleryItem = new GalleryItem();

galleryItem.Caption = "Gallery Item";

galleryItem.Image = Resources.Contact24;

galleryItem.HoverImage = Resources.Contact64;

galleryItem.Hint = galleryItem.Caption;

// Set the galleryItem's tag to the name of the command to fire.

galleryItem.Tag = CommandNames.ContactsOpen;

WorkItem.UIExtensionSites[galleryGroupName].Add<GalleryItem>(galleryItem);


As with the last Ribbon UIAdapters that I developed, the folks over at the CAB DevExpress Extension Kit are free to incorporate these adapters into the extension kit.

However, within this whole concept there is one major piece missing. The CAB requires each UIElement to have it's own command. What if you want elements which are dynamic such as Developer Express skins, fonts, printers, etc? Creating CommandHandler's dynamically just isn't practical. In the CAB DevExpress Extension Kit Quick Start project the skins menu is created dynamically by completely bypassing the CAB; adding the BarButtonItems directly to the menu and handling the ItemClick event. This works very well but, in my opinion, violates the CAB philosophy — I'm uncomfortable going there. More on this later.

dlx

Jun. 8th, 2008

DevLynx

Ribbon Galleries

There are a couple of bugs in this post so there is a modified version of this here.

I have completed a first version of Ribbon Gallery support for the CAB DevExpress Extension Kit. My solution is different than the other UIAdapters in the extension kit because of the way Developer Express chose to implement galleries. Including gallery support in an application involves adding a Gallery to a Ribbon Page Group, then adding a Gallery Group to the Gallery and finally, adding a Gallery Item into the Gallery Group. It is in this final step that the major difference is revealed; the Tag property of the GalleryItem must be set to the CommandName to be run.

There are two reasons that the GalleryItem Tag property is required to be the CommandName. First, the Developer Express gallery implementation has the 'click' events in the RibbonGalleryBarItem not in the GalleryItem. That means that the individual GalleryItem cannot handle it's own click event. This leads to the second reason for placing the CommandName in the Tag. The CAB has, purposefully, highly decoupled the UIElement from the command handler. Within the command handler, you cannot obtain any information about the UIElement that called the command handler.

We end up with a conundrum; Developer Express does not allow us to link a GalleryItem directly to the click event and the CAB does not allow us to find information about which GalleryItem was clicked. My solution is to supply the CommandName within the GalleryItem Tag property. At one point I considered creating a GalleryItem descendant which would add a CommandName property. But since the CAB prevents information about UIElement from being passed to a command handler the Tag property could not be used for anything else. I determined that a GalleryItem descendant was overkill. I'm not entirely happy with this solution but I feel that it is an adequate compromise.

The base solution contains two files RibbonGalleryUIAdapter.cs and RibbonGalleryGroupUIAdapter.cs.

RibbonGalleryUIAdapter.cs

using System;

using DevExpress.XtraBars;

using DevExpress.XtraBars.Ribbon;

using Microsoft.Practices.CompositeUI.UIElements;

using Microsoft.Practices.CompositeUI.Utility;

using Microsoft.Practices.CompositeUI;

using Microsoft.Practices.CompositeUI.Commands;

 

namespace CABDevExpress.UIElements

{

    /// <summary>

    /// An adapter that wraps a <see cref="RibbonGalleryBarItem"/> for use

    /// as an <see cref="IUIElementAdapter"/>.

    /// </summary>

    public class RibbonGalleryUIAdapter : UIElementAdapter<GalleryItemGroup>

    {

        private readonly RibbonGalleryBarItem ribbonGallery;

 

        /// <summary>

        /// Initializes a new instance of the

        /// <see cref="RibbonApplicationMenuUIAdapter"/> class.

        /// </summary>

        /// <param name="ribbonGallery">The application menu.</param>

        /// <param name="rootWorkItem">The root work item. We need to access

        /// the Commands property of the root work item to fire the Command

        /// associated with the GalleryItem.</param>

        public RibbonGalleryUIAdapter(RibbonGalleryBarItem ribbonGallery,

            WorkItem rootWorkItem)

        {

            Guard.ArgumentNotNull(ribbonGallery, "RibbonGalleryBarItem");

 

            this.ribbonGallery = ribbonGallery;

            this.rootWorkItem = rootWorkItem;

            // Since Developer Express does not support click events for

            // GalleryItems we need to handle the ItemClick event here.

            this.ribbonGallery.Gallery.ItemClick +=

                new GalleryItemClickEventHandler(ItemClick);

        }

 

        private readonly WorkItem rootWorkItem;

        /// <summary>

        /// Gets the root work item which was passed in at creation.

        /// </summary>

        /// <value>The root work item.</value>

        protected WorkItem RootWorkItem

        {

            get { return rootWorkItem; }

        }

 

        /// <summary>

        /// Fired when any <see cref="GalleryItem"/> contained in this

        /// <see cref="RibbonGalleryBarItem"/> is clicked. If the

        /// GalleryItem.Tag property contains an active CommandName string,

        /// that command is fired.

        /// </summary>

        /// <param name="sender">The sender.</param>

        /// <param name="e">The <see cref="GalleryItemClickEventArgs"/>

        /// instance containing the event data.</param>

        protected virtual void ItemClick(object sender,

            GalleryItemClickEventArgs e)

        {

            if (e.Item.Tag is string

                && !String.IsNullOrEmpty(e.Item.Tag as string))

            {

                Command command = RootWorkItem.Commands.Get<Command>

                    (e.Item.Tag as string);

                if (command != null)

                    command.Execute();

            }

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Add(TUIElement)"/>

        /// for more information.

        /// </summary>

        protected override GalleryItemGroup Add(GalleryItemGroup uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "GalleryItemGroup");

 

            ribbonGallery.Gallery.Groups.Add(uiElement);

            return uiElement;

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Remove(TUIElement)"/>

        /// for more information.

        /// </summary>

        protected override void Remove(GalleryItemGroup uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "GalleryItemGroup");

 

            ribbonGallery.Gallery.Groups.Remove(uiElement);

        }

    }

}


RibbonGalleryGroupUIAdapter.cs

using System;

using Microsoft.Practices.CompositeUI.UIElements;

using DevExpress.XtraBars.Ribbon;

using Microsoft.Practices.CompositeUI.Utility;

 

namespace CABDevExpress.UIElements

{

    /// <summary>

    /// An adapter that wraps a <see cref="GalleryItemGroup"/> for use

    /// as an <see cref="IUIElementAdapter"/>.

    /// </summary>

    public class RibbonGalleryGroupUIAdapter : UIElementAdapter<GalleryItem>

    {

        private readonly GalleryItemGroup ribbonGalleryGroup;

 

        /// <summary>

        /// Initializes a new instance of the

        /// <see cref="RibbonGalleryGroupUIAdapter"/> class.

        /// </summary>

        /// <param name="ribbonGalleryGroup"></param>

        public RibbonGalleryGroupUIAdapter(GalleryItemGroup ribbonGalleryGroup)

        {

            Guard.ArgumentNotNull(ribbonGalleryGroup, "GalleryItemGroup");

            this.ribbonGalleryGroup = ribbonGalleryGroup;

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Add(TUIElement)"/> for

        /// more information.

        /// </summary>

        protected override GalleryItem Add(GalleryItem uiElement)

        {

            ValidateUiElement(uiElement);

 

            ribbonGalleryGroup.Items.Add(uiElement);

            return uiElement;

        }

 

        /// <summary>

        /// Validates a UIElement to ensure that the GalleryItem.Tag property

        /// contains a non-empty string value. This value is assumed to be a

        /// CommandName associated with an instantiated CAB Command.

        /// </summary>

        /// <param name="uiElement">The UIElement to be validated.</param>

        protected virtual void ValidateUiElement(GalleryItem uiElement)

        {

            // not using Guard here so that we can supply the additional

            // information in the message.

            if (uiElement == null)

                throw new ArgumentNullException("uiElement.Tag cannot null."

                    + " It must contain the CommandName to be fired.");

            if (uiElement.Tag is string)

            {

                if (String.IsNullOrEmpty(uiElement.Tag as string))

                    throw new ArgumentException("uiElement.Tag cannot be empty."

                        + " It must contain the CommandName to be fired.");

            }

            else

                throw new ArgumentException("uiElement.Tag must be a string and"

                    + " must contain the CommandName to be fired.");

        }

 

        /// <summary>

        /// See <see cref="UIElementAdapter{TUIElement}.Remove(TUIElement)"/> for

        /// more information.

        /// </summary>

        protected override void Remove(GalleryItem uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "GalleryItem");

            ribbonGalleryGroup.Items.Remove(uiElement);

        }

    }

}


An example of how galleries are created follows. Note that this example takes shortcuts with UIExtension site names; you will want to follow all of the standard conventions regarding UIExtension site names as constants.

Creating a Gallery
    // First we have to create the Ribbon Page and Group.
    string pageName = "Galleries";
    RibbonPage ribbonPage = new RibbonPage(pageName);
    WorkItem.UIExtensionSites[UIExtensionSiteNames.RibbonControl].
        Add<RibbonPage>(ribbonPage);
    WorkItem.UIExtensionSites.RegisterSite(pageName, ribbonPage);
    string pageGroupName = "My Gallery Page Group";
    RibbonPageGroup ribbonGroup = new RibbonPageGroup(pageGroupName);
    ribbonGroup.ShowCaptionButton = false;
    WorkItem.UIExtensionSites[pageName].Add<RibbonPageGroup>(ribbonGroup);
    WorkItem.UIExtensionSites.RegisterSite(pageGroupName, ribbonGroup);
 
    // Next we create the Gallery within the Ribbon Group
    string galleryName = "Gallery";
    RibbonGalleryBarItem ribbonGallery = new RibbonGalleryBarItem();
    ribbonGallery.Caption = galleryName;
    ribbonGallery.Gallery.AllowHoverImages = true;
    ribbonGallery.Gallery.ColumnCount = 3;
    ribbonGallery.Gallery.ImageSize = Resources.Contact24.Size;
    ribbonGallery.Gallery.HoverImageSize = Resources.Contact64.Size;
    WorkItem.UIExtensionSites[pageGroupName].Add<BarItem>(ribbonGallery);
    WorkItem.UIExtensionSites.RegisterSite(galleryName,
        new RibbonGalleryUIAdapter(ribbonGallery, WorkItem));
 
    // Then create the group within the gallery
    string galleryGroupName = "Gallery Group";
    GalleryItemGroup galleryGroup = new GalleryItemGroup();
    galleryGroup.Caption = galleryGroupName;
    WorkItem.UIExtensionSites[galleryName].Add<GalleryItemGroup>(galleryGroup);
    WorkItem.UIExtensionSites.RegisterSite(galleryGroupName,
        new RibbonGalleryGroupUIAdapter(galleryGroup));
 
    // Finally create the gallery item and add it to the gallery group.
    GalleryItem galleryItem = new GalleryItem();
    galleryItem.Caption = "Gallery Item";
    galleryItem.Image = Resources.Contact24;
    galleryItem.HoverImage = Resources.Contact64;
    galleryItem.Hint = galleryItem.Caption;
    // Set the galleryItem's tag to the name of the command to fire.
    galleryItem.Tag = CommandNames.ContactsOpen;
    WorkItem.UIExtensionSites[galleryGroupName].Add<GalleryItem>(galleryItem);

As with the last Ribbon UIAdapters that I developed, the folks over at the CAB DevExpress Extension Kit are free to incorporate these adapters into the extension kit.

However, within this whole concept there is one major piece missing. The CAB requires each UIElement to have it's own command. What if you want elements which are dynamic such as Developer Express skins, fonts, printers, etc? Creating CommandHandler's dynamically just isn't practical. In the CAB DevExpress Extension Kit Quick Start project the skins menu is created dynamically by completely bypassing the CAB; adding the BarButtonItems directly to the menu and handling the ItemClick event. This works very well but, in my opinion, violates the CAB philosophy — I'm uncomfortable going there. More on this later.

dlx

May. 16th, 2008

DevLynx

DevLynx Labs

At the end of April Microsoft announced a new initiative called Microsoft Office Labs. Microsoft Office Labs is an experimental concept - in their own words: “We're a group of designers and developers that collect ideas from all over Microsoft... We build working prototypes of the most promising of these ideas to see if they work as well as we hope they might.”

Karel Kral responded to my April 17th post with an interesting discussion. In his response he mentions that Rich Newman, skeptical of their value, is not using UIExtensionSites in his project. Karel Kral also notes that the Ribbon contains more than buttons and galleries. He is of the opinion that creating a workable UIExtensionSite from the Ribbon control is not possible; that switching between the menu/toolbar and Ribbon paradigm will result in an interface which caters only to the lowest common denominator.

My response can be summed up in three simple words: “I don’t know”. Welcome to DevLynx Labs. DevLynx.ShellApp is a lab experiment in how far I can take the CAB technology and create a rich user experience. I expect that there is an even chance that I will fall flat on my face - a prospect which, while not appealing, I am willing to risk.

There are a number of reasons that I started this experiment:
  • I need to learn C# - not just the syntax of a new language, but the philosophy of .NET. I have been a Turbo Pascal / Borland Pascal / Borland Delphi programmer for 22 years but it's time to move on.
  • I wanted to experiment with the full range of tasks required to produce excellent software. In all of my development employment I have worked in a code like hell (#27) environment. So I have gone to great effort to try many new things and find out how they work. These are the technologies that I am using (* indicates I am using this technology substantially for the first time in this experiment):
    • *Visual Studio and C#
    • *Loosely coupled development using the Composite UI Application Block
    • *DXperience Enterprise
    • Subversion source control (SmartSVN client)
    • FinalBuilder automated build tool
    • *XML Documentation using Sandcastle and Sandcastle Help File Builder
    • *GhostDoc to stub out the XML documentation
    • FogBugz bug tracking integrated with Subversion (I have used an issue tracking program for all of my career - this is my first try with FogBugz and integrated source control)
    • Help & Manual for writing this journal (and eventually the help file for DevLynx Address Book)
    • *Regular Expressions (using RegexBuddy to help build and learn)
    • *XPath (using SketchPath to help build and learn)
    • Sparx Enterprise Architect for UML diagramming
    • *CodeRush and Refactor! Pro as coding enhancements
    • *NUnit for unit testing
    • *NMock2 for mocking objects
    • *NCover for code coverage testing
    • *TestDriven.NET test runner
    • *Object Relational Mapping for data access and database agnosticism
    • Beyond Compare for file and directory synchronization
    • I have not decided on an installation program but I'm leaning towards Inno Setup
  • I also desire to be a better writer. The best way to improve writing is to write. This journal, the XML documentation and additional help content is an effort to improve this skill.
  • I have used many, many, ideas from people who have posted those ideas on the web. I think that it's time to give back to that community. If I can help someone else struggling with these same issues I feel that I should do so. This is the first step in that direction - I hope that it is a sustainable effort.
  • I left the military because they had taught me everything they were going to teach. I have left other employment because I had stopped learning at that position. If I'm not learning something new I get restless and unhappy. Quite frankly, I was (am) approaching this state at my current job (Is my boss listening? - I hope so!). This experiment is an effort to remain happy in my chosen profession.
  • Finally, the environment at work is not conducive to reflection (code like hell and then directly into the next fire). We can only better our skill if we can take time to reflect on issues and problems. This experiment is done at a pace where I can take time to reflect and really improve my skills.
So in the end will I (or you) be able to use the framework that I am developing? I hope so. I'm working hard to keep that end in site, but there is still risk. If you read the reasons stated above they don't include producing a functional framework. So far I have satisfied ALL of my goals with this project. Plus, I'm having more fun than I have had in years. Even if I fail to produce a usable framework, it has been a raving success for me.

dlx

May. 4th, 2008

DevLynx

CAB Command Problems

While designing and developing the Ribbon Gallery support I ran into a significant issue dealing with CAB Commands. My test functionality for gallery support is to mimic the skins gallery in the Ribbon Simple Pad. The first step is to accomplish this within the menu/toolbar paradigm and ensure that it works properly without the galleries.

A good implementation is to iterate over all skins in the SkinManager and dynamically insert them into the menu/toolbar. Each of the buttons/menu items would be linked to the same CommandHandler and pass the name of the skin to display. Here is the problem: Commands are designed to completely decouple the CommandHandler from the UI element; the Command intentionally discards all information about the UI element that fired the Command. There is no method to pass the information assigned to the UI element (in this case the skin name) to the CommandHandler. All of the research that I conducted pointed to either using the EventBroker or the WorkItem State information. However, as far as I can tell, there is no way to get the information from the dynamically created button into the EventBroker or State either.

OK, skins are a controlled, known entity. I can enumerate each skin and create a separate Command and CommandHandler. When Developer Express adds a skin I can just add a new Command. But what about other dynamic elements? One of the other examples in the Ribbon Simple Pad is a gallery of fonts. Fonts are an uncontrolled entity with each computer having it’s own unique font set. Back to figuring out how to handle dynamically generated UI elements.

We could use Reflection.Emit to create a dynamic assembly with each of the skins or fonts. Or it might be possible to use the DynamicMethod class to emit the CommandHandler methods. But if the DevLynx.ShellApp is going to be a general purpose tool then we don’t want to force this complexity on future developers with their own dynamic content. I spent about three days and walked numerous miles contemplating this problem without results.

The solution that the CABDevExpress.Extensions BankTeller application employs is to ignore the UI Extension sites completely. It creates a SkinMenu object which loads the menu items via the normal BarSubItem.AddItem method. But I wanted to use a more generic method if possible. I could always fall back on this solution if needed

The solution that I finally employed is specific to the Developer Express tool set. It also bypasses the CAB Command structure - perhaps also bypassing some of the more burdensome decoupling. I first created a DynamicCommandEventLink class which has two properties; an EventTopicName string and a Data object. These properties are set when the UI element is added to the UI Extension site. The DynamicCommandEventLink instance eventually is attached to the BarItem Tag property. The DevEx Ribbon and BarManager have an event which is fired when any BarItem is clicked. The following code is attached to this event:

   private void ribbonControl_ItemClick(object sender, ItemClickEventArgs e)
   {
       if (e.Item.Tag != null && e.Item.Tag is DynamicCommandEventLink)
       {
           DynamicCommandEventLink eventLink = e.Item.Tag as DynamicCommandEventLink;
           EventTopic eventTopic = rootWorkItem.EventTopics[eventLink.EventTopicName];
           if (eventTopic != null)
           {
               EventArgs<DynamicCommandEventLink> ex = 
new EventArgs<DynamicCommandEventLink>(eventLink);
               eventTopic.Fire(sender, ex, null, PublicationScope.Global);
           }
        }
}
Here we check to see if the Tag is a DynamicCommandEventLink; if it is, extract the EventTopicName and fire the event passing the data to the event. We bypass the extremely decoupled Commands and are include a coupling to the DevExpress controls. We are already tightly coupled to DevExpress so this appears to be a fairly good compromise. If anyone has a better idea on how to do this please let me know; even a better compromise would be nice.

This works fine for the menu/toolbar and non gallery ribbon items. We will have to see how it works for the galleries when I get the gallery UI adapters complete.

dlx

Apr. 30th, 2008

DevLynx

DevLynx.ShellApp Ribbon Galleries

A thread started by karelkral on the CodePlex Smart Client Guidance discussions prompted me to investigate galleries sooner rather than later. I assumed that I could add gallery support and, as a demo, a skin gallery in a weekend — au contraire.

When I started investigating ribbon galleries, I thought the implementation would be similar to the other UI extensions that I coded for the CAB DevExpress Extension Kit. However, the XtraBars ribbon galleries consist of three parts:
•    RibbonGalleryBarItem - container for the gallery.
•    GalleryItemGroup - named container for groups of gallery items.
•    GalleryItem - the actual clickable item within the gallery.

My first thought was that the clickable GalleryItem would be a descendent of BarItem. I could follow the same pattern as the RibbonApplicationMenu, RibbonPageHeader, and RibbonQuickAccessToolbarUIAdapter. But no, the BarItem descendant is the RibbonGalleryBarItem. The RibbonGalleryBarItem click event passes which GalleryItem was selected. My current pattern relies on each item being assigned a single CAB command which is called when the click event of that item is fired.

One option is to completely ignore the CAB UI Extension Sites and handle galleries with some special exception code in the DevLynx.ShellApp UxExtension service. This would be fairly simple but would not follow the CAB philosophy-something that I am loath to do.

Another option would be to bite the bullet and create CAB UI Extension Sites to handle the ribbon galleries-a more complex undertaking. I also feel some duty to provide galleries within the CAB DevExpress Extension Kit allowing their use without the DevLynx.ShellApp. This solution will also require some special code within the UxExtension service but it feels better to me.

The real trick is to hide all this implementation from the developer and ensure that the ribbon and menu/toolbar paradigms still work seamlessly. The developer will need to set the gallery name, the gallery group name, and some images and it should run - displaying in menus/toolbars or the ribbon depending on user choice. This is the challenge.

More to follow...

dlx

Apr. 18th, 2008

DevLynx

Source Code Release

Well, from March 9, next weekend extended into nearly 6 weeks. Too many irons in the fire extended the planned release date a bit (OK, quite a bit).

This release includes a couple of new features. I have separated the non-CAB specific functionality. The DevLynx.Utilities solution includes the ConfigurationStore, the DataAccess class, the new CommandLineArgument classes, and various helper classes. This new solution allows developers to access the auxiliary functionality without having to include the ShellApp and CAB. I did not, however, separate the help content for these non-CAB specific items.

The basics for command line argument support is included in this release. From the ShellApp documentation:

Command line arguments are a useful tool for temporarily modifying application options; however, configuration files should be used for any long term option changes. DevLynx.Utilities contains a simple method for handling command line arguments relying on .NET attributes and reflection.

The syntax for DevLynx.Utilities compliant command line arguments is as follows:
      [/|-|--]key[=|:]value | key

key is an identifier of any length greater than 1 containing only a-z, A-Z and 0-9. value may be either an identifier with the same restrictions as the key or it may be a string surrounded by double quotes (“) or a string surrounded by single quotes (‘). The optional prefix can be one of slash, dash or double dash. No space is allowed between the prefix and the key. If there is a value it must be separated from the key with a delimiter of an equal or colon character. No spaces are allowed between the key and the delimiter or the delimiter and the value. If the key has no corresponding value it is treated as a boolean value (it either exists or does not exist within the command line arguments).

The DevLynx.Utilities command line arguments are not positional; they cannot be programmatically accessed via an index. Instead they can be used in one of two ways:
• They may be programmatically accessed via the key identifier. In this case the value will be returned as a string.
• The preferred method is to create a class with properties decorated with the CommandLineArgumentAttribute attribute. The CommandLineArguments class will then fill the class properties with the corresponding values. In this case the value will be type safe and validated for the requested type.
A simple validation method may also be included in the data class of the DevLynx.Utilities command line arguments. This method will be called as soon as the instance of the data class is loaded.

Additional Restrictions:
• Duplicate key entries are ignored. The first argument with the key will set the value.
• Strings may not contain the character with which they are quoted.
• key entries are case insensitive.
• Boolean arguments without a value element will not change their corresponding properties to false if the argument does not exist on the command line; the value will remain as set in the class instance.

Currently, the command line arguments support only boolean and string values. Future enhancements will handle additional types.

There may be some readers who are interested in the ShellApp but don’t want to invest the time to examine the code at this early stage. I have uploaded a web version of the documentation so that you can see the current project state without downloading.

The current source code release, the web based documentation, and a PDF of the blog can be found on my web site at tetzel.com.

dlx 

Previous 10