Главная » C#, Компоненты » Компоненты-расширители и глобальные свойства

0

Компонент Tooltip интересен тем, что, не неся самостоятельной функциональности, он добавляет дополнительное свойство "строка подсказки" ко всем визуальным компонентам. Аналогично, компонент-панель TabieLayoutPanel добавляет всем компонентам, расположенным на этой панели свойства cell, column, Row, coiSpan и RowSpan (рис. 12.1). В этом разделе я хочу рассказать, каким образом создаются такие свойства.

Компоненты, добавляющие свойства ко всем компонентам одновременно, называются компонентами-расширителями (component extender), а сами свойства я буду для простоты называть глобальными свойствами. Добавить их позволяет специальный интерфейс IExtenderProvider. При добавлении на форму компонента, реализующего этот интерфейс, Visual Studio автоматически добавляет глобальные свойства ко всем компонентам (вообще говоря, не ко всем подряд, но об этом позже).

Компонент, описывающий глобальные свойства, должен подчиняться трем правилам: реализовывать интерфейс IExtenderProvider, иметь один или несколько атрибутов Provide Property и, конечно, определять сами свойства.

Рис. 12.1. Свойства Cell, Column, ColumnSpan, Row и RowSpan добавляются ко всем компонентам, расположенным на TableLayoutPanel

Атрибут provideproperty определяет имя глобального свойства. Он имеет два конструктора:

public ProvidePropertyAttribute(

string propertyName, string receiverTypeName); public ProvidePropertyAttribute(

string propertyName, Type receiver Type);

Первый параметр определяет имя свойства, а второй— тип компонентов, к которым должно быть добавлено это свойство (может быть указан либо тип, либо его имя). Один компонент может определять один или несколько глобальных свойств. Атрибут определяет только имя свойства, а само свойство должно быть описано с помощью методов gег<имя_свойства> и set<имя_свойства>. Эти методы должны иметь специальное имя и формат. Например, если атрибут задает свойство Title следующим образом: [ProvideProperty("Title", typeof(Control))j В этом случае должны быть описаны два метода:

public string GetTitle(Control parent) {

return ……… ;

}

public void SetTitle(Control parent, string value) {

Параметр parent передает компонент, к которому применяется свойство.

К методу Get можно добавлять дополнительные атрибуты, как для обычного свойства:

[Description("Дополнительный заголовок")]

[Category("Глобальные свойства")]

[DefaultValue("")]

[DisplayName("GiobalTitle")]

public string GetTitle(Control parent)

}

return title;

]

По умолчанию имя глобального свойства имеет вид:

<имя_ свойства> on <имя__компонента >

Например, Tooltip on Tooltipl. Но С помощью атрибута DisplayName можно задать любое другое имя.

Интерфейс lExtenderProvider имеет всего один метод:

bool CanExtend(Object extendee)

Метод должен возвращать true, если дополнительные свойства применимы для компонента, заданного параметром extencfee. Таким образом, этот метод является фильтром, определяющим, к каким типам компонентов применимы данные глобальные свойства. Например, определить добавление свойства только к кнопкам можно так:

public bool CanExtend(object extendee) {

return extendee is Button;

}

Компонент, реализующий добавление глобального свойства Extender index, показан в листинге 12.1. Те же действия можно выполнить с помощью сервиса I Extender Provide г servi се, который позволяет добавить глобальное свойство в список "расширяющих" модулей (листинг 12.2). В принципе, кроме использования сервиса, этот код ничем не отличается от того, что я описал выше. Но, т. к. здесь используется дизайнер, то глобальное свойство будет доступно только в режиме разработки, что иногда тоже бывает полезно.

Листинг 12 1 Добавление глобального свойства с помощью сервиса

IExtendarProv3.derServa.ue (вариант 1)

using System.ComponentModel; using System.ComponentModel.Design; using System.Windows.Forms; using System.Windows.Forms.Design;

namespace ExtenderServiceExample

