【问题标题】:How to add property-level Attribute to the TypeDescriptor at runtime?如何在运行时将属性级属性添加到 TypeDescriptor?
【发布时间】:2012-08-27 14:10:10
【问题描述】:

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

真的,它是为 MS 的应用程序设置生成代码的,所以你不能以任何方式扩展它的属性。请参阅我的另一个问题:Runtime AppSettings.settings editor dialog

【问题讨论】:

    标签: c# .net propertygrid system.componentmodel typedescriptor


    【解决方案1】:

    与其他人所建议的不同,这是很有可能的,也没有那么难。例如,您想为 some 属性添加 some 个新属性,您可以在运行时根据某些条件选择这些属性。

    我们需要两个帮助类来实现它。

    首先是PropertyOverridingTypeDescriptor,它允许我们为一些属性提供我们自己的属性描述符,同时保持其他属性不变:

    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));
            }
        }
    

    几点说明:

    • 构造函数采用ICustomTypeDescriptor,不用担心,我们可以为任何类型获取一个,或者它是TypeDescriptor.GetProvider(_settings).GetTypeDescriptor(_settings) 的实例,其中_settings 可以是该类型的Typeobject
    • OverrideProperty 正是我们需要的,稍后会详细介绍。

    我们需要的另一个类是TypeDescriptionProvider,它将返回我们的自定义类型描述符,而不是默认的。这里是:

    public class TypeDescriptorOverridingProvider : TypeDescriptionProvider
        {
            private readonly ICustomTypeDescriptor ctd;
    
            public TypeDescriptorOverridingProvider(ICustomTypeDescriptor ctd)
            {
                this.ctd = ctd;
            }
    
            public override ICustomTypeDescriptor GetTypeDescriptor (Type objectType, object instance)
            {
                return ctd;
            }
        }
    

    相当简单:您只需在构造时提供类型描述符实例即可。

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

    // 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 进行编辑。

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

    【讨论】:

    • 我正在以稍微不同的方式使用您的逻辑:我正在尝试在运行时在属性上添加 DisplayName 属性。您编写的创建逻辑似乎工作正常,但该属性到元数据的转换似乎没有发生。我创建了一个CustomModelMetadataProvider,当为此属性调用CreateMetadata() 函数时,我的DisplayName 属性不在属性列表中,元数据集也不在其中。我不确定我在这里错过了什么......
    • 您好!我真的不明白,你的情况有什么不同。您只需在最后一个多行代码示例中将EditorAttribute 更改为DisplayNameAttribute。我已经检查过了,它对我有用。对元数据一无所知,你需要它做什么?
    • 嗨。在第三个代码块的最后一行(以TypeDescriptor.AddProvider 开头),您为TypeDescriptorOverridingProvider 使用了一个双参数构造函数,而在第二个代码块中您只编写了一个单参数构造函数。
    • @Colonel Panic 谢谢,我已经解决了这个问题。这只是需要的第二个。 _settings 的类型不是必需的。
    • FWIW,此解决方案仅在未定义派生类型时才有效,因为 TypeDescriptor 将适用于所有派生类,但只会返回基类中的属性。我已经找到了所有子类并为每个子类添加了自己的 typedescriptorprovider……可能有点矫枉过正,但似乎并没有减慢设计时间,而且我个人在运行时不需要这个。
    【解决方案2】:

    接受的答案确实有效,但它有一个缺陷:如果您将提供者分配给基类,它也适用于派生类,但是,因为 PropertyOverridingTypeDescriptor 父类(从中它会得到它的properties) 是针对基类型的,派生类型只会找到基类属性。这会导致例如 winforms 设计器中的破坏(如果您使用 TypeDescriptor 序列化数据,可能会导致您丢失数据)。

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

    【讨论】:

      【解决方案3】:

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

      回答相关问题https://stackoverflow.com/a/12586865/284795的详细说明

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

        • -1 因为我可以从documentation 和实际尝试中看出,TypeDescriptor.AddAttributes 在类级别添加属性,而不是属性级别
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-14
        • 1970-01-01
        • 2012-09-16
        • 2023-03-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多