Question

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;
Was it helpful?

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.

OTHER TIPS

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;
    }
}

In my case, the generic argument isn't known at compile time (options structure for a plugin). You can get a copy of the current value using context.PropertyDescriptor.GetValue(context.Instance); :

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

     object boxed = context.PropertyDescriptor.GetValue(context.Instance);
     foreach (DictionaryEntry entry in propertyValues)
     {
        PropertyInfo pi = boxed.GetType().GetProperty(entry.Key.ToString());
        if (pi != null && pi.CanWrite)
           pi.SetValue(boxed, Convert.ChangeType(entry.Value, pi.PropertyType), null);
     }
     return boxed;
  }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top