Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Owner Drawn Controls - Extendable ListBox

0.00/5 (No votes)
15 Mar 2007 1  
This is the first in a series of articles on Owner Drawn controls, featuring the ListBox control. The basic fuctionality and some handy tips are included to help get you started in developing your own Owner Drawn controls.

Screenshot

Introduction

This is the first in a series of Owner Drawn control How-tos. The object is not to make a glitzy control with a bunch of bells and whistles, that is left to your imagination, but to demonstrate one way to create your own Owner Drawn ListBox control.

This control is different from the other derived ListBox controls that I've seen on CP in that this allows the items to be in either a collapsed or extended state. In other words, when you click on an item, it toggles the item view so that it can be minimized or maximized, allowing for additional information to be viewed when maximized. In the screenshot, the "Just Chill Man!" item is extended, all others are collapsed.

For this demo, I have created an ExtendedListBoxItem class, derived from an object that is used to maintain information about the item, and contains the methods DrawCollapsed, DrawExtended, DrawBorder, and AddGlow. This class is only a template to show what is possible and a beginning point for building your own item class. I'm sure that once these skills are mastered, you will be able to create a bigger, badder, better ListBoxItemControl class that will serve your particular needs.

Getting Started

The ExtendedListBoxControl is derived from the System.Windows.Forms.ListBox control, with the following property set:

ExtendedListBoxControl.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;

This is what allows us to draw the items ourselves. Setting the DrawMode to OwnerDrawVariable means that the items can be of any size that we want to, with a maximum of 255 pixels.

In addition, there are two virtual methods that we must override to enable us to actually do the drawing:

  • OnMeasureItem - Determines what size the item is going to be.
  • OnDrawItem - The method that actually draws the item.

One additional method that we will override is the:

  • OnMouseDown - Captures mouse down events.

These three methods are described in more detail in the next section.

The Overrides

As mentioned, there are two virtual methods we must override to be able to draw our own controls. The first of these is the OnMeasureItem method. This method is called to determine the size to draw the item. In our case, it depends on whether the item is in the collapsed or extended state. These values are determined by the MinSize (collapsed size) and MaxSize (extended size) properties.

/// 
/// Called when invalidated to find the item bounds. Since we are only concerned 
/// with the height we only need to set this value and leave the bounds.width 
/// as is! 
///
protected override void OnMeasureItem(System.Windows.Forms.MeasureItemEventArgs e) 
{
    base.OnMeasureItem(e); 

    if ((e.Index < 0) || (e.Index >= itemCache.Count))
        return; 

    ExtendedListBoxItem xlbi = (ExtendedListBoxItem) itemCache[e.Index];

    //If its the current selection and not collapsed 
    // set height to Max. otherwise just set the item selected to Min. value.
    if ((e.Index == _currentIndex) && !isCollapsed) 
        e.ItemHeight = xlbi.MaxSize; 
    else
        e.ItemHeight = xlbi.MinSize; 

    e.ItemWidth = this.Width; 
}

We finally get to the most important method, the one that actually does the drawing. This method uses the isCollapsed property to determine whether to draw the item in the collapsed or extended state.

///
/// Called when the item needs to be drawn
///    
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
{
    base.OnDrawItem(e);

    
    //If not a valid index just ignore      
    if ((e.Index < 0) || (e.Index >= itemCache.Count))
        return;

    ExtendedListBoxItem xlbi = (ExtendedListBoxItem) itemCache[e.Index];

    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    
    //Draw the item in current state.
    if ((e.Index == _currentIndex) && !isCollapsed)
        xlbi.DrawExpanded(e);
    else
        xlbi.DrawCollapsed(e);
}

The drawing of the item is delegated to the item itself; in this way, you can use the base class to provide basic functionality, roll your own or derive a class from ExtendedLstBoxItem, and use any or all of its functionality to suit your needs.

An index value is passed in via the DrawItemEventArgs. We use this index to retrieve a reference to the item, then call its appropriate DrawXXXX method to do the dirty work!

Next, we override the OnMouseDown instead of the OnSelectedIndexChanged, because the latter only gets called when the user clicks on a different index than the one selected, and we need to capture the mouse down event no matter which item is selected. I ran into a problem when trying to invalidate the list items, when selected. When trying to either expand or collapse the item, the OnMeasureItem handler wasn't getting called. To solve this, I Googled and ran across an obscure article that suggested that the item to be invalidated must be removed then reinserted into the list. The following code does this for the Previous and Current items:

protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) 
{
    base.OnMouseDown(e); 
    
        :
        :
    
    //Invalidate previous selection 
    InvalidateItem(_previousIndex); 
    
    //Invalidate current selection 
    InvalidateItem(_currentIndex); 
}

/// 
/// Invalidates the item at the provided index.
///
public void InvalidateItem(int index) 
{
    if ((index < 0) || (index >= itemCache.Count)) 
        return; 

    //All we need to do here is make sure
    //we get the correct item index.
    this.Items.RemoveAt(index); 
    this.Items.Insert(index, " "); 
}

Points of Interest

When running the demo, I have provided a PropertyGrid so that items can be designed visually. This feature is handy as it allows you to play with the different properties and see what the heck is going on without having to recompile every time you make a change. Thank you Microsoft for this handy control.

Note: The MinSize and MaxSize properties do not take effect immediately; you must toggle the item for changes to be reflected.

I hope this article has been of help!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here