What's the problem?
I had to write a little tool that validates an input file according to a file format specification. To add some visual candy
to the application, I wanted to include a little status window where all relevant parsing messages (errors, warnings and so on)
with some descriptive text are shown.
So the first try was to inherit from the standard ListBox
class and implement custom drawing code with a little icon, a
headline and the message text. To support varying text sizes I subscribed to the MeasureItem
event and calculated the item
heights individually for each item. This worked fine until I resized my control the first time: No more MeasureItem
events!
A quick look into the documentation revealed the sentence: "Occurs when an owner-drawn ListBox is created". As I added
my parsing messages to the listbox on the fly, a continuous re-creation of the control was not an option (besides being inefficient).
So this was a dead end.
The solution
After playing around with some possible workarounds without success, I came to the conclusion that this would require a more
fundamental approach: I had to write an own listbox that would behave like I had expected it from the standard listbox right from the
beginning. The task turned out to be easier that I first expected.
The solution consists basically of two classes: ResizableListBox
and MessageListBox
. The former is the
replacement for the standard ListBox
class. It tries to mimic the real ListBox
as closely as possible
regarding events, properties and methods. Although I have to admit that it's not 100% complete (data binding is missing) but it should do
the trick in most cases. It does not provide any additional benefit over the original listbox when used alone. The only difference is,
that it fires the MeasureItem
event every time the control is redrawn. The latter implements the actual custom listbox with
the fancy drawing.
How to use it
First add the ResizableListBox
to your project. Then create a derived class and subscribe to the MeasureItem
event:
public class MessageListBox : ResizableListBox
{
public MessageListBox()
{
InitializeComponent();
this.MeasureItem += new MeasureItemEventHandler(
this.MeasureItemHandler);
...
}
...
}
Add a event handler and set the MeasureItemEventArgs.ItemHeight
property:
private void MeasureItemHandler(
object sender, MeasureItemEventArgs e)
{
int MainTextHeight;
ParseMessageEventArgs item;
item = (ParseMessageEventArgs) Items[e.Index];
int LinesFilled, CharsFitted;
int width = (int)((this.Width - m_MainTextOffset) * 0.9);
int height = 200;
Size sz = new Size(width , height);
e.Graphics.MeasureString(item.MessageText, this.Font, sz,
StringFormat.GenericDefault, out CharsFitted,
out LinesFilled);
MainTextHeight = LinesFilled * this.Font.Height;
e.ItemHeight = IconList.ImageSize.Height + MainTextHeight + 4;
}
To implement your own drawing code also makes sense:
protected override void OnDrawItem( DrawItemEventArgs e)
{
...
}