Графическое программирование Windows Forms: техники рисования фигур
Для кого эта статья:
- Начинающие разработчики, интересующиеся графическим программированием на .NET
- Специалисты, стремящиеся углубить знания в области создания пользовательских интерфейсов и визуализации данных
Программисты, желающие освоить Windows Forms и графические возможности библиотеки System.Drawing
Графическое программирование в Windows Forms — это настоящий творческий холст для разработчика .NET! От простых линий до сложных интерактивных элементов интерфейса — возможности безграничны. Ошибка многих начинающих разработчиков — полагать, что создание привлекательной графики требует специализированных фреймворков или глубокого понимания DirectX. На деле, встроенные инструменты .NET предоставляют мощный набор методов для рисования практически любых визуальных элементов прямо на форме вашего приложения. 🎨 Погрузимся в мир графики WinForms!
Осваивая графическое программирование в Windows Forms, вы закладываете фундамент для карьеры веб-разработчика, где визуализация данных и создание пользовательских интерфейсов — ключевые навыки. Обучение веб-разработке от Skypro органично дополнит ваши знания WinForms современными веб-технологиями. Представьте: сегодня вы рисуете графики в десктопном приложении, а завтра создаёте интерактивные дашборды для корпоративных клиентов! Инвестируйте в навыки, востребованные на стыке технологий.
Основы графики в Windows Forms и класс Graphics
Класс Graphics — это ключевой элемент для рисования в Windows Forms. Он предоставляет множество методов для создания линий, фигур, текста и изображений. Работа с Graphics начинается с понимания координатной системы Windows Forms, где точка (0,0) находится в левом верхнем углу контрола или формы.
Существует несколько способов получить объект Graphics:
- Через событие Paint формы или контрола (наиболее распространенный метод)
- Через метод CreateGraphics() формы или контрола (для временных операций рисования)
- Через создание Bitmap и получение его Graphics (для рисования в памяти)
Важно понимать, что рисование с использованием CreateGraphics() является временным — при перерисовке формы (например, при минимизации и восстановлении) все нарисованное исчезнет. Для постоянного отображения всегда используйте событие Paint.
| Метод получения Graphics | Применение | Долговечность |
|---|---|---|
| Через Paint | Постоянные элементы интерфейса | Постоянно (переживает перерисовку) |
| CreateGraphics() | Временные эффекты | Временно (исчезает при перерисовке) |
| Из Bitmap | Рисование в памяти, двойная буферизация | В памяти до отображения |
Основные пространства имен для работы с графикой в Windows Forms:
- System.Drawing – содержит базовые классы для работы с графикой
- System.Drawing.Drawing2D – расширенные возможности 2D-графики
- System.Drawing.Imaging – для работы с изображениями
- System.Drawing.Text – для расширенной работы с текстом и шрифтами
Простейший пример использования Graphics:
using System.Drawing;
using System.Windows.Forms;
public class SimpleDrawingForm : Form
{
public SimpleDrawingForm()
{
this.Paint += new PaintEventHandler(SimpleDrawingForm_Paint);
}
private void SimpleDrawingForm_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawLine(Pens.Blue, 10, 10, 200, 200);
}
}
Антон Северцев, ведущий разработчик интерфейсов
На заре своей карьеры я столкнулся с требованием создать кастомный индикатор загрузки для WinForms-приложения. Это был мой первый проект в компании, и я потратил дни, пытаясь использовать CreateGraphics() для отрисовки вращающегося элемента. Всё работало идеально... до тех пор, пока клиент не начинал сворачивать и разворачивать окно.
Моя ошибка была фундаментальной — непонимание жизненного цикла объекта Graphics. После перерисовки все мои старания исчезали, и экран оставался пустым. Переписав код с использованием события Paint и метода Invalidate() для запроса перерисовки, я получил стабильно работающий индикатор.
Этот опыт научил меня важнейшему принципу графики в WinForms: "Никогда не привязывайся к конкретному экземпляру Graphics — он временный гость в твоем приложении".

