Click here to Skip to main content
12,821,315 members (32,543 online)
Click here to Skip to main content
Add your own
alternative version


212 bookmarked
Posted 3 Nov 2003

How to Edit and Persist Collections with CollectionEditor

, 26 Nov 2003 CPOL
Rate this:
Please Sign up or sign in to vote.
The article demonstrates how to edit and persist collections with CollectionEditor.


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



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:

  • CustomControls project

    A small controls library containing the following controls:

    CTextBox, DropDownCalendar, DropDownColorPicker, DropDownBool, DropDownList, PushButton, ToggleButton, DropDownListBoxButton, and most important, CustomCollectionEditorForm.

  • TestProject project

    A test project where you can find support code for the issues discussed here.

    • CollectionEditorTest.cs contains classes used for demonstrating collection editing and persisting techniques with System.ComponentModel.Design.CollectionEditor.
    • CustomCollectionEditorTest.cs contains classes related with CustomControls.CollectionEditor.CustomCollectionEditor functionality.
    • TestForm.cs. Here you can test the design-time serialization and editing of collections implemented by a test control.
    • CustomControlsForm.cs serves as a showroom for the controls in CustomControls library.

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);
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();
// (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 
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,

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";
  (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. Test.Items.SimpleItem_FullTc(-1, "Item1")); Test.Items.SimpleItem_FullTc(-1, "Item2"));

When the designer uses the AddRange method, all the items are added on a single line.
  (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.

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: 
    private Type[] types; 

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

    ,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)]
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: 
    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 : 
    protected override object CreateInstance(Type ItemType)
        ComplexItem ci=(ComplexItem)base.CreateInstance(ItemType);
        if ( this.Context.Instance!=null)
               if (this.Context.Instance is ISupportUniqueName)
         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


