Modifying structure property in a PropertyGrid
Asked Answered
D

3

6

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;
Dot answered 1/4, 2013 at 16:3 Comment(4)
structs are supposed to be immutableBlandishments
This one is mutable. Same as Point/Rectangle/etc.Dot
good point (extra characters)Blandishments
Do note the word "Object" in "ExpandableObjectConverter". You'll be editing a boxed copy of the struct, it doesn't propagate back. Use the implementation of PointConverter as an example.Goeselt
F
8

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.

Fastening answered 2/4, 2013 at 7:36 Comment(0)
H
3

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;
    }
}
Horten answered 3/1, 2014 at 2:9 Comment(0)
A
0

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;
  }
Austria answered 22/6, 2015 at 12:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.