Главная » C#, Компоненты » Смарт-теги

0

Одним из интересных новшеств Visual Studio являются смарт-те?их (smart tags), позволяющие вынести в специальный диалог наиболее важные свойства компонента. Например, смарт-тег для компонента ListBox открывает диалог редактирования элементов (рис. 6.3). Сам тег выглядит как небольшой значок со стрелочкой.

Рис. 6.3. Тег для компонента ListBox

В окно смарт-тега можно вынести редактирование свойств и выполнение методов. Редактирование свойств производится с помощью элементов управления, соответствующих типу свойства (например, для типа bool используется флажок), а для выполнения методов — ссылки.

Для создания собственного смарт-тега нужно создать список элементов тега, являющийся наследником класса DesignerActionList, а в перекрытом свойстве ActionLists дизайнера вернуть экземпляр этого списка. Согласно этой инструкции попробуем создать свой тег ДЛЯ нашего компонента GradientLabel из главы 5.

Для начала создадим класс GradientLabeiActionList. являющийся наследником класса DesignerActionList (ЛИСТИНГ 6.1). В КОНСТруКТОре ЭТОГО свойства Я сохраняю ссылку на редактируемый компонент (переменная giabel). В диалог тега попадают все общедоступные (public) свойства и методы, значит, наш диалог будет иметь три элемента: свойство startColor. свойство EndColor и метод invertcolors о, который, по моей задумке, будет менять местами цвета начала и завершения заливки.

При создании свойств следует обратить внимание на существенное отличие метода, возвращающего значение, и метода установки значения. Если в ак- сессоре get нам достаточно вернуть текущее значение свойства компонента, то для аксессора set нужно обязательно использовать установку значения свойства через дескриптор свойства (см. разд. 4.5). Для этого используется метод GetPropertyByNameO, возвращающий дескриптор по имени свойства.

Листинг 6 1 Класс Gradient-Label Act д. onLi s t

using System;

using System.Collections.Generic; using System.Text;

using System.ComponentModel.Design; using System.ComponentModel; using System.Drawing;

namespace MyControl

{

class GradientLabelActionList : DesignerActionList

{

GradientLabel glabel; // Конструктор

public GradientLabelActionList{IComponent component) : base(component)

{

// Сохраняем ссылку на редактируемый компонент glabel = component as GradientLabel;

}

//В диалог тега вынесем два свойства и одну команду

public Color StartColor <

get { return this.glabel.StartColor; )

set { GetPropertyByName{"StartColor").SetValue{glabel, value); }

}

public Color EndColor <

get { return this.glabel.EndColor; )

set { GetPropertyByName{"EndColor").SetValue(glabel, value); }

f

public void invertColors()

{

Color tmp = glabel.StartColor;

StartColor = glabel.EndColor; EndColor = trap;

}

// Возвращает дескриптор свойства по имени

private PropertyDescriptor GetPropertyByName(String propName) <

PropertyDescriptor prop =

TypeDescriptor.GetProperties (glabel) [propNaine] ;

if (prop == null) {

throw new ArgumentException{"Свойство не существует", propName);

f

return prop;

f

}

)

Теперь осталось привязать список элементов тега к компоненту. Для этого нам нужно перекрыть свойство ActionLists дизайнера нашего компонента (листинг 6.2).

Листинг 6 2 Привязка АсгюпЬтаг к компоненту

using System;

using System.Collections;

using System.Collections.Generic;

using System. Text;

using System.Windows. Forms. Design;

using System. Drawing;

using System. ComponentModel;

using System.Windows.Forms;

using System.ComponentModel.Design;

namespace MyControl

}

class GradientLabelDesigner : ControlDesigner

{

private DesignerActionListCollection actionLists;

public override DesignerActionListCollection ActionLists {

get

{

// Если еще не создавали actionList

if (actionLists == null) {

// Создаем ActionList

actionLists = now DesignerActionListCollection (); // Добавляем тег acti onLists.Add(

new GradientLabe]ActionList(this.Component));

}

return actionLists;

}

)

}

}

Компилируем… и у нашего компонента появился гсг, открывающий диалог редактирования цветов градиентной заливки (рис. 6.4).

Рис. 6.4. Тег компонента GradientLabel

Остается решить несколько небольших проблем:

?        при вызове пункта меню InvertColors меняются цвета компонента, но не обновляется окно самого тега;

?        пункт меню InvertColors имеет имя в соответствии с именем метода, и слова написаны слитно, что не очень красиво. Было бы лучше назвать пункт Invert Colors или, даже, перевести названия на русский язык. То же самое относится и к именам свойств:

?        хотелось бы расположить пункты в диалоге в соответствии с логикой заливки: сначала редактирование StartColor. затем EndColor и под ними ссылку па метод InvertColors f) .

Для решения первой проблемы нам нужно получить доступ к сервису тегов (с сервисами нам еще предстоит разбираться в главе 7, пока же примите как должное, что они есть), который и позволит нам обновлять диалоговое окно. Для этого в конструкторе класса GradientLabeiActionList сохраним ссылку на сервис DesignerAotionUIServi.ee:

