Flicker Free API Drawing

Improve the quality of controls using GDI drawing commands with this easy to use class.

Flicker Free Drawing Demonstration

VB offers a way to reduce flicker when drawing a control through its AutoRedraw property. When AutoRedraw is set, VB creates an Off-Screen buffer to draw into, and only transfers this to the drawing surface when the Refresh method is called. Whilst this works well (ish), it doesn't help if you need to draw graphics into some other drawing surface, particularly ones passed to you by the Windows API. This article provides a simple class which allows you to create an Off-Screen buffer you can use for any drawing purposes.

About Flicker Free Drawing and Off-Screen Buffers

The concept of an off-screen buffer is simple: whenever something gets drawn to the screen in Windows it gets there by virtue of being drawn into a Device Context which contains some sort of bitmap to hold the data. Everything that you draw onto a device context that is part of the display is immediately visible on screen: whilst that's good for immediacy it can very easily result in flicker depending on how you draw to it. For example, generally its a whole lot easier to clear the background of a complex graphic display and then redraw everything than it is to work out what all the changes are and then only redraw the changes. If you do this directly onto a device context that's visible to the screen, flicker occurs because you see the background of all the items being cleared prior to them being refreshed. You will see this happen if you try the sample project from the download with the "Use Mem DC" CheckBox unchecked: it flickers a lot. If you check the box, then the drawing suddenly becomes smooth, because all of the disruptive drawing (such as clearing the background) is done offscreen and only transferred at the last moment.

To prevent this flickering, what you'd like to do is to do all of the drawing somewhere off-screen and then transfer the whole lot to the screen more quickly than the screen refreshes. Windows allows you to do both of these things: you can create a device context which isn't part of the screen to draw on and you can use the BitBlt GDI API call to transfer the contents of one device context to another extremely quickly.

This is how the VB AutoRedraw property operates behind the scenes for Forms, PictureBoxes and UserControls. However, as is usually the way with VB the implementation of AutoRedraw is hidden and can't be reused for your own purposes. So if you want to implement this feature for anything that isn't a VB Form, PictureBox or UserControl then you need to write some custom code.

Techno City

So on to writing an equivalent off-screen buffer. The basic requirements are:

  • Ability to create a suitable Device Context.
  • Ability to create a bitmap of the right width and height to draw onto.
  • Ability to draw to the buffer.
  • Ability to copy the contents of the buffer to the screen DC quickly.

The download provides a class which provides all of these facilities in an easy-to-use object called pcMemDC. Before we look at how it works, first we'll look at the handful of methods and properties required to use it.

Ingredients: Water-Malt-Hops-Yeast

  • Width
    Gets or Sets the width of the offscreen buffer.
  • Height
    Gets or Sets the height of the offscreen buffer.
  • hDC
    Gets the device context of the offscreen buffer to draw onto using GDI methods.
  • Draw
    Transfers the contents of the offscreen buffer to another device context using BitBlt
  • CreateFromPicture
    Helper method: if you have a VB StdPicture object then an offscreen buffer of the same dimensions is created containing the same image.

Sex In The City

The class itself is simple enough, so into how it works. There are various ways to create an offscreen DC, but the simplest is to use the Desktop as a basis, since otherwise you need to work out what the colour depth of the display is. By this method, we get a handle to the desktop device context and then create a Compatible device context:

Private Declare Function CreateDC Lib "gdi32" _
   Alias "CreateDCA" _
   (ByVal lpDriverName As String, ByVal lpDeviceName As String, _
   ByVal lpOutput As String, lpInitData As Any) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" _
   (ByVal hdc As Long) As Long

   ...

   lhDCC = CreateDC("DISPLAY", "", "", ByVal 0&)
   If Not (lhDCC = 0) Then
      m_hDC = CreateCompatibleDC(lhDCC)
      If Not (m_hDC = 0) Then
	.. 
      End If
   End If

Once you have a DC, then you need a bitmap selected into the device context to actually draw into. Again, we can create an object which is compatible with the desktop and simplify the calls:

Private Declare Function CreateCompatibleBitmap Lib "gdi32" ( _
       ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long _
    ) As Long

    ...
         m_hBmp = CreateCompatibleBitmap(lhDCC, Width, Height)
         If Not (m_hBmp = 0) Then
            m_hBmpOld = SelectObject(m_hDC, m_hBmp)
            If Not (m_hBmpOld = 0) Then
               m_lWidth = Width
               m_lHeight = Height
               DeleteDC lhDCC
               Exit Sub
            End If
         Else
            DeleteDC lhDCC
         End If

That is about it for creating the offscreen DC, but you may have noticed the calls to DeleteDC. One of the things that's most important to using GDI successfully is that whenever you get a handle to an object from the API, you must always delete it again. So wherever a GDI object is created, the class always has a corresponding call to delete it. Here's the destroy call which is performed whenever you change the size of the offscreen buffer, or the object terminates:

   If Not m_hBmpOld = 0 Then
      SelectObject m_hDC, m_hBmpOld
      m_hBmpOld = 0
   End If
   If Not m_hBmp = 0 Then
      DeleteObject m_hBmp
      m_hBmp = 0
   End If
   If Not m_hDC = 0 Then
      DeleteDC m_hDC
      m_hDC = 0
   End If
   m_lWidth = 0
   m_lHeight = 0

For performance, the class is set up to only recreate the internal bitmap when you make the offscreen buffer larger than it was previously. This also makes it a lot easier to use the class, since you simply set the Width and Height properties regardless of whether they have been set before.

Sheep In The City

The sample application provided with the download aims to show how you can easily prevent the most basic GDI code from flickering when drawing. The code itself creates a number of Rectangular objects which move around the screen in response to a timer in an old-school BreakOut fashion (I admit it is fairly unlikely that a real control will draw this way, but anyway). Since as usual it is difficult to determine the overlaps between the objects and which areas of the background will be revealed by moving any object, the drawing is accomplished by first clearing the background and then drawing each of the rectangles in turn. When this is done without using the pcMemDC class, the drawing flickers a lot, both from the background being repainted and from the overlap between the object rectangles. Once the class is turned on however, you should see that the flicker is completely eliminated.