{

// Реализация IExtenderprovider

{ProvidepropertyAttribute("Extenderlndex", typeof(IComponent))]

public class ComponentExtender : Component/ IExtenderProvider {

// Значение свойства public int index = 0;

public CcraponentExtender {)

{

}

public bool CanExtend(object extendee)

{

// разрешаем добавлять свойство к любым компонентам return true;

}

// Метод Get

[DisplayName{"Extenderlndex")]

public int GetExtenderlndex(IComponent component)

{

return index;

}

// Метод Set

public void SetExtenderlndex(IComponent component/ int index)

{

this.index = index;

}

}

}

Листинг 12 2 Добавление глобального свойства с помощью сервиса IExtancjerProviderServLOs (вариант 2)

using System.ComponentModel; using System.ComponentModel.Design; using System.Windows.Forms; using System.Windows.Forms.Design;

namespace ExtenderServiceExample {

// Компонент, связанный с дизайнером ExtenderServiceDesigner [DesignerAttribute(typeof(ExtenderServiceDesigner))] public class ExtenderServiceTestControl : Component

1

public ExtenderServiceTestControl() {

}

}

// Дизайнер, добавляющий глобальное свойство ExtenderIndex, // описанное в классе ComponentExtender, // с помощью сервиса IExtenderProviderService. public class ExtenderServiceDesigner ; ComponentDesigner t

// Ссыпка на сервис

IExtenderProviderService extenderServiceReference; // Ссыпка на ComponentExtender private ComponentExtender extender;

public ExtenderServiceDesigner() {

}

// Инициализация

public override void Initialize(IComponent component) {

base.Initialize(component);

// Получаем интерфейс IExtenderProviderService IExtenderProviderService extenderService = (IExtenderProviderService) component.Site.GetService(

typeof{IExtenderProviderService}};

if {extenderService != null) {

// Создаем ComponentExtender и добавляем // его в список "расширителей"

extender = new ComponentExtender(); extenderService.AddExtenderProvider(extender); extenderServiceReference = extenderService;

}

else

MessageBox.Show

("He удалось получить IExtenderProviderService.");

}

protected override void Dispose(bool disposing)

{

// Удаляем глобальное свойство if (extenderServiceReference != null)

{

extenderServiceReference.RemoveExtenderProvider(extender); extenderServiceReference = null;

}

}

}

// Реализация IExtenderProvider

[ProvidePropertyAttribute("Extenderlndex", typeof(IComponent))]

public class ComponentExtender : IExtenderProvider {

// Значение свойства public int index = 0;

public ComponentExtender() {

}

public bool CanExtend(object extendee) {

// Разрешаем добавлять свойство к любым компонентам return true;

}

// Метод Get

public int GetExtenderlndex(IComponent component}

{

return index;

// Метод Set

public void SetExtenderlndex(IComponent component, int index)

[

this.index = index;

}

}

}

Итак, посмотрим, что у нас получилось. Глобальное свойство действительно появилось в списке свойств всех компонентов, но есть одна большая "недоработка" — наше свойство имеет единственное значение для всех компонентов. Значит, нужно каким-то образом хранить таблицу значений нашего свойства для каждого компонента, к которому добавляется это свойство. Такую таблицу мы реализуем с помощью класса Hashtabie. В методе set мы будем сохранять в нее значения, а в методе Get— извлекать. Новая реализация класса ComponentExtender показана в листинге 12.3. Теперь, каждый компонент, к которому добавлено глобальное свойство, имеет свое значение этого свойства.

Листинг 12 3 Реализация таблицы свойств для lExter.deri>i:ovi.clurServ3.ce

// Реализация IExtenderProvider

[ProvidePropertyAttribute("Extenderlndex", typeof(IComponent))] public class ComponentExtender : IExtenderProvider

}

// Таблица значений свойства Index private Hashtabie properties;

public Component Ext ende r() {

properties = new Hashtable();

}

public bool CanExtend(object extendee) {

// Разрешаем добавлять только к кнопкам return extendee is Button;

}