class GradientLabelActionList : DesignerActionList (

DesignerActionUIService designerActionUIService; // Конструктор

public GradientLabeiActionList(IComponent component) : base(component)

{

// Сохраняем ссылку на сервис

designerActionUIService = GetService(typeof(DesignerActionUIService)) as DesignerActionUIService;

}

Теперь, при вызове метода invertcoiors (), нужно вызвать обновление диалогового окна тега. Это делается с Помощью метода Refresh ():

public void InvertColors()

{

it Меняем цвета местами Color tmp glabel.StartColor; StartColor = glabel.EndColor; EndColor = tmp; // Обновляем диалог тега

designerActionUIService.Refresh(glabel);

}

Полный код показан в листинге 6.3.

г Листинг 6.3.KnaecGraciLentLabeIActionList с обновлением диалога [ при смене свойств

using System;

using System. Col lections. Generic; using System.Text;

using System.ComponentModel.Design; using System.ComponentModel; using System. Drawing;

namespace MyControl

{

class GradientLabelActionList : DesignerActionList

{

GradientLabel glabel;

DesignerActionUIService designerActionUIService; // Конструктор

public GradientLabelActionList{IComponent component) : base(component)

11 Сохраняем ссылку на редактируемый компонент glabel = component as GradientLabel; // Сохраняем ссылку на ActionList-сервис designerActionUIService =

GetService(typeof(DesignerActionUIService)) as DesignerActionUIService;

}

// В диалог тега вынесем два свойства и одну команду

public Color StartColor <

get { return this.glabel.StartColor; }

set { GetPropertyByName("StartColor").SetValue(glabel, value); )

}

public Color EndColor {

get { return this.glabel.EndColor? }

set { GetPropertyByName("EndColor").SetValue{glabel, value); }

}

public void InvertColors() {

// Меняем цвета местами Color tmp – glabel.StartColor; StartColor = glabel.EndColor; EndColor = tmp; // Обновляем диалог тега

designerActionUIService.Refresh(glabel);

}

// Возвращаем дескриптор свойства no имени

private PropertyDescriptor GetPropertyByName(String propName) {

PropertyDescriptor prop =

TypeDescriptor.GetProperties(glabel)[propName];

if (prop == null) {

throw new ArgumentException("Свойство не существует", propName);

}

return prop;

}

}

}

Для решения двух других проблем нам нужно научиться каким-то образом вмешиваться в порядок формирования пунктов меню диалога тега. Сделать это можно, перекрыв метод GetsortedActionitemsO, возвращающий коллекцию элементов диалогового окна. Более того, этот метод позволяет не только управлять порядком элементов, но формировать более информативный и красивый диалог.

Коллекция Элементов Представляется классом DesignerActionltemCollection. Каждый элемент этой коллекции является одной из реализаций абстрактного класса DesignerActionltem:

?  DesignerActionMethoditem— элемент представления метода (ссылка для вызова метода);

?  DesignerActionPropertyitem— элемент представления свойства (в зависимости от типа свойства может быть виден как выпадающий список, текстовое поле или переключатель);

?  Des i gne г Ac t i опт ex 11 tem — элемент для вывода статического текста;

?  DesignerActionHeaderltem’——- элемент ДЛЯ ВЫВОДЭ заголовка ГруППЫ.

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

DesignerActionHeaderlteni (имя группы для отображения, категория)

Код для создания групп выглядит следующим образом:

items.Add(new DesignerActionHeaderltemC’CBoiicTBa" ,        "Properties") ) ;

items.Add(new DesignerActionHeaderltem("Методы" ,           "Methods"));

items.Add{new DesignerActionHeaderltem("Информация",        "Info"))?

Конструктор элемента для редактирования свойства, метода и вывода статического текста выглядит так:

Desigr.erAct lonPropertyl torn {имя съойства, отображаемое имя,

к а 1 егор! /я, оппса Н14е) DosignerAct ionMothodltem (ссылка иа список, имя метода,

отображаемое имя, категория, опиелгпе) Designer Act i cnTextl t.ern ( текст, категория)

Полный код метода CetSortedActionitems {) показан в листинге 6.4, а результат его работы — на рис. 6.5.

Кстати, с помошью этого метода можно также формировать список элементов динамически, в зависимости от текущих значений свойств компонента. Например, если мы хотим отображать метод перестановки цветов только r том случае, когда цвета не равны, то можем просто добавить соответствующей проверку в метод GeiSortedActaonitems о, а при каждом изменении свойств выполнять обновление диалога. Соответствующий код показан в листинге 6.5.

листинг 6.4. Метод GetSortedActxonlte^

public override Оеь igner.Actiop.T terr.Ccliect j on GetSortedActionltems () t

l^esigne rActionl teinCol lect l on > terns -

new DcsignerActienllemCollect ion();

/ ‘ Добавляем три группы

i Lerr? . Add (new Desi gnerAetionHeaderltem { "Свойства", "Properties") ) ; i terns .Ada (new DesignerActiDnlleaaerltem {"Метода" , "Methods") ) ; leras . Add {new DesignerActionHeaderlt.em ("Информация" , "[nfo") ) ;

/ / ,l;oudHj[H6;K cDO’.’ici’Bd в категорию Proper t xes i^cTs.Addfriow L’e s i gr.erAct i on PropcrtyItem( "StartColor", "Начальный цвот", "Ptrooertios", "Цвет начала градиентной заливки")); з terns .Add (new DeaiqnyrAccionPropcr t«/lten( "F ndColor" , "Конечный зет", "Properties", "Цвет завершения градиентной заливки"));

/! -Тоб^влчем метод в категорию Met-.r-.ods

items. Add ;nev; DebignerAct ionMet hcdltem (this, "Invert-Colors", "Перевернуть цвета", "Methods",

"Поменчть начальный и конечным цвета местами"));

