Flicker Free API Drawing
Improve the quality of controls using GDI drawing commands with this easy to use class.
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.
So on to writing an equivalent off-screen buffer. The basic requirements are:
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.
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.