Украина, Киев |
Пользовательские элементы управления
Вычисление размеров кнопки
Каждый раз, когда изображение кнопки на экране требует перерисовки, операционная система вызывает метод OnPaint(). Перед его выполнением необходима информация о размерах перерисуемой области. Такую информацию возвращает автоматически вызываемый метод GetPreferredSize(). Переопределим его в соответствии с нашими требованиями, чтобы он возвращал размер описывающего квадрата, способного вместить в себя круглую кнопку вместе с надписью на ней, хранящуюся в свойстве Text.
// Вычисляем размер описывающего квадрата кнопки // в зависимости от размера надписи на ней public override Size GetPreferredSize(Size proposedSize) { // Получаем ссылку на контекст графического устройства Graphics gr = CreateGraphics(); // Измеряем размер описывающего текст прямоугольника SizeF box = gr.MeasureString(this.Text, this.Font); // Вычисляем диагональ текстового блока надписи int diagonal = (int)Math.Sqrt(Math.Pow(box.Width, 2) + Math.Pow(box.Height, 2)); // Возвращаем объект с размерами описывающего квадрата кнопки return new Size(diagonal, diagonal); }Листинг 19.12. Вычисление размера кнопки в файле CircleButton.cs
Метод GetPreferredSize() вызывается диспетчером размещения для определения предпочтительных размеров компонента, когда значение его свойства AutoSize=true (мы его установили в методе Init() ) и это компонент готов к перерисовке в методе OnPaint(). Наш код при начальной загрузки компонента выдаст размеры квадрата и эллипсная кнопка будет круглой.
Задание круглой формы для кнопки
Все визуальные элементы управления наследуют от класса System.Windows.Forms.Control вместе с его свойством Region - экземпляром класса System.Drawing.Region. Свойство Region определяет форму элемента управления и по умолчанию ссылается на прямоугольник. Но эту форму можно изменить на любую сложную фигуру, передав контур этой фигуры в конструктор класса Region. После назначения свойства Region у элемента управления по-прежнему остаются размеры прямоугольника, но все, что выходит за пределы указанного контура, будет невидимо как визуально, так и для мыши.
Графический контур формируется как экземпляр класса System.Drawing.Drawing2D.GraphicsPath, который содержит множество методов для создания нужных графических примитивов. Если текущая фигура состоит из отрезков линий, то она считается законченной после выполнения метода CloseFigure(), который замыкает начальную и конечную точки рисования. Кода рисуется последовательность замкнутых фигур, то каждая новая фигура проецируется на предыдущие как вырезание.
- Подключите в начале файла CircleButton.cs с помощью инструкции using пространство имен System.Drawing.Drawing2D для сокращенной идентификации класса GraphicsPath
using System; using System.ComponentModel; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Windows.Forms; using System.Drawing.Drawing2D;Листинг 19.13. Подключение к файлу CircleButton.cs пространства имен System.Drawing.Drawing2D
- Переопределите в классе CircleButton унаследованный виртуальный метод OnResize(), начав ввод с ключевого слова override
// Вычисление контура кнопки при изменении ее размеров protected override void OnResize(EventArgs args) { // Объект для размещения контура GraphicsPath path = new GraphicsPath(); // Размещаем эллипс path.AddEllipse(this.ClientRectangle); // Создаем объект рисования и адресуем его кнопке this.Region = new Region(path); // Вызываем предка для инициации OnPaint() // или Invalidate() base.OnResize(args); // this.Invalidate(); }Листинг 19.14. Настройка контура круга в методе OnResize() при изменении размеров элемента
// Отрисовка круглой кнопки protected override void OnPaint(PaintEventArgs args) { // Адресовались к контексту графического устройства Graphics gr = args.Graphics; // Настраиваем качество рисования - сглаживать контуры gr.SmoothingMode = SmoothingMode.AntiAlias; // Флаг попадания курсора внутрь кнопки при ее нажатии bool bPressed = this.Capture & // Связь с мышью есть! ((Control.MouseButtons & MouseButtons.Left) != 0) & // Нажата левая кнопка мыши // Нажатый курсор попал внутрь описывающего прямоугольника this.ClientRectangle. Contains(this.PointToClient(Control.MousePosition)); Rectangle rect = this.ClientRectangle; // Псевдоним // Отрисовка внутренней части кнопки // При нажатии она становится темнее GraphicsPath path = new GraphicsPath();// Заготовили контур для заливки path.AddEllipse(rect); // Вписали эллипс в клиентскую область кнопки PathGradientBrush gradient = new PathGradientBrush(path);// Создали градиент для заливки int k = bPressed ? 2 : 1;// Для нажатой кнопки сдвинем центр градиента вправо и вниз // Располагаем центр радиального градиента левее и выше центра кнопки gradient.CenterPoint = new PointF( k * (rect.Left + rect.Right) / 3, k * (rect.Top + rect.Bottom) / 3); // Настраиваем цвет радиального градиента gradient.CenterColor = bPressed ? Color.PowderBlue : Color.White;// Центр gradient.SurroundColors = new Color[] { Color.SkyBlue };// Окраина gr.FillRectangle(gradient, rect); // Залить градиентом // Отрисовка контура кнопки, утолщенного для активных кнопок // Создаем кисть с линейным градиентом, изменяющимся по диагонали Brush brush = new LinearGradientBrush( rect, Color.FromArgb(0, 0, 255), Color.FromArgb(0, 0, 128), LinearGradientMode.ForwardDiagonal); // Создаем перо для рисования градиентной кистью Pen pen = new Pen( brush, (this.IsDefault ? 3 : 1) * gr.DpiX / 72);// Если кнопка имеет фокус ввода - контур толще gr.DrawEllipse(pen, rect); // Нарисовать контур // Отображение надписи в центре прямоугольника кнопки // Для недоступной кнопки цвет надписи должен быть бледно-серым StringFormat strFormat = new StringFormat(); // Определение точки привязки в центре текстового блока strFormat.Alignment = strFormat.LineAlignment = StringAlignment.Center; brush = this.Enabled ? SystemBrushes.WindowText // Для активной : SystemBrushes.GrayText;// Для неактивной gr.DrawString(this.Text, this.Font, brush, rect, strFormat); // Отображение пунктира вокруг текста, если кнопка имеет фокус if (this.Focused) { SizeF sizeText = gr.MeasureString( this.Text, this.Font, PointF.Empty, StringFormat.GenericTypographic); pen = new Pen(this.ForeColor);// Текущий цвет pen.DashStyle = DashStyle.Dash;// Пунктир gr.DrawRectangle( pen, rect.Left + rect.Width / 2 - sizeText.Width / 2, rect.Top + rect.Height / 2 - sizeText.Height / 2, sizeText.Width, sizeText.Height); } // Метод диспетчеризации своего события - для тренировки if (this.Paint != null)// Если есть хоть одна подписка this.Paint(this, args);// Инициируем событие } new public event PaintEventHandler Paint;// Объявляем свое событие в классеЛистинг 19.15. Переопределение метода отрисовки круглой кнопки
Поскольку в классе расширения кнопки мы переопределили виртуальный метод OnPaint() и не предусмотрели его вызов для базового класса, то событие Paint никогда инициировано не будет. Потому что оно вызывается в методе OnPaint() именно базового класса, который мы перехватили. Чтобы не потерять событие Paint, мы его заново объявили в классе расширения, а в переопределенный метод OnPaint() вставили инициализацию этого события с предварительной проверкой, подписано ли оно в вызывающем коде.
- В панели Solution Explorer вызовите контекстное меню для узла UserControls библиотечных компонентов и выполните команду Build
Компонент круглой кнопки мы сделали, теперь нужно его оттестировать.
Тестирование компонента круглой кнопки
- Добавьте к решению командой File/Add/New Project новый проект типа оконного приложения ( Windows Application ) с именем CircleButtonTest и назначьте его стартовым командой Project/Set as StartUp Project меню оболочки (или аналогичной командой контекстного меню)
- Поместите на форму Form1 из панели Toolbox несколько экземпляров компонента CircleButton
- Перейдите в файле Form1.cs в режим View Code и добавьте в начало инструкцию
- Настройте свойства компонентов через панель Properties и создайте для всех кнопок общий обработчик circleButton_Click
private void circleButton_Click(object sender, EventArgs e) { // Создаем псевдоним переданной ссылки CircleButton button = sender as CircleButton; if (button != null) MessageBox.Show("Нажата кнопка: " + button.Name); }Листинг 19.16. Общий обработчик для круглых кнопок
- Запустите приложение и испытайте работу компонентов
Результат выполнения будет таким