I've written a Windows Forms app where I do custom drawing on a Panel
using Control.CreateGraphics()
. Here's what my Form
looks like at startup:
The custom drawing is performed on the top panel in the Click
event handler of the "Draw!" 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 a click on drawButton
, the form looks like this:
Success!
But when I shrink the form by dragging a corner...
...and expand it back to its original size,
part of what I drew is gone!
This also happens when I drag part of the window offscreen...
...and drag it back onscreen:
If I minimize the window and restore it, the whole image is erased:
What is causing this? How can I make it so the graphics I draw are persistent?
Note: I've created this self-answered question so I have a canonical Q/A to direct users to, as this is a common scenario that's hard to search for if you don't already know the cause of the problem.
Don't do your drawing in response to a one-time UI event with Control.CreateGraphics
. Instead, register a Paint
event handler for the control on which you want to paint, and do your drawing with the Graphics
object passed via the PaintEventArgs
.
If you want to paint only after a button click (for example), in your Click
handler, set a boolean flag indicating that the button has been clicked and then call Control.Invalidate()
. Then do your rendering conditionally in the Paint
handler.
Finally, if your control's contents should change with the size of the control, register a Resize
event handler and call Invalidate() there too.
Example 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();
}
Take a look at the documentation for Control.CreateGraphics:
The Graphics object that you retrieve through the CreateGraphics method should not normally be retained after the current Windows message has been processed, because anything painted with that object will be erased with the next WM_PAINT message.
Windows doesn't take responsibility for retaining the graphics you draw to your Control
. Rather, it identifies situations in which your control will require a repaint and informs it with a WM_PAINT message. Then it's up to your control to repaint itself. This happens in the OnPaint
method, which you can override if you subclass Control
or one of its subclasses. If you're not subclassing, you can still do custom drawing by handling the public Paint
event, which a control will fire near the end of its OnPaint
method. This is where you want to hook in, to make sure your graphics get redrawn every time the Control
is told to repaint. Otherwise, part or all of your control will be painted over to the control's default appearance.
Repainting happens when all or part of a control is invalidated. You can invalidate the entire control, requesting a full repaint, by calling Control.Invalidate()
. Other situations may require only a partial repaint. If Windows determines that only part of a Control
needs to be repainted, the PaintEventArgs
you receive will have a non-empty ClipRegion
. In this situation, your drawing will only affect the area in the ClipRegion
, even if you try to draw to areas outside that region. This is why the call to drawPanel.Invalidate()
was required in the above example. Because the appearance of drawPanel
needs to change with the size of the control and only the new parts of the control are invalidated when the window is expanded, it's necessary to request a full repaint with each resize.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With