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

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

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


person Gman    schedule 27.08.2012    source источник


Ответы (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.

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

person Community    schedule 27.08.2012
comment
Я использую вашу логику несколько иначе: я пытаюсь добавить атрибут DisplayName в свойство во время выполнения. Написанная вами логика создания работает нормально, но преобразования этого атрибута в метаданные не происходит. Я создал CustomModelMetadataProvider, и когда для этого свойства вызывается функция CreateMetadata(), моих атрибутов DisplayName нет ни в списке атрибутов, ни в наборе метаданных. Я не уверен, что мне здесь не хватает ... - person Timothée Bourguignon; 20.09.2012
comment
Привет! Я толком не понял, чем отличается твой случай. Вам просто нужно изменить EditorAttribute на DisplayNameAttribute в последнем примере многострочного кода. Проверил, у меня работает. Ничего не знаю о метаданных, зачем они вам нужны? - person Gman; 20.09.2012
comment
Разница в том, что я не использую PropertyGrid; но насколько я могу судить, это не должно иметь значения. Вот проблема, которую я пытаюсь решить: Как добавить метаданные в динамически создаваемую модель представления MVC3? - person Timothée Bourguignon; 20.09.2012
comment
Привет. В последней строке третьего блока кода (начиная с TypeDescriptor.AddProvider) вы используете конструктор с двумя параметрами для TypeDescriptorOverridingProvider, но во втором блоке кода вы написали только конструктор с одним параметром. - person Colonel Panic; 25.09.2012
comment
@Colonel Panic, спасибо, я исправил это. Это просто второй, который нужен. Тип _settings не требуется. - person Gman; 25.09.2012
comment
@ Тим Бургиньон, извини, ничем не могу помочь, я слишком далек от asp, mvc3. - person Gman; 25.09.2012
comment
Я выполнил все ваши шаги, и если я использую Reflection, чтобы проверить наличие нового атрибута, он существует. Но с LINQ to SQL не вызывает TypeDescriptorOverridingProvider. Любая идея? - person lorddarkangel; 17.06.2013
comment
@lorddarkangel, не могли бы вы опубликовать код? Может быть, даже создадим вопрос: «комментарии coz не совсем подходят для публикации кода :) - person Gman; 18.06.2013
comment
Я разместил еще один вопрос с моей проблемой: stackoverflow.com/questions/17164112/ - person lorddarkangel; 18.06.2013
comment
FWIW, это решение будет работать только в том случае, если не определены производные типы, поскольку TypeDescriptor будет работать для всех производных классов, но вернет только свойства в базовом классе. Я работал над поиском всех подклассов и добавлением их собственного typedescriptorprovider для каждого ... вероятно, немного излишне, но, похоже, не замедляет время разработки, и мне лично это не нужно для времени выполнения. - person Jcl; 29.12.2015
comment
Было отклонено изменение этого сообщения с комментарием: В TypeDescriptorOverridingProvider возвращайте базовый TypeDescriptor, когда в GetTypeDescriptor передается пустой экземпляр, в противном случае при вызове GetTypeDescriptor будут извлечены только свойства родительских классов (и ни одно из свойств дочернего класса) для детского класса. Редакция заменила строку return ctd; на return instance == null ? base.GetTypeDescriptor(objectType, instance) : ctd;. Не могу проверить, действительно ли это, но хотел сообщить читателям. Конечно, я установил этот пост в режим вики-сообщества, чтобы разрешить редактирование, как это - person Gman; 23.03.2016

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

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

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

person veb    schedule 15.04.2014

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

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

person Colonel Panic    schedule 25.09.2012

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

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

person Jcl    schedule 30.12.2015