using System.ComponentModel; namespace UnshackleGUI { /// /// This is the object assigned to PropertyGrid.SelectedObject /// It correctly implements ICustomTypeDescriptor. /// public class DynamicObject : ICustomTypeDescriptor { private readonly Dictionary _values; private readonly List _defs; public DynamicObject(Dictionary values, List defs) { _values = values; _defs = defs; } public PropertyDescriptorCollection GetProperties() { return new DynamicCustomTypeDescriptor(_values, _defs).GetProperties(); } public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return GetProperties(); } public object GetPropertyOwner(PropertyDescriptor pd) => this; public AttributeCollection GetAttributes() => AttributeCollection.Empty; public string GetClassName() => nameof(DynamicObject); public string GetComponentName() => null; public TypeConverter GetConverter() => null; public EventDescriptor GetDefaultEvent() => null; public PropertyDescriptor GetDefaultProperty() => null; public object GetEditor(Type editorBaseType) => null; public EventDescriptorCollection GetEvents() => EventDescriptorCollection.Empty; public EventDescriptorCollection GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty; } internal class DynamicCustomTypeDescriptor : CustomTypeDescriptor { private readonly Dictionary _values; private readonly List _defs; private PropertyDescriptorCollection _cache; public DynamicCustomTypeDescriptor(Dictionary values, List defs) { _values = values; _defs = defs; } public override PropertyDescriptorCollection GetProperties() { return GetProperties(null); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { if (_cache == null) { var props = new List(); foreach (var def in _defs) { props.Add(new DynamicPropertyDescriptor(def, _values)); } _cache = new PropertyDescriptorCollection(props.ToArray()); } return _cache; } } internal class DynamicPropertyDescriptor : PropertyDescriptor { private readonly UnshackleParameter _parameter; private readonly Dictionary _values; public DynamicPropertyDescriptor(UnshackleParameter parameter, Dictionary values) : base(parameter.Name, null) { _parameter = parameter; _values = values; } public override string Category => _parameter.Category; // IMPORTANT FIX public override Type ComponentType => typeof(object); public override bool IsReadOnly => false; public override Type PropertyType => _parameter.Type switch { "Bool" => typeof(bool), "Number" => typeof(int), _ => typeof(string) }; public override TypeConverter Converter => (_parameter.Type == "Selection" && _parameter.Options != null) ? new StandardValuesConverter(_parameter.Options) : base.Converter; public override object GetValue(object component) { return _values.ContainsKey(_parameter.Name) ? _values[_parameter.Name] : _parameter.Default; } public override void SetValue(object component, object value) { _values[_parameter.Name] = value; OnValueChanged(component, EventArgs.Empty); // Force grid refresh TypeDescriptor.Refresh(component); } public override bool CanResetValue(object component) => true; public override void ResetValue(object component) { _values[_parameter.Name] = _parameter.Default; } public override bool ShouldSerializeValue(object component) => true; } internal class StandardValuesConverter : TypeConverter { private readonly List _values; public StandardValuesConverter(List values) { _values = values; } public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true; public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => true; public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { return new StandardValuesCollection(_values); } } }