Thread: WM_PAINT + back buffer [GDI]

  1. #1
    Registered User
    Join Date
    Aug 2006
    Posts
    74

    Talking WM_PAINT + back buffer [GDI]

    Hello. My first post by the way .... I am basically writing a Solitaire card game by hand just using Win32 API [GDI] for graphics.

    Anyways, I have a few questions regarding the WM_PAINT message and using a back buffer to eliminate flicker. Basically I'm trying to piece this information together from multiple sources (websites; books; etc.) and wouldn't mind some input as to whether I am doing it correctly. i.e. Did i free all resources properly? Ex: I've seen SelectObject(hdcBuffer, bmpBuffer); examples that say you need to store a HBITMAP that gets returned which you should restore at end, but this didn't work for me.

    Relevant code:

    Code:
    case WM_PAINT:
        PAINTSTRUCT paintStruct;    // Hold information about the area to be repainted.
                
        // Load relevant information into the PAINTSTRUCT.
        BeginPaint(windowHandle, &paintStruct);
    
        repaint(paintStruct);    // Call custom function for repainting the window.
    
        // Cleanup.
        EndPaint(windowHandle, &paintStruct);
    break;
    Code:
    void repaint(PAINTSTRUCT &paintStruct)
    {
        // Create a buffer.
        HDC hdcBuffer = CreateCompatibleDC(paintStruct.hdc);    
    
        // Create a bitmap in memory to draw to.
        HBITMAP bmpBuffer = CreateCompatibleBitmap(paintStruct.hdc, paintStruct.rcPaint.right, paintStruct.rcPaint.bottom);
        
    // Select bitmap into the buffer.
        SelectObject(hdcBuffer, bmpBuffer);
        
        HBRUSH background;    // Brush used for painting the background.
    
        // Create a dark green brush.
        background = CreateSolidBrush(RGB(0,155,0));
                
        // Repaint the buffer with the background brush defined above.
        FillRect(hdcBuffer, &paintStruct.rcPaint, background);
    
        // Redraws all relevant cards to the buffer.  Basically draws stuff to hdcBuffer. 
        deck.redrawAll(hdcBuffer,  paintStruct.rcPaint);    
    
        // Copy back buffer into screen device context for display.
        BitBlt(paintStruct.hdc, 0,  0, paintStruct.rcPaint.right, paintStruct.rcPaint.bottom, hdcBuffer, 0,  0, SRCCOPY);
    
        DeleteDC(hdcBuffer);    // Free resources.
        DeleteObject(bmpBuffer); // Free resources.
    
        // Delete brush created earlier.
        DeleteObject(background);
    }
    Last edited by Kurisu33; 08-22-2006 at 03:07 PM.

  2. #2
    Registered User Queatrix's Avatar
    Join Date
    Apr 2005
    Posts
    1,342
    Welcome to the boards,

    I would suggest making your HDCs, HBITMAPs, HBRUSHs, all globals rather than recreating the same varible over and over. And call your CreateSolidBrush() and CreateCompatibleBitmap() in an init function. (As for the CreateCompatibleBitmap() function, use GetDC(hwnd) rather than the PAINTSTRUCT struct.)

  3. #3
    Registered User Queatrix's Avatar
    Join Date
    Apr 2005
    Posts
    1,342
    >> Is it redrawing the entire client window? or just the new parts that weren't visible before?

    Test it out. I thing it's redrawing though.

  4. #4
    Registered User
    Join Date
    Aug 2006
    Posts
    74
    Is it redrawing the entire client window? or just the new parts that weren't visible before?
    Yeah, I think it is redrawing the entire window.. Wasn't sure because walk through debugging was actin funny on my machine but I fixed it.
    ----------------------------------------------------
    Okay, I can do that. Few questions though:

    1. Are the functions like CreateCompatibleDC; CreateSolidBrush; CreateCompatibleBitmap CPU labor intensive? That why it is better to make them global rather than keep creating/deleting them?

    2. My application's window is resizeable both manually by dragging the borders and by using the maximize/minimize buttons therefore the buffer's size (the HBITMAP bmpBuffer) needs to keep resizing. If I create a global variable and just call CreateCompatibleBitmap() in an init function how would i resize the bitmap in every WM_PAINT call? Is SetBitmapDimensionEx() the right function?

    So basically my code would resemble something like this?:

    Code:
    RECT rect;
    HDC hdc, hdcBuffer;
    HBITMAP bmpBuffer;
    
    bool init()
    {
        background = CreateSolidBrush(RGB(0,155,0));
    
        hdc = GetDC(hwnd);
    
        rect = ...(...);
        
        hdcBuffer = CreateCompatibleDC(hdc);
        
        bmpBuffer = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
        
        SelectObject(hdcBuffer, bmpBuffer);
    }
    .......
    
    WM_PAINT
    {
       rect = ...(...);
    
       SetBitmapDimensionEx(...); // Resize buffer BITMAP here.  Do I need to select the new BITMAP into hdcBuffer again?  Can I resize while it is selected into a HDC?
    
       FillRect(hdcBuffer, rect, background);
    
       deck.redrawAll(hdcBuffer, rect);
    
       BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcBuffer, 0, 0, SRCCOPY);
    }
    So would I still need BeginPaint() & EndPaint() since I'm not using the PaintStruct anymore?
    And just of course I would need to clean up globals only on application exit?
    Last edited by Kurisu33; 08-22-2006 at 03:33 PM.

  5. #5
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Yeah, or (as you're using C++, by virtue of your code not being valid C) you could put the handles into a class, where you do the initializing in the class constructor, and cleanup on the destructor, and make an object that survives as long as your main program.

    Depending on how much restructuring of the code you want to do, the best C++ solution would be to make a Window class that contains virtually all the code for your project; then the HDC/HBITMAP would be private data members.

    In general, I prefer not to use globals. They make code less reusable and less encapsulated -- it works fine if you're making one window. My last application uses seven different types of window and it's possible to have more than one window of a given kind. Global variables in such a place would be a nightmare.

    In general, encapsulating windows inside C++ classes is ideal.
    Last edited by Cat; 08-22-2006 at 06:23 PM.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  6. #6
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    If you want ideas about encapsulation, here is my simple way of doing it:

    Code:
    #include "MyWindow.h"
    
    
    // The entry point in windows applications.
    int _stdcall WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int ){
    
    	// The name for our window class
    	std::string name = "MY_SimpleWindow";
    	
    	MyWindow window;
    	// The window is initialized and created
    	window.Initialize(instance, name, "Your Title Goes Here");
    
    	int returnValue = window.MainLoop();
    
    	return returnValue;
    }
    In reality I'd also have a try... catch there to handle any exception that propagated this far.

    As to how things work inside MyWindow:

    MyWindow.Initialize() does the registration of the window class and the creation of the window. It also stores the "this" pointer as the user data for the window (used by the window procedure).

    MyWindow.MainLoop() is the message pump.

    There are 2 functions for the window procedure:

    StaticWindowProc() is, as it implies, static. This is the window procedure for the class. It reads the pointer for the object, if it's not NULL it casts it to an object pointer and calls the "real" WindowProc(); it calls DefWindowProc() if the pointer is NULL (which can mean the initialization hasn't finished yet).

    WindowProc() is the (non-static) message handler; it dispatches all messages to their appropriate member function.

    You can't use a non-static window procedure directly, but by using the window's userdata to store a pointer, you can encapsulate everything in a C++ class. No need for globals, they become member variables.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  7. #7
    Registered User
    Join Date
    Aug 2006
    Posts
    74
    Okay, I'll look into creating a nice Class before my code gets really hectic.

    Also, I've tried altering my code so that instead of constantly creating and destroying HDCs, BITMAPS, etc. for every repaint they last the entire life cycle of my program and are initialized early, but my program is eating up the CPU like 96% now. If I acquire a device context at the birth of my program (i.e. init() function) and do not release it until the end of my program (i.e. freeResources()) is this what is causing my app. to run at 96% CPU?
    Also, I had hoped SetDimensionsEx() would resize the bitmap buffer later, but there turns out to be no function capable of this.

    My altered code from my previous code posted earlier:

    Globals
    Code:
    HDC screenHDC;
    HDC hdcBuffer;
    HBITMAP bmpBuffer;
    HBRUSH backgroundBrush;	// Brush used for painting the background.
    RECT rect;
    Code:
    case WM_PAINT:
    	GetClientRect(windowHandle, &rect);
    			
           // Repaint the window with the background brush defined above.
    	FillRect(hdcBuffer, &rect, backgroundBrush);
    
    	deck.redrawAll(hdcBuffer, rect);	// Redraws all relevant cards to the screen.
    
    	// Copy back buffer into screen device context for display.
    	BitBlt(screenHDC, 0, 0, rect.right, rect.bottom, hdcBuffer, 0, 0, SRCCOPY);
    break;
      
    case WM_DESTROY:
    	freeResources(windowHandle);
    	PostQuitMessage(0);
    break;
    Code:
    bool initialize(HWND windowHandle)
    {
        screenHDC = GetDC(windowHandle);	
    
        GetClientRect(windowHandle, &rect);
    
        hdcBuffer = CreateCompatibleDC(screenHDC);
    	
        // Forced to define a large size 1280x1024 because impossible to resize bmpBuffer later.
        bmpBuffer = CreateCompatibleBitmap(screenHDC, 1280, 1024);
    	
        SelectObject(hdcBuffer, bmpBuffer);
    
        backgroundBrush = CreateSolidBrush(RGB(0,155,0));
    
        return true;
    }
    
    void freeResources(HWND windowHandle)
    {
    
        ReleaseDC(windowHandle, screenHDC);
        DeleteDC(hdcBuffer);	// Free resources.
        DeleteObject(bmpBuffer); // Free resources.
    
        // Delete brush created earlier.
        DeleteObject(backgroundBrush);
    }

  8. #8
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    What IDE (compiler) are you using?


    GDI objects (pens, fonts bmps) were changed to default (cleaned up automatically) in .NET versions of MSVC (>2002)

    MSVC v6 is not .NET and GDI object leaks quickly cause your app/PC to stop drawing.

    A a general rule you MUST return the HDC to the state you found/created/got it.

    In some cases a BMP will not delete if selected into a HDC.

    Open your TaskManager, Click 'VIEW' and 'SELECT COLUMNS'. In the list check 'GDI Objects'.

    Run your app.

    The count of GDI objects should be (mainly) constant (ie not leak).

    Code:
    HBITMAP   hbmpOrig=(HBITMAP)SelectObject(hdcBuffer,hbmpMem);
    //use
    SelectObject(hdcBuffer,hbmpOrig);
    DeleteObject(hbmpMem);
    DeleteDC(hdcBuffer);
    Create the back buffer the same size as the whole screen (display) and then there is no need to resize. Just use the bit you need.

    >>BitBlt(paintStruct.hdc, 0, 0, paintStruct.rcPaint.right, paintStruct.rcPaint.bottom, hdcBuffer, 0, 0, SRCCOPY);


    Beware, the rect in the PaintStruct may NOT start at zero (left,top != 0). Your code assumes that in two places.

    Many paint msgs are generated outside your app by other applications.

    What happens if you drag a MessageBox across one corner of your app?

    In my apps I have two back buffers.

    One is the current screen HDC.
    In the Paint handler this HDC is BitBlt() to the HDC from the PaintStruct using the PaintStruc rect.

    The other is where I construct a new screen (back buffer HDC).

    When an event in the app requires the current screen to change;
    Clear back buffer HDC
    Draw new screen to back buffer HDC
    StretchBlt() or BitBlt() to Screen HDC (swap the buffers)
    Call for a paint (InvaldateRect() then UpdateWindow() so we bypass the OS msg que)

    Sorry about the rambling post, clear as mud??
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  9. #9
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    A paint msg means there is a section of the screen that needs to be redrawn. It is INVALID.

    You must call BeginPaint() / EndPaint() (or similar) to make this area/region VALID.

    Otherwise the WM_PAINT msg will not be removed from the OS que and will get continuiosly processed.

    WM_PAINT msgs are low priority and so can be ignored (and combined) in the que (as the que is only 8 msgs by default).

    This may make it appear that the app is working normally but using excessive CPU.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  10. #10
    Registered User
    Join Date
    Aug 2006
    Posts
    74
    GDI objects (pens, fonts bmps) were changed to default (cleaned up automatically) in .NET versions of MSVC (>2002)

    MSVC v6 is not .NET and GDI object leaks quickly cause your app/PC to stop drawing.

    What IDE (compiler) are you using?
    Yeah I'm using MSVC6 because I have yet to download the Microsoft SDK that would allow me to create Win32 Apps in my MVC++ 2005 Express Edition.

    Are you saying though that by just using my Express edition without using the .NET framework all GDI object cleanup is auto? Or do i have to be using the .NET framework?

    --------------------------------------------------------------------------------------
    Open your TaskManager, Click 'VIEW' and 'SELECT COLUMNS'. In the list check 'GDI Objects'.
    Cool never knew about this.. definitely gonna check it out.
    ---------------------------------------------------------------------------------------
    HBITMAP hbmpOrig=(HBITMAP)SelectObject(hdcBuffer,hbmpMem);
    Ah, I tried this before but wasn't casting the return object into a HBITMAP and thus was getting errors about VOID pointers and I abandoned the whole return to original state concept. Strange that all examples I had seen in my book were
    Code:
    HBITMAP hbmpOrig=SelectObject(hdcBuffer,hbmpMem);
    -----------------------------------------------------------------------------------------
    Beware, the rect in the PaintStruct may NOT start at zero (left,top != 0). Your code assumes that in two places.
    Ah I wondered about that, but never could verify cause step through debugging a WM_PAINT message didn't work too well.
    ------------------------------------------------------------------------------------------
    You must call BeginPaint() / EndPaint() (or similar) to make this area/region VALID.

    Otherwise the WM_PAINT msg will not be removed from the OS que and will get continuiosly processed.
    Ah I get it now..
    ------------------------------------------------------------------------------------------


    Although, I have one remaining question:

    Code:
    HDC hdc1, hdc2;
    
    hdc1 = BeginPaint(windowHandle, &paintStruct);
    hdc2 = GetDC(windowHandle);
    At this point are the variables hdc1 & hdc2 & paintStruct.hdc all equivalent to eachother with the exception that hdc1 requires a call to ReleaseDC()?
    Last edited by Kurisu33; 08-22-2006 at 08:11 PM.

  11. #11
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,868
    At this point are the variables hdc1 & hdc2 & paintStruct.hdc all equivalent to eachother with the exception that hdc1 requires a call to ReleaseDC()?
    hdc1 and hdc2 should be the same but.....

    hdc2 (from GetDC() ) requires the ReleaseDC() not hdc1 (a typo?)
    hdc2 is the whole screen.

    hdc1 (from the BeginPaint() ) may not be the same size as hdc2.

    hdc1 is ONLY the invalidated area of hdc2. ie it may be all of the screen but can just be a part of the screen. It has the same GDI objects selected into it as hdc2.

    EDIT:
    Are you saying though that by just using my Express edition without using the .NET framework all GDI object cleanup is auto? Or do i have to be using the .NET framework?
    AFAIK you have to be using the .NET IDE ie MSVC 2002 or later not just the .NET framework (but I am not 100%)

    I tried this before but wasn't casting the return object into a HBITMAP and thus was getting errors about VOID pointers
    Depends on the file type you use, .cpp (C++) or .c (C)

    .cpp is the default file type and requires a cast.
    Last edited by novacain; 08-23-2006 at 12:00 AM.
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. clear buffer
    By justins in forum C Programming
    Replies: 5
    Last Post: 05-19-2007, 06:16 AM
  2. Question about Multiple threads and ring buffer.
    By qingxing2005 in forum C Programming
    Replies: 2
    Last Post: 01-15-2007, 12:30 AM
  3. Replies: 16
    Last Post: 10-29-2006, 05:04 AM
  4. Console Screen Buffer
    By GaPe in forum Windows Programming
    Replies: 0
    Last Post: 02-06-2003, 05:15 AM
  5. need some help with this engine
    By DavidP in forum A Brief History of Cprogramming.com
    Replies: 6
    Last Post: 09-01-2001, 01:27 PM