Home

Advertisement

Previous 20

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 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 

Mar. 9th, 2008

DevLynx

Progress Update

I had to leave town for a week on personal business so I didn’t get as much done as I would have liked. I have completed the class structure to manage command line arguments. I now need to create the automated build tools for managing the MD5 hash information. After that I will update the documentation to include the command line arguments classes. As soon as those tasks are complete I’ll do another source code release.

As I have said, we may not use the DevLynx ShellApp at work. However, we will almost certainly be using the ConfigurationStore and the command line arguments classes. So I have separated these general purpose classes into a DevLynx.Utilities solution. That way they can be used without the ShellApp.

I hope to have a source code release next weekend.

dlx

Feb. 24th, 2008

DevLynx

How do I compile the DevLynx ShellApp?

Based on a comment by manayat regarding my Feb 17th post, I thought it would be a good time to describe how to use the DevLynx ShellApp. So this weekend I created a virtual machine and loaded Visual Studio 2005 and the Developer Express components. I then documented how to get both the ShellApp and Address Book to compile. There were some modifications to make it easier for the first time user. There are way too many steps but I will hopefully automate some of them as the product gets more mature.

The documentation is in the help file found in the Source\ShellApp\Support\Help directory. Go to the task-based help and view the articles there.

This code (along with the help file) and a PDF of the blog can be found on my web site at http://www.tetzel.com.

dlx
Tags: ,

Feb. 17th, 2008

DevLynx

Documentation and Source Code Release

Well, I was right; documentation is a daunting task. Even the little bit that I have produced took much longer than expected - I have a whole new respect for technical writers. I don’t have everything documented but I do have a good start and will hopefully not fall behind again.

There have been very few changes to the code base since adding the splash screen.
•    ConfigurationStore.CreateStore now writes cleaner XML into the configuration section.
•    I have added a DefaultUxParadigm to the ProductInformation.config file. This value allows you to select which paradigm comes up when the application is first initialized.

The latest code and PDF of the blog can be found on my web site http://www.tetzel.com.

Next steps:
•    Keeping the MD5 hash codes in both the ProductInformation.config file and the ValidateProductInformation is too prone to error. I will be creating automated build tools which will allow these items to be set during the build process. There will be a series of console applications that can be run from batch files or integrated into build tools such as FinalBuilder.
•    The automated build tools will rely on command line arguments. I looked around the internet for a class that I could use. However, they were either too simple or more complicated than I wanted. So I will write a class to manage command line arguments.
•    I will also work on a feature to allow some rudimentary ordering of UxExtension elements. Right now my Exit menu item always appears at the top of the menu which would be very confusing for users expecting to see it at the bottom.

dlx
Tags: ,

Jan. 28th, 2008

DevLynx

Sandcastle January 2008 Release

I downloaded the Sandcastle January release earlier and just checked the speed. Compiling the DevLynx ShellApp help content took 9 minutes with their previous release; it now takes 1:48. Very nice work! Wow.

dlx
DevLynx

Protecting a decoupled ShellApp and the splash screen

One of the features that I added for the last release was a ProductInformation.config file. This configuration file stores information about the company, product and splash screen. This information is accessed via the ProductInformation service. From the help content:
The DevLynx ShellApp is designed to be used with multiple applications without recompiling. However, the ShellApp need to know some information about the product including the company name, product name, paths where product specific information is to be stored, splash screen image, etc. This information must be decoupled from the ShellApp. The product information service addresses this need.
The product information service reads the appropriate information from a configuration file. This file is named "ProductInformation.config" and must reside in the same directory with the application executable. The configuration file is a standard app config file with an appSettings section.
The ProductInformation.config file is designed to never be changed by the user or the application. So only place information in this file that is constant for the lifetime of this release.
Having a text file with your company name and splash screen may be an enticement for your users. They may want to change the company name or the splash screen image. So how do we go about protecting this information from the “vandalism” of users?

The ProductInformation service has the capability to store an MD5 hash for a file and then check that value to ensure that the file has not changed. This can be done for the splash screen image. But of course the ProductInformation.config file must be protected as well. Here we must store the pre-computed MD5 hash of the ProductInformation.config file somewhere else and check it against the file. The ValidateProductInformation method takes an MD5 hash string as a parameter. The stored string can be located anywhere. In the DevLynx Address Book I have it stored directly in the main Address Book assembly which can be strongly named as well. Of course if you don’t care if someone makes changes to these files then you don’t have to use these capabilities.

