1

I am fairly new to C++ and am trying to learn the best way to get a custom fill color on a window created for a registered class using the CreateWindow (or CreateWindowEx) function.

I wrote a program demonstrating 3 methods that all work but am not sure which method is considered best.

Can someone more experienced than me comment? suggestions? best practices?

My three methods:

1) set a custom background color when registering the window (red background in my example)

2) handle wm_erasebkgnd message (green color in my example)

3) handle wm_paint and use fillrect (blue color in my example)

I also found sending back a return statement in wm_erasebkgnd messes up the green or red backgrounds when wm_paint is not used

Thanks in advance.

//demonstrate different ways to customize the main window with a custom back color
//https://stackoverflow.com/questions/3463471/how-to-set-background-color-of-window-after-i-have- 
registered-it
//https://learn.microsoft.com/en-us/windows/win32/gdi/window-background?redirectedfrom=MSDN

#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
 #elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif

#include <tchar.h>
#include <windows.h>

//whichmethod = 0 --> red background (window registration background applies)
//whichmethod = 1 --> green background (wm_erasebkgnd used)
//whichmethod = 2 --> blue background (wm_paint is used)
#define Whichmethod 1 //0=no wm_paint and no WM_ERASEBKGND, color set in window registration
//#define Whichmethod 1 //1 = erasebkgnd method + window registration (wmerasebkgnd wins)
//#define Whichmethod 2 //2 = erasebkgnd method + wm_paint + window registration (wm_paint wins)

 //the return statement in wm_erasebkgnd can cause wm_erasebkgnd to change behavior, not sure why
 //set EraseRtn to 0 or 1 and if Whichmethod = 0 or 1 the screen is white, it is blue if Whichmethod = 2
 #define EraseRtn -1 //-1--> no return if WM_ERASEBKGND is called, next call is DefWindowProc
 //#define EraseRtn 1 //1 -> return a value of 1 if WM_ERASEBKGND is called, no call to DefWindowProc
 //#define EraseRtn 0 //0 -> return a value of 0 if WM_ERASEBKGND is called, no call to DefWindowProc

 /*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");

int WINAPI WinMain (HINSTANCE hThisInstance,
                HINSTANCE hPrevInstance,
                LPSTR lpszArgument,
                int nCmdShow)
{
HWND hwnd;               /* This is the handle for our window */
MSG messages;            /* Here messages to the application are saved */
WNDCLASSEX wincl;        /* Data structure for the windowclass */

/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);

/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL;                 /* No menu */
wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
wincl.cbWndExtra = 0;                      /* structure or the window instance */

/* Original from Codeblocks generator: Use Windows's default colour as the background of the window
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
*/
wincl.hbrBackground = (HBRUSH) CreateSolidBrush(RGB(255, 0, 0)); //red background (works unless background color defined in wm_paint or wm_erasebkgnd)

/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
    return 0;

/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           _T("Code::Blocks Template Windows App"),       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           544,                 /* The programs width */
           375,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
       );

/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);

/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0)) {
    /* Translate virtual-key messages into character messages */
    TranslateMessage(&messages);
    /* Send message to WindowProcedure */
    DispatchMessage(&messages);
}

/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
RECT rc;
PAINTSTRUCT ps;

switch (message) {                /* handle the messages */
case WM_DESTROY:
    PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
    break;

#if Whichmethod == 2
case WM_PAINT: {
    hdc = BeginPaint(hwnd, &ps);   // prepares the specified window for painting
    HBRUSH hcolor = CreateSolidBrush(RGB(0, 0, 255));//blue
    HGDIOBJ holdbrush = SelectObject(hdc, hcolor); // select the brush we want (hbrush) and store the previous brush holdbrush
    // SetBkMode( hdc, TRANSPARENT);     // sets the background mode to transparent (not needed for this exercise)
    // SetTextColor( hdc, RGB(255,255,255)) ;//white text, sets the text color
    GetClientRect (hwnd, &rc) ;  //retrieves the coordinates of the window's client area.
    FillRect(hdc, &rc, hcolor); //uses the new brush
    //for fun  DrawText(hdc,"DrawText: hello world",-1,&rc,DT_SINGLELINE | DT_CENTER | DT_VCENTER); 
 //draw text in the rectangle
    EndPaint(hwnd,&ps);  // marks the end of painting in the specified window.
    long retval = (long) SelectObject(hdc, holdbrush);  // select old brush
    bool tbool = DeleteObject(hcolor);  //destroy the new brush else gdi count grows
    return 0L;//says we processed the message
}
#endif

#if Whichmethod == 1 | WhichMethod == 2
case WM_ERASEBKGND: {
//this section doesn't repaint background if wm_paint uses fillrect and returns 0
    HBRUSH brush = CreateSolidBrush(RGB(0, 255, 0));//Green
    SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)brush);
    //An application should return nonzero if it erases the background; otherwise, it should return zero.
