Search code examples
c#visual-studiowinformsuser-controlswindows-forms-designer

Get designer to generate AddRange for simple List property of UserControl


I have created a new UserControl like so:

    public partial class MyControl : UserControl {
    List<Fruit> _fruits = new List<Fruit>();

    public List<Fruit> Fruits {
        get {
            return _fruits;
        }
        set {
            _fruits = value;
        }
    }

    public UserControl1() {
        InitializeComponent();
    }
}

The Fruit class simply contains two get/set properties and nothing else:

public class Fruit {
    public bool Edible {
        get;
        set;
    }

    public string Name {
        get;
        set;
    }
}

When I drag an instance of MyControl from the Visual Studio toolbox on a Form and then proceed to add Fruit objects to the Fruits collection of MyControl using the Visual Studio designer, I expected the designer to generate new Fruit instances and automatically add them to the Fruits collection in the designer-generated code by generating a call to the collections AddRange or Add method.

However it does not generate any AddRange code to add them to MyControl's Fruits collection and so I end up with "lingering" Fruit instances in the code-behind. I already tried adding the [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] attribute to the Fruits property, but that did nothing. What am I missing?


Solution

  • [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] is needed for Fruits to let the designer know to serialize its content. Also the Fruits property doesn't need a public setter:

    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows.Forms;
    public partial class MyControl : UserControl
    {
        public MyControl()
        {
            Fruits = new List<Fruit>();
            InitializeComponent();
        }
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public List<Fruit> Fruits { get; private set; }
    }
    

    As a result the following code will be generated:

    Sample.Fruit fruit1 = new Sample.Fruit();
    Sample.Fruit fruit2 = new Sample.Fruit();
    Sample.Fruit fruit3 = new Sample.Fruit();
    this.myControl1 = new Sample.MyControl();
    // 
    // myControl1
    // 
    fruit1.Edible = true;
    fruit1.Name = "Apple";
    fruit2.Edible = true;
    fruit2.Name = "Orange";
    fruit3.Edible = true;
    fruit3.Name = "Banana";
    this.myControl1.Fruits.Add(fruit1);
    this.myControl1.Fruits.Add(fruit2);
    this.myControl1.Fruits.Add(fruit3);
    

    Cleaner designer generated code

    If you want to have a cleaner code generated, like following:

    this.myControl1 = new Sample.MyControl();
    // 
    // myControl1
    // 
    this.myControl1.Fruits.Add(new Sample.Fruit(true, "Apple"));
    this.myControl1.Fruits.Add(new Sample.Fruit(true, "Orange"));
    this.myControl1.Fruits.Add(new Sample.Fruit(true, "Banana"));
    

    You need to create a TypeConverter for your Fruit class which uses InstanceDescriptor to create an instance of the class:

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design.Serialization;
    using System.Globalization;
    [TypeConverter(typeof(FruitConverter))]
    public class Fruit
    {
        public Fruit() { }
        public Fruit(bool edible, string name) : this()
        {
            Edible = edible;
            Name = name;
        }
        public bool Edible { get; set; }
        public string Name { get; set; }
    }
    public class FruitConverter : TypeConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor)) return true;
            return base.CanConvertTo(context, destinationType);
        }
        public override object ConvertTo(ITypeDescriptorContext context, 
            CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(InstanceDescriptor)) {
                var ci = typeof(Fruit).GetConstructor(new Type[] { 
                    typeof(bool), typeof(string) });
                var t = (Fruit)value;
                return new InstanceDescriptor(ci, new object[] { t.Edible, t.Name });
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }