Компоненты - это программные единицы, которые функционируют в двух режимах: проектирования (design-time) и выполнения (run-time). Готовый компонент представляется классом, который имеет три вида интерфейса:
Интерфейс времени проектирования сказывается только на удобствах программистов, использующих компонент, и не влияет на поведение компонента во время выполнения. К тому же, разработка интерфейса времени проектирования требует дополнительных усилий, сравнимых, а иногда и превышающих затраты на разработку функциональности времени выполнения. Но если программист специализируется не на разработке готовых приложений, а на разработке готовых компонентов, то интерфейсу проектирования он должен уделить должное внимание.
Библиотечные компоненты тщательно разработаны и имеют высокоразвитые
интерфейсы для любых видов использования. Их можно применять
как на этапе проектирования приложения, помещая на форму и
используя сервисы среды разработки, так и создавать динамически
как экземпляры класса во время выполнения. Библиотечные компоненты
являются хорошим примером сбалансированного программирования
при разработке
Мы хотим создать компонент GradientLabel как
расширение библиотечной текстовой метки Label,
в которой цвет фона текстового блока имел бы градиентную
заливку. На этом примере мы познакомимся с основами техники
создания
Создадим решение и настроим оболочку для отладки компонента в режиме разработки.
Командой File/New/Project вызовите мастер создания решения и заполните
его как показано на экранном снимке
Командой File/Add/New Project опять вызовите мастер создания
проекта и заполните его так
После выполнения этих шагов мы получим каталог MySolution размещения решения, в котором будут созданы два подкаталога проектов с именами Test и MyControl.
В проводнике
решений вызовите контекстное меню для узла MyControl проекта
компонента и выполните команду Properties,
чтобы вызвать мастер настройки свойств проекта
Откройте
вкладку Debug, установите переключатель Start
Action в значение Start external program и выберите в качестве
внешней программы саму среду devenv.exe ( Development
Environment - среда развития). Этот файл, скорее всего, будет
иметь 
В проводнике
решений удалите автоматически добавленный при создании проекта MyControl файл UserControl1.cs
В проводнике
решений вызовите контекстное меню для узла MyControl и командой Add/Component добавьте новый файл GradientLabel.cs
Переведите
файл GradientLabel.cs в режим View
Code и замените в классе GradientLabel базовый
класс Component на производный от него компонент Label -
ближайший по функциональности библиотечный предок нашего
будущего
Добавьте
в заголовок файла подключение дополнительных пространств
именusing System.Drawing;using System.Drawing.Drawing2D;using System.Windows.Forms;После выполненных действий заготовка файла GradientLabel.cs должна стать такой
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
/////////////////////////////////////////////////////////////////
// Блок кода №1
/////////////////////////////////////////////////////////////////
namespace MyControl
{
public partial class GradientLabel : Label
{
public GradientLabel()
{
InitializeComponent();
}
public GradientLabel(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
}
Нам нужно добавить
к классу GradientLabel:
EventHandler ;Control
метод OnPaint() (override ,
чтобы активизировать подсказчик кода IntelliSense ).Для этого:
Поместите
в самый GradientLabel/////////////////////////////////////////////////////////////////
// Блок кода №2 продолжения класса GradientLabel
/////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabel
{
// Закрытые поля
private Color startColor = Color.Yellow;
private Color endColor = Color.Red;
public Color StartColor // Общедоступное свойство
{
get { return startColor; }
set
{
// Меняем значение
startColor = value;
// Инициируем событие
OnGradientChange(EventArgs.Empty);
}
}
public Color EndColor // Общедоступное свойство
{
get { return endColor; }
set
{
// Меняем значение
endColor = value;
// Инициируем событие
OnGradientChange(EventArgs.Empty);
}
}
// Объявление события изменения свойств цвета градиента
public event EventHandler GradientChange;
// Функция диспетчеризации события GradientChange
protected virtual void OnGradientChange(EventArgs args)
{
// Если есть зарегистрированные обработчики,
// то инициируем событие и вызываем обработчики
if (GradientChange != null)
GradientChange(this, args);
}
// Переопределение метода OnPaint()
protected override void OnPaint(PaintEventArgs e)
{
// Контекст графического устройства
Graphics gr = e.Graphics;
// Создаем кисть и заливаем фон текстового блока
float angle = 10.0F;
Brush brush = new LinearGradientBrush(
this.ClientRectangle, startColor, endColor, angle);
gr.FillRectangle(brush, this.ClientRectangle);
// Сразу освобождаем кисть как ограниченный ресурс
brush.Dispose();
// Вызываем после заливки, иначе закрашивается текст
base.OnPaint(e);
}
}
}
В проводнике
решений вызовите контекстное меню для узла MyControl и
выполните команду Rebuild (или Build ),
чтобы откомпилировать компонент GradientLabel
Откройте
в проекте Test файл Form1.cs в режиме View
Designer и поместите
на форму из панели инструментов Toolbox компонент GradientLabel,
который там появится после компиляцииВ результате компонент на форме должен выглядеть так

После того, как компонент GradientLabel мы
поместили на форму, то вправе ожидать от него такое поведение
на этапе проектирования, которое присуще библиотечным компонентам.
Настройкой такого поведения мы сейчас и займемся.
Выделите
на форме экземпляр компонента gradientLabel1 и в панели Properties установите режим представления свойств по категориям, щелкнув
на соответствующей кнопке в верхней части панели
К поведению (во время проектирования) полученного на данном этапе компонента можно сделать следующие замечания:
StartColor и EndColor отображаются
в стандартной категории Misc, а не в желаемой
нами категорииInitializeComponent() класса Form1 родительской
формыToolbox не имеет своего индивидуального
значка (пиктограммы)Рядом последующих действий устраним эти замечания.
Перерисовку компонента при изменении значений свойств можно
обеспечить, если в функцию диспетчеризации OnGradientChange() события GradientChange,
которая вызывается в аксессоре set редакции свойств, вставить вызов унаследованного
метода разрушения окна компонента .
Добавьте
в метод диспетчеризации события компонента блока кода №2
следующую инструкцию// Функция диспетчеризации события GradientChange
protected virtual void OnGradientChange(EventArgs args)
{
// Перерисовываем компонент
this.Invalidate();
// Если есть зарегистрированные обработчики,
// то инициируем событие и вызываем обработчики
if (GradientChange != null)
GradientChange(this, args);
}
Остальные перечисленные замечания по поведению компонента
в панели Properties устраняются с помощью
атрибутов.
Атрибут, это дополнительный механизм уточнения
поведения класса или его члена, относящийся к современным технологиям
программирования. Атрибуты - это типы, порожденные абстрактным
классом . Имена всех библиотечных
атрибутов заканчиваются словом ,
но для простоты язык C# позволяет сокращать запись и указывать
название атрибута без этого постфикса (
Атрибут прикрепляется к объекту с помощью
описания конструктора в квадратных скобках, но для атрибутов
уровня сборки или модуля прикрепление выполняется с помощью
ключевых слов и соответственно.
К объекту могут прикрепляться несколько атрибутов в индивидуальных квадратных скобках или списком в общих квадратных скобках. Например, инструкция прикрепления нескольких атрибутов к классу вот такая
// Всплывающая подсказка компонента в панели Toolbox
[Description("Текстовая метка с градиентной заливкой фона")]
// Подключение пиктограммы компонента для панели Toolbox
[ToolboxBitmap(typeof(GradientLabel))]
// Подключение класса дизайнера к классу компонента
[Designer(typeof(GradientLabelDesigner))]
partial class GradientLabel
{
.........................................
}
равносильна такой инструкции
// Всплывающая подсказка компонента в панели Toolbox
[Description("Текстовая метка с градиентной заливкой фона"),
// Подключение пиктограммы компонента для панели Toolbox
ToolboxBitmap(typeof(GradientLabel)),
// Подключение класса дизайнера к классу компонента
Designer(typeof(GradientLabelDesigner))]
partial class GradientLabel
{
.........................................
}
Конструктор атрибута может принимать два вида параметров:
Различают следующие атрибуты:
В таблице приведены некоторые атрибуты, используемые самой средой проектирования. Они нам понадобятся для настройки поведения компонента в режиме проектирования.
| Класс атрибута Конструкторы атрибута Примеры прикрепления | Пояснения |
|---|---|
public DefaultPropertyAttribute(string name)
|
Указывает свойство по умолчанию. Это свойство будет активно в панели Properties при первом редактировании экземпляра компонента. Обычно таким атрибутом помечают часто изменяемые свойства компонента, например, для Label, TextBox, Button - это свойство Text. Прикрепляется к классу |
public DefaultEventAttribute(string name)
|
Указывает событие по умолчанию, для которого будет создан обработчик при двойном щелчке пользователя на экземпляре компонента, помещенного на форму в режиме View Designer. Прикрепляется к классу |
public DefaultValueAttribute(bool value)
public DefaultValueAttribute(byte value)
public DefaultValueAttribute(char value)
public DefaultValueAttribute(double value)
public DefaultValueAttribute(float value)
public DefaultValueAttribute(int value)
public DefaultValueAttribute(long value)
public DefaultValueAttribute(object value)
public DefaultValueAttribute(short value)
public DefaultValueAttribute(string value)
public DefaultValueAttribute(System.Type type, string value)
|
Указывает значение свойства по умолчанию. При наличии
такого атрибута, прикрепленного к свойству, редактор свойств
Properties отображает значение стилем |
public CategoryAttribute()
public CategoryAttribute(string category)
|
Создает категорию в панели свойств и помещает в нее прикрепленное свойство. Прикрепляется к свойству |
public DescriptionAttribute()
public DescriptionAttribute(string description)
|
Описание выделенного свойства или события, которое появится в нижней части панели свойств. Прикрепляется к свойству или событию при их описании в теле класса |
public BrowsableAttribute(bool browsable)
|
Отключает с аргументом false появление свойства или события в панели Properties. По умолчанию все публичные свойства и события класса компонента показываются в редакторе свойств. Прикрепляется к свойству или событию при их описании в теле класса |
public DisplayNameAttribute()
public DisplayNameAttribute(string displayName)
|
Задает представление, как имя свойства будет отображаться
в панели свойств. Иногда может понадобиться разделить пробелами
|
public PasswordPropertyTextAttribute()
public PasswordPropertyTextAttribute(bool password)
|
Устанавливает режим отображения значения свойства в панели Properties звездочками |
public ReadOnlyAttribute(bool isReadOnly)
|
true - свойство нельзя редактировать в панели Properties (в режиме проектирования) и оно представляется серым цветом. Для статических свойств такой механизм установлен по умолчанию |
public MergablePropertyAttribute(bool allowMerge)
|
При групповом выделении разнотипных компонентов, имеющих одинаковое имя свойства, запрещает для помеченного этим атрибутом свойства изменение "за компанию". По умолчанию true - разрешено групповое изменение значения любого общедоступного свойства |
public ParenthesizePropertyNameAttribute()
public ParenthesizePropertyNameAttribute(bool needParenthesis)
|
С параметром true указывает редактору свойств отображать имя свойства в круглых скобках |
public AttributeProviderAttribute(string typeName)
public AttributeProviderAttribute(string typeName, string
propertyName)
public AttributeProviderAttribute(System.Type type)
|
Присоединяет к прикрепленному типу атрибуты из другого типа |
public ToolboxItemAttribute(bool defaultType)
public ToolboxItemAttribute(string toolboxItemTypeName)
public ToolboxItemAttribute(System.Type toolboxItemType)
|
Значение true по умолчанию обязывает компонент появляться в панели Toolbox оболочки. Прикрепляется к классу |
public ToolboxBitmapAttribute(string imageFile)
public ToolboxBitmapAttribute(System.Type t)
public ToolboxBitmapAttribute(System.Type t, string name)
|
Загрузка пиктограммы компонента размером 16x16 px в формате
|
public DesignTimeVisibleAttribute()
public DesignTimeVisibleAttribute(bool visible)
|
Аргумент false указывает оболочке, что компонент не должен быть виден в режиме разработки |
Воспользуемся некоторыми из приведенных атрибутов для частичной настройки нашего компонента GradientLabel. Вспомним, что атрибуты прикрепляются перед объектом влияния по-отдельности в квадратных скобках каждый, либо списком в общих квадратных скобках.
Дополните
вторую часть класса компонента в файле GradientLabel.cs атрибутами,
выделенными в следующем листинге/////////////////////////////////////////////////////////////////
// Блок кода №2 продолжения класса GradientLabel
/////////////////////////////////////////////////////////////////
namespace MyControl
{
// Всплывающая подсказка компонента в панели Toolbox
[Description("Текстовая метка с градиентной заливкой фона")]
// Подключение пиктограммы компонента для панели Toolbox
[ToolboxBitmap(typeof(GradientLabel))]
partial class GradientLabel
{
// Закрытые поля
private Color startColor = Color.Yellow;
private Color endColor = Color.Red;
// Категория свойства в панели Properties
[Category("Gradient")]
// Пояснение для выделенного свойства
// в нижней части панели Properties
[Description("Цвет начала заливки")]
// Значение свойства по умолчанию
[DefaultValue(typeof(Color), "Yellow")]
// Представление имени свойства в панели Properties
// [DisplayName("Начало заливки")]
public Color StartColor // Общедоступное свойство
{
get { return startColor; }
set
{
// Меняем значение
startColor = value;
// Инициируем событие
OnGradientChange(EventArgs.Empty);
}
}
// Альтернативный синтаксис прикрепления атрибутов
[Category("Gradient"), Description("Цвет завершения заливки"),
DefaultValue(typeof(Color), "Red")]
// [DisplayName("End Color")]
public Color EndColor // Общедоступное свойство
{
get { return endColor; }
set
{
// Меняем значение
endColor = value;
// Инициируем событие
OnGradientChange(EventArgs.Empty);
}
}
// Пояснение для выделенного события
// в нижней части панели Properties
[Description("Событие изменения цвета\n"
+ "границ градиентной заливки")]
// Объявление события изменения свойств цвета градиента
public event EventHandler GradientChange;
// Функция диспетчеризации события GradientChange
protected virtual void OnGradientChange(EventArgs args)
{
// Перерисовываем компонент
this.Invalidate();
// Если есть зарегистрированные обработчики,
// то инициируем событие и вызываем обработчики
if (GradientChange != null)
GradientChange(this, args);
}
// Переопределение метода OnPaint()
protected override void OnPaint(PaintEventArgs e)
{
// Контекст графического устройства
Graphics gr = e.Graphics;
// Создаем кисть и заливаем фон текстового блока
float angle = 10.0F;
Brush brush = new LinearGradientBrush(
this.ClientRectangle, startColor, endColor, angle);
gr.FillRectangle(brush, this.ClientRectangle);
// Сразу освобождаем кисть как ограниченный ресурс
brush.Dispose();
// Вызываем после заливки, иначе закрашивается текст
base.OnPaint(e);
}
}
}
Мы добавили атрибут [ToolboxBitmap(typeof(GradientLabel))] уровня
класса для присоединения пиктограммы компонента на этапе проектирования.
Теперь нужно подготовить саму пиктограмму и настроить ее так,
чтобы она размещалась в сборке компонента. Использованная
Вызовите
в проводнике решений контекстное меню для узла MyControl компонента и добавьте к проекту новый рисунок командой Add/New Item с именем компонента GradientLabel.bmp
Оболочка откроет окно
При
выделенном шаблоне рисования установите в панели Properties размер
рисунка 16x16
Нарисуйте
пиктограмму компонента, например, такую
Сохраните
рисунок, закройте окно 
В панели
проводника решений вызовите контекстное меню для узла компонента MyControl и выполните команду Rebuild
Откройте
в проекте Test на редактирование в режиме View
Designer форму Form1.cs и убедитесь, что в дежурной вкладке MyControl Components появился компонент GradientLabel с пиктограммой по умолчанию
В панели Toolbox вызовите контекстное меню для компонента GradientLabel и
командой Choose Items откройте окно добавления
ссылок на динамические библиотеки с компонентами. Кнопкой Browse вызовите диалог 
После выполнения указанных действий в панели Toolbox будет представлен наш компонент с собственной пиктограммой и всплывающей подсказкой, которые мы прикрепили к нему ранее соответствующими атрибутами

Перенесите
компонент на форму Form1, выделите экземпляр
компонента GradientLabel и
убедитесь через панель Properties в наличие
добавленных соответствующими атрибутами настроекДополнительные возможности по редактированию компонентов в
режиме проектирования, в том числе
System.ComponentModel.Design
System.Windows.Forms.Design
Подключим к проекту MyControl компонента сборку и упомянутые пространства имен с классами дизайнеров.
В панели Solution Explorer вызовите контекстное
меню для узла References проекта MyControl, выполните команду Add Reference и выберите в списке сборку System.Design
Для того, чтобы четко разделить функциональность компонента на этапе проектирования и на этапе выполнения, будем размещать код этапа проектирования в отдельном файле и подключать его к компоненту одной инструкцией-атрибутом.
В панели
проводника решений Solution Explorer вызовите
контекстное меню для узла MyControl проекта
компонента и командой Add/Class добавьте к проекту компонента
новый файл с именем GradientLabelDesigner.cs
Модифицируйте
заготовку файла GradientLabelDesigner.cs следующим
образомusing System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
namespace MyControl
{
// Класс-дизайнер компонента
internal partial class GradientLabelDesigner : ControlDesigner
{
}
}
Созданный класс-дизайнер GradientLabelDesigner нужно
привязать к классу GradientLabel компонента
с помощью атрибута Designer.
Дополните
в файле GradientLabel.cs блок кода №2
класса компонента GradientLabel следующим
атрибутом привязки класса-дизайнера GradientLabelDesigner/////////////////////////////////////////////////////////////////
// Блок кода №2 продолжения класса GradientLabel
/////////////////////////////////////////////////////////////////
namespace MyControl
{
// Всплывающая подсказка компонента в панели Toolbox
[Description("Текстовая метка с градиентной заливкой фона")]
// Подключение пиктограммы компонента для панели Toolbox
[ToolboxBitmap(typeof(GradientLabel))]
// Подключение класса дизайнера к классу компонента
[Designer(typeof(GradientLabelDesigner))]
partial class GradientLabel
{
// Закрытые поля
private Color startColor = Color.Yellow;
private Color endColor = Color.Red;
..............................................
}
}
Теперь можно разрабатывать дополнительные возможности поведения
компонента на этапе проектирования, размещая соответствующий
код в классе дизайнера GradientLabelDesigner и
не смешивая интерфейсный код с функциональным кодом самого
компонента GradientLabel.

System.ComponentModel.Design.DesignerActionList
Для создания DesignerActionList.
Он должен содержать ссылки на включаемые в ControlDesigner, нужно
переопределить унаследованное виртуальное свойство ActionLists,
чтобы включить список редактируемых элементов в
Создадим GradientLabel.
Добавьте
в //*///////////////////////////////////////////////////////////////
// Блок кода №3. Создание списка выносимых в смарт-тег элементов компонента
//////////////////////////////////////////////////////////////////
namespace MyControl
{
// Пусть класс доступен коду только внутри сборки компонента
internal partial class GradientLabelActionList : DesignerActionList
{
// Объявили ссылку на объект компонента
GradientLabel gradientLabel;
// Конструктор
public GradientLabelActionList(IComponent component)
: base(component)
{
// Привели и сохранили ссылку на редактируемый компонент
gradientLabel = (GradientLabel)component;
}
// Вспомогательная функция реализации отражения
private PropertyDescriptor GetPropertyByName(String propName)
{
PropertyDescriptor prop =
TypeDescriptor.GetProperties(gradientLabel)[propName];
if (prop == null)
throw new ArgumentException("Свойство не существует", propName);
return prop;
}
// Элементы компонента, выносимые в диалог смарт-тега
public Color StartColor
{
get { return this.gradientLabel.StartColor; }
set
{
this.GetPropertyByName("StartColor").SetValue(gradientLabel, value);
}
}
public Color EndColor
{
get { return this.gradientLabel.EndColor; }
set
{
this.GetPropertyByName("EndColor").SetValue(gradientLabel, value);
}
}
public void InvertColors()
{
Color tmp = gradientLabel.StartColor;
this.StartColor = gradientLabel.EndColor;
this.EndColor = tmp;
}
}
}
//***************************************************************/
Добавьте
в //*///////////////////////////////////////////////////////////////
// Блок кода №4. Создание смарт-тега с заданным списком элементов
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
// Базовое поле
DesignerActionListCollection actionLists;
// Переопределение виртуального свойства ActionLists, унаследованного
// классом System.Windows.Forms.Design.ControlDesigner от
// класса System.ComponentModel.Design.ComponentDesigner
public override DesignerActionListCollection ActionLists
{
get
{
// Если еще не создавали смарт-тег actionLists
if (actionLists == null)
{
// Создали окно смарт-тега
actionLists = new DesignerActionListCollection();
// Создали список элементов окна
GradientLabelActionList gradientLabelActionList =
new GradientLabelActionList(this.Component);
// Добавили список элементов в коллекцию окна
actionLists.Add(gradientLabelActionList);
}
return actionLists;
}
}
}
}
//***************************************************************/
Откомпилируйте
проект компонента и убедитесь, что у компонента появился

Обратите внимание на то, что код интерфейсной части компонента работает в режиме проектирования и нам не нужно запускать приложение с компонентом на выполнение. Оболочка сама выполняет необходимые действия на этапе проектирования в соответствии с заложенной нами логикой поведения компонента на этом этапе.
К созданному
InvertColors успешно
меняются цвета компонента, но цветовые поля самого
Для обновления диалогового окна GradientLabelActionList формирования
списка элементов сохранить ссылку на сервис System.ComponentModel.Design.DesignerActionUIService.
Затем в нашем методе InvertColors() или в
аксессорах set{ } свойств StartColor и EndColor вызвать
метод этого сервиса.
Дополните
класс GradientLabelActionList компонента
следующим кодом//*///////////////////////////////////////////////////////////////
// Блок кода №3. Создание списка выносимых в смарт-тег элементов компонента
//////////////////////////////////////////////////////////////////
namespace MyControl
{
// Пусть класс доступен коду только внутри сборки компонента
internal partial class GradientLabelActionList : DesignerActionList
{
// Объявили ссылку на объект компонента
GradientLabel gradientLabel;
// Объявили ссылку на сервис смарт-тега
DesignerActionUIService designerActionUIService;
// Конструктор
public GradientLabelActionList(IComponent component)
: base(component)
{
// Привели и сохранили ссылку на редактируемый компонент
gradientLabel = (GradientLabel)component;
// Привели и сохранили ссылку на сервис смарт-тега
designerActionUIService = (DesignerActionUIService)
this.GetService(typeof(DesignerActionUIService));
}
// Вспомогательная функция реализации отражения
private PropertyDescriptor GetPropertyByName(String propName)
{
PropertyDescriptor prop =
TypeDescriptor.GetProperties(gradientLabel)[propName];
if (prop == null)
throw new ArgumentException("Свойство не существует", propName);
return prop;
}
// Элементы компонента, выносимые в диалог смарт-тега
public Color StartColor
{
get { return this.gradientLabel.StartColor; }
set
{
this.GetPropertyByName("StartColor").SetValue(gradientLabel, value);
// Обновляем диалоговое окно смарт-тега
designerActionUIService.Refresh(gradientLabel);
}
}
public Color EndColor
{
get { return this.gradientLabel.EndColor; }
set
{
this.GetPropertyByName("EndColor").SetValue(gradientLabel, value);
// Обновляем диалоговое окно смарт-тега
designerActionUIService.Refresh(gradientLabel);
}
}
public void InvertColors()
{
Color tmp = gradientLabel.StartColor;
this.StartColor = gradientLabel.EndColor;
this.EndColor = tmp;
}
}
}
//***************************************************************/
Для того, чтобы изменить порядок следования элементов System.ComponentModel.Design.DesignerActionItemCollection.
Для этого в классе формирования списка элементов окна GradientLabelActionList нужно
переопределить унаследованный библиотечный
метод DesignerActionList.GetSortedActionItems() и
в нем вручную формировать коллекцию окна
Каждый элемент коллекции окна
System.ComponentModel.Design.DesignerActionHeaderItem -
устанавливает заголовок группы элементов окна System.ComponentModel.Design.DesignerActionTextItem -
выводит надписи к элементам System.ComponentModel.Design.DesignerActionPropertyItem -
представляет свойство и в зависимости от типа свойства виден
как текстовое поле, выпадающий список или флажокSystem.ComponentModel.Design.DesignerActionMethodItem -
представляет ссылку для вызова методаИспользуем перечисленные классы представления элементов окна
и сформируем коллекцию в GetSortedActionItems(). При этом создадим в смарт-теге три группы элементов:
Добавьте
в файл GradientLabelDesigner.cs блок кода
№5, переопределяющий DesignerActionList.GetSortedActionItems() базового
класса//*///////////////////////////////////////////////////////////////
// Блок кода №5. Ручное формирование списка элементов в классе GradientLabelActionList
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelActionList
{
// Переопределяем виртуальный метод базового класса DesignerActionList
public override DesignerActionItemCollection GetSortedActionItems()
{
// Создаем объект коллекции элементов смарт-тега
DesignerActionItemCollection items = new DesignerActionItemCollection();
// Формируем категорию "Свойства" (Properties)
// Заголовок категории
items.Add(new DesignerActionHeaderItem("Свойства", "Properties"));
// Элементы категории
items.Add(new DesignerActionPropertyItem("StartColor", "Начальный цвет",
"Properties", "Цвет начала градиентной заливки"));
items.Add(new DesignerActionPropertyItem("EndColor", "Конечный цвет",
"Properties", "Цвет завершения градиентной заливки"));
// Формируем категорию "Методы" (Methods), если цвета не равны!
if (StartColor != EndColor)
{
// Заголовок категории
items.Add(new DesignerActionHeaderItem("Методы", "Methods"));
// Ссылка вызова метода с одновременным созданием опции в контекстном меню
items.Add(new DesignerActionMethodItem(this, "InvertColors",
"Перевернуть цвета", "Methods",
"Поменять начальный и конечный цвета местами", true));
}
// Формируем категорию "Информация" (Info)
// Заголовок категории
items.Add(new DesignerActionHeaderItem("Информация", "Info"));
// Текстовый элемент категории
string info = String.Format("Размер компонента {0}x{1}",
gradientLabel.Width, gradientLabel.Height);
items.Add(new DesignerActionTextItem(info, "Info"));
return items;
}
}
}
//***************************************************************/
Откомпилируйте
проект компонента командой Build/Build Solution и
убедитесь, что окно диалога компонента в режиме проектирования
приобрело профессиональный вид
Контекстное меню компонента появляется при щелчке на нем правой кнопкой мыши. Естественно, что пункты любого меню выполняют закрепленные за ними действия с помощью соответствующих методов. Без специальных действий над кодом компонента для него существует стандартное меню. Мы можем несколько модифицировать стандартное меню компонента режима разработки, если учтем следующее.
При добавлении ссылки на метод в System.ComponentModel.Design.DesignerActionMethodItem(),
три из которых своим последним параметром управляют
опцией с названием displayName в контекстном
меню:
public DesignerActionMethodItem(System.ComponentModel.Design.DesignerActionList
actionList, string memberName, string displayName, bool
includeAsDesignerVerb)public DesignerActionMethodItem(System.ComponentModel.Design.DesignerActionList
actionList, string memberName, string displayName, string
category , bool includeAsDesignerVerb)public DesignerActionMethodItem(System.ComponentModel.Design.DesignerActionList
actionList, string memberName, string displayName, string
category , string description, bool includeAsDesignerVerb)В блоке кода №5 класса GradientLabelActionList при GetSortedActionItems(), унаследованного от базового класса DesignerActionList,
использована includeAsDesignerVerb,
что обеспечило включение опции метода InvertColors() с
названием "Перевернуть цвета" в
стандартное контекстное меню компонента.

Для формирования своего контекстного меню необходимо в подключаемом
к компоненту в качестве GradientLabelDesigner переопределить
виртуальное свойство , унаследованное
от класса ComponentDesigner. В свою очередь,
к свойству нужно подключить экземпляр
класса, содержащего коллекцию элементов контекстного меню,
производного от библиотечного System.ComponentModel.Design.DesignerVerbCollection.
Добавьте
в //*///////////////////////////////////////////////////////////////
// Блок кода №6. Формирование коллекции элементов контекстного меню компонента
//////////////////////////////////////////////////////////////////
namespace MyControl
{
internal class GradientLabelVerbCollection : DesignerVerbCollection
{
// Объявили ссылку на объект компонента
GradientLabel gradientLabel;
// Конструктор
public GradientLabelVerbCollection(IComponent component)
{
// Привели и сохранили ссылку на редактируемый компонент
gradientLabel = (GradientLabel)component;
// Создаем пункты меню (всего один пункт)
this.Add(new DesignerVerb("Перевернуть цвета_1",
new EventHandler(VerbInvertColors)));
}
// Соблюдаем сигнатуру библиотечного делегата EventHandler
private void VerbInvertColors(object sender, EventArgs args)
{
// Извлекаем ссылки на свойства компонента
PropertyDescriptor start = GetPropertyByName("StartColor");
PropertyDescriptor end = GetPropertyByName("EndColor");
// Извлекаем значения свойств и меняем их местами
Color tmp = (Color)start.GetValue(gradientLabel);
start.SetValue(gradientLabel, end.GetValue(gradientLabel));
end.SetValue(gradientLabel, tmp);
}
// Вспомогательная функция реализации отражения, дублирована
private PropertyDescriptor GetPropertyByName(String propName)
{
PropertyDescriptor prop =
TypeDescriptor.GetProperties(gradientLabel)[propName];
if (prop == null)
throw new ArgumentException("Свойство не существует", propName);
return prop;
}
}
}
//***************************************************************/
В конец
файла блок
кода №7 переопределения наследуемого виртуального свойства Verbs для
добавления опции " Перевернуть цвета_1 " в
контекстное меню компонента//*///////////////////////////////////////////////////////////////
// Блок кода №7. Добавление коллекции контекстного меню к компоненту
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
DesignerVerbCollection verbs; // Базовое поле
// Переопределение виртуального свойства Verbs, унаследованного
// классом System.Windows.Forms.Design.ControlDesigner от
// класса System.ComponentModel.Design.ComponentDesigner
public override DesignerVerbCollection Verbs
{
get
{
// Если еще не создавали контекстное меню компонента
if (verbs == null)
{
verbs=new GradientLabelVerbCollection(this.Component);
}
return verbs;
}
}
}
}
//***************************************************************/
Откомпилируйте
проект и убедитесь, что контекстное меню компонента на этапе
проектирования работает нормально и имеет вид
Мы рассмотрели два способа создания контекстного меню. На
нашем примере можно убедиться, что оба способа обладают одинаковой
функциональностью. Первый способ при добавлении элементов в
includeAsDesignerVerb = true проще,
но второй способ может пригодиться, когда формируется контекстное
меню без предварительного создания
Закомментируйте
блок №4 файла GradientLabelDesigner.cs,
отвечающий за создание /*////////////////////////////////////////////////////////////////
// Блок кода №4. Создание смарт-тега с заданным списком элементов
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
// Базовое поле
DesignerActionListCollection actionLists;
// Переопределение виртуального свойства ActionLists, унаследованного
// классом System.Windows.Forms.Design.ControlDesigner от
// класса System.ComponentModel.Design.ComponentDesigner
public override DesignerActionListCollection ActionLists
{
get
{
// Если еще не создавали смарт-тег actionLists
if (actionLists == null)
{
// Создали окно смарт-тега
actionLists = new DesignerActionListCollection();
// Создали список элементов окна
GradientLabelActionList gradientLabelActionList =
new GradientLabelActionList(this.Component);
// Добавили список элементов в коллекцию окна
actionLists.Add(gradientLabelActionList);
}
return actionLists;
}
}
}
}
//***************************************************************/
Откомпилируйте
все решение и убедитесь, что 
Раскомментируйте
блок кода №4 построения //*////////////////////////////////////////////////////////////////
// Блок кода №4. Создание смарт-тега с заданным списком элементов
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
..................................................
}
}
//***************************************************************/
При BackColor становится
излишним. Мы не можем его скрыть с помощью атрибута Browsable(false),
поскольку оно в текущем слое расширения не объявляется, а наследуется
из библиотечного класса System.Windows.Forms.Control через
класс Label. Здесь нужно применить специальную
технику, основанную на System.Windows.Forms.Design.ControlDesigner.
В классе-расширении для сокрытия свойств и событий нужно переопределить методы
protected virtual void PreFilterProperties(System.Collections.IDictionary properties)
protected virtual void PreFilterEvents(System.Collections.IDictionary events)
Этим методам передаются коллекции properties и events всех свойств и методов, отображаемых дизайнером в панели Properties.
Для отключения показа свойства или метода в панели Properties достаточно внутри соответствующего метода-фильтра выполнить одну из инструкций
properties.Remove("BackColor");
events.Remove("DoubleClick");
Добавьте
в BackColor и
событие DoubleClick из панели Properties оболочки//*///////////////////////////////////////////////////////////////
// Блок кода №8. Сокрытие свойств и событий из панели Properties
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
protected override void PreFilterProperties(System.Collections.IDictionary properties)
{
base.PreFilterProperties(properties);
// Скрываем в панели Properties свойство BackColor
properties.Remove("BackColor");
}
protected override void PreFilterEvents(System.Collections.IDictionary events)
{
base.PreFilterEvents(events);
// Скрываем в панели Properties событие DoubleClick
events.Remove("DoubleClick");
}
}
}
//***************************************************************/
Откомпилируйте
решение и убедитесь в работоспособности кода, который скрывает
как свойство BackColor, так и событие DoubleClick компонента GradientLabelНекоторые свойства нужны и существуют только на этапе проектирования.
Например, свойства категории Design в панели
свойств: GenerateMember, , - реально
отсутствуют у экземпляра нашего компонента, но в то же время
на этапе разработки оболочка их создает и поддерживает.

Для того, чтобы добавить свойство для режима разработки, нужно
добавить его в коллекцию properties в PreFilterProperties() класса ControlDesigner. Очень важно проследить,
чтобы добавляемое свойство режима разработки объявлялось с
атрибутом
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
Этот атрибут не дает такому свойству класса компонента попасть в режим выполнения.
Дополните
блок кода №8 в файле GradientLabelDesigner.cs следующими
строками//*///////////////////////////////////////////////////////////////
// Блок кода №8. Сокрытие свойств и событий из панели Properties
// Добавление свойств режима разработки в панель Properties
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
// Создание свойства в классе компонента
int designProp; // Базовое поле
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Category("Design")]
[Description("Свойство режима проектирования")]
public int DesignProp
{
get { return designProp; }
set { designProp = value; }
}
protected override void PreFilterProperties(System.Collections.IDictionary properties)
{
base.PreFilterProperties(properties);
// Скрываем в панели Properties свойство BackColor
properties.Remove("BackColor");
////////////////////////////////////////////////////////////////
// Добавление свойства этапа проектирования в панель Properties:
////////////////////////////////////////////////////////////////
// Извлекаем существующее свойство из класса компонента
PropertyDescriptor oldPropDescr = TypeDescriptor.GetProperties(this)["DesignProp"];
// Объявляем массив для атрибутов нужной размерности
Attribute[] attributes = new Attribute[oldPropDescr.Attributes.Count];
// Копируем атрибуты свойства в массив
oldPropDescr.Attributes.CopyTo(attributes, 0);
// Создаем дополнительное свойство для панели Properties
PropertyDescriptor propDescr =
TypeDescriptor.CreateProperty(this.GetType(), oldPropDescr, attributes);
// Добавляем дополнительное свойство в панель Properties
properties["DesignProp"] = propDescr;
}
protected override void PreFilterEvents(System.Collections.IDictionary events)
{
base.PreFilterEvents(events);
// Скрываем в панели Properties событие DoubleClick
events.Remove("DoubleClick");
}
}
}
//***************************************************************/
Откомпилируйте
компонент и убедитесь, что свойство DesignProp класса GradientLabelDesigner появилось в категории Design панели Properties,
хотя в самом классе компонента GradientLabel такого свойства
нетИногда может возникнуть необходимость пометить компонент в
режиме проектирования надписью, которая в режиме выполнения
не будет видна. Здесь нужно перегрузить метод OnPaintAdornments() класса дизайнера, присоединенного к компоненту,
который работает только в режиме проектирования.
Добавьте
в //*///////////////////////////////////////////////////////////////
// Блок кода №9. Отрисовка компонента в режиме проектирования
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
protected override void OnPaintAdornments(PaintEventArgs pe)
{
base.OnPaintAdornments(pe);
pe.Graphics.Clear(Color.White);// Стираем старое
// Рисуем дополнительную информацию в поле компонента
String text = "Мой компонент";
pe.Graphics.DrawString(
text,
this.Control.Font,
new SolidBrush(Color.Black),
0.0F,
0.0F
);
}
}
}
//***************************************************************/
Откомпилируйте
решение и убедитесь, что дополнительная надпись на компоненте
работает только на этапе проектирования![]() |
![]() |
| Режим времени проектирования | Режим времени выполнения |
Здесь есть одно неудобство, которое нарушает преимущества визуального программирования. Пользователь нашего компонента не сможет представить, как будет выглядеть компонент во время выполнения, пока не запустит форму, где он используется. А в режиме проектирования он будет видеть только дополнительную надпись (или наложение надписей, если на выполнить очистку поля компонента). Поэтому желательно сделать так, чтобы дополнительная надпись разработчика компонента появлялась бы только при определенных условиях, например, при наведении на компонент курсора мыши.
Класс System.Windows.Forms.Design.ControlDesigner содержит
ряд GradientLabelDesigner,
а именно
protected virtual void OnMouseEnter() - вызывается автоматически
при попадании курсора мыши на компонентprotected virtual void OnMouseLeave() - вызывается автоматически
при уходе курсора мыши с компонентаНам удобно ввести булев флаг mouseOver, который
будет подниматься в методе OnMouseEnter() и
опускаться в методе OnMouseLeave(). В этих
же методах будет вызываться метод разрушения окна компонента,
что будет инициировать выполнение метода перерисовки компонента OnPaintAdornments(). А в самом методе перерисовки
код очистки поля компонента и вывода дополнительной надписи
мы поставим на условие в зависимости от состояния флага mouseOver.
С учетом
сказанного модифицируйте блок №9 файла GradientLabelDesigner.cs следующим образом//*///////////////////////////////////////////////////////////////
// Блок кода №9. Отрисовка компонента в режиме проектирования
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
bool mouseOver = false;
protected override void OnPaintAdornments(PaintEventArgs pe)
{
base.OnPaintAdornments(pe);
if (mouseOver)
{
pe.Graphics.Clear(Color.White);// Стираем старое
// Рисуем дополнительную информацию в поле компонента
String text = "Мой компонент";
pe.Graphics.DrawString(
text,
this.Control.Font,
new SolidBrush(Color.Black),
0.0F,
0.0F
);
}
}
// Мышь попала на компонент
protected override void OnMouseEnter()
{
base.OnMouseEnter();
mouseOver = true;
this.Control.Invalidate();
}
// Мышь сошла с компонента
protected override void OnMouseLeave()
{
base.OnMouseLeave();
mouseOver = false;
this.Control.Invalidate();
}
}
}
//***************************************************************/
Действие по умолчанию - это действие
Откройте
файл Form1.cs в режиме View Designer и
выполните двойной щелчок на помещенном на форму экземпляре
компонента gradientLabel1. Удостоверьтесь, что графический
редактор автоматически создал обработчик события Click объекта
компонента gradientLabel1 с заготовкойprivate void gradientLabel1_Click(object sender, EventArgs e)
{
}

В файле GradientLabel.cs добавьте в блок кода №2 перед объявлением
класса GradientLabel атрибут, назначающий компоненту событие
по умолчанию/////////////////////////////////////////////////////////////////
// Блок кода №2 продолжения класса GradientLabel
/////////////////////////////////////////////////////////////////
namespace MyControl
{
// Всплывающая подсказка компонента в панели Toolbox
[Description("Текстовая метка с градиентной заливкой фона")]
// Подключение пиктограммы компонента для панели Toolbox
[ToolboxBitmap(typeof(GradientLabel))]
// Подключение класса дизайнера к классу компонента
[Designer(typeof(GradientLabelDesigner))]
// Событие по умолчанию
[DefaultEvent("GradientChange")]
partial class GradientLabel
{
// Закрытые поля
private Color startColor = Color.Yellow;
private Color endColor = Color.Red;
// Категория свойства в панели Properties
[Category("Gradient")]
...............................................................
}
}
Откомпилируйте
решение с введенными изменениями в коде компонента
Вновь
откройте файл Form1.cs в режиме View
Designer и
выполните двойной щелчок на помещенном на форму экземпляре
компонента gradientLabel1. Удостоверьтесь,
что GradientChange объекта компонента gradientLabel1 с
заготовкойprivate void gradientLabel1_GradientChange(object sender, EventArgs e)
{
}
Событие по умолчанию можно отменить или как-то дополнить,
если переопределить
public
в котором не вызывать базовый метод base.DoDefaultAction() цепочки
наследования.
Добавьте
в DoDefaultAction()//*///////////////////////////////////////////////////////////////
// Блок кода №10. Перекрытие события по умолчанию
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
public override void DoDefaultAction()
{
//base.DoDefaultAction();
MessageBox.Show("Событие по умолчанию компонента перекрыто");
}
}
}
//***************************************************************/
Откомпилируйте
компонент и выполните двойной щелчок мыши на экземпляре компонента,
помещенного на форму Form1Дизайнер запустит только диалоговое окно, но сам обработчик события по умолчанию создавать не будет.

У базового класса System.Windows.Forms.Design.ControlDesigner имеется
protected
который в качестве параметра принимает ссылку на структуру System.Windows.Forms.Message, связанную с
Метод WndProc() вызывается автоматически
всякий раз, когда в цикл сообщений операционной системы попадает
новое сообщение от внутреннего или внешнего источника. Испытаем
работу этой техники на примере обработки левого щелчка мыши
на компоненте. Будем отлавливать сам щелчок левой кнопки мыши,
определять координаты курсора относительно формы,
приводить эти координаты относительно компонента и проверять,
выходят ли они за пределы компонента.
Добавьте
в WndProc() для
прямой обработки сообщений Windows//*///////////////////////////////////////////////////////////////
// Блок кода №11. Перекрытие метода WndProc()
// для прямой обработки сообщений Windows
//////////////////////////////////////////////////////////////////
namespace MyControl
{
partial class GradientLabelDesigner
{
const int WM_LBUTTONCLICK = 0x201;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONCLICK)
{
// Нажата левая кнопка мыши, приводим координаты курсора к компоненту
Point point = this.Control.PointToClient(Cursor.Position);
if (point.X > 0 point.X < this.Control.Width
point.Y > 0 point.Y < this.Control.Height)
{
MessageBox.Show(String.Format("Попали в компонент\n"
+ "с координатами {0}x{1}", point.X, point.Y));
}
}
base.WndProc(ref m);
}
}
}
//***************************************************************/
Откомпилируйте
компонент GradientLabel и выполните щелчок
левой кнопки мыши на его экземпляре, помещенном
на форму Form1Диалоговое окно сообщений свидетельствует о реакции дизайнера на щелчки мыши способом прямой обработки событий Windows.

Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.