Click here to Skip to main content
15,867,986 members
Articles / Desktop Programming / Windows Forms

Owner Drawn Component - Extended MenuItem

Rate me:
Please Sign up or sign in to vote.
4.50/5 (8 votes)
18 Mar 2007CPOL5 min read 43.7K   1.1K   46   3
This is the second installment in the Owner Drawn Controls' series of articles
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.

C#
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:

C#
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:

C#
[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/Se<code>t definitions cannot be used here. We must replace them with their counterpart methods as shown here:

C#
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.

C#
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;

C#
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)


Written By
Retired
United States United States
Currently enjoying retirement and working on projects without pressure, deadlines or any kind of management.

Comments and Discussions

 
QuestionCan I only extend to have "Checked" property on parent? Pin
ricardok123-Dec-11 22:15
ricardok123-Dec-11 22:15 
GeneralNot bad Pin
Stanley Gillmer20-Mar-07 1:16
Stanley Gillmer20-Mar-07 1:16 
GeneralRe: Not bad Pin
Mike Hankey20-Mar-07 15:59
mveMike Hankey20-Mar-07 15:59 
Stanley,


Rasgis wrote:
I hope my 5 helps

Always

Thanks, glad you like it. Hope it can be of some help!

Mike

If you can't find time to do it right the first time how are you going to find time to do it again?

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

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