Click here to Skip to main content
Email Password   helpLost your password?

Summary

This article demonstrates how to edit and persist collections with CollectionEditor. In addition, it presents a new, very versatile and customizable collection editor, CustomCollectionEditor.

Contents

Introduction

In .NET framework, collections are omnipresent: ComboBox, ListBox, ListView, DataGrid, Menu, all of these objects, and many others, make use of collections. Not only is it important to understand how these objects use them, but can be extremely useful to know how to take the most out of them yourself.

CollectionEditor is a very powerful tool for editing and persisting collections at design time, but is a poor option for run time editing, when you often need a high level of customization and versatility for implementation of themes, multilingual support and some other real world requirements. Those who need such a tool can find one here in CustomControls.CollectionEditor.CustomCollectionEditor.

In order to persist a collection with CollectionEditor, you need to do three things: implement a collection item, implement a collection to hold the items, and many times, depending on the item and collection implementation, subclass the CollectionEditor to obtain the right behavior.

This article is accompanied by source code structured as follows:

CollectionEditingTest solution:

When you open the solution for the first time, you should compile it before doing anything else.

All these classes are well organized in suggestive namespaces, so it is a good idea to explore the solution with the Class View window.

How to implement a Collection Item

(You can find the accompanying source code in the TestProject�s file, CollectionEditorTest.cs.)

A first requirement for any item is introduced by CollectionEditor which needs a parameter-less constructor, simply because every time the Add button of CollectionEditor is pushed, the CollectionEditor must create an object of the type of your item, from nothing else but the type, and it would be impossible to automatically provide parameters. This is slightly different from persisting, where you have to create an object from an existing one, and it is possible to automatically provide the right parameters.

You can see an example of how to remotely create an object in the CustomCollectionEditorForm's CreateInstance function, which is responsible for creating new items from a type:

protected virtual object CreateInstance(Type itemType)
{ 

/* ***this is just another way***

//Get a parameterless constructor of that type
ConstructorInfo ci = itemType.GetConstructor(new Type[0]);

//Create an instance descriptor
InstanceDescriptor id = new InstanceDescriptor(ci,null,false);
return id.Invoke();
*/

// with the help of Activator class it is straightforward

object instance=Activator.CreateInstance(itemType,true);
OnInstanceCreated(instance);
return instance ;
}

The process of creating a collection item class depends very much on what you want to do with your collection. If you are interested only in editing collections, then all that you must do is to assure that your item is of the same type as the collection�s Item type (or vice versa). But if you want to persist (that is, automatically generate source code to describe your component state) collections during design time, things are a little bit more difficult.

To persist an object, code generation engine must first know how to create an instance of that object, and it is exactly here where most of collection item's implementation fail. There are two main options: implementing IComponent or creating a custom TypeConverter for your class.

Implementing IComponent

(See SimpleItem_Component class for an example.)

This is enough for the code generation engine, since any class that implements IComponent must provide a basic constructor that requires no parameters or a single parameter of type IContainer. Knowing this, it�s possible to create an instance of that class.

Implementing IComponent has its advantages and disadvantages. First, you should decide if implementing IComponent helps you, or is it just another rock that you have to carry with you. Immediately after creation, the CollectionEditor adds the newly created item to the form's container. This can be useful since it will appear in the Component Tray and you will be able to edit it with the PropertyGrid, but can bring a lot of confusion in the case of menu items, which are usually many. To avoid this, you can add the DesignTimeVisible(false) attribute to your item class.

The serialization of an object that inherits from Component is a standard one, like for normal controls: first a variable is declared, after that in the first part of the InitializeComponent procedure, an object is created, and somewhere later inside the body of InitializeComponent, the public properties of the object are set. This kind of serialization brings a major setback, because you have no way of knowing if by the time the Add or AddRange collection�s methods are called, all (or at least some) of the object properties were set. (in the next example� you can�t know what object the code generation engine will serialize first: compItem or tc.)

private void InitializeComponent()
{
this.compItem = new Test.Items.SimpleItem_Component();
.
.
.
//

//tc

//

this.tc.SimpleItems.AddRange (new Test.Items.SimpleItem[]{this.compItem});
//it would be much easier to maintain your collection integrity if you 

//could validate each item before adding it to the collection.

//let�s say, to check if there is already an item with the id 45 

.
.
.
//

//compItem

//

this.compItem.Id = 45;
this.compItem.Name = "SimpleItem Comp";
}

