Custom Drawing Controls in C# – Manual Double Buffering

I feel like this article is about four years too late. All the cool kids have moved on to WPF or something really awesome, like XNA, for their graphics needs. However, there are still a lot of us playing around in the .NET 2.0 mucky muck for various reasons. Of those still doing .NET 2.0 / WinForms / GDI+ programming, I’m surprised to see how many people draw their custom controls the wrong way. Yep, I said “wrong.” I blame the majority of the C# books out there. Most of them, in the chapter on System.Drawing, just tell everyone to draw their stuff in OnPaint() or in response to a paint event. PLEASE, STOP DOING THIS. I’ll explain why.

One should not have a lot going on in one’s OnPaint method. It should be very small, very straight forward. In 95% of my custom controls, my paint event looks something like this:

1
2
3
4
5
protected override void OnPaint(PaintEventArgs e)
{
    if (!isDisposing && backbufferGraphics != null)
        backbufferGraphics.Render(e.Graphics);
}

That’s it. Just two lines. Here’s what’s going on. OnPaint is the .NET equivalent of the Win32 WM_PAINT message being raised. It happens when a region of your control is “invalidated.” Invalidation occurs when Windows thinks your control needs to be redrawn for some reason. Examples include your control being resized, another window moving in front of it, your window losing and regaining focus, etc. Because OnPaint() is called so frequently and unpredictably, it is not wise to have a lot of complex and CPU-intensive operations going on inside of it.

You may be wondering what we do instead. You be smart and efficient about it. Because the drawing logic is often complex and expensive, you’ll want to do it only as often as necessary. This means having an off-screen buffer that you draw to. When it comes time to actually paint your control on to the screen, your control can just go take a look at our off-screen buffer and copy it to the display. Pretty simple. Now, there is added complexity when you consider that you may be drawing to this buffer at the exact same time your control needs to draw it to the screen. In this scenario, your control would have to site around and wait with an invalidated region until you’ve finished drawing to your buffer. What this usually looks like to the end user is an irritating flicker on the control. The flicker they see is a rapid switch between invalidated control regions (which are usually painted pure white, black, or some other random bit of color) and the drawn buffer.

A quick look around the net will reveal that the solution to flicker is “double buffering.” Now, mind you, none of this is a new concept. People have been doing this for about as long as computer graphics have existed. The difference is, it’s a lot easier now. .NET literally makes it as easy as setting a property on your forms or controls. Beyond that, a programmer used to have to actually manage two buffers manually and swap between them. This is more automated with the introduction of the BufferedGraphics and BufferedGraphicsContext classes. Basically, you draw to the BufferedGraphics class, and it takes care of the double buffering for you. Here is an example constructor and buffer creation routine.

Constructor setting up the form’s double buffering properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public DoubleBufferedControl()
{
    InitializeComponent();

    // Set the control style to double buffer.
    this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
    this.SetStyle(ControlStyles.SupportsTransparentBackColor, false);
    this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

    // Assign our buffer context.
    backbufferContext = BufferedGraphicsManager.Current;
    initializationComplete = true;

    RecreateBuffers();

    Redraw();
}

And here is an example of creating the buffers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void RecreateBuffers()
{
    // Check initialization has completed so we know backbufferContext has been assigned.
    // Check that we aren't disposing or this could be invalid.
    if (!initializationComplete || isDisposing)
    return;

    // We recreate the buffer with a width and height of the control. The "+ 1"
    // guarantees we never have a buffer with a width or height of 0.
    backbufferContext.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);

    // Dispose of old backbufferGraphics (if one has been created already)
    if (backbufferGraphics != null)
        backbufferGraphics.Dispose();

    // Create new backbufferGrpahics that matches the current size of buffer.
    backbufferGraphics = backbufferContext.Allocate(this.CreateGraphics(),
    new Rectangle(0, 0, Math.Max(this.Width, 1), Math.Max(this.Height, 1)));

    // Assign the Graphics object on backbufferGraphics to "drawingGraphics" for easy reference elsewhere.
    drawingGraphics = backbufferGraphics.Graphics;

    // This is a good place to assign drawingGraphics.SmoothingMode if you want a better anti-aliasing technique.

    // Invalidate the control so a repaint gets called somewhere down the line.
    this.Invalidate();
}

Now, there is one critical place where we make sure to call RecreateBuffers(). That is when the control is resized. The buffers we create in RecreateBuffers() are all sized to match the control’s size. If the buffer wasn’t the size of the control, the control would repaint with a large region of invalid graphics. So, just make sure you have a call to RecreateBuffers in OnResize for your control:

1
2
3
4
5
6
protected override void OnResize(EventArgs e)
{
    base.OnResize(e);
    RecreateBuffers();
    Redraw();
}

That’s pretty much all you need to get a custom double buffered control going. You may have noted that I made a few calls to “Redraw().” Redraw() is where I put my actual drawing logic. What goes in Redraw() is the meat of how you render your control. Just remember, call it only when needed. Don’t go and put the call to Redraw() in OnPaint(). That would be against everything this article tried to teach. This method of rendering is very powerful and I’ve created a number of real-time fast-rendering controls with this mechanism. I’ve been using this method for many years now, so it is tried and true. One of the first times I started double buffering on a custom control was for a custom chart and spectrogram I made for some spectrum analyzer software I was working on. Here is a screenshot:

Realtime Graph and Spectrogram

Realtime Graph and Spectrogram

Thanks for reading. If it helps anyone, I’ve attached a very simple sample project below that contains all the code I’ve shown above. If you have any questions or comments, feel free to use the comments below.

Download Sample Project

foo_g15lcd 0.3b

I was looking at my bandwidth logs, something I do every year and a half or so, and noticed a lot of my traffic was for something I uploaded a long time ago. Let’s take a trip back.

Title screen - before a song is played.

Title screen - before a song is played.

The year was 2006. Bell-bottoms were the height of fashion and the telephone had just caught on (we called it the “harmonic telegraph” back then). Anyone who was anyone was using the foobar2000 media player. And anyone who was anyone, but was also a huge geek, was using it with their brand new Logitech G15 keyboard. However, through some perverseness in the space-time continuum, these two things didn’t work together.

The song playing display.

The song playing display.

Adrenaline pumping, I ducked into the nearest phone booth, donned my cape, and flexed my mighty C++ muscles (which have now all atrophied). I tossed all sorts of code together, not caring who was hurt in the process. After a good three, maybe four, hours of blood, sweat, and tears, I had a plug-in that made Foobar2000 songs show up on a G15 keyboard.

Here we are, almost 3 years later, and guess what, some people are still using this thing! Weird! I know! Turns out, however, that because I haven’t recompiled it with the latest SDK, it has some real trouble working with the latest version of Foobar. So I’ve recompiled it! Grab it below:

Recompiled for Foobar 1.0!:
Download Foo_G15LCD Component Version 0.3c

PS: It looks like some cool dude by the name of “ectotropic” has gone ahead and obsoleted my sorry ass. He wrote a plug-in that not only does what mine did, but does all sorts of cool things that I wanted to do (like a spectrum analyzer display!). I encourage you to check out his plug-in. I don’t really use a G15 keyboard anymore, so I haven’t tried it, but it certainly looks cool!