Как добавить атрибут уровня свойства в TypeDescriptor во время выполнения?
Я хочу добавить некоторые пользовательские атрибуты, ориентированные на PropertyGrid, в свойства объекта, чтобы обеспечить более полное редактирование, скрыть некоторые значения и сгруппировать их по категориям, потому что тот класс, с которым я работаю, не предоставляет такой функциональности, и я ничего не могу сделать об этом.
На самом деле, это для настроек приложения MS, которая генерирует код, так что вы не можете расширять его каким-либо образом в отношении свойств. Смотрите мой другой вопрос: диалог редактора AppSettings.settings во время выполнения
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 атрибутом.
Принятый ответ работает, но у него есть недостаток: если вы назначите провайдера базовому классу, он также будет работать для производных классов, так как PropertyOverridingTypeDescriptor
parent (от которого он получит свои свойства) предназначен для базового типа, производный тип найдет только свойства базового класса. Это приводит к ошибкам, например, в дизайнере winforms (и может привести к потере данных, если вы используете TypeDescriptor
для сериализации данных).
Просто для протокола, я сделал общее решение, основанное на ответе @Gman, и разместил его здесь как решение моего собственного вопроса (это был другой вопрос, хотя решение работало с использованием этого).
Если вам нужен богатый пользовательский PropertyGrid, альтернативный дизайн - сделать ваш тип переносимым в класс, унаследованный от CustomTypeDescriptor. Затем вы можете переопределить GetProperties, аннотируя свойства базового класса атрибутами, необходимыми для PropertyGrid.
Подробное описание в ответе на связанный вопрос /questions/33907829/kak-dobavit-polzovatelskij-uitypeeditor-dlya-vseh-svojstv-tipa-s-zakryityim-ishodnyim-kodom/33907843#33907843