Reputation: 306
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;
Upvotes: 6
Views: 3224
Reputation: 1192
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;
}
Upvotes: 0
Reputation: 138950
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.
Upvotes: 8
Reputation: 8706
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;
}
}
Upvotes: 3