For this case, the solution is to validate the value in the Set{} accessor of the Id property. But it must be validated against the collection, and as you probably know a collection item doesn�t have any reference to its collection. You can set a reference to the collection in the item�s constructor (by passing the collection as parameter), and certify that you�ll always have a reference to the parent collection. However, this won�t help if you want to be able to change items between different collections.

Please notice that when a component is added to the form�s container, three design time properties are added: DynamicProperties, Name and Modifiers. Of most use is Name, especially if you want to maintain a standard naming:

private Test.SimpleItem_Component mi_Save;
private Test.SimpleItem_Component mi_Undo;

instead of:

private Test.SimpleItem_Component simpleItem_Component17;
private Test.SimpleItem_Component simpleItem_Component21;

Keep in mind: Having a property named Name of type System.String (as BasicItem has.. ooopps!!) can confuse the designer in the code generation process (especially if it�s under the default, �Misc�, category). This is an example of the error message: " Identifier 'Simple Item Comp' is not valid."

Note: It is not mandatory to implement the IComponent interface, you can also inherit from System.ComponentModel.Component. SimpleItem_Component implements the interface instead of using inheritance, because, for demonstration purposes, it must inherit from BasicItem.

Creating a TypeConverter

(You can find an example of the following in SimpleItem_BasicTc and SimpleItem_FullTc item classes and the correspondent type converter classes SimpleItemBasicConverter and SimpleItemFullConverter).

SimpleItemBasicConverter is an example of the minimum requirements when implementing a type converter for serialization, while SimpleItemFullConverter is a more complex example. You can see the difference by looking how PropertyGrid displays the two members of TestControl class: SimpleItem_BasicTC and SimpleItem_FullTC.

Creating a custom type converter for your class, will give you more control about how your items are serialized but it will also require more coding from you. The biggest advantage of this, is that you can serialize your items using any of its constructors. Now, you can fully initialize your item before adding it to the collection.

When you have simple items, with only two or three properties, using a constructor that initializes all of the properties seems obvious, but if you have more complex items, with many properties that need to be serialized (like ToolbarButton) this can become inappropriate. An elegant solution is to initialize in the constructor, only those properties that are crucial for a validation (Id, Name etc.), and let the other ones to be set in a normal way.

If you want to use a type converter to control how your object is serialized, then your type converter must be able to convert your object to an InstanceDescriptor. This is done by overriding the ConvertTo() function. InstanceDescriptor class has two constructors, one of which has three parameters. For this constructor, the third parameter is a boolean value indicating if the initialization of the object is or not complete. That is, if the object is completely initialized by its constructor or if the designer must check the public properties and fields to see if they should or not be serialized.

The InstanceDescriptor returned by SimpleItemBasicConverter for a SimpleItem_BasicTc:

return new InstanceDescriptor
    (typeof(SimpleItem_BasicTc).GetConstructor(new Type[0]), null,false);

The InstanceDescriptor returned by SimpleItemFullConverter for a SimpleItem_FullTc:

return new InstanceDescriptor
    (typeof(SimpleItem_FullTc).GetConstructor(new Type[]{typeof(int), 
    typeof(string)}), new object[]{((SimpleItem_FullTc)value).Id,
    ((SimpleItem_FullTc)value).Name},true);

Here you can see how the designer serializes the two cases:

private void InitializeComponent()
{
Test.Items.SimpleItem_BasicTc simpleItem_BasicTc1 = 
                    new Test.Items.SimpleItem_BasicTc();
.
.
.
// 

// tc

// 

simpleItem_BasicTc1.Id = -10;
simpleItem_BasicTc1.Name = "SimpleItem BasicTC";
this.tc.SimpleItems.AddRange
  (new Test.Items.SimpleItem[]{ simpleItem_BasicTc1,
  new Test.Items.SimpleItem_FullTc(-10, "SimpleItem FullTC")}); 
}

This kind of serialization has a small disadvantage in the fact that the item is not accessible outside the InitializeComponent(). If you want a mixt serialization of your items, (initialize part of the properties in the constructor, and set the other properties one by one), it is better to mark those properties that are initialized in the constructor with the [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] attribute, or they will be set twice.

What are the requirements for a Collection

(You can find the accompanying source code in the TestProject�s file CollectionEditorTest.cs.)