// Метод Get

public int GetExtenderIndex(IComponent component) {

// Если компонент уже есть в таблице, // возвращаем сохраненное значение

if (properties.Contains(component))

}

return (int)properties[component];

}

else (

// Компонента нет — возвращаем значение по умолчанию return 0;

}

}

// Метод Set

public void SetExtenderlndex(IComponent component, int index) {

// Если компонент уже есть в таблице, // обновляем сохраненное значение if (properties.Contains(component))

{

properties[component] = index;

}

else

// Компонента нет – добавляем его в таблицу

{

properties[component] = index;

1

}

I

Но добавление глобального свойства— это только половина дела, ведь компонент не умеет сам использовать это свойство. Дальнейшие действия зависят от того, каким образом предполагается использовать новое свойство. Если, например, нужен обработчик события, такой как Paint, то лучший способ внедрения своего обработчика— это использование интерфейса iSupportinitialize (см. разд. 4.6). Реализацию метода начала инициализации можно оставить пустой, а в методе завершения инициализации создавать свой обработчик, например, Paint:

public void Endlnit{} {

foreach {DictionaryEntry de in properties}

{

Button button = de.Key as Button;

if (button != null) {

button.Paint += new PaintEventHandler(Component?xtender_Paint);

}

}

}

Как видно из кода, мы пробегаем по всей таблице компонентов, к которым добавлено глобальное свойство. Для каждого компонента регистрируется обработчик события Paint. Кроме того, при изменении свойства (метод set) нужно перерисовать компонент. Полный код реализации глобального свойства н обработчика Paint показан в листинге 12.4.

Листинг 12 4 Использование isupportinitialize для установки обработчика Paint

using System;

using System.Collections;

us ing Sys tem.ComponentMode1;

using System.ComponentModel.Design;

using System.Windows. Forms;

using System.Windows.Forms.Design;

using System.Drawing;

namespace ExtenderServiceExample

{

[ProvidePropertyAttribute("Extenderlndex", typeof(IComponent))] public class ComponentExtender :

Component, IExtenderProvider, iSupportlnitialize

{

// Таблица значений свойства Index private Hashtable properties;

public ComponentExtender(}

{

properties = new Hashtable(};

}

public bool CanExtend(object extendee}

{

// Разрешаем добавлять только к кнопкам return extendee is Button;

// Метод Get

public int GetExtenderlndex(IComponent component) {

// Если компонент уже есть в таблице, // возвращаем сохраненное значение if (properties.Contains(component})

1

return (int}properties[component];

}

else

{

// Компонента нет — возвращаем значение по умолчанию return 0;

}

}

void ComponentExtender_Paint(object sender, PaintEventArgs e)

{

if (properties.Contains(sender})

1

int index =* {int}properties[sender];

e.Graphics.Drawstring(index.ToString(}, Control.DefaultFont, SystemBrushes.ControlText, 5, 5);

}

}

// Метод Set

public void SetExtenderlndex(IComponent component, int index)

{

// Если компонент уже есть в таблице, // обновляем сохраненное значение

if (properties.Contains(component})

{

properties[component] = index;

}

else

// Компонента нет — добавляем его в таблицу

{

// Регистрируем обработчик properties[component] = index;

// Перерисовать компонент (component as Button).Invalidate();

}

public void BeginlnitO

( }

public void EndlnitO (

// Регистрируем обработчик OnPaint

// для всех компонентов, имеющих наше глобальное свойство

foreach (DictionaryEntry de in properties)

[

Button button = de.Key as Button;

if (button != null) (

button.Paint += new PaintEventHandler( Component?xtender_Paint);

}

}

}

}

}

Вообще говоря, можно использовать не только внедрение н перехват свойств, НО И интерфейсы самого компонента CoirponentExtender, к которому точно так же, как н к обычному компоненту, можно добавлять контекстное меню, смарт-тегн н т. п.

Теперь перейдем к следующему вопросу. Посмотрите на код, нз листинга 12.3:

public void SetExtenderlndex(IComponent component, int index)

{

properties[component] = index;

}

При задании глобального свойства компонент и значение свойства сохраняются в таблице properties. Что произойдет, если одни из компонентов будет удален с формы? Компонент исчезнет, а ссылка на него в нашей таблице останется. Исправить эту проблему можно с помощью событий сервиса IComponentChangeService (см. разд. 7.4). Получать интерфейс этого сервиса лучше всего в перекрытом свойстве Site:

public override ISite Site

{

get {

return base.Site;

}

set {

base.Site = value; // Получаем интерфейс сервиса componentChangeService = GetService(

typeof(IComponentChangeService)) as IComponentChangeService; // Подписываемся на события добавления и удаления компонентов if (componentChangeService !— null) (

componentChangeService.ComponentAdded +- new ComponentEvent Handler (

componentChangeService_ComponentAdded); componentChangeService.ComponentRemoved += new ComponentEventHandler(

componentChangeService_ComponentRemoved);

}

}

}