I also added a splash screen to the shell since I felt that the pause between launching the application and the shell appearing was too great. The splash screen is a modification of the asynchronous splash screen at: http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=27DF8F87-37A7-4751-8198-B9A3526BBCDD This is a general purpose splash screen and all you have to provide an image to display and a few properties and the rest is done automatically. If you don’t provide an image it will still run but as a thin window with the status text displayed. The splash screen options are stored in the ProductInformation.config file and are listed below (again straight from the help content):
  • SplashImageFileName - Contains the file name of the image that will show on the splash screen. If this name is blank then ShellApp will look for DevLynx.ShellApp.Shell.exe.png for the splash screen image.
  • SplashImageFileMD5 - If this value is present the ShellApp will check to ensure that someone has not altered the splash screen image file. It must contain the MD5 hash of the image file.
  • SplashStatusLocation - The X, Y coordinates of the splash screen status text. The default location will be 10 pixels from the left and 10 pixels from the bottom of the image.
  • SplashTextColor - The color of the status text on the splash screen. The default is black. Colors must be one of the named colors from the System.Drawing.Color class.
  • SplashTransparencyKey - The portions of the image which are this color will be completely transparent. Note that partial transparency is not supported here. Hard edges are required to make this look good. Colors must be one of the named colors from the System.Drawing.Color class.
  • SplashOpacity - Setting the SplashOpacity to something less than 1.0 will make the splash screen transparent. This number needs to be between 0 and 1.0.
  • SplashIsHaloText - Since we are placing the status text directly on an image the text may blend into the image and be unreadable. By setting the SplashIsHaloText to "true" the status text will surrounded by a halo effect in the SplashTextHaloColor. Note that if the halo text goes into a transparent section of the splash screen it will not look very good.
  • SplashTextHaloColor - The color of the halo surrounding the status text on the splash screen. The default is WhiteSmoke. Colors must be one of the named colors from the System.Drawing.Color class.
Using these options you can develop nearly any splash screen that is desired. I’ll admit I got a little carried away with the splash screen options. I always wanted to see an algorithm for halo text (http://www.bobpowell.net/halo.htm) and once I found one I had to implement it.

I also continued to develop the help content. While it is far from complete I am pleased with what I have to this point. The next step is to write the help content for all of the features that I have developed so far - time to play catch up. As with many developers, I would like to continue adding features; but if I don’t catch up with the help content now it will be too daunting a task.

dlx

Jan. 20th, 2008

DevLynx

Refactoring, Sandcastle, Data Access, and Source

This is the first release in which I have included the sample program, DevLynx Address Book (DAB). The DAB has all of one function, it will allow you to enter contact names. It uses the new DataAccess service which centralizes some of the common data access needs; more on that later. The DAB comes with source and a zip file which includes the running executable.

Here are some of the new features/refactorings: First I removed the “Infrastructure” level of the ShellApp. The SCSF added the infrastructure level as an organizational method anticipating that business modules would be in the same solution. Since that is not the case with the ShellApp and everything was in the infrastructure directory I felt it needed to be removed.

I found and added the Versioning Controlled Build application to help synchronize the version numbers. This tool has both a Visual Studio add-in and a command line tool. I added the command line tool to the FinalBuilder automated build and now I no longer have to think about versioning any builds.

As soon as I started the DAB sample application I immediately needed a way to persist the data. I wrote the DataAccess service which handles connection string storage, concurrent connections to multiple databases and in memory data sources. The DataAccess service is based on the DevExpress eXpress Persistent Objects (XPO) which allows connecting to a number of different databases without code changes (XPO is an Object Relational Mapping tool). I have tested it with the in memory data source, VistaDB3, Access and SQL Server.

One of the nice features of XPO is support for an in memory data source. It stores persistent data in a .NET DataSet but allows access via the same method as other databases. The DataAccess service can use the in memory data source and includes the ability to save and load the dataset from the disk. This was first developed for testing speed but will have use in general purpose development.

The final functionality was to create additional content for the DevLynx ShellApp help files. There is now an overview topic discussing the functionality of the ShellApp. I will add examples from the DAB in the future. The additional content is simply HTML files included by the Sandcastle Help File Builder. However, I wanted the additional content to use the same CSS files, scripts and images and look and feel of the XML comment generated source. After a bit of experimentation it seems to be working great. I encountered two problems. First Sandcastle is SLOW. It takes about nine minutes to generate the help files. So every time I needed to test my new content I would have to wait. I hope that Microsoft speeds up the processing a bit. I looked around for a free HTML editor that would satisfy my simple needs. After a bit of searching I settled on Komposer. I worked with it for a few hours but finally got tired of it reformatting my HTML source into something unreadable. I set the option to keep the original formatting but it didn’t matter. So I finally downloaded a trial copy of TopStyle by Nick Bradbury. It provides few frills but it works and doesn’t try to “help me out”.

To run the DAB all you need to do is to unzip the files and run the executable. It places nothing in the registry and does not register anything on your computer. It does, however, place configuration files and its database into the directories pointed to by Environment. SpecialFolder .CommonApplicationData and Environment. SpecialFolder. ApplicationData. To change the database used by the DAB you can modify the SharedProductSettings.config file in the CommonApplicationData directory. Just change the “Primary” stringVaue connection string link to point to one of the other connection strings in the ConnectionString category. You can look into the connection strings group to see the different databases that have been tested. If you use the SQL Server connection string you will need to insert your own server and instance names.
  <category name="ConnectionStringLinks">
<value name="Primary" stringValue="VistaDB3" />
</category>
You can change to the menu/toolbar paradigm by changing UserCommonSettings.config in the ApplicationData directory. Just change UxExtensionName stringValue from DxRibbon to DxMenu.
  <category name="UxSettings">
<value name="UxExtensionName" stringValue="DxRibbon" />
</category>
You will find the source code at: http://s3.amazonaws.com/DevLynx/2008 01 20 DevLynx CAB Source.zip and the DAB binary at: http://s3.amazonaws.com/DevLynx/2008 01 20 DevLynx CAB.zip

dlx

Jan. 14th, 2008

DevLynx

Delay in source release

Well, unfortunately I ran into some coding issues and I'm a bit late on my promised release. I'll try to get it done in the next couple of days. I apologize for the inconvenience.

dlx

Jan. 8th, 2008

DevLynx

NUnit, NMock2 and Sandcastle

It’s been a month since my last update. The holiday season is over and I’m back to work on my CAB test application. These past few weeks I have been focusing on unit testing, documentation and automated builds. I have also added one service, the ConfigurationStore.

The ConfigurationStore allows the user to generically read and write type safe values to configuration files in a more structured way then the standard appSettings. It allows multiple stores with categories and values - basically a two organizational levels and a terminal nodes to store key-value pairs. Currently types supported are: Bool, DateTime, DateTimeUtc, Double, Enum, Integer, and String. I will add more as they become required. Since I am testing the CAB for multiple large scale projects the ConfigurationStore can manage information for the following situations:
•    AppConfig: application specific values that the user should not change (we are assuming that the user does not have access to the application location so we don’t store values that should be user modifiable in the application configuration file).
•    SharedCommon: values which are shared between users and common to all products.
•    UserCommon: values which are user specific and common to all products.
•    SharedProduct: values which are shared between users and product specific.
•    UserProduct: values which are user specific and product specific.
•    UserCustomization: values which are user specific and holds user customizations (such as window placement, etc).

The ConfigurationStore class has 100% code coverage through NUnit tests. This is my first real attempt at unit testing (I know, I’m a late bloomer :-). I’m sure that all paths are not covered but I hope that will be resolved as I learn better testing techniques.

Overall unit testing is up to date. I have unit tests on much of my code but have excluded unit tests on classes that the SCSF created. The code that does not have unit tests are the Shell and those that must interact with the WorkItem.UIExtensionSites. The CAB code included a TestableWorkItem that recreated some of the WorkItem but it did not include the UIExtensionSites. I may be able to work something out at a later date but for now I’m going to continue.

I also experimented with mock objects for the first time. This was a bit frustrating since NMock2 does not have the best documentation; but I was pleased with the final result. All of the tests will be included in the next source release.

I have also started to smooth out the XML documentation of classes. It’s by no means complete but it is usable. I’m using Microsofts SandCastle with Eric Woodruffs Sandcastle Help File Builder to produce a help file. I have also experimented with adding additional content to the generated help files so that I can include a framework overview and task oriented entries into the help file. I’m going to say something that is perhaps not so popular when dealing with open source software. My job is to create software for sale. I don’t have time to peruse the code of an open source tool to figure out how it works-I want documentation. If a development tool has insufficient documentation then it’s not going to be used. Documentation is important to me and I’m going to supply it.

So where to next? I have enough structure in place to start the actual test application; the DevLynx Address Book. The next task is to set up a database and start using the framework. As framework issues arise I’ll make modifications to ensure that it meets the goals.

dlx

Dec. 8th, 2007

DevLynx

Source Code Release

I have consolidated all changes to date for another source code release. Since my work on the CAB and Developer Express components is a work in progress there will be breaking changes in this code. Most of the code releases that I will publish in the next months will have breaking code changes. I apologize in advance for this inconvenience.

This release includes all changes since the last source code release, the items mentioned in my Dec 2, 2007 entry as well as an additional UIElementAdapter for the Ribbon Quick Access Toolbar. I also changed the name of the ApplicationMenuUIAdaptor to RibbonApplicationMenuUIAdaptor so that all of the Ribbon centric classes are prefixed with ‘Ribbon’.

The RibbonQuickAccessToolbarUIAdapter code follows:

using System;

using Microsoft.Practices.CompositeUI.UIElements;

using DevExpress.XtraBars;

using DevExpress.XtraBars.Ribbon;

using Microsoft.Practices.CompositeUI.Utility;

 

namespace CABDevExpress.UIElements

{

    public class RibbonQuickAccessToolbarUIAdapter : UIElementAdapter<BarItem>

    {

        private RibbonQuickAccessToolbar ribbonQuickAccessToolbar;

 

        public RibbonQuickAccessToolbarUIAdapter(RibbonQuickAccessToolbar ribbonQuickAccessToolbar)

        {

            Guard.ArgumentNotNull(ribbonQuickAccessToolbar, "ribbonQuickAccessToolbar");

            this.ribbonQuickAccessToolbar = ribbonQuickAccessToolbar;

        }

 

        protected override BarItem Add(BarItem uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "uiElement");

            if (ribbonQuickAccessToolbar == null)

                throw new InvalidOperationException();

 

            ribbonQuickAccessToolbar.ItemLinks.Add(uiElement);

            return uiElement;

        }

 

        protected override void Remove(BarItem uiElement)

        {

            Guard.ArgumentNotNull(uiElement, "uiElement");

            if (ribbonQuickAccessToolbar == null)

                throw new InvalidOperationException();

 

            ribbonQuickAccessToolbar.ItemLinks.Remove(uiElement);

        }

    }

}