There are three requirements that a collection should meet in order to be successfully persisted with the CollectionEditor:

  1. First, the collection must implement the IList interface (inheriting from System.Collections.CollectionBase is in most of the cases the best option).
  2. Second, it must have an Indexer (Item in VB.NET) property. The type of this property is used by the CollectionEditor to determine the default type of the instances that will add to the collection.

    To better understand how this works, take a look at GetItemType() function of the CustomCollectionEditorForm:

    protected virtual Type GetItemType(IList coll)
    {
        PropertyInfo pi= coll.GetType().GetProperty("Item",
                                               new Type[]{typeof(int)});
        return pi.PropertyType
    }
  3. Third, the collection class must implement one or both of the following methods: Add and AddRange. Although IList interface has an Add member and CollectionBase implements IList, you still have to implement an Add method for your collection, given that CollectionBase declares an explicit member implementation of the IList�s Add member. The designer serializes the collection according to what method you have implemented. If you have implemented both, the AddRange is preferred.

When serializing with the Add method, for every item it uses a new line.

this.tc.SimpleItems.Add(new Test.Items.SimpleItem_FullTc(-1, "Item1"));
this.tc.SimpleItems.Add(new Test.Items.SimpleItem_FullTc(-1, "Item2"));

When the designer uses the AddRange method, all the items are added on a single line.

this.tc.SimpleItems.AddRange
  (new Test.Items.SimpleItem[]{new Test.Items.SimpleItem_FullTc(-1, "Item1"),
new Test.Items.SimpleItem_FullTc(-1, "Item2")});

If you want to serialize a collection of nested items, like System.Windows.Forms.Menu.MenuItemCollection, it is obvious that your collection must have an AddRange method. (Check how ComplexItems collection of the TestControl is serialized.)

CollectionEditor, How to

(The accompanying source code is in the TestProject�s file CollectionEditorTest.cs.)

CollectionEditor can be found in the System.ComponentModel.Design namespace, but only if you add a reference to System.Design.dll in your project.

How to associate a CollectionEditor with a property

(See TestControl class for an example.)

Declare and initialize a local variable of the type of your collection.

private SimpleItems _SimpleItems= new SimpleItems();

Create a read only property and add these two attributes: DesignerSerializationVisibility and Editor.

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(System.ComponentModel.Design.CollectionEditor), 
                       typeof(System.Drawing.Design.UITypeEditor))]
public SimpleItems SimpleItems
{
get{return _SimpleItems;}
}

Now, SimpleItems property will be edited with the CollectionEditor.

How to add more than one Item type to the Collection

(See SimpleItem_CollectionEditor class for an example.)

By default, the CollectionEditor, adds only one type of item to the collection. Considering the above case, it will add only items of the SimpleItem type (the type of the Item property of the SimpleItems collection). Since the SimpleItems collection can hold all the items derived from SimpleItem (SimpleItem_FullTc, SimpleItem_BasicTc, etc.), it would be nice to be able to add them too.

To do this, you have to inherit from CollectionEditor and override CreateNewItemTypes.

public class SimpleItem_CollectionEditor: 
           System.ComponentModel.Design.CollectionEditor
{
    private Type[] types; 

    public SimpleItem_CollectionEditor (Type type):base(type )
    {
        types = new Type[] {typeof(SimpleItem),typeof(SimpleItem_BasicTc), 
                                                    typeof(SimpleItem_FullTc)

    ,typeof(SimpleItem_Component),    typeof(ComplexItem) }; 
    } 


    protected override Type[] CreateNewItemTypes() 
    {    
        return types; 
    } 
}

Next change the Editor attribute of your collection, instructing the designer to use your custom collection editor:

[DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
[Editor(typeof(SimpleItem_CollectionEditor,
     typeof(System.Drawing.Design.UITypeEditor))]
public SimpleItems SimpleItems
{
    get{return _SimpleItems;}
}

How to make CollectionEditor to edit a nested Collection

(See ComplexItemCollectionEditor class for an example.)

If you try to edit a nested collection with the standard CollectionEditor, most probably you will get an error. The error is caused by the fact that the designer is using only one instance of CollectionEditor, and when you want to edit the collection of sub items, it tries to open the same instance that is already opened.

The solution for that consists in checking if an instance of the CollectionEditor is already opened, and if so, create another instance:

public class ComplexItemCollectionEditor: 
         System.ComponentModel.Design.CollectionEditor
{
    private CollectionForm collectionForm; 

    public ComplexItemCollectionEditor (Type type):base(type ){}

    public override object EditValue( ITypeDescriptorContext context, 
                                 IServiceProvider provider,object value) 
    { 
        if(this.collectionForm != null && this.collectionForm.Visible) 
        { 
            ComplexItemCollectionEditor editor = 
               new ComplexItemCollectionEditor(this.CollectionType); 
            return editor.EditValue(context, provider, value); 
        }     

    else return base.EditValue(context, provider, value);
    } 

    protected override CollectionForm CreateCollectionForm() 
    { 
        this.collectionForm = base.CreateCollectionForm(); 
       return this.collectionForm;
     }
}

How can someone access the Items before they are added to the Collection

(See ComplexItemCollectionEditor class for an example.)

Sometimes you may need to do some processing immediately after the items are created (setting a flag indicating that the item was created with a CollectionEditor, setting a more appropriate name, etc.). In this case all you need to do is to override CreateInstance function.

Here is an example:

public class ComplexItemCollectionEditor : 
         System.ComponentModel.Design.CollectionEditor
{
    protected override object CreateInstance(Type ItemType)
    {
        ComplexItem ci=(ComplexItem)base.CreateInstance(ItemType);
        if ( this.Context.Instance!=null)
            {
               if (this.Context.Instance is ISupportUniqueName)
               {
                 ci.Name=((ISupportUniqueName) 
                     this.Context.Instance).GetUniqueName();
               }
         else{ci.Name="ComplexItem";  }

       return ci;
    }
}

How to do some cleaning before an Item is destroyed

Of course, the best way to do that is to override the item�s Dispose method, but this might not be always possible. In that case, you can override the DestroyInstance method of the CollectionEditor.

protected override void DestroyInstance (object instance)
{
    //do some cleaning here

    base.DestroyInstance(instance);
}

CustomCollectionEditor

(You can find the accompanying code in the TestProject�s file CustomCollectionEditorTest.cs and the class implementation in CustomCollectionEditor.cs and CustomCollectionEditorForm.cs files of CustomControls project.)

As you probably noticed by now, the CollectionEditor is perfectly capable of editing and persisting a collection during design time, and here is no need for another collection editor. However, in .NET, collections are a very convenient way of storing small pieces of data, and their use in the user interface of an application can be very rewarding. To make this possible, a tool that can be easily integrated in the application is needed. Of course, the first thought is to use the CollectionEditor, but this raises various problems:

A new tool has to be created, and the most obvious thing to do is to copy the CollectionEditor style since a lot of people used it, is already familiar with it, and it proved to be an excellent tool for editing collections.

The requirements for it are:

Convention: The term collection item will be used to represent an item in the collection and TVitem for the TreeView node that CustomCollectionEditor use to visually represent that collection item.

In order to satisfy the above requirements, it became obvious that there are two things needed: a Form, CustomCollectionEditorForm for run time editing, and a UITypeEditor that wraps the form, CustomCollectionEditor, for design time.

Compared with CollectionEditor, CustomCollectionEditor presents some new features:

  1. It allows you to edit all generations of a nested collection from the same window, by using a TreeView to display the collections tree. To be more specific, for each collection item it can display a collection of sub items, at your choice (in the case that your item has at least one collection of sub items).
  2. You can specifically set the name of the TVitem that TreeView displays. By default, CustomCollectionEditorForm will look to see if your collection item has a Name property, and if so, it will name each TVitem with the value of the Name property. If the collection item doesn�t have a Name property, it will name all TVitems with the collection item�s class name.
  3. It allows you to set different editing levels (FullEdit, AddOnly, RemoveOnly, ReadOnly). An edit level can be defined for an instance of CustomCollectionEditorForm, but it can also be defined for a collection type. The edit level of the form has prevalence over the collection edit level. You can specify the edit level for a CustomCollectionEditorForm by setting the EditLevel property to one of the above values of the CustomControls.Enumerations.EditLevel enumeration. To set an edit level for a collection, you have to override the SetEditLevel() function.
  4. For each collection item, you have a direct reference to the TVitem displayed in the TreeView. Like this, for each TVitem you can set properties like ForeColor, BackColor, Font, etc., according with the collection item state.
  5. Finally, but not less important, it is open source.

Note: If you are interested in globalizing CustomCollectionEditor, you�ll have to globalize the PropertyGrid that it contains. Here you can find an article explaining how to do that: Globalized property grid.

How it works

Because all the logistic is implemented in CustomCollectionEditorForm, it is here that you can set the desired behavior by overriding some key members:

Conclusions

The .NET framework has in CollectionEditor, a powerful tool for editing and persisting collections at design time. For most situations, it is more than enough. But for more advanced scenarios, especially at run time, it can�t help you too much. It is here that CustomCollectionEditor and CustomCollectionEditorForm enter into action, providing you a straightforward and flexible way to cover those scenarios.

References

Revision history

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralYou are AWESOME!
Andy Missico
3:58 21 Sep '09  
You are AWESOME!
QuestionComplex Collection Editor, Integrated with form
JaedenRuiner
11:37 21 May '09  
I've been designing this component model for a little over a week and it is very frustrating the issues i'm having with the Visual Studio forms designer. The basic format is my component has three internal collections. Two of which are serializing just fine. the third is a collection of objects which also contain a sub collection. these two collections are a collection of Components (pulled from the components that exist on the Form) and a collection of properties based upon the component in question. the problem is that if I declare the LayoutControls collection to have design visibility content, i get this addrange in my component definition. The issue is assigning the actual component to each object that is in the collection. So I delved into IExtenderProvider, and found I could have each component define its link to the appropriate LayoutControl object. But that meant the AddRange() was superfluous, so I set the visibilty to Hidden. but with the hidden attribute the LayoutControl editor doesn't create any instances of the LayoutControls class.

Not to mention on top of this, Visual Studio has this rather heinous pattern of suddenly claiming my type converters can't convert my class types to IntanceDescriptors when that is all my typeconverters do is convert to instancedescriptors.

Can anyone provide a more indepth mapping on how to generate rather complex component model within the forms generator, because so far, nobody seems to know the answer.
Questionnot removing automatically
jores
2:36 15 Jan '09  
Hi Daniel! The nice article!
I've build similar in VB. But, unfortunately, I don't understand, why SimpleItems declaration code not removing automatically from InitializeComponent when I deleted TestControl using the Windows Form Designer? Confused

Yours Sincerely
Sergey Eliseev
QuestionCancel not working for changes made in property grid
WilsC
20:49 18 Nov '08  
Great work! Thanks.. I love it.

Just a simple problem...
The cancel function works fine for the add/remove item in the treeview.
However, changes made on the property grid stay and never revert back to old values when cancel button is clicked.
How can I cancel changes made in property grid?
GeneralUser Freindly Name
Joe Sonderegger
2:17 3 Jul '08  
Thankyou for your example. It is really helpful. I just have a question: Do you know how to make user friendly names on the "Add" button? I always get the class names...

Have a nice life!!

GeneralThanks!
Daniel C.
17:47 28 Apr '08  
Just wanted to say thanks. I had a problem with persisting the data after editing it in the CollectionEditor. It simply wouldn't update the property grid.

adding this attribute to the collection property did the trick:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

thanks a lot! Great article!
Daniel
GeneralWhy mine does not persist [modified]
Mizan Rahman
6:19 31 Jan '08  
Hi everyone,

Can anyone can give me some pointer why my collections are not being persisted.
Here is the entire code - you place in .cs file and then it should compile and run fine:
Thanks in advance.
-Mizan
--code---
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Collections;
using System.ComponentModel.Design;
using System.Drawing.Design;
using System.ComponentModel.Design.Serialization;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.Reflection;

namespace WindowsApplication9
{
[DesignTimeVisible(false)]
public abstract class MyBaseButton : Component
{
private System.ComponentModel.IContainer components = null;

public MyBaseButton()
{
InitializeComponent();
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}

base.Dispose(disposing);
}

private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}

public MyBaseButton(IContainer container)
{
container.Add(this);

InitializeComponent();
}
private string m_Text = string.Empty;

public string Text
{
get { return m_Text; }
set { m_Text = value; }
}
}
[TypeConverter(typeof(MyButtonATypeConverter))]
public class MyButtonA : MyBaseButton
{
private string m_TextA = string.Empty;

public MyButtonA()
{

}
public MyButtonA(string sText, string sTextA)
{
Text = sText;
m_TextA = sTextA;
}
public string TextA
{
get { return m_TextA; }
set { m_TextA = value; }
}
internal class MyButtonATypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(InstanceDescriptor))
{
return true;
}
if (sourceType == typeof(string))
{
return true;
}

return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
{
return true;
}
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
{
ConstructorInfo ci = typeof(MyButtonA).GetConstructor(new Type[] { typeof(string), typeof(string) });
MyButtonA btn = (MyButtonA)value;

return new InstanceDescriptor(ci, new object[] { btn.Text, btn.TextA });
}

if (destinationType == typeof(string))
{
MyButtonA btn = (MyButtonA)value;
return (btn.Text + "|" + btn.TextA);
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string sData = (string)value;
string[] arrString = sData.Split('|');
MyButtonA btn = new MyButtonA();
btn.Text = arrString[0];
btn.TextA = arrString[1];
return btn;
}
return base.ConvertFrom(context, culture, value);
}
}

}