В обработчике удаления компонента можно удалять ссылку на него из нашей таблицы:

void componentChangeServiсe_ComponentRemoved(

object sender, ComponentEventArgs e)

{

properties.Remove(e.Component);

1

В обработчике добавления компонента на форму не обязательно выполнять какие-либо действия. Его реализация зависит от функциональности компонента-расширителя. Например, в нашем случае не требуется никакой дополнительной функциональности:

void componentChangeService_ComponentAdded(

object sender, ComponentEventArgs e)

{

// Пока никаких действий не нужно

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

Листинг 12 5 Обраоожл добавления и удлюнии компонентой га компоненто-расширигеле

using   System;

using   System.Collections;

using   System.ComponentModel;

using System. ComponentModel.Design;

using System.Windows.Forms;

using System.Windows.Forms.Design;

using System.Drawing;

namespace ExtenderServiceExample

t

[ProvidePropertyAttribute("Extenderlndex", typeof(IComponent))] public class ComponentExtender :

Component, IExtenderProvider, ISupportInitialize

{

// Таблица значений свойства Index private Hashtable properties;

IComponentChangeService componentChangeService;

public ComponentExtender() (

properties — new Hashtable();

}

public override ISite Site (

get

{

return base.Site;

}

set

{

base.Site =- value;

componentChangeService =

GetService(typeof(IComponentChangeService)) as IComponentChangeService;

if (componentChangeService != null)

{

componentChangeService. ComponentAd.de d += new ComponentEventHandler(

componentChangeService_ComponentAdded); componentChangeService.ComponentRemoved += new ComponentEventHandler(

componentChangeService_ComponentRemoved);

}

}

I

// Обработчик удаления компонента void componentChangeSe rvice__ComponentRemoved ( object sender. ComponentEventArgs e)

{

properties.Remove(e.Component);

}

// Обработчик добавления компонента void componentChangeService_ComponentAdded( obj ect sender, ComponentEventArgs e)

{

// Пока ничего не делаем

}

I

1

Последний вопрос, который я хотел бы обсудить в этом разделе, — это сериализация глобальных свойств. Действительно, ведь реально эти свойства "не существуют", и, соответственно, сам компонент сохранить их не может, Зато за него это делает создатель свойства. Код сериализации выглядит так: this.componentExtenderl.SetExtenderlndex(компонент, значение);

Например, для нашего компонента, добавляющего глобальное свойство цело го типа, этот код будет таким:

this.componentExtenderl.SetExtenderlndex(this.button2, 1);

В качестве еще одного примера в разд. 12.5 я покажу, как реализовать свой компонент всплывающей подсказки BaiioonTooiTip, добавляющей свойство BaiioonText ко всем визуальным компонентам (см. листинг 12.12).

Литература:

Агуров П. В. C#. Разработка компонентов в MS Visual Studio 2005/2008. – СПб.: БХВ-Петербург, 2008. — 480 е.: ил.

По теме:

  • Комментарии