Click here to Skip to main content
Click here to Skip to main content

Owner Drawn Component - Extended MenuItem

By , 18 Mar 2007
Rate this:
Please Sign up or sign in to vote.
Screenshot - ScreenShot002.png

Introduction

I've been out of programming for quite a while and I'm catching up on the technology I've missed since I left. These articles and controls are an attempt to learn how to develop controls that are easy to use and look good, and pass this knowledge on to others that might be sharing this same path. From the response I've gotten on my two previous articles, it seems I might be on the right path. Thank you all for the encouragement and all the good people here on CodeProject.

I would also like to give credit to James T. Johnson for his excellent article on CodeProject "Getting to know IExtenderProvider" for the concept.

This is the second article in the Owner Drawn series. In this instalment, we'll be looking at the MenuItem component and extending it to suit our needs. I had originally written an app a few years ago using an earlier version of this component but had to configure MenuItem manually so when I decided to update and enhance it, I ran across the article mentioned above and thought "so that's how you do that". What the extender does is allows us to add design time properties to allMenuItem components. Similar to ToolTips!

Disclaimer: For this demo, I've provided a very limited version of this component for instructional purposes only. The code can be used as a template to extend it to meet your own specs. I only ask that if you use it, you give credit to me and if possible let me know what you did with it. I'd appreciate it!

Concepts

When creating an Owner Drawn control/component, there are three areas that need to be addressed that will allow us to customize the control. As a general rule, we derive a control and override the following event handlers to handle this interaction.

public class MyListBox : ListBox
{
    protected override void OnMeasureItem(System.Windows.Forms.MeasureItemEventArgs e) 
    {
        base.OnMeasureItem(e); 
        //Other code
    }
    
    protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
    {
        base.OnDrawItem(e);
        //Other code
    }

    protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) 
    { 
    base.OnMouseDown(e); 
        //Other code
    }
}

and set the controls DrawMode property to OwnerDraw in the designer or programmatically:

MyListBox.DrawMode = DrawMode.OwnerDrawVariable;

In the following sections, I will describe the design methodology needed to implement the functionality provided in this demo. I'm taking a layered approach in the way in which I will present this material starting with an overview of general concepts and working down to specifics, using code snippets and examples. Kinda like a well digger... he starts digging with a shovel at the surface and kinda corkscrews his way into the ground until he either gets worn out or finds water.

Break Out the Shovel

The rule above applies to this component but we have to be a tad creative here. We use the IExtenderProvider interface to allow our custom properties to be applied to all MenuItem components to be added to the MainMenu or ContextMenu components. This is a small step back from the MenuStrip and ContextMenuStrip components supplied with VS2005 but this step is required if you want to deviate from the supplied implementation. We do this by creating a component derived from component and implementing the IExtenderProvider interface, declare the properties we are providing to the component, then install and supply handlers for the requisite events. If we have done this correctly, the new extender can be dug from the toolbox onto the component tray and we will see our properties in the PropertyGrid for the MenuItems that we add to the MainMenu or ContextMenu components.

Digging In

OK, enough talk. Let's roll up our sleeves and sweat out some of that beer we drank last night.
First we need to declare the attributes we intend to associate with the component:

[ProvideProperty("XPMenuItem", typeof(MenuItem))]
[ProvideProperty("XPMenuItemImage", typeof(MenuItem))]
[ProvideProperty("XPHeaderText", typeof(MenuItem))]
[ProvideProperty("XPHeaderImage", typeof(MenuItem))]
class XMenuItemExtender : Component, IExtenderProvider
{

Notice that each attribute declared is to be associated with the MenuItem component, but as we will see later, the property itself can be of any type.
Because there may be many instances of MenuItem associated with this extender, we will use a hash table to store references to the objects, along with the associated properties for each item. We will further use the item itself as the key into the hash table to reference properties for that item.
The regular property Get/Set definitions cannot be used here. We must replace them with their counterpart methods as shown here:

public string GetXPHeaderText(MenuItem mi)
{
    return EnsurePropertyExists(mi)._headerText;
}

public void SetXPHeaderText(MenuItem mi, string str)
{
    EnsurePropertyExists(mi)._headerText = str;
}

The coding is typical for all properties. The EnsurePropertyExists is a method that guarantees that we will always have a reference to a valid object. For a complete description of IExtenderProvider, see article mentioned in the Introduction.

In Deep Water

Now let's get the event handlers hooked up and start using this new toy.

public void SetXPMenuItem(MenuItem mis, MenuItem mid)
{
    EnsurePropertyExists(mis)._menuItem = mid;

    //Add or remove event handlers for the required events
    if (mid != null)
    {
        mis.MeasureItem += new MeasureItemEventHandler(OnMeasureItem);
        mis.DrawItem += new DrawItemEventHandler(OnDrawItem);
        mis.Click += new EventHandler(OnClick);
    }
    else
    {
        mis.MeasureItem -= new MeasureItemEventHandler(OnMeasureItem);
        mis.DrawItem -= new DrawItemEventHandler(OnDrawItem);
        mis.Click -= new EventHandler(OnClick);
    }
} 

Each time an item is added to the extender, it installs the event handlers we need along with the item. All we need to do now is to implement the handlers for each and we're ready to rock-n-roll.

 private void OnClick(object sender, EventArgs e)
 private void OnMeasureItem(object sender, MeasureItemEventArgs e)
 private void OnDrawItem(object sender, DrawItemEventArgs e)

NOTE: I've used the override names for consistency, they may be any legal name.

The way I've laid out the MenuItem itself is a little restrictive. I divide the item into header and client areas and assume that header and items are a fixed size. The header information is retained in the first item in each menu along with the information for that item. I use the following calculation to determine the overall height of the menu;

Rectangle hrct = new Rectangle(0, 0, 20, mi.Parent.MenuItems.Count * 20);

Where mi.Parent.MenuItems.Count is the number of items in the Menu. To have a variable size item, you will need to add each individual items height to a total to determine true height of Menu. When I go to draw the items, I use the first item in the menu as a trigger to draw the header area, then I don't touch that area on subsequent draws. I treat it like a nonclient area.

I've tried to comment the code to make it easier to follow and so I would only have to touch on key points in the article itself. Although theory is important, I always like to dig in first and anything I don't understand, I try to find the explanation for in the article.

Points of Interest

Ever wonder where the expression "Colder than a well diggers a**" came from? Well, after you get a few feet down, the ground starts to get cold and the diggers posterior rests against the walls all the way down!

History

  • Released Sunday 3/18/07

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Mike Hankey
Retired
United States United States
I'm an old assembler and C programmer self taught in the object oriented languages and Web design. Upon graduating from Murray State University in 1983 I Started doing embedded systems for the automotive industry, got a wild hair and moved to Florida in 1986 and did some work on DEC PDP and MicroVax computers.
My latest project:
ToDoManager extension for VS2010 and Atmel Studio 6.1

Comments and Discussions

 
QuestionCan I only extend to have "Checked" property on parent? Pinmemberricardok123-Dec-11 22:15 
GeneralNot bad PinmemberRasgis20-Mar-07 1:16 
I hope my 5 helps, this is not half bad, I'll take a look at your code when i have time.
Good job
Smile | :)
 
Stanley G
GeneralRe: Not bad PinmemberMike Hankey20-Mar-07 15:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 18 Mar 2007
Article Copyright 2007 by Mike Hankey
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid