Draw in a Paint event handler in C#

[paint event handler]

Using images and drawing in the Paint event handler are the two most common ways to display graphics. Drawing in the Paint event handler is easy, but there are a couple of things you should keep in mind when you do so.

First, the e.Graphics parameter gives you the Graphics object on which you should draw. Don’t create your own Graphics object, for example by using the CreateGraphics method. Otherwise the e.Graphics parameter will draw over anything you draw on a Graphics object that you create.

Second, don’t set the form’s BackgroundImage property inside the Paint event handler. Not only is that confusing, but it also makes the form raise its Paint event again so you get a never-ending series of Paint events. Similarly if you’re handling a PictureBox control’s Paint event handler, don’t set the control’s Image or BackgroundImage property.

Either draw in the Paint event handler or use an image but not both.

Third, you should generally assume that the current image contains results of previous drawings. For example, when you enlarge a form, it raises its Paint event but it clips the drawing area so new drawing can only occur on parts of the form that are newly exposed. If you’re drawing a 100×100 circle in the upper left corner, there’s no problem.

In contrast, suppose you’re drawing an ellipse that fills the form. (As this example does.) In that case, the new ellipse doesn’t exactly match up with the previous one when you resize the form. That means parts of the previous drawing may still be visible.

You can make a form automatically redraw itself whenever it resizes by setting its ResizeRedraw property to true when the form loads. This example does that in the following code.

// Redraw on resize.
private void Form1_Load(object sender, EventArgs e)
{
    ResizeRedraw = true;
}

To see why this is important, comment out that line of code and see what happens.

Similarly you should assume pieces of the old image remain if you’re drawing in a PictureBox. Unfortunately the PictureBox doesn’t have a ResizeRedraw property. To redraw a PictureBox when it resizes, catch its Resize event and refresh the control as in the following code.

private void picEllipse_Resize(object sender, EventArgs e)
{
    picEllipse.Refresh();
}

Calling the control’s Refresh method makes it raise its Paint event so the event handler can draw whatever is needed. Calling Refresh also clears the clipping rectangle so the entire PictureBox is redrawn.

This example uses the following Paint event handler to draw an ellipse on the form.

// Draw an ellipse.
private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.White);
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    Rectangle rect = new Rectangle(10, 10,
        this.ClientSize.Width - 20,
        this.ClientSize.Height - 20);
    e.Graphics.FillEllipse(Brushes.Yellow, rect);
    using (Pen thick_pen = new Pen(Color.Red, 5))
    {
        e.Graphics.DrawEllipse(thick_pen, rect);
    }
}

The code clears the Graphics object to give it a white background and sets SmoothingMode to AntiAlias. It then makes a Rectangle to define where the ellipse will go. Using the same Rectangle to fill and outline the ellipse guarantees that the two line up correctly. It fills the ellipse with yellow and then outlines it with a thick red pen.

The program uses the following code to draw an ellipse on its PictureBox when that control raises a Paint event.

private void picEllipse_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.White);
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    Rectangle rect = new Rectangle(10, 10,
        picEllipse.ClientSize.Width - 20,
        picEllipse.ClientSize.Height - 20);
    e.Graphics.FillEllipse(Brushes.Pink, rect);
    using (Pen thick_pen = new Pen(Color.Blue, 5))
    {
        e.Graphics.DrawEllipse(thick_pen, rect);
    }
}

This code is very similar to the code that draws an ellipse on the form.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in drawing, graphics and tagged , , , , , , , , , , , . Bookmark the permalink.