#if EraseRtn == 1
    return 1l;//return 1 or 0 creates a problem, our custom (green or red) color is lost (white instead); blue from wm_paint is not affected
#endif // EraseRtn
#if EraseRtn == 0
    return 0l;
#endif // EraseRtn
//if here we don't return anything and we eventually call defwindowproc below
}
#endif //whichmethod 1 or 2


default:  /* for messages that we don't deal with */
    return DefWindowProc (hwnd, message, wParam, lParam);
}

return 0;
}
Ken_sf
  • 89
  • 1
  • 9
  • 3
    A class brush is usually assigned when you register the window class, not every time the window's painted. Not deleting the previous one means you have a GDI resource leak. In terms of your question, the end result is the same no matter who paints the background. But if you're also doing foreground painting (e.g. something other than a solid colour) then doing everything including the background in `WM_PAINT` can result in less visible flicker (although these days the DWM smooths that out a lot anyway). – Jonathan Potter May 13 '20 at 19:35
  • If you just want to draw the background color of the window, `WM_ERASEBKGND` is commonly used. The message is sent to prepare an invalidated portion of a window for painting. – Strive Sun May 14 '20 at 07:18

1 Answers1

1

If you want to use the WM_ERASEBKGND method, then your WindowProcedure should itself do the background erasure, rather than trying to reset the class background brush. It is a fairly trivial (and rapid) operation:

//...
    case WM_ERASEBKGND: {
        HDC hdc = (HDC)(wParam); 
        RECT rc; GetClientRect(hwnd, &rc); 
        HBRUSH brush = CreateSolidBrush(RGB(0, 255, 0));//Green
        FillRect(hdc, &rc, brush); 
        DeleteObject(brush); // Free the created brush: see note below!
        return TRUE;
    }
//...

NOTE: As pointed out in the comments, it is far more efficient to make the HBRUSH object a 'global' variable, then create it (only once) on program startup and delete it on exit.

Feel free to ask for further clarification and/or explanation.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • So, if Jonathan is correct and using wm_paint is less likely to result in flicker then it makes sense to use wm_paint as my standard way to get a custom background color and then I would simply return 1 in wm_erasebkgnd to tell Windows that I handled erasing. Agreed? – Ken_sf May 13 '20 at 20:19
  • 1
    you not free `brush` and better create it once and save, instead create and destroy on every ` WM_ERASEBKGND` – RbMm May 13 '20 at 20:22
  • @Ken_sf Jonathan Potter is seldom wrong on such issues! However, I don' think the difference would really be that noticeable. Further, if your `WM_PAINT` message is called frequently, and doesn't *always* have to redraw the entire client window, then it's best to keep the `WM_ERASEBKGND` handler separate. – Adrian Mole May 13 '20 at 20:35
  • 1
    I did pick up the advice to store the brush as a static variable and remove in wm_destroy. Just to help other newbes that stumble on this post. in WindowProcedure I declared two brushes (two because my example implements both wm_paint and wm_erasebkgnd) and in wm_destroy I delete any that are defined, like this: static HBRUSH hcolor = CreateSolidBrush(RGB(0, 0, 255));//blue and in wm_destroy: if (hcolor) {DeleteObject((HDIOBJ) hcolor);} //delete brush good suggestions. – Ken_sf May 13 '20 at 21:26
  • If you want custom background drawing,then pass NULL as the hbrBackground brush.And if you want background drawing with a specific brush, then pass that brush. Refer [An article on Raymond Chen's The Old New Thing](https://devblogs.microsoft.com/oldnewthing/?p=1593) – Strive Sun May 14 '20 at 07:37
  • 1
    @ken There are two issues with this approach, one mostly stylistic, and the other one catastrophic. Tackling the stylistic one first: Globals make code hard to read. Every global removed is a win. In this case, simply create a brush and assign it to the window class during registration. There's no need to hold on to it. If you ever need the handle back you can get to it by calling `GetClassInfo`. On to the catastrophic issue: If you destroy your brush when handling `WM_DESTROY` you instantly leave all other window instances of that window class without a valid background brush. – IInspectable May 14 '20 at 09:09
  • I stumbled on Raymond's post after writing my sample program and note his advice. I wonder if I am really at risk for the catastrophic warning made by IInspectable given that I didn't follow Raymond's advice (through ignorance). My sample program reassigns the brush to the one used in registration in wm_paint. I am wondering if 1) I should be deleting the custom brush I assigned in registration of my window class and 2) use a standard brush when registering my class (contrary to Raymond's advice) to avoid IInspectable's concern. Thoughts? – Ken_sf May 15 '20 at 14:50
  • @Ken_sf In your specific case, I *think* you're safe, because you call `PostQuitMessage()` in the `WM_DESTROY` handler, so you don't (intend to) have multiple windows of your class open or active. Nonetheless, the advice from IInspectable is sound. – Adrian Mole May 15 '20 at 14:57