//
you're reading...
Articles, Technology

MenuExtenderComponent for .NET – Part 1

MenuExtenderComponent — Building an Even Better Mouse Trap

Part 1 of 4

A sample of the Office XP style

Table of Contents

Introduction

This component got its start way back in June or July of 2003; it started when I read an article by Chris Beckett (http://www.codeproject.com/cs/menu/menuimage.asp) where he created a component which used the IExtenderProvider interface to add menu images to the standard Windows Forms menus. His component did not support the Office XP style and there were a few other things I wanted to change.

The major one being that it didn’t support the use of what I call MDI Lists, named after the property that can be set on menu items to tell Windows to add a MenuItem at the end for each opened MDI child.

So I set off to create my own component based on his. After working on it a few days I decided I would be better off just rewriting the component so that it would be easier to add new features. At that point I had already modified large portions of the code and the only part that remained intact was the code to produce a string from the menu shortcut.

In September of 2003, I reached a point where the component was done, but I wasn’t very happy with it. I had some developments in my personal life which forced me to put the component on the back-burner. By mid-February 2004 I was able to work on it again and decided that the old component needed to be rewritten yet again and this time I would concentrate on making the code as clean as possible which would make it as easy as possible to work with.

Here I am, a little over one week into March 2004 and the new component has accomplished all of the goals I set out and I’m writing this article. I think I made some progress.

What’s New Then?

I don’t think there are many new features, most of them are improvements; improvements I hope you will find useful.

  • Office XP style drawing
  • Proper handling of the RadioCheck property on MenuItems
  • Support for both regular Images and ImageLists (one ImageList can be specified for each of the 3 image states – Normal, Disabled, and Hot).
  • Automatic creation of “Hot” images from the Normal state
  • Shadows drawn under “Hot” image under Office XP style
  • Styles can be changed at runtime with completely new sizes used
  • MDI Lists work with the new style
  • New styles can be created and used without touching the MenuExtenderComponent's code.
  • All menus draw with the same style, regardless of which instance of the MenuExtenderComponent “owns” the menu item
  • When the MDI List functionality is used, a “Windows” menu item and dialog is added to let the end-user better manage their opened windows.
  • The “Windows” dialog’s columns are customizable by implementing a single interface in the Form that represents the MDI children.

Using the Component

First Things First, Adding the Component to the Toolbox

Open up a Form, any will do since we aren’t interested in the Form but with the toolbox categories associated with Forms. Open the toolbox and right-click in the middle of one of the tabs. If you want you can add a new tab, if you do open up that tab then right click in the middle of it again. Now choose “Add/Remove Items…” your system will whirr for a bit while it loads up all the assemblies it has referenced and all of their components and controls.

Once the “Customize Toolbox” dialog comes up, it will open up right to the “.NET Framework Components” tab. Click the “Browse” button in the lower right corner and open the MenuExtenderComponent.dll included with the demo application. It may whirr around for a bit yet again. Once it is done you will see two new components are highlighted: the MdiChildManager and the MenuExtenderComponent.

Do not worry about the MdiChildManager for now, you can uncheck it if you wish. Refer to the MdiChildManager section for more information about it.

Click the OK button to close the “Customize Toolbox” dialog and return to your form. Now that you have done this, you shouldn’t have to add the components back to your toolbox again, barring reinstalls and clicking the “Reset” button in that “Customize Toolbox” dialog.

Making a Typical Application

There isn’t much that is special about the demo application, I started with a blank form and dropped a new instance of the MenuExtenderComponent on to it. If you are happy with the default behavior supplied by the component I recommend not using the ImageLists because you wind up with three times the memory usage from the images. I only need the default behavior here so I will not drop any ImageLists onto the form.

If you do want to use ImageLists then drop them on to your form and set the various ImageList properties on the MenuExtenderComponent to reference the correct ImageList on your form. Below when I set the Image property, you should set the ImageIndex property instead.

Next, drop a MainMenu and a ContextMenu component on to the form. Set the Form's ContextMenu property to the newly added ContextMenu component. The Form's Menu property should automatically be set to the newly added MainMenu component.

Now add MenuItem's to the MainMenu. Use what ever you want; it’s your application so add what you want. Refer to my SDI demo Form from the demo application if you need a pointer as to the different standard menu items.

Setting the Extended-Properties

Once you have added the MenuItems its time to start setting up the MenuExtenderComponent's properties.

The MenuExtenderComponent provides three real properties and one pseudo-property for all MenuItems. I’ve already written one article on what an IExtenderProvider component is and how to create and use them, so I’m not going to go through all of that again.

In short, an extended-property is a property that gets added to other controls and components at design-time. Because a component can’t just modify the code for another control or component, these properties are implemented as a Get and Set methods on the component doing the extending. When you set a property to its non-default value the Forms designer will write the code needed to call those methods.

EnableExtension

public void SetEnableExtension( MenuItem mi, bool value );
public bool GetEnableExtension( MenuItem mi );

This property tells the MenuExtenderComponent whether it should be responsible for drawing that MenuItem. There isn’t much of a purpose to it, except that for the MenuExtenderComponent to “know” about a menu item one of the extended-properties must be set on it.

This property defaults to true, so it will automatically associate new MenuItems to the component. It doesn’t have a design-time default value set, so this extended-property will always have a value set in the code generated by the windows forms designer.

Image

public void  SetImage( MenuItem mi, Image value );
public Image GetImage( MenuItem mi );

The Image extended-property provides the component with the image to use when drawing the MenuItem. With the built-in drawers this property is a last-resort option, giving the ImageLists their chance first. Custom made drawers may not use the ImageLists first (such as the custom drawer sample included in the demo project, it completely ignores the ImageLists).

This property defaults to null.

ImageIndex

public void SetImageIndex( MenuItem mi, int value );
public int  GetImageIndex( MenuItem mi );

The ImageIndex extended-property tells the component which image from the ImageLists should be drawn on the MenuItem. If no image is to be drawn from the ImageList for this MenuItem it should be set to -1.

This property defaults to -1.

ShouldDisposeOfImage

public void SetShouldDisposeOfImage( MenuItem mi, bool value );
public bool GetShouldDisposeOfImage( MenuItem mi );

The only pseudo-extended-property of the component; I call this a pseudo-extended-property because it has the same method signature of an extended-property but it isn’t added to the design-time environment. As far as I know there is no way to assign an image to the Image extended-property with the designer that would allow you to re-use that same instance of an Image. But it is very easy to do when you write the code by hand, for example you use the same “Save” graphic for the “Save” MenuItem in the MainMenu and in the ContextMenu.

In this case you would need to tell the component not to call Dispose on the image when it is disposed of because it isn’t in control of the Image to begin with.

Typical usage is as follows:

myMenuExtenderComponent.SetImage( menuItem, myImage );
myMenuExtenderComponent.SetShouldDisposeOfImage( menuItem, false );

This property defaults to true.

Setting the Component Properties

Host

public Form Host { get; set; }

The Host property is required to be set to the Form hosting the MenuExtenderComponent when you wish to use the MDI List replacement functionality. If you don’t use it, it doesn’t need to be set.

The designer will attempt to set this to the correct value, but if you don’t use the designer you will need to set this yourself.

UseDoubleBuffer

public bool UseDoubleBuffer { get; set; }

This property determines whether the drawing is done first to an off-screen buffer before being drawn on to the screen. Having this set to true should result in a flicker-free redraw for complicated drawing styles.

It defaults to true.

ImageList, DisabledImageList, HotImageList

public ImageList ImageList { get; set; }
public ImageList DisabledImageList { get; set; }
public ImageList HotImageList { get; set; }

These three properties are used to reference any ImageLists you want to use for storing Images.

They default to null.

TransparentColor

public Color TransparentColor { get; set; }

If you don’t use an ImageList for your images, this property will determine the color to make transparent when drawing them. Currently there is only support for one TransparentColor but I may change this later by making this an extended property.

This property defaults to Color.Transparent.

ChildManager

public MdiChildManager ChildManager { get; }

This property provides access to the internally created MdiChildManager object.

This property will be null at design-time, and at run-time after the call to ISupportInitialize.EndInit if the Host property was set to a non-null value it will be set to an instance of the MdiChildManager component.

Style

public MenuStyle Style { get; set; }

I saved this one for last because it is a little confusing.

The MenuStyle enumeration has 4 values:

  • Global
  • Normal
  • OfficeXP
  • Custom

Global is the default for the component and simply tells it to use which ever MenuStyle the static property is set to. Setting the component to one of the other 3 styles changes the static property, which affects all instances of the component.

Unfortunately, I don’t see much of a way to make it easier to understand. I need to have a value in the style which says “don’t change the global value”, but still allows the global value to be changed in the designer.

This set up does make it very easy to change all styles in an MDI application though. Set the MDI parent’s component’s Style value to what you want to use. Set each of the MDI children’s component’s Style properties to MenuStyle.Global. Now changing just the MDI parent’s Style property will affect all of the menus drawn.

The Custom style requires you to implement your own MenuDrawer derived class, an example is provided and I discuss creating one below. In addition to the MenuDrawer derived class you must also handle the only event exposed by the component: GetDrawer.

public delegate MenuDrawer GetDrawerDelegate();

public event GetDrawerDelegate GetDrawer;

License

The code and binary component follows the FreeBSD license:

MenuExtenderComponent Copyright © 2004 James T. Johnson. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution

THIS SOFTWARE IS PROVIDED BY JAMES T. JOHNSON “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This article is Copyright © 2004 James T. Johnson and requires my written permission to be reproduced elsewhere.

Continue reading the next part

Article History

  • March 18, 2004
    • Initial Version
Advertisements

About James

I am a Senior Developer/Consultant for InfoPlanIT, LLC. I previously spent over 7 years as a Product Manager for what eventually became ComponentOne, a division of GrapeCity. While there, I helped to create ActiveReports 7, GrapeCity ActiveAnalysis, and Data Dynamics Reports.

Discussion

Trackbacks/Pingbacks

  1. Pingback: Mr. .NET » Blog Archive » Article rewrite in process - March 11, 2005

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archive

%d bloggers like this: