Overview
MultiLineListBox
is a fully owner drawn ListBox
derived class that supports
multi-line items as well as in-place editing of items. It has been wrapped into
a class library for ease of deployment. You don't have to do anything special
when using it. Just use it as you would use a normal list box. Whenever you add
a string that's too long for a single line, the MultiLineListBox
will wrap the long string into multiple lines. Editing items is also easy. Just right click
on any item or press the F2 key when an item is selected, this will superimpose
an edit box exactly on top of the selected or clicked item. You can make your
changes and either press the Enter key or you can click anywhere else within the
list box for the modifications to take effect. If you want to cancel the changes
and get back to the original text, simply hit the Escape key.
The class will not allow you to make an item null, and will pop up a message
box alert and won't let you take focus away from the item till you've either
cancelled the modification by pressing Escape or you have entered some text.
If you enter a lot of text into the item edit text box, the overlaid text box
itself will allow you to scroll vertically.
Using the class
Simply add the control to your toolbox. Now use it just as you would be
using a regular list box. Try entering long strings. And run the program. Try
editing the strings by either pressing the F2 key or by right clicking on an
item. See how the alert message box pops up when you try to modify an item
string to a null string. I added this feature because in the project where I'll
be using this, null strings are strictly forbidden. Perhaps you might want to
allow null strings, if so feel free to comment out the relevant code in the
source code.
Technical details
Well obviously the DrawMode
is set to DrawMode.OwnerDrawVariable
. We need to
figure out the height required per item. This is how we do it it in the
OnMeasureItem
override,
string s = Items[e.Index].ToString();
SizeF sf = e.Graphics.MeasureString(s,Font,Width);
int htex = (e.Index==0) ? 15 : 10;
e.ItemHeight = (int)sf.Height + htex;
e.ItemWidth = Width;
Now we need to actually draw the text as this is an owner drawn list box. We
override OnDrawItem
as shown below.
protected override void OnDrawItem(DrawItemEventArgs e)
{
if(e.Index > -1)
{
string s = Items[e.Index].ToString();
if((e.State & DrawItemState.Focus)==0)
{
e.Graphics.FillRectangle(
new SolidBrush(SystemColors.Window),
e.Bounds);
e.Graphics.DrawString(s,Font,
new SolidBrush(SystemColors.WindowText),
e.Bounds);
e.Graphics.DrawRectangle(
new Pen(SystemColors.Highlight),e.Bounds);
}
else
{
e.Graphics.FillRectangle(
new SolidBrush(SystemColors.Highlight),
e.Bounds);
e.Graphics.DrawString(s,Font,
new SolidBrush(SystemColors.HighlightText),
e.Bounds);
}
}
}
Well, so far so good. Now for the in-place editing, what we do is to derive a
class from TextBox
and add it to our list box. And we handle the
OnMouseUp
method to check if the user has right clicked an item.
protected override void OnMouseUp(
System.Windows.Forms.MouseEventArgs e)
{
if(e.Button == MouseButtons.Right)
{
string s = Items[index].ToString();
Rectangle rect = GetItemRectangle(index);
tbox.Location = new Point(rect.X,rect.Y);
tbox.Size = new Size(rect.Width,rect.Height);
tbox.Text = s;
tbox.index = index;
tbox.SelectAll();
tbox.Show();
tbox.Focus();
}
}
And similarly we handle OnKeyDown
for checking whether the user
has pressed the F2 key.
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.KeyData == Keys.F2)
{
string s = Items[index].ToString();
Rectangle rect = GetItemRectangle(index);
tbox.Location = new Point(rect.X,rect.Y);
tbox.Size = new Size(rect.Width,rect.Height);
}
}
In the TextBox
derived class we override both OnLostFocus
and OnKeyPress
(check for Enter key) and make checks to ensure that
the user will not enter a null string. The source code can be examined for the
finer details, but basically that's all the class does and this class is a
private inner class of the MultiLineListBox
class. I also check for
the Escape key in the OnKeyPress
handler to allow the user to
cancel a modification.
Conclusion
I hope to get your feedback - both good and bad. I'd also like to know
whether anyone actually found this useful enough to use in an application. Or
whether the class can be improved in any particular manner. As for myself I
spent the whole day dealing with various kinds of issues I ran into, mostly due
to my custom control coding inexperience rather than due to any great difficulty
in what I was attempting.