Drawing errors when using CreateGraphics rather than a custom paint event handler
I wrote a Windows Forms application where I am making a custom drawing Panel
using Control.CreateGraphics()
. This is what mine looks like Form
when launched:
The custom drawing is executed on the top panel in the Click
Draw! Event handler . button. Here's my button click handler:
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);
}
}
After clicking the button, the drawButton
form looks like this:
Success!
But when I shrink the shape by dragging the corner ...
... and expand it back to its original size,
some of what I drew is gone!
This also happens when I drag part of the window onto the screen ...
... and drag it back to the screen:
If I minimize the window and restore it, the entire image is removed:
What is causing this? How can I make the graphics I draw consistently?
Note. I created this question with an autoresponder, so I have a canonical Q / A for users to use directly, as this is a common scenario and difficult to find if you don't already know the cause of the problem.
source to share
TL; DR:
Don't execute your drawing in response to a one-off UI with Control.CreateGraphics
. Instead, register an event handler Paint
for the control you want to draw on and draw with the object Graphics
passed through PaintEventArgs
.
If you only want to draw after a button is clicked (for example), in your handler Click
, set the boolean flag indicating that the button was clicked and then call Control.Invalidate()
. Then render conditionally in the handler Paint
.
Finally, if your control content needs to change with the size of the control, register an event handler Resize
and call Invalidate () there too.
Sample code:
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();
}
But why? What was I doing wrong and how do I fix it?
Take a look at the documentation for Control.CreateGraphics :
The Graphics object that you retrieve using the CreateGraphics method is usually not persisted after the current Windows message has been processed, because anything drawn by that object will be removed by the next WM_PAINT message.
Windows is not responsible for saving the graphics you draw on Control
. Rather, it identifies situations in which your control will need to be repainted and informs it with a WM_PAINT message. Then it's under your control to repaint yourself. This happens in a method OnPaint
that you can override if you subclass Control
or one of its subclasses. If you are not a subclass, you can still make a custom drawing by calling a public event Paint
that will fire on the control at the end of its method OnPaint
. This is where you want to connect so that your graphics are redrawn every timeControl
it is proposed to redraw. Otherwise, some or all of your control will be colored with the default appearance of the control.
Repainting occurs when all or part of a control is invalid. You can invalidate the entire control by requesting a complete redraw by calling Control.Invalidate()
. In other situations, only partial repainting may be required. If Windows determines that only a portion needs to be repainted Control
, the resulting one PaintEventArgs
will be non-empty ClipRegion
. In this situation, your drawing will only affect the area in ClipRegion
, even if you try to draw areas outside of that region. This is why a call was required in the above example drawPanel.Invalidate()
. Because the appearance drawPanel
has to resize with the size of the control, and only when new controls are invalidated when the window is expanded, a complete redraw should be requested with each resize.
source to share