Introduction
ImageIndex
and ImageList
in a component work as a pair. Having these two properties in the same control, you can have a list of images displayed as a dropdown combo by using the ImageIndexConverter
and also the private editor ImageIndexEditor
in the System.Windows.Forms.Design
namespace.
[Category("Appearance"),
Description("..."), DefaultValue(-1)]
[TypeConverter(typeof(ImageIndexConverter)),
Editor("System.Windows.Forms.Design.ImageIndexEditor", typeof(UITypeEditor))]
public int ImageIndex
{
get
{
return _ImageIndex;
}
set
{
if (_ImageIndex = value)
{
_ImageIndex=value;
}
}
However, there are number of limitations in using these two classes:
- The
ImageIndexConverter
and ImageIndexEditor
in the Framework Class Library (FCL) work only when the control has both imageindex
and ImageList
properties. How can we obtain the same behavior for children without ImageList
, like the TabPage
class? There is no ImageList
in TabPage
. The normal ImageIndexConverter
and ImageIndexEditor
are not applicable for this scenario.
- One approach is to add an
ImageList
property in the child class. Extra coding is required to duplicate the content of the ImageList
property from the parent control to the children. Although you may set the BrowsableAttribute
of the ImageList
property to false to avoid unexpected user interaction through the visual IDE, user still can set the ImageList
in the child class by code. Confusion may happen. Definitely, it is not a good solution.
ImageIndexEditor
is a private class in the System.Windows.Forms.Design
namespace. To use this editor, you pass the string identifying the full reference of ImageIndexEditor
in the EditorAttribute
for the ImageIndex
property. The VisualStudio designer object (i.e. the Site property of the object in design time) will resolve the editor for the property. The problem of using this editor is compatibility with future .NET versions. Microsoft has no guarantee to maintain this editor or the features of this editor since it is private.
General Implementation
ExtImageIndexConverter
and ExtImageIndexEditor
work for two different scenarios. The typical case is the class having its own ImageList
and ImageIndex
. The TypeDescriptor
class is used to determine whether the ImageList
is in the property list of the context instance.
public override StandardValuesCollection
GetStandardValues(ITypeDescriptorContext context)
{
...
PropertyDescriptorCollection PropertyCollection
= TypeDescriptor.GetProperties(context.Instance);
PropertyDescriptor Property;
if ((Property = PropertyCollection.Find("ImageList", false)) != null)
ImageList = (ImageList) Property.GetValue(context.Instance);
...
If the class has its own
ImageList
, the image list will be built from that
ImageList
.
The other scenario is the ImageList
exists in the Parent
object, just likes TabPages
and TabControl
classes. The Parent
property of children is used to locate the ImageList
. Only object inherited from the Control
class has the Parent
property. The value of the Parent
property is only assigned to when the object is added into a container Controls
collection. Otherwise, the value of Parent
is null.
PropertyDescriptorCollection PropertyCollection
= TypeDescriptor.GetProperties(context.Instance);
PropertyDescriptor Property;
if ((Property = PropertyCollection.Find("ImageList", false)) != null)
ImageList = (ImageList) Property.GetValue(context.Instance);
else if ((Property = PropertyCollection.Find("Parent", false)) != null)
{
object Parent = Property.GetValue(context.Instance);
PropertyDescriptorCollection ParentPropertyCollection
= TypeDescriptor.GetProperties(Parent);
if (ParentPropertyCollection != null)
{
PropertyDescriptor ParentProperty
= ParentPropertyCollection.Find("ImageList", false);
if (ParentProperty != null)
ImageList = (ImageList) ParentProperty.GetValue(Parent);
...
When you add a TabPage
into the TabPages
collection of a TabControl
object, the collection event handler adds that TabPage
object into the Control's
collection of the TabControl
object. That is also what we did in our ImageListBrowser.
public class ImageItemCollection : CollectionBase
{
private Control _Parent;
public ImageItemCollection(Control Parent)
{
_Parent = Parent;
}
}
protected override void OnRemoveComplete(int index,object value)
{
if (_Parent.Controls.Contains((Control) value))
{
_Parent.Controls.Remove((Control) value);
OnCollectionChanged();
}
}
protected override void OnInsertComplete(int index,object value)
{
if (!_Parent.Controls.Contains((Control) value))
{
_Parent.Controls.Add((Control) value);
OnCollectionChanged();
}
}
protected override void OnClearComplete()
{
_Parent.Controls.Clear();
OnCollectionChanged();
}
protected virtual void OnCollectionChanged()
{
if (CollectionChanged != null)
CollectionChanged(this, EventArgs.Empty);
}
We also want to refresh the parent control for whatever changes that we made in Items
collection. The OnCollectionChanged
event is for this purpose.
Proprietary Implementation
We may not always work on container model. In some case, we may only have a collection of Items
inherited from Component
class, and there is an ImageIndex
property in it. The items are not maintained in Controls
collection of the parent. For this case, what we need is a reference from child to the parent just like the Parent
property of Control
class. We can do this in the Collection
class. The ImageBrowser2
shows how to have the same result for non-container type parent. The ExtImageIndexConverter
and ExtImageIndexEditor
in this case only work on the ImageItem
class.
public override StandardValuesCollection
GetStandardValues(ITypeDescriptorContext context)
{
...
ImageList ImageList = null;
if (context.Instance != null && context.Instance is ImageItem)
{
if (((ImageItem)context.Instance).ReferenceParent != null)
ImageList = ((ImageItem)context.Instance).ReferenceParent.ImageList;
if (ImageList != null)
...
Other Concerns
- Since the
Controls
collection is maintained by the Items
collection , the Controls
collection in the parent control should not be serialized by the designer. A new Controls
collection is defined and set as hidden in DesignerSerializeVisibility
. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Control.ControlCollection Controls
{
get
{
return base.Controls;
}
}
- In order to have the Designer generating code for the
Item
, the item must be inherited from Component
or one of its derived classes. Otherwise, you need to develop your own serializer to take care the code generation. To have a light weighted class, you should do that.
- In
ImageListBrowser2
, we don't want to have the icon displayed for the ImageItem
objects in the component tray of the windows form. The DesignTimeVisibleAttribute
is used to make it invisible. [DesignTimeVisible(false)]
public class ImageItem : System.ComponentModel.Component
Conclusion
The ExtImageIndexConverter
and ExtImageIndexEditor
in the ImageListBrowser
work on most scenarios related to ImageList
and ImageIndex
. In fact, I used these two classes in my projects.
You may also review the ExtImageIndexConverter
and ExtImageIndexEditor
in ImageListBrowser2
for the proprietary implementation.