Table of Contents

In this article I want to demonstrate the technique of creation of a list box
control that draws a small image to the left of each item's text (see Fig.1).
This control exposes the Items collection
property containing custom items which may be edited by means of
Visual Studio's collection editor.

Fig.1 Control appearance at run time

There are several articles devoted to the same problem, as for
example ListBox with Icons by nhgiang.
But none of these implement a control having the Items property
that may be edited by means of the Visual Studio's Windows Forms Designer.
They use some manually created code fragments like:

The control which implementation is described in present article
provides visual editing opportunity that speeds up the development process.
But, of course, it still supports the programmatic way of managing items.

Visually added items - you do not have to keep in mind what index
is associated with an image in the image list. All the editing is done in the
collection editor and you don't need to touch the code (see Fig.2).

Fig.2 Collection editing at design time

In the following paragraphs I will shortly describe the problems I had
encountered. For implementation you better look at the code - it speaks for
itself.

Using the control in your project

If you want to try first the demo project, please do not
forget to compile it first (at least the ImageListBox control
library), or otherwise the control won't appear in the test form at design
time.

Fig.3 Principal properties of the control

If you want to use the ImageListBox on your form,
please also add the ImageList control to your form, fill it with
images, then set the ImageListBox's ImageList property
and then start adding the items to Items property (see Fig.3).

Control class

There's not much to say about the control class. The ImageListBox
is inherited from standard ListBox control. We should implement
the ImageList property that will allow to provide the source for
images to be drawn in the list box. Another step will be overriding the ListBox's
Items property. The only important thing is to set the
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
attribute to inform the designer that we want to serialize the contents of the
collection, not the collection itself during design time. The DrawMode
property is set to OwnerDrawFixed value and then is hidden from
property browser by the [Browsable(false)] attribute. The drawing
is made in the OnDrawItem protected method and there's nothing
complex to be described.

There are several ways of implementing the collection,
as internal or nested class, or somehow else. In this
demonstration we will use the base class Items collection to store
the object of our collection, so it will be better to implement the collection
as a nested class to allow access to control's private members. The collection
class should implement the IList, ICollection and IEnumerable
interfaces. We can't use in this case CollectionBase inheritance
since there's more complex behavior than just simple item storage. I recommend
you to look the code for the details of implementation. Another important issue
is to implement this[int index] property for the collection class
that will allow to access the collection members.

Implementing item class from scratch (no base class)

First we need to implement at least two constructors for the item class. The
first is parameterless that allows the Collection editor to create an "empty"
item with all properties set to default. Another constructor is the one with
the largest number of parameters needed to fully reconstruct all editable
properties of the item object. In our case this is ImageListBoxItem(string
Text, int ImageIndex).

The item class implements the ISerializable and ICloneable
interfaces (I am not sure whether the ICloneable is needed, but
this simply copies the set of interfaces implemented by ListViewItem).

The ISerializable interface is employed by the Collection editor to
allow item properties to be set in the editor. Each item serializes two
properties: Text (string) and ImageIndex (integer).
These properties appear in the property grid on the right-hand part of
Collection editor (see Fig.4)

Fig.4 ImageListBoxItemproperties. Method I.

Another noticeable problem is to get the Collection editor to display correct values
(as well as thumbnail images) for the ImageIndex
property. We'll do this in three steps:

Add ImageList read-only property to the item class.
Hide it from property browser by [Browsable(false)] attribute.
This property is used by the UITypeEditor assigned to ImageIndex
property and it is the image list from which this value editor takes the images
to be displayed in the combobox.

Add [TypeConverter(typeof(ImageIndexConverter))] attribute
before the implementation of ImageIndex property. This
tells the editor that it needs to take the indexes from the ImageList
associated with an item.

Add [Editor("System.Windows.Forms.Design.ImageIndexEditor",
typeof(UITypeEditor))] attribute before the implementation of ImageIndex
property. This tells the Collection editor to use standard UI editor for the properties
that are image indexes.

Fig.5 Combobox with images from the control's ImageList

And voilа! Here is the combobox filled with images as desired (see Fig.5).

This is performed by implementing the type converter class that inherits
from the TypeConverter. We will use the ExpandableTypeConverter
(as Microsoft does for ListViewItem) to lessen the amount of
functions to override. The only functions we should override are CanConvertTo
and ConvertTo. We should convert only to InstanceDescriptor
type used by the Windows Form Designer. The ConvertTo function
bulds the InstanceDescriptor object by providing the ConstructorInfo
for the constructor that will appear in the code added by the designer (ImageListBoxItem(string
Text, int ImageIndex) in our case). Then we associate this converter class with the
ImageListBoxItem by means of [TypeConverter(typeof(ImageListBoxItemConverter))]
attribute. Now the control is ready to be edited by the designer.

Pluses and minuses of Method I

Pluses:

The class does not have any base classes (except System.Object).
You can fully control the class you have created.

Another solution is to derive the collection item class from the System.ComponentModel.Component.
In this case you won't need to implement the type converter class, neither the
serializability. All other methods and attributes will be the same. The
Collection editor appearance will be different in this case (see Fig.6) as well
as the code generated.

