Глюки рисования при использовании CreateGraphics вместо обработчика событий Paint для пользовательского рисования
Я написал приложение для Windows Forms, где я рисую на Panel
с помощью Control.CreateGraphics()
, Вот что мой Form
выглядит при запуске:
Пользовательский рисунок выполняется на верхней панели в 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
Необходимо изменить в зависимости от размера элемента управления, и только новые части элемента управления становятся недействительными при расширении окна, необходимо запросить полную перерисовку при каждом изменении размера.