🎨 Графическое программирование на C#

← Вернуться к модулю 3

Урок 3.2: Оптимизация перерисовки

Модуль 3 ⏱️ ~1.5 часа

📌 Ключевые моменты этого урока:

  • Частичная перерисовка экономит ресурсы (редкое полное обновление)
  • Invalidate(region) обновляет только нужную область вместо всей формы
  • SetClip ограничивает область рисования для дополнительной оптимизации
  • ClipRectangle показывает где именно нужна перерисовка (важно для профилирования)
  • FPS счётчик помогает оценить реальное улучшение производительности

Полный и частичный Invalidate

ПОЧЕМУ это важно: У каждого PixelFormat полный Invalidate перерисовывает ВСЮ форму - это дорого! Частичный Invalidate перерисовывает ТОЛЬКО изменённую область.

Сравнение методов:

Сравнение методов Invalidate

Сравнение методов Invalidate для оптимизации перерисовки

Частичная перерисовка

Вместо перерисовки всей формы, можно перерисовывать только измененную область:

// Перерисовка только определенной области
Rectangle updateRect = new Rectangle(50, 50, 100, 100);
this.Invalidate(updateRect);
this.Update(); // Немедленная перерисовка

// Для сложных форм используйте Region
Region updateRegion = new Region();
updateRegion.Union(new Rectangle(10, 10, 50, 50));
updateRegion.Union(new Rectangle(100, 100, 50, 50));
this.Invalidate(updateRegion);

ClipRectangle: область, требующая перерисовки

e.ClipRectangle внутри Paint события показывает область, которую операционная система решила перерисовать. Используйте это для оптимизации:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    // e.ClipRectangle - это область, которая требует обновления
    // Рисуйте ВСЁ, но оптимизируйте для этой области
    
    Graphics g = e.Graphics;
    Rectangle clipArea = e.ClipRectangle;

    // Пример: если у нас 1000 фигур, рисуем только те, что в clipArea
    for (int i = 0; i < allShapes.Count; i++)
    {
        if (clipArea.IntersectsWith(allShapes[i].Bounds))
        {
            // Рисуем только фигуры, которые пересекаются с областью обновления
            g.DrawRectangle(Pens.Black, allShapes[i].Bounds);
        }
    }
}

SetClip: дополнительное ограничение рисования

// Ограничение области рисования
Graphics g = e.Graphics;
Rectangle clipRect = new Rectangle(10, 10, 200, 200);
g.SetClip(clipRect);

// Теперь рисование происходит только в этой области
g.DrawEllipse(Pens.Black, 0, 0, 500, 500); // Нарисуется только часть

g.ResetClip(); // Сброс ограничения

Избегание лишних вызовов Invalidate

Не вызывайте Invalidate слишком часто. Группируйте изменения:

// Плохо: много вызовов
x += 1; this.Invalidate();
y += 1; this.Invalidate();
angle += 1; this.Invalidate();

// Хорошо: один вызов
x += 1;
y += 1;
angle += 1;
this.Invalidate();

Профилирование производительности

// Измерение времени выполнения
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();

// Ваш код рисования
for (int i = 0; i < 1000; i++)
{
    g.DrawEllipse(Pens.Black, i, i, 50, 50);
}

sw.Stop();
Console.WriteLine($"Время выполнения: {sw.ElapsedMilliseconds} мс");

Практическое задание

Задание 1: Частичная перерисовка

  1. Создайте приложение с кнопкой
  2. Добавьте поле для позиции прямоугольника:
    private int rectX = 10;
    private int rectY = 10;
  3. В обработчике Click кнопки:
    private void button1_Click(object sender, EventArgs e)
    {
        // Инвалидируем только область, где был прямоугольник
        Rectangle oldRect = new Rectangle(rectX, rectY, 100, 50);
        this.Invalidate(oldRect);
    
        // Перемещаем прямоугольник
        rectX += 20;
        rectY += 20;
    
        // Инвалидируем новую область
        Rectangle newRect = new Rectangle(rectX, rectY, 100, 50);
        this.Invalidate(newRect);
    }
  4. В обработчике Paint нарисуйте прямоугольник
  5. Запустите приложение и нажмите кнопку несколько раз

Задание 2: Использование ClipRectangle

  1. Создайте приложение с множеством прямоугольников:
    private List rectangles = new List();
    private Random random = new Random();
    
    public Form1()
    {
        InitializeComponent();
    
        // Создаем 1000 случайных прямоугольников
        for (int i = 0; i < 1000; i++)
        {
            rectangles.Add(new Rectangle(
                random.Next(0, 500),
                random.Next(0, 500),
                random.Next(20, 50),
                random.Next(20, 50)
            ));
        }
    }
    
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        Rectangle clipArea = e.ClipRectangle;
    
        // Рисуем только прямоугольники, пересекающиеся с областью обновления
        foreach (Rectangle rect in rectangles)
        {
            if (clipArea.IntersectsWith(rect))
            {
                g.DrawRectangle(Pens.Blue, rect);
            }
        }
    }
  2. Запустите приложение

Задание 3: SetClip

  1. Попробуйте ограничить область рисования:
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
    
        // Ограничиваем область рисования
        Rectangle clipRect = new Rectangle(100, 100, 200, 200);
        g.SetClip(clipRect);
    
        // Рисуем большой круг — будет видна только часть
        g.DrawEllipse(Pens.Red, 0, 0, 500, 500);
    
        // Рисуем границу области для наглядности
        g.ResetClip();
        g.DrawRectangle(Pens.Black, clipRect);
    }
  2. Запустите приложение

Задание 4: Профилирование

  1. Измерьте время рисования:
    using System.Diagnostics;
    
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        Stopwatch sw = new Stopwatch();
        sw.Start();
    
        // Рисуем 1000 фигур
        for (int i = 0; i < 1000; i++)
        {
            g.DrawRectangle(Pens.Blue, i, i % 300, 20, 20);
        }
    
        sw.Stop();
        Console.WriteLine($"Время выполнения: {sw.ElapsedMilliseconds} мс");
    }
  2. Запустите приложение и посмотрите в Output
💡 Совет: Используйте Invalidate(region) для оптимизации, если вы знаете, какая область изменилась. Это значительно ускоряет перерисовку.

Проверка знаний

Ответьте на вопросы, чтобы проверить, насколько хорошо вы усвоили материал:

1. Как перерисовать только определенную область формы?
  • this.Invalidate()
  • this.Invalidate(region)
  • this.Refresh()
  • this.Update()
2. Что делает метод SetClip?
  • Устанавливает цвет
  • Устанавливает шрифт
  • Ограничивает область рисования
  • Устанавливает прозрачность
3. Что такое ClipRectangle?
  • Область экрана, которая требует перерисовки
  • Прямоугольник для рисования
  • Свойство формы
  • Метод оптимизации
4. Какой метод сбрасывает ограничение области рисования?
  • ClearClip()
  • RemoveClip()
  • Reset()
  • ResetClip()
5. В каком пространстве имен находится Stopwatch?
  • System
  • System.Diagnostics
  • System.Timers
  • System.Threading
6. Какой метод Rectangle проверяет пересечение с другим прямоугольником?
  • Contains()
  • IntersectsWith()
  • Overlaps()
  • Intersects()
7. Что делает метод Invalidate() без параметров?
  • Перерисовывает всю форму
  • Перерисовывает только видимую область
  • Не перерисовывает ничего
  • Удаляет форму
8. Какой класс используется для измерения времени выполнения?
  • Timer
  • PerformanceCounter
  • TimeSpan
  • Stopwatch