/ ‘ Добавляем статический текст sLгi ng iTlij = s-.c:ng. Fona=t (

" Га змее > :1} x| 1 } ", glabel .Width, gl abel .Height) ;

items.Add(new DesignerActionTextItem(info, "Info")); return items;

Рис. 6.6. Новый тег компонента ‘irridientLabr 1 (с группировкой)

Гистинг 6 5 Динамическое формирование списка элементов диалога

System;

using System.Collections .Generic; using System.Text?

using System.ComponentModei.Design; using System.ComponentModei; using System. Drawing;

namespace MyControl (

class GradientLabelActionList : DesignerActionList

{

// В диалог тега вынесем два свойства и одну команду public Color StartColor

f

get { return this.glabel.StartColor; }

set {

GetPropertyByName{"StartColor").SetValue(glabel, value) // Обновляем диалог тега

designerActionUIService.Refresh(glabel);

public Color EndColor

{

get { return this.glabel.EndColor; }

set {

GetPropertyByName("EndColor").SetValue(glabel, value); // Обновляем диалог тега designerActionUIService.Refresh(glabel);

}

}

public void InvertColors()

{

// Меняем цвета местами Color tmp = glabel.StartColor; StartColor = glabel.EndColor; EndColor = tmp;

}

public override DesignerActionltemCollection GetSortedActionltems ()

{

DesignerActionltemCollection items =•

new DesignerActionltemCollection(};

// Добавляем две группы

items.Add(new DesignerActionHeaderItem("CBoftcTBa", "Properties"));

// Добавляем свойства в категорию Properties items.Add(new DesignerActionPropertyItem("StartColor", "Начальный цвет", "Properties", "Цвет начала градиентной заливки")); items.Add(new DesignerActionPropertyItem("EndColor", "Конечный цвет", "Properties", "Цвет завершения градиентной заливки"));

// Добавляем метод InvertColors, только если цвета не равны

if (StartColor != EndColor) {

items .Add (new DesignerActionHeaderItem("MeTO№", "Methods") ) ;

// Добавляем метод в категорию Methods items.Add(new DesignerActionMethodltem( this, "InvertColors",

"Перевернуть цвета", "Methods",

"Поменять начальный и конечный цвета местами"));

}

// Добавляем статический текст

items.Add(new DesignerActionHeaderltem("I4H<?opMa4Hfl", "Info") ) ; string info = string.Format("Размер (0}х{1}",

glabel.Width, glabel.Height); items.Add(new DesignerActionTextItem(info, "info"));

return items;

}

}

)

Теперь еще раз посмотрим на код формирования ActionList: // Создаем ActionList

actionLists = new DesignerActionListCollection(); // Добавляем тег

actionLists.Add{new GradientLabeiActionList(this.Component))t

Как видно из этого фрагмента кода, ActionList является коллекцией, каждый элемент которой представляет собой объект типа DesignerActionList. И я задаю себе неожиданный вопрос: а что будет, если мы добавим еще один элемент в эту коллекцию? Вот так:

// Создаем ActionList

actionLists = new DesignerActionListCollectionO ; // Добавляем два тега

actionLists.Add(new GradientLabeiActionList(this.Component)); actionLists.Add(new GradientLabelAdditionalActionList(this.Component));

В этом случае Visual Studio не будет показывать два тега, как могло бы показаться. На самом деле оба списка будут объединены в один и показаны последовательно, поэтому особой разницы в методе формирования списка параметров нет. Можно делать один большой список, а можно разбить список параметров на несколько тегов. Пожалуй, единственное различие возникнет при необходимости обновить отображение некоторого набора параметров, а не всех параметров сразу. Сделать это можно, разбив список параметров на наборы ActionList и обновляя только те части, которые нужно. В следующем разделе я расскажу про контекстное меню и еще одну интересную возможность класса смарт-тегов.

Литература:

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

По теме:

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