如何将运行时列表接入PropertyGrid,实现属性值从列表选择?
解决PropertyGrid中动态为现有属性提供可选值的方案
针对不能修改程序集A中Data类、仅能在程序集B中操作的场景,提供两种可行的ComponentModel方案:
方案一:基于TypeDescriptionProvider的全局/子类兼容方案
该方案无需修改原始类,自动支持所有Data的子类,适合需要给同类型所有实例统一提供可选列表的场景。
步骤1:实现自定义类型转换器
public class DynamicStringListConverter : StringConverter { private readonly List<string> _options; public DynamicStringListConverter(List<string> options) { _options = options; } // 启用下拉列表支持 public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } // 返回运行时的可选字符串列表 public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { return new StandardValuesCollection(_options); } // 设置是否仅允许选择列表内的值(true=禁止手动输入,false=允许输入+选择) public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) { return true; } }
步骤2:包装原始属性描述器
创建自定义PropertyDescriptor,替换Text属性的转换器:
public class CustomPropertyDescriptor : PropertyDescriptor { private readonly PropertyDescriptor _originalProp; private readonly TypeConverter _customConverter; public CustomPropertyDescriptor(PropertyDescriptor originalProp, TypeConverter customConverter) : base(originalProp) { _originalProp = originalProp; _customConverter = customConverter; } // 替换为自定义转换器 public override TypeConverter Converter => _customConverter; // 转发所有其他属性/方法到原始描述器 public override bool CanResetValue(object component) => _originalProp.CanResetValue(component); public override object GetValue(object component) => _originalProp.GetValue(component); public override void ResetValue(object component) => _originalProp.ResetValue(component); public override void SetValue(object component, object value) => _originalProp.SetValue(component, value); public override bool ShouldSerializeValue(object component) => _originalProp.ShouldSerializeValue(component); public override Type ComponentType => _originalProp.ComponentType; public override bool IsReadOnly => _originalProp.IsReadOnly; public override Type PropertyType => _originalProp.PropertyType; }
步骤3:实现自定义类型描述提供器
该提供器会动态修改Data类及其子类的属性描述:
public class DynamicListTypeDescriptionProvider : TypeDescriptionProvider { private readonly TypeDescriptionProvider _originalProvider; private readonly List<string> _stringList; public DynamicListTypeDescriptionProvider(TypeDescriptionProvider originalProvider, List<string> stringList) { _originalProvider = originalProvider; _stringList = stringList; } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { var originalDescriptor = _originalProvider.GetTypeDescriptor(objectType, instance); return new CustomTypeDescriptorWrapper(originalDescriptor, _stringList); } private class CustomTypeDescriptorWrapper : CustomTypeDescriptor { private readonly List<string> _stringList; public CustomTypeDescriptorWrapper(ICustomTypeDescriptor originalDescriptor, List<string> stringList) : base(originalDescriptor) { _stringList = stringList; } public override PropertyDescriptorCollection GetProperties() { return WrapProperties(base.GetProperties()); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return WrapProperties(base.GetProperties(attributes)); } private PropertyDescriptorCollection WrapProperties(PropertyDescriptorCollection originalProps) { var wrappedProps = new List<PropertyDescriptor>(); foreach (PropertyDescriptor prop in originalProps) { if (prop.Name == nameof(Data.Text) && prop.PropertyType == typeof(string)) { // 替换Text属性的转换器 var converter = new DynamicStringListConverter(_stringList); wrappedProps.Add(new CustomPropertyDescriptor(prop, converter)); } else { wrappedProps.Add(prop); } } return new PropertyDescriptorCollection(wrappedProps.ToArray()); } } }
步骤4:注册并使用
在程序集B中注册提供器,然后绑定到PropertyGrid:
List<string> stringList = GetSomeStringsAtRunTime(); Data obj = new Data(); // 获取原始类型提供器,创建并注册自定义提供器 var originalProvider = TypeDescriptor.GetProvider(typeof(Data)); var customProvider = new DynamicListTypeDescriptionProvider(originalProvider, stringList); TypeDescriptor.AddProvider(customProvider, typeof(Data)); // 绑定到PropertyGrid,此时Text属性会显示下拉列表 propertyGrid.SelectedObject = obj;
如果需要为单个实例提供专属列表,只需将注册代码改为:
TypeDescriptor.AddProvider(customProvider, obj);
方案二:动态属性+实例关联的轻量方案
适合单个/少量实例的场景,实现更简洁,但需要注意内存管理:
步骤1:实现带实例关联的转换器
public class InstanceBoundStringConverter : StringConverter { // 存储实例与可选列表的映射,使用弱引用避免内存泄漏 private static readonly Dictionary<WeakReference<object>, List<string>> _instanceOptions = new(); // 注册实例的可选列表 public static void RegisterInstanceOptions(object instance, List<string> options) { _instanceOptions.Add(new WeakReference<object>(instance), options); } public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return context.Instance != null && _instanceOptions.Any(kv => kv.Key.TryGetTarget(out var target) && target == context.Instance); } public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { if (context.Instance != null) { var kv = _instanceOptions.FirstOrDefault(kv => kv.Key.TryGetTarget(out var target) && target == context.Instance); if (kv.Value != null) { return new StandardValuesCollection(kv.Value); } } return base.GetStandardValues(context); } }
步骤2:绑定实例并使用
List<string> stringList = GetSomeStringsAtRunTime(); Data obj = new Data(); // 注册实例的可选列表 InstanceBoundStringConverter.RegisterInstanceOptions(obj, stringList); // 动态给实例添加转换器属性 TypeDescriptor.AddAttributes(obj, new TypeConverterAttribute(typeof(InstanceBoundStringConverter))); propertyGrid.SelectedObject = obj;
注意:使用完实例后,建议清理_instanceOptions中的无效弱引用条目,避免不必要的内存占用。
内容的提问来源于stack exchange,提问作者Piglet
相关产品推荐
相关产品推荐

