Search code examples
.netwinformspropertygrid

Modifying structure property in a PropertyGrid


Why SomeClass.ClassField.StructField property doesn't change in a propertyGrid? It seems, propertyGrid doesn't call SomeClass.ClassField.set after SomeStruct instance has been changed. But same code works well with Point instead of SomeStruct.

[TypeConverter(typeof(ExpandableObjectConverter))]
public struct SomeStruct
{
    private int structField;

    public int StructField
    {
        get
        {
            return structField;
        }
        set
        {
            structField = value;
        }
    }

    public override string ToString()
    {
        return "StructField: " + StructField;
    }
}

[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class SomeClass
{
    public SomeStruct ClassField
    {
        get;
        set;
    }
}

...

var someClass = new SomeClass
{
    ClassField = new SomeStruct
    {
        StructField = 42
    }
};
propertyGrid.SelectedObject = someClass;

Solution

  • You need a special TypeConverter that overrides TypeConverter.GetCreateInstanceSupported because otherwise copy-by-value/boxing magic happens behind the scene in the way the property grid handles all this.

    Here is one that should work for most value types. You declare it like this:

    [TypeConverter(typeof(ValueTypeTypeConverter<SomeStruct>))]
    public struct SomeStruct
    {
        public int StructField { get; set; }
    }
    
    
    public class ValueTypeTypeConverter<T> : ExpandableObjectConverter where T : struct
    {
        public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
        {
            return true;
        }
    
        public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
        {
            if (propertyValues == null)
                throw new ArgumentNullException("propertyValues");
    
            T ret = default(T);
            object boxed = ret;
            foreach (DictionaryEntry entry in propertyValues)
            {
                PropertyInfo pi = ret.GetType().GetProperty(entry.Key.ToString());
                if (pi != null && pi.CanWrite)
                {
                    pi.SetValue(boxed, Convert.ChangeType(entry.Value, pi.PropertyType), null);
                }
            }
            return (T)boxed;
        }
    }
    

    Note it doesn't support pure field-only structs, only the one with properties, but the ExpandableObjectConverter doesn't support these either, it would require more code to do it.