As I mentioned in the Dec 2, 2007 entry, I have added support for both common command images as well as command overlays images. Images that I use have been purchased from glyFX and, therefore, I cannot release the actual images. I have blurred the images (using ImageMagick) so that I could include placeholders in the source distribution. You will have to replace these blurred images with your own if you choose to use the command images and overlays.

You will find the source code at:
http://s3.amazonaws.com/DevLynx/2007 12 08 DevLynx CAB Deployment.zip

You will also find an updated PDF of this blog series at: http://s3.amazonaws.com/DevLynx/LiveJournal Blog.pdf

dlx

Dec. 2nd, 2007

DevLynx

Status Update

It’s been two weeks since my last entry. I have been busy with the day job as well as other personal matters. So, I have not had a great deal of time to contribute to the project. Some of the issues that I have worked on:
•    Removing the Source/Infrastructure directories in the ShellApp. Since the ShellApp is going to be shared and no other modules placed into the solution it seemed wrong to have the additional directory levels. So I flattened out the directory structure.
•    In the previous released source of the ShellApp I had placed our standard copyright notice from the day job. This has been replaced with the MIT License so that others can legally use my additions.
•    Added structure to centralize some of the common command images that are invariably in most projects. This way when someone demands something silly like  “We must change the ‘cut’ command image to a bloody butcher’s knife?” it can be done in all projects at once.
•    Added command overlay images. At work I always seem to be the one who creates command images. I know, we should get a graphic artist to do this - and actually we have access to one now! I have spent untold hours creating various images for our commands (they were adequate but I am not an artist). One of the things that takes so much time are what I call overlays. An overlay is a small command image modifier such as a plus sign for ‘add’. For instance a ‘Project’ command will have modifiers for add, delete, edit, import, export, make it spin around on it’s head three times, etc. I always do this with a base image and the modifier is placed in the lower right hand corner. When someone asked for an overlay replacement I would have to go in and change many command images. Now overlays can be placed on the image automatically, are easy to change, and are consistent across commands and projects.
•    Created automated builds using FinalBuilder.
•    I have set up FogBugz to use as an issue tracking system and have started to track issues.

In other words, I have not been completely idle. My next step is also going to be more structure. I will be adding unit tests and doing code coverage. So it probably won’t be until after the holidays that I have more features implemented.

dlx
Tags:

Previous 20

Advertisement

Customize