Fig.6 ImageListBoxItemproperties. Method II.

The code that is added automatically, will be split in two parts. First, the
member variables for all the items are added to your form class. Then in form's
class InitializeComponent method the constructors are called for
items, the items are added to listbox and then their properties are set.

This article demonstrates the method of exposing the collection as a property.
This technique may be useful for a bunch of other controls (bands for
OutlookBar, column and row headers for custom grid controls, etc.). If you
spend a couple of days in implementing a set of classes as described in this
technique, this will gain you time in the projects where you are going to use
the designed controls. Instead of adding lines of code, simply click, type and
enjoy. I already did.

Problems

The principal problem that remains unresolved, is the localization for Method I.
I use the [Localizable(true)] attribute for all members I
want to be localizable, but this doesn't seem to work in a proper manner. When
I set the Localizable property for the form to true and
then try to switch between the languages, the ImageListBox contents
remain invariant. If anyone has already solved such a problem, please contact me
if possible. I have noticed that almost the same problem is encountered
for ListView control.

Thanks to James T. Johnson, now the toolbox bitmap is displayed correctly. I
have added an embedded bitmap resource (ImageListbox.bmp) that
appears in the Visual Studio's toolbox. The name of the resource must match
the control's name (minus name of the project default namespace).

Another method of implementing the item class that supports localization has been added.

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.

Comments and Discussions

It would be good if you could set the horizontal and vertical text aligment fot the items.

For instance, i want to use 32x32 icons as images in the list, so I set the ItemHeight to 34 (to get a little free space between icons). But the text is painted in the upper left corner, and it looks odd to say the least. I would prefer it if I could set a vertical alignment to "center"...

Otherwise, good work! I'll give you a 4. (If you fix the alignment, I COULD be persuaded to change it to 5 )

You have the code - you can change the text alignment yourself - it's easy. The article is about exposing a custom object collection as a property. The image list was used only to make the article more attractive

Sure I could - to be perfectly honest, I have almost finished doing that between my initial post and your response...

BUT! There is one big possible problem with that approach:

I am now going to use your control with my own modifications in my current project.
At some point in the future, you are going to release an update to the control.
I will want to update the code in my project also.
But because my memory is like a sieve and I'm unable to remember what I did more than 2 hours ago, I will have forgotten 1) That I did any modifications at all! and 2) What modifications I actually did.
And then I will have to do it all over!

So you see why I think it best that changes are made in YOUR project instead of mine?

If you want to, I can send you the modifications when I'm completely done...

Hey Alex.
First off, nice project. Secondly, I've been passing this back and forth to everybody I can and can't get an answer: I'd like a component like yours but with the ability to display bigger pictures. I'm trying to find something like this for a twitter project, and in as good as yours is it doesn't seem to scale right. Maybe I'm doing wrong, but I think I need another control. Got any ideas?

Dear Brustem,
The only exception I have encountered was "method not supported", because I did not implement the Remove method of the exposed collection, since it wasn't required for the sample.
You may implement something like this in the ImageListBoxItemCollection class:
public int IndexOf(object item)
{
for(int i = 0; i < this.Count; i++)
{
if(this[i] == item)
return i;
}
return -1;
}

I've made the ImageListBox display in a comboBox style dropdown. Upon dropping down the items, the image show perfectly. However, when I click one, the dropdown collapses and just the text shows (actually it's the ToString() override).

How can I show both the image and the proper text while in the collapsed view?

I implemented something similar to your control. I have a control and I added to it an ImageList property. Then I added another property that represents the index within the ImageList. I want to be able to edit it like the ImageIndex in the Button class. So I added the TypeConverterAttribute and the EditorAttribute exactly as you did with the ImageListBoxItem. It works fine but I cannot see the small icons of the images inside the drop-down list. Do you have any idea what the problem might be?

I have a play with your sample and I am impressed. It's the nearest example I've found to what I want to do which is make the TabPage Collection Editor use my own TabPage class.
I've worked out that the Collection Editor takes the "type" of object from the collection's Item method but I am having real problems sub-classing the TabControl.TabPageCollection class so that I can override it's indexer. Have you or anyone else done any work in this area?

Yes, I was initially trying to follow your guide then realized I was doing unnecessary work. I changed the based to only IList and got the same result.

Better still, the best way to implement collection is to extend the CollectionBase abstract class, which also implements the IList with some events to walk home with. In fact, all the IList and related interfaces are already implemented for you.
I finally changed my classes to inherit from the CollectionBase and got a more simplified code (no change in functionality).

But I don't see how your sample connects with my article. You have just simply implemented your own collection class (as in MSDN). The idea to inherit from CollectionBase in your case is good, but in my reply I meant the case of the control I have demonstrated. Please give a more sophisticated sample of a collection that may be edited in Windows Forms Designer.
Regards, Alexander.

I don't know what you mean by sophisticated sample. The code I posted was taken from a sample which is edited in Windows Forms Designer, the Layer is a control, so the Layers is a collection of the controls.
If you mean a modification of your code, then I can do that and post it to you tomorrow (it is midnight here in Japan!).

The layer/layers sample I built was just for trial, the actual application is big, with at least 15 assemblies.