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:

blank form with Draw!  button

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:

form with green filled ellipse

Success!

But when I shrink the shape by dragging the corner ...

same form, shrunk to partially hide ellipse

... and expand it back to its original size,

form with missing graphics on right and bottom

some of what I drew is gone!

This also happens when I drag part of the window onto the screen ...

form partially offscreen

... and drag it back to the screen:

form back onscreen but with half of graphics missing

If I minimize the window and restore it, the entire image is removed:

blank form like first image

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.

+3


source to share


1 answer


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.

+6


source







All Articles