Click here to Skip to main content
15,878,871 members
Articles / Programming Languages / C#
Article

Flicker Free Drawing in C#

Rate me:
Please Sign up or sign in to vote.
3.96/5 (24 votes)
31 Mar 20024 min read 263.3K   6.2K   86   22
An article to describe the ways to avoid flicker when drawing.
Image 1

Introduction

Flicker free animated drawing had been a very hot issue with Win32 and MFC. Many excellent articles are available to explain the techniques to get a flicker free animated effect. As many of the reader know that most popular technique has been to use off-screen DC (device context) to do the entire complex drawing and then copying this off-screen DC to the screen DC directly. This technique is also known as double buffering.

C# is projected by Microsoft as the future for C++ programmers. So like many other C++ programmers, I used some of my spare time to play around with C# to have a feel of it. A few days back I was trying to write an application in C# to simulate an analog clock. After establishing a base frame work and seeing my clock work (with flicker of course) I was excited to use the old double buffering technique to let my clock animate smoothly. But my first dilemma was when I could not find functions like CreateCompatibleDC, CreateCompatibleBitmap and SelectObject etc. So I started to search around MSDN and studied the Graphics class. After some research I was able to find two ways to produce smooth animated effects and these techniques I will be explaining below.

Double-buffering technique the old way

I was glad to know that there was a way in C# to use the old Win32 techniques for smooth animation. Although one cannot find direct implementation for functions like CreateCompatibleDC, CreateCompatibleBitmap and SelectObject, but there is an indirect way to use these functions for your GDI+ device context. The idea is to let C# know that you will be using some functions from an unmanaged dll. You can import a function that is exported by a dll using the DllImport attribute. The detailed documentation for DllImport can be found in .NET documentation. In short with the help of DllImport we can tell the compiler that we will be using the specified function from the specified dll. For example,

C#
[DllImport("msvcrt.dll")] 
public static extern int puts(string c);

The above declaration will declare the function named puts with static and extern attributes and the actual implementation of this function will be imported from msvcrt.dll. I used DllImport to import all the necessary functions from gdi32.dll. To keep things managed, I declared a separate class to import all such functions. The code below shows the actual implementation for this class

C#
/// <summary>
/// Summary description for Win32Support.
/// Win32Support is a wrapper class that imports all the 
/// necessary functions that are used in old
/// double-buffering technique for smooth animation.
/// </summary>
public class Win32Support
{
    /// <summary>
    /// Enumeration to be used for those Win32 function 
    /// that return BOOL
    /// </summary>
    public enum Bool 
    {
        False = 0,
        True
    };

    /// <summary>
    /// Enumeration for the raster operations used in BitBlt.
    /// In C++ these are actually #define. But to use these
    /// constants with C#, a new enumeration type is defined.
    /// </summary>
    public enum TernaryRasterOperations
    {
        SRCCOPY = 0x00CC0020, // dest = source
        SRCPAINT = 0x00EE0086, // dest = source OR dest
        SRCAND = 0x008800C6, // dest = source AND dest
        SRCINVERT = 0x00660046, // dest = source XOR dest
        SRCERASE = 0x00440328, // dest = source AND (NOT dest)
        NOTSRCCOPY = 0x00330008, // dest = (NOT source)
        NOTSRCERASE = 0x001100A6, // dest = (NOT src) AND (NOT dest)
        MERGECOPY = 0x00C000CA, // dest = (source AND pattern)
        MERGEPAINT = 0x00BB0226, // dest = (NOT source) OR dest
        PATCOPY = 0x00F00021, // dest = pattern
        PATPAINT = 0x00FB0A09, // dest = DPSnoo
        PATINVERT = 0x005A0049, // dest = pattern XOR dest
        DSTINVERT = 0x00550009, // dest = (NOT dest)
        BLACKNESS = 0x00000042, // dest = BLACK
        WHITENESS = 0x00FF0062, // dest = WHITE
    };

