Расширение компонентов
Несколько в стороне от общей системы режима дизайна находится возможность расширения компонентов дополнительными свойствами. Иными словами, компонент, находящийся в некотором контейнере, например, форме, имеет возможность добавить ряд «виртуальных» свойств к другим компонентам в этом же контейнере. Эти псевдо-свойства отображаются как свойства расширенного компонента, однако на самом деле задание и считывание их значений осуществляются через компонент-расширитель. Это делается посредством интерфейса IExtenderProvider и атрибута ProvideProperty.
Чтобы создать компонент-расширитель, создайте класс компонента и реализуйте интерфейс IExtenderProvider. Система дизайна, обнаружив среди компонентов такой поставщик свойств, вызовет его метод CanExtend для каждого компонента на форме. Если возвращаемое значение будет true для некоторого компонента, дизайнер добавит к нему псевдо-свойства, описанные в атрибутах ProvideProperty.
Псевдо-свойства расширения описываются как [ProvideProperty("PropertyName", ExtendeeType)]. При этом у расширенного компонента имя свойства в дизайнере будет выглядеть как «PropertyName on ExtenderName», где PropertyName – имя свойства из атрибута ProvideProperty, а ExtenderName – имя компонента расширения в контейнере. К сожалению, при выборе объектов для расширения система дизайна не фильтрует объекты по типу ExtendeeType, и в метод CanExtend попадут все возможные компоненты, включая и расширитель, и контейнер. Поэтому метод CanExtend должен делать соответствующие проверки:
public bool CanExtend(object extendee) { ExtendeeType ext = extendee as ExtendeeType; if (ext == null) // не наш тип return false; if (ext == this) // не расширяем себя return false;
// дальше, собственно, логика расширения if (...) return true; return false; }
|
Учтите, что система дизайна вызывает CanExtend довольно часто, особенно при открытии формы, по несколько раз на каждый компонент и контрол. Поэтому метод CanExtend должен отрабатывать максимально быстро. Исследования показывают, что эта проверка выполняется после установки каждого свойства, описанного в InitializeComponent.
Код getter и setter для таких псевдо-свойств описывается в классе-расширителе следующим образом:
PropertyType GetPropertyName(ExtendeeType extendee) { ... return value; }
void SetPropertyName(ExtendeeType extendee, PropertyType value) { ... }
|
Здесь PropertyName – это имя свойства, описанного в атрибуте. Тип свойства определяется типом возвращаемого значения метода GetPropertyName. Вообще, GetPropertyName является «представителем» свойства. Если создаваемому виртуальному свойству необходимо назначить некоторые атрибуты (такие, как DefaultValue, Browsable, DesignerSerializationVisibility и аналогичные), их нужно применять к этому методу.
Для компонентов, реализующих IExtenderProvider, поддерживаются методы ShouldSerializeNNN и ResetNNN, принимающие один параметр – расширяемый объект указанного в атрибуте типа:
// вовзращает true, если нужно сериализовать // псевдо-свойство PropertyName для объекта extendee bool ShouldSerializePropertyName(ExtendeeType extendee);
// устанавливает псевдо-свойство PropertyName // для объекта extendee в значение по умолчанию void ResetPropertyName(ExtendeeType extendee);
|
Разработчикам приходится часто сталкиваться с несколькими проблемами в существующей системе компонентов-расширителей. Во-первых, при добавлении компонента на форму опрос CanExtend происходит до того, как заданы значения его свойств. Это особенно неудобно, если для принятия решения о расширении нужно иметь ссылку на контейнер (свойство Parent). Во-вторых, дизайнер никак не сообщает компоненту-расширителю, что некоторый компонент был удалён из контейнера.
У контейнеров, хранящих контролы нужно подписаться на события ControlAdded и ControlRemoved или перегрузить соответствующие защищенные методы. При добавлении или удалении контрола необходимо обновить внутренние структуры данных, хранящие соответствия между расширенными компонентами и значениями псевдо-свойств. Если по каким-то причинам CanExtend вернул False, псевдо-свойство не будет добавлено. Заставить дизайнер сделать еще одну попытку добавить псевдо-свойство можно, только перезагрузив состояние дизайнера (то есть все компоненты). Чтобы это сделать, нужно получить сервис IDesignerLoaderService и вызвать у него метод Reload. Сделать это можно так:
protected override void OnControlAdded(ControlEventArgs e) { base.OnControlAdded (e); // проверяем, что мы в режиме дизайна if (Site != null && Site.DesignMode) { // получаем сервис нужного типа IDesignerLoaderService service = (IDesignerLoaderService)Site.GetService( typeof(IDesignerLoaderService));
// если сервис предоставлен, перезагружаем дизайнер if (service != null) service.Reload(); } }
|
При этом дизайнер перезагрузит все компоненты, опросит их на предмет свойств и учтёт компоненты-расширители. В данном случае контрол уже будет иметь, например, выставленное свойство Parent, и его расширение пройдёт успешно.
Существует еще один способ внедрения собственного кода в процесс редактирования компонентов. В окне свойств компонента можно добавить маленькую картинку около имени свойства и обработать двойной щелчок по этой картинке. Для этого нужно воспользоваться сервисом IPropertyValueUIService, который доступен описанным ранее способом через Site. Этот сервис позволяет добавить в систему дизайна делегат типа PropertyValueUIHandler, который вызывается для каждого свойства в данном контейнере. В обработчике вы можете выбрать свойства, которые нужно расширить, и добавить объекты PropertyValueUIItem в полученный массив. Эти объекты как раз и представляют картинку около свойства (размером 8х8), обработчик её активации и строку подсказки (ToolTip). К сожалению, строка подсказки в настоящее время не используется, но, возможно, в будущих версиях окна свойств значение этого свойства будет учтено. Приведем пример добавления обработчика:
// перегружаем свойство Site для добавления своего обработчика public override ISite Site { get { return base.Site; } set { base.Site = value; // получаем сервис IPropertyValueUIService uiService = (IPropertyValueUIService)this.GetService( typeof(IPropertyValueUIService)); // если сервис присутствует, добавляем свой обработчик if( uiService != null ) uiService.AddPropertyValueUIHandler( new PropertyValueUIHandler(MyPropertyValueUIHandler)); } }
// описываем обработчик, который выбирает редактируемые свойства, // помеченные атрибутом [Bindable(true)] private void MyPropertyValueUIHandler(ITypeDescriptorContext context, PropertyDescriptor propDesc, ArrayList itemList) { if (!propDesc.IsReadOnly && propDesc.Attributes.Contains(BindableAttribute.Yes)) { // получаем картинку из ресурсов Image img = Image.FromStream( Assembly.GetExecutingAssembly().GetManifestResourceStream( "BindableProperty.bmp")); // добавляем новый элемент расширения для данного свойства itemList.Add(new PropertyValueUIItem(img, new PropertyValueUIItemInvokeHandler( this.MyUIHandlerInvoke), "Bindable Property") ); } }
// обработчик нашего встроенного элемента private void MyUIHandlerInvoke(ITypeDescriptorContext context, PropertyDescriptor propDesc, PropertyValueUIItem item) { MessageBox.Show("Clicked: " + propDesc.DisplayName + " on " + context.Instance.ToString()); }
|
Если вам нужно передавать дополнительные данные в обработчик щелчка по картинке, можно создать класс, производный от PropertyValueUIItem, и передавать дополнительные данные через него. Также не забывайте вовремя удалять обработчик, используя IPropertyValueUIService.RemovePropertyValueUIHandler.
Улучшение компонента
Большинство компонентов и контролов создаются для решения некоторой конкретной задачи в некотором конкретном приложении. Если же вы создаете компоненты на продажу или как open-source и рассчитываете на признание его в мире .NET, то необходимо учесть еще некоторые аспекты. В этой части кратко рассказано о трёх из них – локализации, источниках данных и поддержке людей с ограниченными возможностями.
- Краткий путеводитель
- Проектируйте
- Сохраняйте гибкость
- Коллекции элементов
- Отрисовка
- Придерживайтесь стандартов
- Оптимизируйте
- Взаимодействие с мышью
- Взаимодействие с клавиатурой
- Поведение в режиме дизайна
- Использование атрибутов
- Коллекции
- Конверторы типов
- Расширение компонентов
- Локализация
- Источники данных
- Доступность
- Конвертор типа