[TypeConverter(typeof(MyButtonBTypeConverter))]
public class MyButtonB : MyBaseButton
{
private string m_TextB = string.Empty;
public MyButtonB()
{

}
public MyButtonB(string sText, string sTextB)
{
Text = sText;
m_TextB = sTextB;
}
public string TextB
{
get { return m_TextB; }
set { m_TextB = value; }
}
internal class MyButtonBTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(InstanceDescriptor))
{
return true;
}
if (sourceType == typeof(string))
{
return true;
}

return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
{
return true;
}
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
{
ConstructorInfo ci = typeof(MyButtonB).GetConstructor(new Type[] { typeof(string), typeof(string) });
MyButtonB btn = (MyButtonB)value;

return new InstanceDescriptor(ci, new object[] { btn.Text, btn.TextB });
}

if (destinationType == typeof(string))
{
MyButtonB btn = (MyButtonB)value;
return (btn.Text + "|" + btn.TextB);
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
string sData = (string)value;
string[] arrString = sData.Split('|');
MyButtonB btn = new MyButtonB();
btn.Text = arrString[0];
btn.TextB = arrString[1];
return btn;
}
return base.ConvertFrom(context, culture, value);
}
}

}

public class CustomCollectionEditor : CollectionEditor
{
public CustomCollectionEditor(Type type)
: base(type)
{
Console.WriteLine("public CustomCollectionEditor(Type type)");
}

// Return the types that you want to allow the user to add into your collection.
protected override Type[] CreateNewItemTypes()
{
Console.WriteLine("protected override Type[] CreateNewItemTypes()");

return new Type[] { typeof(MyButtonA), typeof(MyButtonB) }; ;
}
}

