Как добавить атрибут уровня свойств в TypeDescriptor во время выполнения?

Я хочу добавить некоторые настраиваемые атрибуты PropertyGrid-centric к свойствам объекта, чтобы обеспечить более богатое редактирование, скрыть некоторые значения и сгруппировать их в категории, потому что этот класс, с которым я работаю, не предоставляет такой функциональности, и я ничего не могу с этим поделать.

действительно, это для настроек приложения MS, которые генерируют код, поэтому вы не можете его каким-либо образом расширить. См. мой другой вопрос:Runtime AppSettings.диалог редактора настроек

4 ответов


в отличие от других предложили, это вполне возможно, а также не так сложно. Например, вы хотите добавить некоторые новые атрибуты некоторые свойства, которые можно выбрать во время выполнения на основе некоторых критериев.

есть два вспомогательных класса, которые нам понадобятся для реализации этого.

сначала идет 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 может быть либо Type или object этого типа.
  • 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;
        }
    }

довольно просто: вы просто предоставляете экземпляр дескриптора типа при построении, и вот вы идете.

и, наконец, код обработки. Например, мы хотим, чтобы все свойства заканчивались на ConnectionString в нашем объекте (или типе) _settings для редактирования с 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.

как вы можете видеть, мы просто переопределите некоторые функции реализации по умолчанию каждый раз, поэтому система должна быть довольно стабильной и вести себя так, как ожидалось.


Если вам нужно добавить атрибуты, такие как [ExpandableObject] или [Editor] к свойствам объекта, класс которого вы не можете редактировать, вы можете добавить атрибуты к типу свойства. Таким образом, вы можете использовать отражение для проверки объекта и использовать

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

тогда он ведет себя так, как будто вы украсили все свойства типа YourType С атрибутом.


Если вы хотите rich custom PropertyGrid, альтернативный дизайн заключается в том, чтобы обернуть ваш тип в классе, наследуемом от CustomTypeDescriptor. Затем можно переопределить GetProperties, аннотируя свойства базового класса атрибутами, необходимыми для PropertyGrid.

подробное описание в ответе на вопрос https://stackoverflow.com/a/12586865/284795


принятый ответ работает, но у него есть недостаток: если вы назначаете поставщика базовому классу, он также будет работать для производных классов, однако, поскольку PropertyOverridingTypeDescriptor parent (из которого он получит свои свойства) для базового типа, производный тип найдет только свойства базового класса. Это вызывает havok, например, конструктор winforms (и может привести к потере данных, если вы используете TypeDescriptor для сериализации данных).

просто для записи, я сделал универсальный решение на основе ответа @Gman, и я опубликовал его здесь как решение моего собственного вопроса (который был другим вопросом, хотя решение работало с использованием этого).