11 Responses to Draw in a Paint event handler in C#

  1. Charles says:

    “Second, don’t set the form’s BackgroundImage property inside the Paint event handler. Not only is that confusing, but it also makes the form raise its Paint event again so you get a never-ending series of Paint events. ”

    Indeed! Thanks for the tip – as you and I discussed, that behavior did confuse me.

  2. Pingback: Draw on a bitmap in C# - C# HelperC# Helper

  3. John says:

    Great little tutorial.
    A slightly different method is to invalidate the picture box, picEllipse, in the Form1_Paint() method which gives the same results.
    i.e.

    private void Form1_Load(object sender, EventArgs e)
    {
        ResizeRedraw = true; // Same as before - nice tip.
    }
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        picEllipse.Invalidate();
    }
    private void picEllipse_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.Clear(Color.White);
        // ... Same as before
    }
    • RodStephens says:

      Yes, that should work as long as the ellipse PictureBox doesn’t need to resize when the form doesn’t. That would be pretty unusual, though. Normally it will only resize if the form does.

  4. John says:

    Good point. I missed that possibility. I will stick to you method in future. Better safe than sorry 🙂

    • RodStephens says:

      Nah. I think your approach is fine except in really unusual circumstances. And you can handle that by refreshing the PictureBox in its Resize event handler.

      Your approach also has the advantage that it keeps the code closer to its “consumer.” In this case it keeps the code that draws in the PictureBox inside the PictureBox’s code and not in the form’s code.

  5. John says:

    Thanks Rod for the reply.
    I did a bit more investigating and added a timer to the form which increased the picturebox width by 10 every 20 seconds, unusual but still possible. My method simply did not work as the Form1_Paint event handler did not get called and so the pictureBox was never invalidated. I also tried anchoring the pictureBox on the top,left and right but not the bottom. When resizing the form vertically with your method, I noticed that the picEllipse_Resize event was not being called, (it would only be called when the form was resized to expose the picture box). This makes your method much more efficient as my method would always result in the picture box being painted even when not needed. Although my method may seem more intuitive your method is not only safer but more efficient. I also tried using the picEllipse_ClientSizeChanged event instead of the picEllipse_Resize and this also seemed to work. Not sure if there is a difference?
    🙂

    • RodStephens says:

      I it should be about the same whether you use Resize or ClientSizeChanged.

      If things ever get that complicated or you need to worry about a few extra redraws because redrawing takes a long time, then you’re probably better off setting the PictureBox’s Image property to a bitmap and not bothering with all of the redrawing. For example, if you’re drawing a complicated street network with 5,000 segments on it, the user might notice the drawing so you’re better off with an image.

  6. Pete says:

    “Either draw in the Paint event handler or use an image but not both.”
    Can you suggest a good way to bypass this limitation?

    I’m using BackgroundImage to display a checkerboard grid, on top of that I’m drawing a selection of alpha blended images (tiles) into the Image, and finally I’m drawing a faint alpha blended grid layout to show where the tile boundaries are directly into the Graphics of the PictureBox.
    I can’t flatten this lot because after manipulating the tile contents I need to copy the relevant Image section back into the original Tile List and it must have transparency and no grid lines or checker pattern in it.

    The problem that lead me here, is after changing the offset of the tiles drawn into the grid, the altered Image does not show up until the *following* paint event.

    • RodStephens says:

      In general you should only use the Paint event if you’re drawing something relatively simple and fast. For anything more complicated I recommend using an image.

      I don’t think I quite understand what you’re trying to do. One thing that might be catching you is that the Paint event only redraws the parts of the image that it thinks has changed so you may have some parts not redrawn depending on why the redraw is happening. If you called the PictureBox’s Refresh method, it should redraw everything.

      It might be less confusing if you set the PictureBox’s Image property and not use the BackgroundImage property. When you need to update the image, you can create a new bitmap initialized from the background image, which you would store in a separate bitmap.

      Bitmap new_bm = new Bitmap(background_bm);

      Then you can draw the tiles and grid lines on top and display the result in the PictureBox. It requires you to use up memory to store the extra bitmap, but you don’t need to worry about the Paint event confusing things.

      • Pete says:

        Thanks for the advice Rod. I was trying to avoid creating a new image by keeping the layers separate, but your solution will work out neater (and this is a PC based tool so I’m not short of memory).

Leave a Reply

Your email address will not be published. Required fields are marked *