?

Log in

No account? Create an account
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

Comments

Executing Commands at RootWorkItem

Hi, DevLynx. I have one note here.
You are executing Command at RootWorkItem context. In my opinion, this will not work with solution where there is more WorkItems.

When CAB is loading module (e.g. MyModule), it creates Commands in the appropriate WorkItem (root WorkItem at MyModule), not in RootWorkItem. This Command is not the same as Command at RootWorkItem. Command executed at the RootWorkItem does not causes Command at MyModule to be run. I fall into this problem some time before: http://www.codeplex.com/smartclient/Thread/View.aspx?ThreadId=27287
Everything worked in all of my tests. I'll look into it a bit more. Thanks!

dlx

Incorrect documentation

After checking into the issue I determined that the code is correct but I misnamed the work item and documentation. In the RibbonGalleryUIAdapter, the constructor should be labeled workItem and the XML documentation corrected. The documentation should say that the commands WorkItem is passed in not the RootWorkITem. I'll fix this up and make a new post.

Thanks again Karel for pointing this out to me.

dlx

Modified version of this post

I have fixed this post in a follow up. I have changed the code and documentation to fix the command firing problem. Thanks again to Karel Kral for pointing out the "error of my ways" :-)

dlx