Properly display list of custom objects in Windows Forms' Property Grid

8.9k views Asked by At

I'm trying to display the properties of my Wall class in a properties grid. Right now my wall class looks like this:

[TypeConverter(typeof(ExpandableObjectConverter))]
public class WallType
{
    #region _private variables and Properties
    public string Name { get; set; } //Name of the wall to identify it. For the wall program, it is automatically generated (ex. E4-1 where E = exterior wall, 4 = 2x4, and 1 means first exterior wall defined)
    public string Type { get; set; } //Type of wall (exterior, interior, etc.)
    public bool IsMirrorable { get; set; } //Identifies if the wall is mirrorable or not
    public Distance Height { get; set; } //Height of the wall from bottom to talk
    public string StudPattern { get; set; } //Pattern in which the studs of the wall are laid out
    public Distance StudSpacing { get; set; } //Spacing between each stud
    public VaporBarrier Barrier { get; set; }
    public LetIn LetInOfWall { get; set; }
    public Sheathing SheathingUsed { get; set; }
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public List<Member> Members { get; set; }
    public bool HasLetIn { get; set; }
    public bool HasCapPlate { get; set; }
    public bool HasVaporBarrier { get; set; }
    public bool HasSheathing { get; set; }
    #endregion

    /*Constructors and Methods not shown*/
 }

When I display an instance of WallType on the property grid it comes out like this:

enter image description here

Everything looks good EXCEPT the Members property. Right now all it does is display the capacity and count. What I want is for it to display the names property of each member in the list. I thought that putting an ExpandableObjectConverter over the list would solve the problem but it didnt. So I tried putting a custom TypeConverter on top of the Member class

[TypeConverter(typeof(MemberObjectConverter))]
public class Member
{
    #region _private variables and Properties
    public string Name { get; set; } //Name of the member used to uniquely identify it
    public string Size { get; set; } //Size of the member (ex. 2x4)
    public string Grade { get; set; } //Grade of the wood used to make the member
    public Distance Length { get; set; } //Length of the member
    public string Species { get; set; } //Type of wood the member is made of
    public string Treatment { get; set; } //Type of treatment applied to the member
    public string Other { get; set; } //Variable for other notes about the member
    #endregion
    /*Constructors and Methods not shown*/
}

And finally, my custom type converter

namespace Wall_Program
{
    /// <summary>
    /// Extending the ExpandableObjectConverter to display member objects properly
    /// </summary>
    public class MemberObjectConverter : ExpandableObjectConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(Member))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, System.Type destinationType)
        {
            if (destinationType == typeof(System.String) && value is Member)
            {
                Member m = (Member)value;
                return "Name: " + m.Name +
                    ", Size: " + m.Size +
                    ", Grade: " + m.Grade +
                    ", Length: " + m.Length.Architectural +
                    ", Species: " + m.Species +
                    ", Treatment: " + m.Treatment +
                    ", Other: " + m.Other;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is string)
            {
                try
                {
                    string s = (string)value;
                    int colon = s.IndexOf(':');
                    int comma = s.IndexOf(',');

                    if (colon != -1 && comma != -1)
                    {
                        string name = s.Substring(colon + 1, (comma - colon - 1));

                        colon = s.IndexOf(':', comma + 1);
                        comma = s.IndexOf(',', comma + 1);
                        string size = s.Substring(colon + 1, (comma - colon - 1));

                        colon = s.IndexOf(':', comma + 1);
                        comma = s.IndexOf(',', comma + 1);
                        string grade = s.Substring(colon + 1);

                        colon = s.IndexOf(':', comma + 1);
                        comma = s.IndexOf(',', comma + 1);
                        Distance length = new Distance(s.Substring(colon + 1, (comma - colon - 1)));

                        colon = s.IndexOf(':', comma + 1);
                        comma = s.IndexOf(',', comma + 1);
                        string species = s.Substring(colon + 1, (comma - colon - 1));

                        colon = s.IndexOf(':', comma + 1);
                        comma = s.IndexOf(',', comma + 1);
                        string treatment = s.Substring(colon + 1, (comma - colon - 1));

                        colon = s.IndexOf(':', comma + 1);
                        string other = s.Substring(colon + 1);

                        Member m = new Member(name, size, grade, length, species, treatment, other);
                        return m;
                    }
                }
                catch
                {
                    throw new ArgumentException("Can not convert '" + (string)value + "' to type Member");
                }
            }
            return base.ConvertFrom(context, culture, value);
        }
    }
}

The custom type converter is having some effect because when I click the button to manually show the collection everything is listed properly. But what I want is for the names to be what is shown instead of the capacity and count.

enter image description here

1

There are 1 answers

2
Simon Mourier On BEST ANSWER

This is rather unusual as Plutonix commented, but here is a way to do it.

Just use the following TypeConverter on the Members property. Since the PropertyGrid is based on properties, you have to create fake properties that represent each member. That's what represents the MemberDescriptor class here.

public class MyTypeConverter : TypeConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType != typeof(string))
            return base.ConvertTo(context, culture, value, destinationType);

        List<Member> members = value as List<Member>;
        if (members == null)
            return "-";

        return string.Join(", ", members.Select(m => m.Name));
    }

    public override bool GetPropertiesSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
        List<PropertyDescriptor> list = new List<PropertyDescriptor>();
        List<Member> members = value as List<Member>;
        if (members != null)
        {
            foreach (Member member in members)
            {
                if (member.Name != null)
                {
                    list.Add(new MemberDescriptor(member, list.Count));
                }
            }
        }
        return new PropertyDescriptorCollection(list.ToArray());
    }

    private class MemberDescriptor : SimplePropertyDescriptor
    {
        public MemberDescriptor(Member member, int index)
            : base(member.GetType(), index.ToString(), typeof(string))
        {
            Member = member;
        }

        public Member Member { get; private set; }

        public override object GetValue(object component)
        {
            return Member.Name;
        }

        public override void SetValue(object component, object value)
        {
            Member.Name = (string)value;
        }
    }
}

As a bonus, I have also added the conversion to string so it looks nicer:

enter image description here