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.
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 ((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.
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
{
base.OnDrawItem(e);
if ((e.Index < 0) || (e.Index >= itemCache.Count))
return;
ExtendedListBoxItem xlbi = (ExtendedListBoxItem) itemCache[e.Index];
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
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);
:
:
InvalidateItem(_previousIndex);
InvalidateItem(_currentIndex);
}
public void InvalidateItem(int index)
{
if ((index < 0) || (index >= itemCache.Count))
return;
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!