C# 如何在运行时向TypeDescriptor添加属性级属性?

C# 如何在运行时向TypeDescriptor添加属性级属性?,c#,.net,propertygrid,system.componentmodel,typedescriptor,C#,.net,Propertygrid,System.componentmodel,Typedescriptor,我想向对象的属性中添加一些以自定义PropertyGrid为中心的属性,以提供更丰富的编辑,隐藏一些值并将它们分组,因为我正在使用的类不提供此类功能,我对此无能为力 实际上,生成代码的是MS的应用程序设置,所以您不能以任何方式扩展它。请看我的另一个问题:与其他人的建议不同,这是完全可能的,也没有那么难。例如,您希望向某些属性添加一些新属性,您可以在运行时根据某些条件选择这些属性 我们需要两个助手类来实现这一点 首先,它允许我们为某些属性提供自己的属性描述符,同时保持其他属性的完整性: publi

我想向对象的属性中添加一些以自定义PropertyGrid为中心的属性,以提供更丰富的编辑,隐藏一些值并将它们分组,因为我正在使用的类不提供此类功能,我对此无能为力


实际上,生成代码的是MS的应用程序设置,所以您不能以任何方式扩展它。请看我的另一个问题:

与其他人的建议不同,这是完全可能的,也没有那么难。例如,您希望向某些属性添加一些新属性,您可以在运行时根据某些条件选择这些属性

我们需要两个助手类来实现这一点

首先,它允许我们为某些属性提供自己的属性描述符,同时保持其他属性的完整性:

public class PropertyOverridingTypeDescriptor : CustomTypeDescriptor
    {
        private readonly Dictionary<string, PropertyDescriptor> overridePds = new Dictionary<string, PropertyDescriptor>();

        public PropertyOverridingTypeDescriptor(ICustomTypeDescriptor parent)
            : base(parent)
        { }

        public void OverrideProperty(PropertyDescriptor pd)
        {
            overridePds[pd.Name] = pd;
        }

        public override object GetPropertyOwner(PropertyDescriptor pd)
        {
            object o = base.GetPropertyOwner(pd);

            if (o == null)
            {
                return this;
            }

            return o;
        }

        public PropertyDescriptorCollection GetPropertiesImpl(PropertyDescriptorCollection pdc)
        {
            List<PropertyDescriptor> pdl = new List<PropertyDescriptor>(pdc.Count+1);

            foreach (PropertyDescriptor pd in pdc)
            {
                if (overridePds.ContainsKey(pd.Name))
                {
                    pdl.Add(overridePds[pd.Name]);
                }
                else
                {
                    pdl.Add(pd);
                }
            }

            PropertyDescriptorCollection ret = new PropertyDescriptorCollection(pdl.ToArray());

            return ret;
        }

        public override PropertyDescriptorCollection GetProperties()
        {
            return GetPropertiesImpl(base.GetProperties());
        }
        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            return GetPropertiesImpl(base.GetProperties(attributes));
        }
    }
相当简单:只需在构造时提供类型描述符实例,就可以了

最后,处理代码。例如,我们希望对象(或类型)
\u设置
中以
ConnectionString
结尾的所有属性都可以通过
System.Web.UI.Design.ConnectionString编辑器
进行编辑。为此,我们可以使用以下代码:

// prepare our property overriding type descriptor
PropertyOverridingTypeDescriptor ctd = new PropertyOverridingTypeDescriptor(TypeDescriptor.GetProvider(_settings).GetTypeDescriptor(_settings));

// iterate through properies in the supplied object/type
foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(_settings))
{
    // for every property that complies to our criteria
    if (pd.Name.EndsWith("ConnectionString"))
    {
        // we first construct the custom PropertyDescriptor with the TypeDescriptor's
        // built-in capabilities
        PropertyDescriptor pd2 =
            TypeDescriptor.CreateProperty(
                _settings.GetType(), // or just _settings, if it's already a type
                pd, // base property descriptor to which we want to add attributes
                    // The PropertyDescriptor which we'll get will just wrap that
                    // base one returning attributes we need.
                new EditorAttribute( // the attribute in question
                    typeof (System.Web.UI.Design.ConnectionStringEditor),
                    typeof (System.Drawing.Design.UITypeEditor)
                )
                // this method really can take as many attributes as you like,
                // not just one
            );

        // and then we tell our new PropertyOverridingTypeDescriptor to override that property
        ctd.OverrideProperty(pd2);
    }
}

// then we add new descriptor provider that will return our descriptor instead of default
TypeDescriptor.AddProvider(new TypeDescriptorOverridingProvider(ctd), _settings);
就是这样,现在所有以
ConnectionString
结尾的属性都可以通过
ConnectionStringEditor
进行编辑


正如您所看到的,我们每次都会覆盖默认实现的一些功能,因此系统应该相当稳定并按预期运行。

如果您想要丰富的自定义PropertyGrid,另一种设计是将您的类型包装在继承自CustomTypeDescriptor的类中。然后可以重写GetProperties,用PropertyGrid所需的属性注释基础类的属性


回答相关问题的详细说明

如果需要将[ExpandableObject]或[Editor]等属性添加到无法编辑的类的对象属性中,则可以将属性添加到属性类型中。因此,您可以使用反射来检查对象并使用

TypeDescriptor.AddAttributes(typeof (*YourType*), new ExpandableObjectAttribute());

然后,它的行为就像您用属性修饰了YourType类型的所有属性。

接受的答案确实有效,但它有一个缺陷:如果您将提供程序分配给基类,它也适用于派生类,但是,因为
属性overridingtypedescriptor
父类(它将从中获取其属性)对于基类型,派生类型将仅查找基类属性。这会导致winforms designer中出现havok(如果使用
TypeDescriptor
序列化数据,则可能会导致数据丢失)


为了记录在案,我根据@Gman的答案制作了一个通用解决方案,并将其作为我自己问题的解决方案发布(这是一个不同的问题,尽管该解决方案使用了这个问题)。

我以一种稍微不同的方式使用您的逻辑:我试图在运行时在属性上添加一个
DisplayName
属性。您编写的创建逻辑似乎工作正常,但该属性到元数据的转换似乎没有发生。我创建了一个
CustomModelMetadataProvider
,当为该属性调用
CreateMatadata()
函数时,我的
DisplayName
属性不在属性列表中,也不在元数据集中。我不确定我错过了什么…你好!我真的不明白,你的案子有什么不同。在上一个多行代码示例中,只需将
EditorAttribute
更改为
DisplayNameAttribute
。我检查过了,它对我有用。我对元数据一无所知,你需要它做什么?区别在于我没有使用PropertyGrid;但据我所知,这不会有什么不同。我想解决的问题是:嗨。在第三个代码块的最后一行(从
TypeDescriptor.AddProvider
开始),您为
TypeDescriptorOverridingProvider
使用了一个双参数构造函数,但在第二个代码块中,您只编写了一个单参数构造函数。@谢谢,我已经解决了这个问题。这只是第二个需要的_设置的类型不是必需的。
TypeDescriptor.AddAttributes(typeof (*YourType*), new ExpandableObjectAttribute());