public class MyButtonCollection : CollectionBase
{
public MyButtonCollection()
: base()
{
}

public MyBaseButton Add(MyBaseButton objValue)
{
this.InnerList.Add(objValue);
return objValue;
}

public void Remove(MyBaseButton objValue)
{
this.InnerList.Remove(objValue);
}

public bool Contains(MyBaseButton objValue)
{
return this.InnerList.Contains(objValue);
}
public MyBaseButton this[int index]
{
get { return (MyBaseButton)this.InnerList[index]; }
set { this.InnerList[index] = value; }
}

public void AddRange(MyBaseButton[] objValue)
{
this.InnerList.AddRange(objValue);
}

protected override void OnInsert(int index, object value)
{
base.OnInsert(index, value);
}
}

public class MyCustomButtons : Component
{
private System.ComponentModel.IContainer components = null;

public MyCustomButtons()
{
InitializeComponent();
}

public MyCustomButtons(IContainer container)
{
container.Add(this);
InitializeComponent();

}

private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}

protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

private string m_TextC = string.Empty;

public string TextC
{
get { return m_TextC; }
set { m_TextC = value; }
}
private MyButtonCollection m_Buttons = new MyButtonCollection();

[EditorAttribute(typeof(CustomCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public MyButtonCollection Buttons
{
get { return m_Buttons; }
set { m_Buttons = value; }
}
}
}



modified on Friday, August 8, 2008 8:31 AM

GeneralAdvantages of IComponent
coder
20:49 4 Dec '07  
Hi,
Can you please tell me what is the advantage of using IComponent. Component provides reusability. Even a normal class can be made reusable by making it as a public class and adding a reference of it. I know that coomponent(Eg. Timer Control) will not have a UI but its methods can be accessed,etc... But i want to know what advantages it has over the normal reusable classes. Assume that i have a class which has two functions A1() and A2(). I can make this reusable by making it a public and adding a ref of it. Can this be made a component. If so what is the difference between the prior and the latter approach?..

Thanks
GeneralArticle is great
Kountree
7:13 24 Aug '07  
This article is the best that I have seen on the entire Internet. Initially, it was a challenge understand, but after studying and comparing the Microsoft documetation, I think you did a wonderful job in putting this together. The explanation of certain members such as GetValues could be explained a bit better

Kountree
QuestionAuto-Generated code stays after removing the control
Mizan Rahman
22:57 21 Jan '07  
Hi,

Thank you for this article. I created my own collection using your article as guideline.

My collection has multi-type itmes. All are derived from same base class. The base class is derived from System.ComponentModel.Design.Component.

At design-time, I can add/remove items to my control using the a collection editor which I derived from CollectionEditor class. And when I look at the auto-generated code in Form1.Designer.cs file, the code looks correct. It creates instances of the items and sets the properties of the items and then use AddRange method of the control's Collection property to add the items to the collection.

The problem occurs when I remove the control from the Form at design-time. It removes the AddRange code from Form1.Designer.cs but it retains the code that instanciates the items and sets the propties of the items.

I tested your source project for this case, and this project also retains those auto-generated code.

Please help.

Thank you.




Mizan Rahman
AnswerRe: Auto-Generated code stays after removing the control
Paullus Castro
11:28 20 Sep '07  
Hi Mizan Rahman,

I also had the same problem, it seems that I solved the problem overriding the Dispose property of the Component that hosts the collection.

I the example we have from the author it should be something like this:

public class TestControl:CustomControls.Win32Controls.PushButton,ISupportUniqueName
{
// component code

protected override void Dispose(bool disposing)
{
// Removing SimpleItems form memory
while (_SimpleItems.Count > 0)
{
_SimpleItems[0].Dispose();
_SimpleItems.RemoveAt(0);
}
base.Dispose(disposing);
}
}

Well, I hope that helps you, and other visitors. I spend a lot of time trying to solve this one...
Wink

Paullus Martins de Sousa Nava Castro
Brazil - Brasília/DF
GeneralProblems with SimpleItem
Teko Arpi
9:52 19 Jan '07  
Hi Daniel,

I downloaded your source code and made a user control with a public property in your TestProject:

private SimpleItems _items = new SimpleItems();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SimpleItems TestItems
{
get
{
return _items;
}
set
{
_items = value;
}
}

Everything (including serialization) worked fine.
My goal in a user control I'm developing in Visual Studio 2005 (.NET 2.0) is to manage a collection of ToolStrips with some extra properties in design time with a standard or custom editor. I was not able to implement it, so i decided to go an another way - after downloading your code i made a simple change. Now the class SimpleItem derives from ToolStrip:

public class SimpleItem : ToolStrip

This is the only one change i made. When i compiled the code, i could not add a new SimpleItem in the collection editor. By pressing the Add button it displays an error: Object reference not set to an instance of an object.

Can you help me please?

(Note: When the class SimpleItem derives from the class Button (for example), I have no problems to add an item in the collection editor)

Thank you.
Richard.
GeneralCollection item as interface
Edward Diener
17:44 4 Jan '07  
My custom collection has its item as an interface reference, so that all items in the collection must implement the interface. The interface itself also implements IComponent. Is it possible to create a custom collection editor, derived from the base class collection editor, for such a collection ? The problem, of course, is that such a collection item does not have an empty constructor since an interface has no constructor. However in reality all the items implementing the interface must be components, and therefore have an empty constructor.

Edward Diener

GeneralRe: Collection item as interface
Daniel Zaharia
22:33 4 Jan '07  
I'm not sure i understand the question but if your item implements an interface, and this interface itself implements IComponent, than, yes, it's working.

Daniel
GeneralRe: Collection item as interface
Edward Diener
4:01 5 Jan '07  
My collection item itself is an interface reference. I would like to have a property of this collection such that the user at design time can add a component, already dropped onto the same form, which implements this interface, to the collection, and have it the connection in the Visual Designer. Is this possible using your source, or otherwise in my own collection editor ?

The typical collection editor creates its items and adds them to the collection. Therefore the Add button must instantiate the item from the type, using an empty constructor, as you so well explain. In my collection of items, each of which is a particular interface, this can not be done, since an interface has no constructor although a component implementing the interface does. But I do not want it to be done, since I am not adding an item to the collection by creating one, but instead adding an already created item to the collection in the form of a component implementing my interface.

Edward Diener

GeneralRe: Collection item as interface
Daniel Zaharia
22:09 8 Jan '07  
I never try this, but try overriding 'CreateInstance' method of the CollectionEditor, and there create yourself a new item and associate it with an already existing component.
Daniel
GeneralGreat Article!
Me233232
14:03 11 Nov '06  
This is an exceptional - yet simplistic example of how to integrate cutom-defined objects and collections with what is readily available with the .NET framework.
I stumbled upon your code after I had spent several hours doing it the "hard way" - I reflected Microsoft's code to see their internal class workings, and wrote/copied/modified new/existing code from various assemblies to get mine to work. I learned a lot, but after seeing your working example, I know my way isn't worth the amount of work anymore, even if I had centralized any utility classes needed.
I truly have to say that this is in the top 10 best articles I have read from CodeProject. Thank you.
GeneralMy hands are hurting...
angolmo
5:46 7 Nov '06  
..of so many applauses for you to have published this wonderful article.

Cheers.

One out of millions of anonymum users who can write their own Collection-absed components since they found your article.
GeneralPersisting items in derived classes...
tomcstein
5:04 11 Oct '06  
I have a problem with persisting info in a class that derives from MyBaseClass, which contains the collection property.
Now, everything works fine if I add new items to the collection in the derived class, but if I want to modify an item, which the ancestor class added to the collection, then the persistence fails.

Any suggestions?
GeneralRe: Persisting items in derived classes...
tomcstein
7:26 17 Oct '06  
The thing I want the CollectionEditor to do is generate code that looks like this:

MyDerivedObject.Collection["ItemName"].PropertyName = "NewValue";


instead of

MyDerivedObject.Collection.AddRange(
new Item[] { new Item("AllOldValues+OneNewOne") }
);

GeneralThank you so damn much for this!
Gavelcade
10:26 4 Aug '06  
I can't believe how many different options I looked at for a collection editor, hitting glitch after glitch (the latest being the nested-collection one) before finding your ComplexItemCollectionEditor. (It didn't help that it took me two days to realize that .NET 1.1 HAS the CollectionEditor, just not in one of the DLLs loaded by default by Visual Studio, at least not into a Web control project.) So simple, and so necessary. Thank you.

GC
GeneralThanks and .NET 2.0
boomer78
6:03 20 Jul '06  
Thank you for this example! I've been searching for two days for something that would allow me to add objects to a collection in C# 2005 through a property Grid.

I have a Generic Collection derived from System.Collections.ObjectModel.Collection and I needed to add type specific objects via a property grid. I returned this collection through a public property, but I couldn't get the drop down list populated, nor could I get anything else to work, because all "Add" did was try to create a new instance of the base abstract class.

This demo gave me exactly what I needed to better understand how to solve my problem in C# 2005Big Grin . I had to create a new class which inherits from "System.ComponentModel.Design.CollectionEditor", and override the CreateNewItemTypes() method to return an array of the various types that I needed to populate into the dropdown list. It was very easy, looking back at it, but research always seems to take the longest amount of time.

- Alex

GeneralLocalize issues
Snowprog77
4:52 1 Apr '06  
Hi,

Your tutorial helped me a lot.
I currently write a component that can localize a collection of strings (that will be stored also in the Forms ResX file).

I thought I only need to setup a component with a stringcollection (hehe you know it. it didnt work).

So your tutorial helped me a lot to work with collections in user components and finally I was able to create the desired component.
Now I have the following problem. As long as I turn of the forms localization , everything works fine. But when I turn on the localization (yeah I need it to be turned on Smile ), the solution with the TypeConverter doesnt work. I was only able to get the IComponent solution running. It look like that the class is not properly translated into xml (uhhh i have to be very carefully what i say, cause I really don't know it!).

Do you (or anyone else) have a clue on what this may depend?

Thanks in advance
Snow.
GeneralHow to change the Collection Editor Left hand side Member's display name?
wailit
18:05 29 Mar '06  
Hi,

When I am using the Collection Editor, the left hand side member always display the collection class name.
1. How to display specify property name?(e.g. Collection of TabPage in TabControl, it can show TabPage.Name value instead of "System.Windows.Form.TabPage")

2. How to do increament of the object Name??(e.g. Collection of TabPage in TabControl, it can be "TabPage1".."TabPage2"....to be increament, so what do i need to implement this case?

Thx
GeneralRe: How to change the Collection Editor Left hand side Member's display name?
Mattman206
9:49 11 Jul '06  
I think you just need to override the ToString() method. Say that just want the name of the class to be displayed:

public override string ToString()
{
return this.Name;
}


Last Updated 27 Nov 2003 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010