Обработка события Paint для рисования на форме
Событие Paint — краеугольный камень графического программирования в Windows Forms. Оно возникает каждый раз, когда система определяет, что область формы или контрола должна быть перерисована. Это может происходить при:
- Первоначальном отображении формы
- Изменении размеров окна
- Разворачивании окна после минимизации
- Перекрытии окна другими окнами
- Явном вызове методов Invalidate(), Refresh() или Update()
Обработчик события Paint получает объект PaintEventArgs, который содержит два ключевых свойства:
- Graphics – объект для рисования
- ClipRectangle – прямоугольник, определяющий область, которую нужно перерисовать
Вот базовый шаблон для работы с Paint:
private void MyForm_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
// Улучшение качества отрисовки
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// Здесь код рисования
g.DrawEllipse(Pens.Red, 50, 50, 100, 100);
}
Для программного запроса перерисовки используются следующие методы:
| Метод | Описание | Использование |
|---|---|---|
| Invalidate() | Помечает всю область контрола как требующую перерисовки | Асинхронное, отложенное обновление |
| Invalidate(Rectangle) | Помечает указанную область как требующую перерисовки | Оптимизация при частичных обновления |
| Refresh() | Вызывает Invalidate() с последующим Update() | Принудительное немедленное обновление |
| Update() | Форсирует обработку ожидающих сообщений перерисовки | Редко используется напрямую |
Важно понимать принцип двойной буферизации, который предотвращает мерцание при отрисовке сложных сцен. Windows Forms по умолчанию использует двойную буферизацию для форм, но для пользовательских контролов её нужно включать явно:
public class MyCustomControl : Control
{
public MyCustomControl()
{
this.DoubleBuffered = true; // Включение двойной буферизации
// Или альтернативный способ
// SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
Практический пример анимации с использованием таймера и Paint:
public class AnimatedCircleForm : Form
{
private int circleX = 0;
private readonly Timer animationTimer = new Timer();
public AnimatedCircleForm()
{
this.DoubleBuffered = true;
animationTimer.Interval = 30; // ~33 FPS
animationTimer.Tick += (s, e) => {
circleX = (circleX + 5) % this.Width;
this.Invalidate(); // Запрос перерисовки
};
this.Paint += AnimatedCircleForm_Paint;
this.Load += (s, e) => animationTimer.Start();
}
private void AnimatedCircleForm_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(this.BackColor);
e.Graphics.FillEllipse(Brushes.DodgerBlue, circleX, 100, 50, 50);
}
}
Рисование фигур и линий с помощью System.Drawing
Библиотека System.Drawing предоставляет богатый набор инструментов для рисования всевозможных фигур и линий. Класс Graphics содержит множество методов Draw для контуров и Fill для заливки фигур. 🖌️
Основные фигуры, которые можно рисовать:
- Линии (DrawLine)
- Прямоугольники (DrawRectangle, FillRectangle)
- Эллипсы и круги (DrawEllipse, FillEllipse)
- Дуги (DrawArc)
- Многоугольники (DrawPolygon, FillPolygon)
- Кривые Безье (DrawBezier, DrawCurve)
- Замкнутые кривые (DrawClosedCurve, FillClosedCurve)
- Сектора (DrawPie, FillPie)
Для настройки внешнего вида контуров используются классы Pen и различные атрибуты:
// Создание пера с указанием цвета и толщины
Pen redPen = new Pen(Color.Red, 3);
// Установка стиля линии
redPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
// Настройка соединений линий
redPen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;
// Использование в отрисовке
g.DrawRectangle(redPen, 50, 50, 200, 100);
// Важно освобождать ресурсы
redPen.Dispose();
Для заливки фигур используются объекты Brush различных типов:
- SolidBrush – однотонная заливка
- HatchBrush – заливка шаблоном
- LinearGradientBrush – линейный градиент
- PathGradientBrush – градиент по пути
- TextureBrush – заливка текстурой (изображением)
Пример создания сложной композиции с различными фигурами:
private void DrawShapesDemo(Graphics g)
{
// Линия с настраиваемым пером
using (Pen pen = new Pen(Color.Blue, 2))
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
g.DrawLine(pen, 10, 10, 200, 10);
}
// Прямоугольники
g.DrawRectangle(Pens.Red, 10, 20, 100, 50);
g.FillRectangle(Brushes.LightBlue, 120, 20, 100, 50);
// Прямоугольник со скругленными углами
using (System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath())
{
int radius = 20;
Rectangle rect = new Rectangle(10, 80, 150, 60);
path.AddArc(rect.X, rect.Y, radius, radius, 180, 90);
path.AddArc(rect.X + rect.Width – radius, rect.Y, radius, radius, 270, 90);
path.AddArc(rect.X + rect.Width – radius, rect.Y + rect.Height – radius, radius, radius, 0, 90);
path.AddArc(rect.X, rect.Y + rect.Height – radius, radius, radius, 90, 90);
path.CloseAllFigures();
g.FillPath(Brushes.Gold, path);
g.DrawPath(Pens.DarkGoldenrod, path);
}
// Эллипс и круг
g.DrawEllipse(Pens.Green, 170, 80, 60, 60);
// Секторы (Pie)
g.FillPie(Brushes.Purple, 240, 20, 100, 100, 0, 45);
g.FillPie(Brushes.Violet, 240, 20, 100, 100, 45, 90);
g.DrawPie(Pens.Black, 240, 20, 100, 100, 0, 135);
// Многоугольник
Point[] points = {
new Point(50, 150),
new Point(150, 150),
new Point(200, 220),
new Point(100, 250),
new Point(20, 200)
};
g.FillPolygon(Brushes.LightGreen, points);
g.DrawPolygon(Pens.DarkGreen, points);
// Кривая Безье
g.DrawBezier(
new Pen(Color.Blue, 2),
new Point(250, 150),
new Point(290, 190),
new Point(320, 170),
new Point(350, 220)
);
}
Градиентная заливка добавляет визуальной привлекательности вашим элементам:
using (LinearGradientBrush gradientBrush = new LinearGradientBrush(
new Point(10, 280),
new Point(310, 380),
Color.Blue,
Color.Yellow))
{
// Добавление промежуточной точки градиента
gradientBrush.InterpolationColors = new ColorBlend {
Colors = new[] { Color.Blue, Color.Purple, Color.Red, Color.Yellow },
Positions = new[] { 0.0f, 0.3f, 0.7f, 1.0f }
};
g.FillRectangle(gradientBrush, 10, 280, 300, 100);
}
Мария Светлова, разработчик приложений визуализации данных
Работая над проектом визуализации статистики для медицинского центра, я столкнулась с интересной задачей. Клиент хотел видеть "живые" графики с плавной анимацией при изменении данных. Изначально я думала об использовании сторонних библиотек для графиков, но сроки и бюджет были ограничены.
Решение пришло в виде комбинации System.Drawing с таймерами и интерполяцией. Я создала класс, который хранил как текущие, так и целевые значения графика. При изменении данных запускался таймер, который шаг за шагом приближал текущие значения к целевым, вызывая Invalidate() для перерисовки.
csharpСкопировать кодprivate void AnimateGraphValue(float targetValue) { float startValue = _currentValue; float distance = targetValue – startValue; _animationSteps = 20; // Количество шагов анимации _currentStep = 0; _animationTimer.Interval = 20; // 50 FPS _animationTimer.Tick += (s, e) => { _currentStep++; if (_currentStep >= _animationSteps) { _currentValue = targetValue; _animationTimer.Stop(); } else { // Плавная интерполяция с замедлением float progress = (float)_currentStep / _animationSteps; progress = 1 – (1 – progress) * (1 – progress); // Easing function _currentValue = startValue + distance * progress; } Invalidate(); // Запрос перерисовки }; _animationTimer.Start(); }Результат превзошел ожидания. Врачи получили плавно анимированные графики, которые интуитивно отображали динамику показателей пациентов. А я поняла, что даже с базовыми инструментами System.Drawing можно создавать визуально впечатляющие решения.
Работа с изображениями через DrawImage и PictureBox
Работа с изображениями — важная часть многих приложений Windows Forms. Есть два основных подхода: использование PictureBox и прямое рисование изображений через метод DrawImage класса Graphics.
PictureBox — готовый контрол для отображения изображений с множеством полезных свойств:
PictureBox pictureBox = new PictureBox
{
Image = Image.FromFile("path/to/image.jpg"),
SizeMode = PictureBoxSizeMode.Zoom,
Dock = DockStyle.Fill
};
this.Controls.Add(pictureBox);
Свойство SizeMode определяет способ отображения изображения внутри контрола:
| Значение SizeMode | Описание | Применение |
|---|---|---|
| Normal | Изображение размещается в левом верхнем углу без изменения размера | Точное представление пикселей |
| StretchImage | Растягивает изображение для заполнения всего контрола | Может искажать пропорции |
| AutoSize | Размер контрола подстраивается под размер изображения | Для небольших изображений |
| CenterImage | Центрирует изображение без изменения размера | Для декоративных элементов |
| Zoom | Масштабирует с сохранением пропорций, заполняя контрол | Лучший вариант для большинства случаев |
Для более гибкой работы с изображениями используется метод DrawImage класса Graphics:
private void Form_Paint(object sender, PaintEventArgs e)
{
// Загрузка изображения
using (Image img = Image.FromFile("path/to/image.jpg"))
{
// Базовый вариант отрисовки
e.Graphics.DrawImage(img, 10, 10);
// Отрисовка с указанием размера
e.Graphics.DrawImage(img, new Rectangle(10, 150, 200, 150));
// Частичная отрисовка (вырезание части изображения)
Rectangle srcRect = new Rectangle(0, 0, img.Width / 2, img.Height / 2);
Rectangle destRect = new Rectangle(220, 150, 100, 100);
e.Graphics.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}
}
При работе с изображениями важно учитывать управление ресурсами. Изображения занимают память, и их следует освобождать, когда они больше не нужны:
// Правильная работа с ресурсами изображения
Image tempImage = null;
try
{
tempImage = Image.FromFile("path/to/image.jpg");
// Работа с изображением
graphics.DrawImage(tempImage, 0, 0);
}
catch (Exception ex)
{
MessageBox.Show("Ошибка загрузки изображения: " + ex.Message);
}
finally
{
// Освобождение ресурсов
tempImage?.Dispose();
}
Для оптимизации производительности при работе с множеством изображений рекомендуется:
- Кэшировать изображения в памяти вместо постоянной загрузки с диска
- Использовать предварительное масштабирование изображений до нужного размера
- Применять двойную буферизацию при частом обновлении
- Создавать миниатюры для больших изображений
Пример создания эффекта полупрозрачности при наложении изображений:
private void DrawOverlappingImages(Graphics g)
{
using (Image background = Image.FromFile("background.jpg"))
using (Image overlay = Image.FromFile("overlay.png"))
{
// Рисуем фон
g.DrawImage(background, 0, 0, this.Width, this.Height);
// Создаем матрицу цветового преобразования для прозрачности
float[][] matrixItems = {
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 0.5f, 0}, // 0.5 = 50% прозрачность
new float[] {0, 0, 0, 0, 1}
};
using (System.Drawing.Imaging.ColorMatrix colorMatrix =
new System.Drawing.Imaging.ColorMatrix(matrixItems))
using (System.Drawing.Imaging.ImageAttributes imgAttr =
new System.Drawing.Imaging.ImageAttributes())
{
imgAttr.SetColorMatrix(colorMatrix);
// Рисуем наложение с прозрачностью
Rectangle destRect = new Rectangle(50, 50, overlay.Width, overlay.Height);
g.DrawImage(overlay, destRect, 0, 0, overlay.Width, overlay.Height,
GraphicsUnit.Pixel, imgAttr);
}
}
}
Изображения можно также динамически создавать и модифицировать в памяти:
// Создание нового изображения в памяти
using (Bitmap newImage = new Bitmap(300, 200))
{
// Получение Graphics для рисования на изображении
using (Graphics gImg = Graphics.FromImage(newImage))
{
gImg.Clear(Color.White);
gImg.DrawRectangle(Pens.Blue, 10, 10, 280, 180);
gImg.DrawString("Динамическое изображение",
new Font("Arial", 14), Brushes.Black, 40, 80);
}
// Теперь можно использовать newImage как обычное изображение
e.Graphics.DrawImage(newImage, 350, 10);
// Можно сохранить результат
newImage.Save("dynamic_image.png");
}
Создание пользовательских элементов с GraphicsPath
Класс GraphicsPath из пространства имен System.Drawing.Drawing2D — это мощный инструмент для создания сложных фигур и пользовательских элементов управления. Он позволяет объединять различные графические примитивы в единый путь, который можно отрисовывать, заполнять и использовать для определения областей. 🛠️
Основные возможности GraphicsPath:
- Создание составных фигур произвольной формы
- Определение областей с нестандартной формой для элементов управления
- Создание сложных траекторий для анимации
- Работа с текстом как с векторными объектами
- Построение фигур со сглаженными углами и изгибами
Пример создания нестандартной формы кнопки с использованием GraphicsPath:
public class RoundButton : Button
{
public RoundButton()
{
this.FlatStyle = FlatStyle.Flat;
this.FlatAppearance.BorderSize = 0;
this.BackColor = Color.MediumSlateBlue;
this.ForeColor = Color.White;
this.Font = new Font("Arial", 10, FontStyle.Bold);
this.Resize += new EventHandler(RoundButton_Resize);
}
private void RoundButton_Resize(object sender, EventArgs e)
{
UpdateRegion();
}
private void UpdateRegion()
{
using (GraphicsPath path = new GraphicsPath())
{
// Создаем скругленный прямоугольник
float radius = Math.Min(this.Width, this.Height) * 0.5f;
path.AddArc(0, 0, radius * 2, radius * 2, 180, 90);
path.AddArc(this.Width – radius * 2, 0, radius * 2, radius * 2, 270, 90);
path.AddArc(this.Width – radius * 2, this.Height – radius * 2,
radius * 2, radius * 2, 0, 90);
path.AddArc(0, this.Height – radius * 2, radius * 2, radius * 2, 90, 90);
path.CloseFigure();
// Устанавливаем область кнопки по созданному пути
this.Region = new Region(path);
}
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (GraphicsPath path = new GraphicsPath())
{
float radius = Math.Min(this.Width, this.Height) * 0.5f;
path.AddArc(0, 0, radius * 2, radius * 2, 180, 90);
path.AddArc(this.Width – radius * 2, 0, radius * 2, radius * 2, 270, 90);
path.AddArc(this.Width – radius * 2, this.Height – radius * 2,
radius * 2, radius * 2, 0, 90);
path.AddArc(0, this.Height – radius * 2, radius * 2, radius * 2, 90, 90);
path.CloseFigure();
// Рисуем фон кнопки
using (SolidBrush brush = new SolidBrush(this.BackColor))
{
e.Graphics.FillPath(brush, path);
}
// Центрируем текст
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
// Рисуем текст
using (SolidBrush textBrush = new SolidBrush(this.ForeColor))
{
e.Graphics.DrawString(this.Text, this.Font, textBrush,
new RectangleF(0, 0, this.Width, this.Height), sf);
}
}
}
}
GraphicsPath также позволяет создавать сложные градиентные заливки с помощью PathGradientBrush:
private void DrawPathGradient(Graphics g)
{
// Создаем путь
using (GraphicsPath path = new GraphicsPath())
{
path.AddEllipse(50, 50, 200, 150);
// Создаем градиент на основе пути
using (PathGradientBrush pgBrush = new PathGradientBrush(path))
{
// Центральный цвет
pgBrush.CenterColor = Color.White;
// Цвета на границе
Color[] colors = { Color.Blue, Color.Red, Color.Green, Color.Yellow };
pgBrush.SurroundColors = colors;
// Точка центра градиента (относительные координаты 0.0-1.0)
pgBrush.CenterPoint = new PointF(0.3f, 0.4f);
// Рисуем заливку
g.FillPath(pgBrush, path);
g.DrawPath(Pens.Black, path);
}
}
}
Применение GraphicsPath для создания сложных форм и клиппинга:
private void DrawComplexShapes(Graphics g)
{
// Создаем составную фигуру из базовых примитивов
using (GraphicsPath complexPath = new GraphicsPath())
{
// Добавляем прямоугольник
complexPath.AddRectangle(new Rectangle(50, 50, 200, 150));
// Вычитаем из него эллипс (создаем отверстие)
complexPath.AddEllipse(100, 75, 100, 100);
complexPath.FillMode = FillMode.Alternate; // Важно для "вычитания" форм
// Рисуем получившуюся составную фигуру
g.FillPath(Brushes.LightGreen, complexPath);
g.DrawPath(new Pen(Color.Green, 2), complexPath);
// Используем путь для ограничения области рисования (клиппинг)
Region previousClip = g.Clip;
// Создаем клиппинг на основе пути
g.SetClip(complexPath);
// Рисуем что-то только внутри клипа
for (int i = 0; i < 10; i++)
{
g.DrawLine(new Pen(Color.Blue, 3),
0, i * 20, this.Width, i * 20);
}
// Восстанавливаем предыдущую область клиппинга
g.Clip = previousClip;
}
}
Создание пользовательского контрола прогресс-бара с использованием GraphicsPath:
public class FancyProgressBar : Control
{
private int _value = 50;
private int _maximum = 100;
public int Value
{
get { return _value; }
set
{
_value = Math.Max(0, Math.Min(value, _maximum));
Invalidate();
}
}
public int Maximum
{
get { return _maximum; }
set
{
_maximum = Math.Max(1, value);
Value = _value; // Для пересчета в пределах нового максимума
}
}
public FancyProgressBar()
{
this.SetStyle(
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw,
true);
this.Height = 30;
this.Width = 200;
this.BackColor = Color.WhiteSmoke;
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Фон
using (GraphicsPath path = CreateRoundRectPath(0, 0, this.Width, this.Height, 5))
{
g.FillPath(Brushes.WhiteSmoke, path);
g.DrawPath(Pens.Gray, path);
}
// Прогресс
float progressWidth = (float)_value / _maximum * this.Width;
if (progressWidth > 0)
{
using (GraphicsPath progressPath = CreateRoundRectPath(0, 0, progressWidth, this.Height, 5))
{
using (LinearGradientBrush brush = new LinearGradientBrush(
new Point(0, 0),
new Point(0, this.Height),
Color.RoyalBlue,
Color.MediumSlateBlue))
{
g.FillPath(brush, progressPath);
}
}
}
// Текст
string progressText = $"{_value}%";
using (StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
})
{
g.DrawString(progressText, this.Font, Brushes.DarkSlateBlue,
new RectangleF(0, 0, this.Width, this.Height), sf);
}
}
private GraphicsPath CreateRoundRectPath(float x, float y, float width, float height, float radius)
{
GraphicsPath path = new GraphicsPath();
if (radius <= 0)
{
path.AddRectangle(new RectangleF(x, y, width, height));
return path;
}
radius = Math.Min(radius, Math.Min(width, height) / 2);
// Верхняя горизонтальная линия с закруглениями
path.AddArc(x, y, radius * 2, radius * 2, 180, 90);
path.AddArc(x + width – radius * 2, y, radius * 2, radius * 2, 270, 90);
// Правая вертикальная линия
path.AddLine(x + width, y + radius, x + width, y + height – radius);
// Нижняя горизонтальная линия с закруглениями
path.AddArc(x + width – radius * 2, y + height – radius * 2,
radius * 2, radius * 2, 0, 90);
path.AddArc(x, y + height – radius * 2, radius * 2, radius * 2, 90, 90);
// Левая вертикальная линия
path.AddLine(x, y + height – radius, x, y + radius);
path.CloseFigure();
return path;
}
}
Погружение в графическое программирование Windows Forms открывает удивительные возможности для создания приложений с уникальным визуальным стилем. Каждый метод рисования, будь то простая линия или сложный градиентный путь, становится инструментом для воплощения вашего творческого видения. Главное, что нужно запомнить — правильная обработка событий Paint, уважение к жизненному циклу Graphics и продуманная работа с ресурсами. С этими знаниями вы готовы создавать приложения, которые не только функциональны, но и визуально привлекательны.
Читайте также