How to make a User control property of type Collection<MyClass> editable in Form Designer?

dialer picture dialer · Sep 15, 2011 · Viewed 16.6k times · Source

Today at work, I stumbled upon a problem that was driving me nuts.

Basically my goal is this:

I have a UserControl1, with a field of the type Collection<Class1> and a corresponding property Collection<Class1> Prop. Like this:

public class UserControl1 : UserControl
{
    private Collection<Class1> field = null;
    // later changed to:
    //private Collection<Class1> field = new Collection<Class1>();
    [Category("Data")]
    [DefaultValue(null)]
    [Description("asdf")]
    public Collection<Class1> prop
    {
        get { return field; }
        set { field = value; }
    }
}
// later added:
//[Serializable]
public class Class1
{
    private bool booltest; public bool Booltest { get...set...}
    private int inttest; public int Inttest { get...set...}
}

If you already know what I screwed up: no need to read the rest. I am going to describe what exactly I did.

Now I put the UserControl onto a random Form and change the Prop property. A generic "Collection Editor" appears, like the one used for the columns and groups in a listview control. I can enter data as expected. However, when I click OK, the data is gone.

It took me over hour to figure out that I actually have to instantiate my field: private Collection<MyClass> field = new Collection<MyClass>();. Very good, only that the designer entered superspazzing mode. Cascading nightmare error message that can be reduced to: "You must put [Serializable] before your Class1." After doing that I could actually put my UserControl1 on the Form again.

But that only works once. When opening the designer of the Form where I use the UserControl1 after editing something, it gives me an error:

Object of type 'userctltest.Class1[]' cannot be converted to type 'userctltest.Class1[]'.

Well. The Error List says:

Warning: ResX file Object of type 'userctltest.Class1[]' cannot be converted to type 'userctltest.Class1[]'. Line 134, position 5. cannot be parsed.

The designer tries to fetch the Property's data from the resx file. Removing the resx file "solves" that exactly once.

The Form can now be displayed again, with my UserControl1. The Collection property is editable, and it is being saved. It actually works. Once. Whenever I change something and then try to open the Form's designer again, the above error occurs again. I can delete the resx file, but that will of course also delete my data.

Relevant resources that helped me so far (among a ton of not so helpful search results):

http://www.codeproject.com/Answers/190675/Usercontrol-with-custom-class-property#answer1
http://www.codeproject.com/KB/cs/propertyeditor.aspx
http://www.csharpfriends.com/Articles/getArticle.aspx?articleID=94
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx

(I also tried implementing ISerializable and overriding GetObjectData with

{ info.AddValue("testbool", testbool); info.AddValue("testint", testint); }

didn't help either (I also tried the property names instead of the field names))

Sorry for writing this like a bad horror novel btw.

Answer

Patko picture Patko · Sep 15, 2011

What you want is a design time support with CodeDom serialization. You do not need SerializableAttribute or ISerializable, those are for binary serialization. Since you want to serialize the collection, you must tell the designer to serialize it as such. That is done with the DesignerSerializationVisibiliby attribute - value of Content tells the designer to serialize property contents rather than property itself. Contents of the property should of course be CodeDom serializable, which simple classes with simple properties are by default.

So if you change your UserControl1 class like this:

public class UserControl1 : UserControl
{
    private Collection<Class1> field = new Collection<Class1>();

    [Category("Data")]
    [Description("asdf")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public Collection<Class1> prop
    {
        get { return field; }
    }
}

... it should do the trick. Oh and collection properties are usually not writeable, although that is not mandatory. But serializer expects the collection property to be initialized, that is why you had to add initialization for the field. Another note, if you do not want that your property is marked with bold in the property editor, you can specify a more complex "default value" through a special method ShouldSerializePropertyName, which can even be private. Like so:

private bool ShouldSerializeprop() 
{
    return (field.Count > 0);
}

Now your property will only be bold when it is not empty. But I digress, this was not a question :)