    /// <summary>
    /// CreateCompatibleDC
    /// </summary>
    [DllImport("gdi32.dll", ExactSpelling=true, 
        SetLastError=true)]
    public static extern IntPtr CreateCompatibleDC(IntPtr hDC);

    /// <summary>
    /// DeleteDC
    /// </summary>
    [DllImport("gdi32.dll", ExactSpelling=true, 
        SetLastError=true)]
    public static extern Bool DeleteDC(IntPtr hdc);

    /// <summary>
    /// SelectObject
    /// </summary>
    [DllImport("gdi32.dll", ExactSpelling=true)]
    public static extern IntPtr SelectObject(IntPtr hDC, 
        IntPtr hObject);

    /// <summary>
    /// DeleteObject
    /// </summary>
    [DllImport("gdi32.dll", ExactSpelling=true, 
        SetLastError=true)]
    public static extern Bool DeleteObject(IntPtr hObject);

    /// <summary>
    /// CreateCompatibleBitmap
    /// </summary>
    [DllImport("gdi32.dll", ExactSpelling=true, 
        SetLastError=true)]
    public static extern IntPtr CreateCompatibleBitmap(
        IntPtr hObject, int width, int height);

    /// <summary>
    /// BitBlt
    /// </summary>
    [DllImport("gdi32.dll", ExactSpelling=true, 
        SetLastError=true)]
    public static extern Bool BitBlt(
        IntPtr hObject, 
        int nXDest, int nYDest, 
        int nWidth, int nHeight, 
        IntPtr hObjSource, int nXSrc, int nYSrc, 
        TernaryRasterOperations dwRop);
}

Now I can use this Win32Support class to use my old techniques. The code snippet below shows how to create a memory DC with help of Win32Support from within your Form class.

C#
Graphics memDC; 
Bitmap memBmp; 
memBmp = new Bitmap(this.Width, this.Height); 

Graphics clientDC = this.CreateGraphics(); 
IntPtr hdc = clientDC.GetHdc(); 
IntPtr memdc = Win32Support.CreateCompatibleDC(hdc); 
Win32Support.SelectObject(memdc, memBmp.GetHbitmap()); 
memDC = Graphics.FromHdc(memdc); 
clientDC.ReleaseHdc(hdc);

One important point to note here is that every call to the function Graphics.GetHdc on some DC must be paired with the call to Graphics.ReleaseHdc. this is what MSDN has to say about this issue "Calls to the GetHdc and ReleaseHdc methods must appear in pairs. During the scope of a GetHdc- ReleaseHdc method pair, you usually make only calls to GDI functions. Calls in that scope made to GDI+ methods of the Graphics object that produced the hdc parameter fail with an ObjectBusy error. Also, GDI+ ignores any state changes made to the Graphics object of the hdc parameter in subsequent operations."

Once you have memDC, you can use it for off screen drawing and then we will use BitBlt to copy the contents of memDC to actual screen DC.

C#
Graphics clientDC = this.CreateGraphics(); 
// do drawing in memDC 
// do drawing in memDC 
// do drawing in memDC 
IntPtr hdc = clientDC.GetHdc(); 
IntPtr hMemdc = memDC.GetHdc(); 

// transfer the bits from memDC to clientDC 
Win32Support.BitBlt(hdc, 0, 0, this.Width, this.Height, 
    hMemdc, 0, 0, Win32Support.TernaryRasterOperations.SRCCOPY); 

clientDC.ReleaseHdc(hdc); 
memDC.ReleaseHdc(hMemdc);

This will have dramatic effect on the animation that you have been trying to produce.

The sample application uses this technique when you click the "Offscreen Drawing Using BitBlt" radio button.

Double-buffering technique the .NET way

Luckily we can achieve the same goal without any direct help from Win32 API. Image rendering in .NET is very simple and efficient compared to MFC. There are two functions in Graphics class to render your Image object on screen, these are DrawImage and DrawImageUnscaled. What makes these functions important is the fact that .NET always uses BitBlt in background to render the image on DC. So if we are able to do our off-screen drawing in an Image object, we can use these functions to render this object directly to DC and have the smooth animated effects.

The technique is same, but the way to implement it differs a little bit. In the code fragment below, we are using a Bitmap object to do our off-screen drawing. In order to draw on some Image object, it must be attached to a Graphics object. We can create a new Graphics object from an Image object using the static member function, of Graphics, named FromImage. Once we get a Graphics object from some Image object, any drawing done on this Graphics object will actually be changing the Image.

C#
Bitmap offScreenBmp; 
Graphics offScreenDC; 
offScreenBmp = new Bitmap(this.Width, this.Height); 
offScreenDC = Graphics.FromImage(offScreenBmp);

Provided that we have created offScreenDC as per shown in example above, we can implement the double buffering technique as per the code fragment below.

C#
Graphics clientDC = this.CreateGraphics(); 
// do drawing in offScreenDC 
// do drawing in offScreenDC 
// do drawing in offScreenDC 
clientDC.DrawImage(offScreenBmp, 0, 0);

I will recommend this technique as it does not involve any call to unmanaged code and is simpler in nature.

The sample application uses this technique when you click the "Offscreen Drawing Using Image" radio button.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Bahrain Bahrain
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralCouple ideas to try to get rid of flicker Pin
reo6311-Sep-08 15:17
reo6311-Sep-08 15:17 
Generalflick in CF Pin
vnt214-Feb-08 13:20
vnt214-Feb-08 13:20 
GeneralRe: flick in CF Pin
mkradjel28-Jul-08 3:17
mkradjel28-Jul-08 3:17 
Questionbitmap operations Pin
nourlaban12-Feb-06 0:25
nourlaban12-Feb-06 0:25 
GeneralIt does not work! Pin
NITH6-Dec-04 0:56
NITH6-Dec-04 0:56 
General... Pin
User 104557519-Jun-04 1:12
User 104557519-Jun-04 1:12 
GeneralFalling Raster Pin
ob63515-Jan-04 9:54
ob63515-Jan-04 9:54 
GeneralGDI+ in MFC/ATL/WTL Pin
TW15-Apr-03 23:43
TW15-Apr-03 23:43 
QuestionNo Flicker?!? Pin
Mark Pitman28-Mar-03 5:53
Mark Pitman28-Mar-03 5:53 
GeneralCompatible Bitmap Pin
Anonymous19-Dec-02 20:39
Anonymous19-Dec-02 20:39 
GeneralWS_EX_COMPOSITED Pin
Swinefeaster11-Jun-02 20:29
Swinefeaster11-Jun-02 20:29 
GeneralListView Pin
4-Jun-02 14:28
suss4-Jun-02 14:28 
QuestionHow to implement Flicker Free Drawing using GDI+ in VC70 Pin
TeleStar6-Apr-02 10:14
TeleStar6-Apr-02 10:14 
AnswerRe: How to implement Flicker Free Drawing using GDI+ in VC70 Pin
Mazdak17-Apr-02 1:54
Mazdak17-Apr-02 1:54 
GeneralErmm Pin
Matthew Adams1-Apr-02 1:34
professionalMatthew Adams1-Apr-02 1:34 
GeneralRe: Ermm Pin
James T. Johnson1-Apr-02 18:13
James T. Johnson1-Apr-02 18:13 
GeneralRe: Ermm Pin
Rehan Nadeem1-Apr-02 23:44
Rehan Nadeem1-Apr-02 23:44 
GeneralRe: Ermm Pin
Rüpel8-Apr-02 22:13
Rüpel8-Apr-02 22:13 
GeneralRe: Ermm Pin
Rüpel9-Apr-02 0:51
Rüpel9-Apr-02 0:51 
GeneralRe: Ermm (solution) Pin
10-Apr-02 5:03
suss10-Apr-02 5:03 
GeneralRe: Ermm Pin
User 4864831-Oct-02 20:29
User 4864831-Oct-02 20:29 
GeneralRe: Ermm Pin
Anonymous26-Aug-02 17:50
Anonymous26-Aug-02 17:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.