Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
like image 251
nitrocaster Avatar asked Apr 01 '13 16:04

nitrocaster


2 Answers

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.

like image 107
Simon Mourier Avatar answered Nov 02 '22 04:11

Simon Mourier


I tweaked Simon Mourier's answer to avoid the need for ValueTypeTypeConverter to be a generic:

public class ValueTypeTypeConverter : System.ComponentModel.ExpandableObjectConverter
{
    public override bool GetCreateInstanceSupported(System.ComponentModel.ITypeDescriptorContext context)
    {
        return true;
    }

    public override object CreateInstance(System.ComponentModel.ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
    {
        if (propertyValues == null)
            throw new ArgumentNullException("propertyValues");

        object boxed = Activator.CreateInstance(context.PropertyDescriptor.PropertyType);
        foreach (System.Collections.DictionaryEntry entry in propertyValues)
        {
            System.Reflection.PropertyInfo pi = context.PropertyDescriptor.PropertyType.GetProperty(entry.Key.ToString());
            if ((pi != null) && (pi.CanWrite))
            {
                pi.SetValue(boxed, Convert.ChangeType(entry.Value, pi.PropertyType), null);
            }
        }
        return boxed;
    }
}
like image 25
yoyo Avatar answered Nov 02 '22 03:11

yoyo