Глюки рисования при использовании CreateGraphics вместо обработчика событий Paint для пользовательского рисования

Я написал приложение для Windows Forms, где я рисую на Panel с помощью Control.CreateGraphics(), Вот что мой Form выглядит при запуске:

бланк с Draw! кнопка

Пользовательский рисунок выполняется на верхней панели в Click обработчик событий "Draw!" кнопка. Вот мой обработчик нажатия кнопки:

private void drawButton_Click(object sender, EventArgs e)
{
    using (Graphics g = drawPanel.CreateGraphics())
    {
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        g.Clear(Color.White);
        Size size = drawPanel.ClientSize;
        Rectangle bounds = drawPanel.ClientRectangle;
        bounds.Inflate(-10, -10);
        g.FillEllipse(Brushes.LightGreen, bounds);
        g.DrawEllipse(Pens.Black, bounds);
    }
}

После нажатия на drawButtonформа выглядит так:

форма с зеленым заполненным эллипсом

Успех!

Но когда я сжимаю форму, перетаскивая угол...

та же форма, сжатая, чтобы частично скрыть эллипс

... и расширить его до первоначального размера,

форма с отсутствующей графикой справа и снизу

часть того, что я нарисовал, исчезла!

Это также происходит, когда я перетаскиваю часть окна вне экрана...

Форма частично за кадром

... и перетащите его обратно на экран:

обратно на экране, но половина графики отсутствует

Если я сверну окно и восстановлю его, все изображение будет стерто:

бланк как первое изображение

Чем это вызвано? Как я могу сделать так, чтобы графика, которую я рисую, была постоянной?

Примечание. Я создал этот вопрос с автоответчиком, поэтому у меня есть канонические вопросы и ответы, на которые следует направлять пользователей, поскольку это распространенный сценарий, который сложно найти, если вы еще не знаете причину проблемы.

1 ответ

TL;DR:

Не выполняйте рисование в ответ на одноразовое событие пользовательского интерфейса с Control.CreateGraphics, Вместо этого зарегистрируйте Paint обработчик событий для элемента управления, на котором вы хотите рисовать, и сделать ваш рисунок с Graphics объект передан через PaintEventArgs,

Если вы хотите рисовать только после нажатия кнопки (например), в вашем Click обработчик, установите логический флаг, указывающий, что кнопка была нажата, и затем вызовите Control.Invalidate(), Затем сделайте рендеринг условно в Paint обработчик.

Наконец, если содержимое вашего элемента управления должно меняться в зависимости от размера элемента управления, зарегистрируйте Resize обработчик событий и вызов Invalidate() тоже там.

Пример кода:

private bool _doCustomDrawing = false;

private void drawPanel_Paint(object sender, PaintEventArgs e)
{
    if (_doCustomDrawing)
    {
        Graphics g = e.Graphics;
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
        g.Clear(Color.White);
        Size size = drawPanel.ClientSize;
        Rectangle bounds = drawPanel.ClientRectangle;
        bounds.Inflate(-10, -10);
        g.FillEllipse(Brushes.LightGreen, bounds);
        g.DrawEllipse(Pens.Black, bounds);
    }
}

private void drawButton_Click(object sender, EventArgs e)
{
    _doCustomDrawing = true;
    drawPanel.Invalidate();
}

private void drawPanel_Resize(object sender, EventArgs e)
{
    drawPanel.Invalidate();
}

Но почему? Что я делал не так, и как это исправить?

Взгляните на документацию по Control.CreateGraphics:

Объект Graphics, который вы извлекаете с помощью метода CreateGraphics, обычно не следует сохранять после обработки текущего сообщения Windows, поскольку все, что было нарисовано этим объектом, будет удалено при следующем сообщении WM_PAINT.

Windows не несет ответственности за сохранение графики, которую вы рисуете на свой Control, Скорее, он идентифицирует ситуации, в которых ваш элемент управления потребует перекраски, и информирует об этом сообщением WM_PAINT. Тогда вам решать перекрасить себя. Это происходит в OnPaint метод, который вы можете переопределить, если вы подкласс Control или один из его подклассов. Если вы не создаете подклассы, вы все равно можете делать собственные рисунки, обрабатывая общедоступные Paint событие, которое управление сработает в конце своего OnPaint метод. Здесь вы хотите подключиться, чтобы убедиться, что ваша графика перерисовывается каждый раз, когда Control сказано перекрасить. В противном случае часть или весь ваш элемент управления будет закрашен до его внешнего вида по умолчанию.

Перекрашивание происходит, когда все или часть элемента управления признаны недействительными. Вы можете сделать недействительным весь элемент управления, запросив полную перерисовку, вызвав Control.Invalidate(), В других ситуациях может потребоваться только частичная перекраска. Если Windows определяет, что только часть Control необходимо перекрасить, PaintEventArgs вы получите не пустой ClipRegion, В этой ситуации ваш рисунок будет влиять только на область в ClipRegion, даже если вы попытаетесь нарисовать в области за пределами этого региона. Вот почему призыв к drawPanel.Invalidate() требовалось в приведенном выше примере. Потому что появление drawPanel Необходимо изменить в зависимости от размера элемента управления, и только новые части элемента управления становятся недействительными при расширении окна, необходимо запросить полную перерисовку при каждом изменении размера.

Другие вопросы по тегам