You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将运行时列表接入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 10:25:54