(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:

  • You can’t call it directly, actually the only way to call it is through the PropertyGrid control.
  • You can’t change its look, neither globalize it.
  • You can’t control the way of how the user can edit the collection (FullEdit, ReadOnly, AddOnly...).

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:

  • to be able to directly edit collection in run time.
  • to be very customizable (from the design point of view, and not only) and easily incorporable into the host application
  • to maintain the facility with what the CollectionEditor can be tuned to edit different types of collections.

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:

  • protected virtual void SetProperties(TItem titem, object reffObject)

    This procedure receives two parameters, one of type TItem which is the TVitem, and one of type object which is the correspondent collection item.

    Titem is derived from TreeNode, and has two new properties:

    • SubItems

      Represents the sub items collection of a collection item that you want to display as child nodes of the TVitem. All collection of sub items must be edited with CustomCollectionEditor (by associating a CustomCollectionEditor as Editor, see the case of tc.CustomItems).

    • Value

      Represents the collection item associated with it.

      By default this property sets the text for the TVitem. Override it to customize even more the TVitem.

    This method is called every time a property of the collection item displayed in the PropertyGrid is changed.

    protected override void SetProperties(TItem titem, object reffObject)
     if(reffObject is CustomItem)
        CustomItem ci =reffObject as CustomItem;
        //Specifically set the name
         //Associate a collection of sub items
        //if you associated a ImageList to
        // this CustomCollectionEditorForm, 
        //you can display some images in front
        // of the TreeNode item
    else if(reffObject is ComplexItem)
         ComplexItem ci =reffObject as ComplexItem;
         // you cannot associate a collection of sub items to this item
         //type, since it has as an editor a CollectionEditor, and not a 
         //Specifically set the name
      // set the default Text for the <CODE lang=cs>TVitem</CODE>
  • protected virtual CustomControls.Enumerations.EditLevel SetEditLevel(IList collection)

    It allows you to specify an edit level for a collection. Unlike the edit level of the form which is valid only for that instance of the CustomCollectionEditorForm, a collection’s edit level is valid in all instances.

    protected override CustomControls.Enumerations.EditLevel 
                                        SetEditLevel(IList collection)
     if(collection is SpecialItems)
       return CustomControls.Enumerations.EditLevel.AddOnly;
     return base.SetEditLevel (collection);
  • protected virtual Type[] CreateNewItemTypes(System.Collections.IList coll)

    It does exactly what it’s homolog from CollectionEditor does, but because CustomCollectionEditorForm can edit more than one collection at the same time, it takes an additional parameter of type System.Collections.IList, to indicate the item types to be available for each collection.

    protected override Type[] CreateNewItemTypes(System.Collections.IList coll)
      if(coll is SimpleItems)
          return new Type[]{typeof(CustomItem), typeof(SimpleItem_BasicTc),
             typeof(SimpleItem_Component), typeof(ComplexItem)};
          return base.CreateNewItemTypes(coll);

    But what about the CustomCollectionEditor? The dialog form that it opens is a CustomCollectionEditorForm, so all that you have to do is to override its CreateForm() function, and return an instance of your customized CustomCollectionEditorForm.

    public class CustomItemCollectionEditor: CustomCollectionEditor
       protected override CustomCollectionEditorForm CreateForm()
          return new CustomCollectionEditorDialog ();


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.


Revision history

  • Solved some problems of the CustomCollectionEditor
    • Solved bug with ReadOnly state
    • Solved problems with Cancel button.
    • The SetProperties method is called every time a property of the selected collection item is changed. The name of the TVitem is now set here, so the SetDisplayName method became obsolete and I removed it.
  • Some corrections and modifications to the article's text.
  • Original article.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Daniel Zaharia
Web Developer
Romania Romania
No Biography provided

You may also be interested in...

Comments and Discussions

QuestionYour Positioning code of DropDownForm needs revising Pin
ZEBRA_CROSSING9-Dec-15 15:46
memberZEBRA_CROSSING9-Dec-15 15:46 
AnswerRe: Your Positioning code of DropDownForm needs revising Pin
YuraKatz11-Feb-16 2:33
memberYuraKatz11-Feb-16 2:33 
QuestionHow are you overriding the look of normal buttons? Pin
Member 110620909-Sep-14 9:06
memberMember 110620909-Sep-14 9:06 
Questionplease help, cant find the testcontrol.cs Pin
Member 895295914-May-12 0:17
memberMember 895295914-May-12 0:17 
GeneralThis works well for *really* simple objects Pin
fuzzmeister14-Feb-11 1:11
memberfuzzmeister14-Feb-11 1:11 
GeneralRe: This works well for *really* simple objects Pin
fuzzmeister14-Feb-11 1:35
memberfuzzmeister14-Feb-11 1:35 
GeneralI've been going crazy! Pin
yourbuddytoo19-Nov-10 9:02
memberyourbuddytoo19-Nov-10 9:02 
GeneralYou are AWESOME! Pin
Andy Missico21-Sep-09 3:58
memberAndy Missico21-Sep-09 3:58 
QuestionComplex Collection Editor, Integrated with form Pin
JaedenRuiner21-May-09 11:37
memberJaedenRuiner21-May-09 11:37 
Questionnot removing automatically Pin
jores15-Jan-09 2:36
memberjores15-Jan-09 2:36 
QuestionCancel not working for changes made in property grid Pin
WilsC18-Nov-08 20:49
memberWilsC18-Nov-08 20:49 
GeneralUser Freindly Name Pin
Joe Sonderegger3-Jul-08 2:17
memberJoe Sonderegger3-Jul-08 2:17 
GeneralThanks! Pin
Daniel C.28-Apr-08 17:47
memberDaniel C.28-Apr-08 17:47 
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:

thanks a lot! Great article!
GeneralWhy mine does not persist [modified] Pin
Mizan Rahman31-Jan-08 6:19
memberMizan Rahman31-Jan-08 6:19 
GeneralAdvantages of IComponent Pin
coder4-Dec-07 20:49
susscoder4-Dec-07 20:49 
GeneralArticle is great Pin
Kountree24-Aug-07 7:13
memberKountree24-Aug-07 7:13 
QuestionAuto-Generated code stays after removing the control Pin
Mizan Rahman21-Jan-07 22:57
memberMizan Rahman21-Jan-07 22:57 
AnswerRe: Auto-Generated code stays after removing the control Pin
Paullus Castro20-Sep-07 11:28
memberPaullus Castro20-Sep-07 11:28 
GeneralProblems with SimpleItem Pin
Teko Arpi19-Jan-07 9:52
memberTeko Arpi19-Jan-07 9:52 
GeneralCollection item as interface Pin
Edward Diener4-Jan-07 17:44
memberEdward Diener4-Jan-07 17:44 
GeneralRe: Collection item as interface Pin
Daniel Zaharia4-Jan-07 22:33
memberDaniel Zaharia4-Jan-07 22:33 
GeneralRe: Collection item as interface Pin
Edward Diener5-Jan-07 4:01
memberEdward Diener5-Jan-07 4:01 
GeneralRe: Collection item as interface Pin
Daniel Zaharia8-Jan-07 22:09
memberDaniel Zaharia8-Jan-07 22:09 
GeneralGreat Article! Pin
Me23323211-Nov-06 14:03
memberMe23323211-Nov-06 14:03 
GeneralMy hands are hurting... Pin
angolmo7-Nov-06 5:46
memberangolmo7-Nov-06 5:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170308.1 | Last Updated 27 Nov 2003
Article Copyright 2003 by Daniel Zaharia
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid