Интерфейс времени проектирования для компонента
Разработка компонента с развитым интерфейсом времени проектирования
Компоненты - это программные единицы, которые функционируют в двух режимах: проектирования (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 - среда развития). Этот файл, скорее всего, будет иметь полное имя C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe
- В проводнике решений удалите автоматически добавленный при создании проекта 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(); } } }Листинг 36.1. Файл GradientLabel.cs
Нам нужно добавить к классу GradientLabel:
- два свойства для управления границами цвета компонента;
- одно событие с сигнатурой библиотечного делегата EventHandler ;
- а также переопределить унаследованный от Control метод OnPaint() (переопределение метода начинают с ввода ключевого слова override, чтобы активизировать подсказчик кода IntelliSense ).
Для этого:
- Поместите в самый конец файла GradientLabel.cs на свободное место следующий блок кода как самостоятельную единицу частичного (partial) класса 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); } } }Листинг 36.2. Блок кода, размещенный в самом конце файла GradientLabel.cs
- В проводнике решений вызовите контекстное меню для узла MyControl и выполните команду Rebuild (или Build ), чтобы откомпилировать компонент GradientLabel
- Откройте в проекте Test файл Form1.cs в режиме View Designer и поместите на форму из панели инструментов Toolbox компонент GradientLabel, который там появится после компиляции
В результате компонент на форме должен выглядеть так
Простейший интерфейс времени проектирования
После того, как компонент GradientLabel мы поместили на форму, то вправе ожидать от него такое поведение на этапе проектирования, которое присуще библиотечным компонентам. Настройкой такого поведения мы сейчас и займемся.
- Выделите на форме экземпляр компонента gradientLabel1 и в панели Properties установите режим представления свойств по категориям, щелкнув на соответствующей кнопке в верхней части панели
К поведению (во время проектирования) полученного на данном этапе компонента можно сделать следующие замечания:
- При изменении значений свойств экземпляр компонента перерисовывается не сразу, а только при перерисовке самой формы
- Добавленные нами свойства StartColor и EndColor отображаются в стандартной категории Misc, а не в желаемой нами категории
- В нижней части панели отображается только название выделенного свойства и нет никаких разъяснений по его назначению и условиям применения
- В рамках категории свойства отсортированы не по алфавиту
- Начальные значения свойств компонента отображаются жирным шрифтом, что свидетельствует о том, что они не считаются значениями по умолчанию, а явно присваиваются оболочкой в функции InitializeComponent() класса Form1 родительской формы
- Компонент в панели Toolbox не имеет своего индивидуального значка (пиктограммы)
Рядом последующих действий устраним эти замечания.
Перерисовку компонента при изменении значений свойств можно обеспечить, если в функцию диспетчеризации OnGradientChange() события GradientChange, которая вызывается в аксессоре set редакции свойств, вставить вызов унаследованного метода разрушения окна компонента Invalidate().
- Добавьте в метод диспетчеризации события компонента блока кода №2 следующую инструкцию
// Функция диспетчеризации события GradientChange protected virtual void OnGradientChange(EventArgs args) { // Перерисовываем компонент this.Invalidate(); // Если есть зарегистрированные обработчики, // то инициируем событие и вызываем обработчики if (GradientChange != null) GradientChange(this, args); }Листинг 36.3. Дополнение функции диспетчеризации события вызовом метода перерисовки
Остальные перечисленные замечания по поведению компонента в панели Properties устраняются с помощью атрибутов.