2

I've sub-classed some button controls, since I'm drawing the whole UI myself (to the dialog's hdc). This is to avoid flicker, the intention is that all drawing is done via a single memDC - preventing the staggered update of the UI.

So, I draw everything to the dialog's background, then position some buttons over the regions of the UI that should react to mouse events. So far so good. Or so I thought.

I sub-classed the buttons, using the following WndProc, expecting that Windows would do everything as per normal, except the drawing.

LRESULT CALLBACK invisibleBtnProc(HWND hwndBtn, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    long oldProc = GetWindowLong(hwndBtn, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_PAINT:
            ValidateRect(hwndBtn, NULL);
            return 0;

        case WM_ERASEBKGND:
            return 1;
    }
    return CallWindowProc((WNDPROC)oldProc, hwndBtn, uMsg, wParam, lParam);
}

The buttons are created and subclassed with the following code:

for (i=0; i<n; i++)
{
    btn = CreateWindow(WC_BUTTON, L"", WS_VISIBLE|WS_CHILD, 0,0,0,0, hwndDlg, (HMENU)(firstBigBtnId+i), hInst, NULL);
    long btnProcCur = GetWindowLong(btn, GWL_WNDPROC);
    SetWindowLong(btn, GWL_USERDATA, btnProcCur);

    SetWindowLong(btn, GWL_WNDPROC, (long) invisibleBtnProc);
}

When I built this code with MinGW & Code::Blocks, it works flawlessly. (both in debug and release builds)

Unfortunately, when built with MSVC & VS2010, I observe different behaviour. The debug-mode build is okay, but the release build is not. When one of the invisible buttons is clicked, the system is drawing it, obscuring the underlying 'button'.

I've a large WMF (emf? I forget) that needs to be drawn - it's quite slow and produces flicker when the window is resized, for those that wonder why the custom-draw-everything approach.

Here's what I'm seeing:

enter image description here

Note, that before I tried to click on the leftmost button it was not visible - just like the one on the right. Only upon clicking it does windows decide to draw it. Resizing the parent window - (a dialog which triggers a call to InvalidateRect for the dialog) removes the erroneous drawing. Clicking the button once again causes it to be drawn.

Any ideas where I've made a mistake in my thinking?

EDIT: Added code below for a SCCCE (This displays the same unwanted behaviour when built with GCC debug & release, that the original program showed in debug build only)

#include <windows.h>

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

RECT btnRect;
const int btnSize = 150;
const int btnId = 1000;
HINSTANCE hInst;


/*  Make the class name into a global variable  */
char szClassName[ ] = "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 */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* 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 */
           "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;
}


LRESULT CALLBACK invisibleBtnProc(HWND hwndBtn, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    long oldProc = GetWindowLong(hwndBtn, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_PAINT:
            ValidateRect(hwndBtn, NULL);
            return 0;

        case WM_ERASEBKGND:
            return 1;
    }
    return CallWindowProc((WNDPROC)oldProc, hwndBtn, uMsg, wParam, lParam);
}

void onSize(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    RECT mRect;
    GetClientRect(hwnd, &mRect);
    btnRect.left = (mRect.right - btnSize) / 2;
    btnRect.top = (mRect.bottom - btnSize) / 2;
    btnRect.right = btnRect.left + btnSize;
    btnRect.bottom = btnRect.top + btnSize;

    HWND btn;
    btn = GetDlgItem(hwnd, btnId);
    MoveWindow(btn, btnRect.left, btnRect.top, btnSize, btnSize, false);

    InvalidateRect(hwnd, NULL, false);
}

void onPaint(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    HBRUSH bkBrush, redBrush;
    RECT mRect;

    GetClientRect(hwnd, &mRect);

    hdc = BeginPaint(hwnd, &ps);

        bkBrush = CreateSolidBrush(RGB(51,51,51) );
        redBrush = CreateSolidBrush(RGB(255,0,0) );
        FillRect(hdc, &mRect, bkBrush);
        FillRect(hdc, &btnRect, redBrush);
        DeleteObject(bkBrush);
        DeleteObject(redBrush);

    EndPaint(hwnd, &ps);
}

/*  This function is called by the Windows function DispatchMessage()  */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
    case WM_CREATE:
            HWND tmp;
            tmp = CreateWindow("Button", "Press Me", WS_VISIBLE|WS_CHILD, 0,0,0,0, hwnd, (HMENU)btnId, hInst, NULL);
            long oldProc;
            oldProc = GetWindowLong(tmp, GWL_WNDPROC);
            SetWindowLong(tmp, GWL_USERDATA, oldProc);
            SetWindowLong(tmp, GWL_WNDPROC, (long)invisibleBtnProc);
            return 0;

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

        case WM_SIZE:
            onSize(hwnd, wParam, lParam);
            return 0;

        case WM_PAINT:
            onPaint(hwnd, wParam, lParam);
            return 0;

        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
                case btnId:
                    MessageBeep(MB_ICONEXCLAMATION);
                    break;
            }
            return 0;

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

    return 0;
}
Werner Henze
  • 16,404
  • 12
  • 44
  • 69
enhzflep
  • 12,927
  • 2
  • 32
  • 51
  • 1
    Did you compile with `gcc -Wall -Wextra` and improve the code till you got no warning? Did you use a recent GCC (e.g. [GCC 4.8](http://gcc.gnu.org/gcc-4.8/) ...)? Did you consider using a cross-platform toolkit (like [Qt](http://qt.digia.com/) ...)? – Basile Starynkevitch Nov 09 '13 at 09:14
  • One difference between the debug and release builds in VS is that uninitialised variables have different values (one is zero, the other is non-zero, I forget which) -- could this be the cause? – Edward Clements Nov 09 '13 at 09:27
  • @BasileStarynkevitch - I used `g++ -Wall -Wextra`. The only warnings remaining are for unused parameters (mainly wParam and lParam). I `gcc -dumpversion` returns `4.7.2` (as does `g++ -dumpversion`). God no! I want this project to be small. I dislike gtk and Qt. wxWidgets routinely makes a 2mb program that I can do in under 100kb (with my own c++ classes that wrap the win32 api and window objects - offering similar or identical interfaces and flexibility) Good implied suggestion though, but not suitable in this case. – enhzflep Nov 09 '13 at 09:37
  • @EdwardClements - yup, uninitialized variables are implicitly zeroed when using a debug build. I considered that, but would have expected the release mode of both compilers to fail, as has been the case for other mistakes in the past. – enhzflep Nov 09 '13 at 09:43
  • 3
    Looks to me you are doing battle with the visual styles renderer. Doesn't have anything to do with the compiler, everything to do with the manifest. The approach is bizarre, just use the WS_EX_COMPOSITED style flag on the main window to get the entire window to be double-buffered. – Hans Passant Nov 09 '13 at 11:24
  • @HansPassant - thanks again for the help. Indeed - the manifest was found to be the source of the different behaviour. While WS_EX_COMPOSITED looked like the light at the end of the tunnel, it doesn't work to avoid flicker during resize (there are many instances where its shortcomings are detailed). None-the-less, you've answered my question regarding the different behaviour. Thank-you kindly. – enhzflep Nov 09 '13 at 15:35
  • no, unitialized memory areas will be filled with 0xCCCCCCCC in MSVC http://stackoverflow.com/questions/370195/when-and-why-will-an-os-initialise-memory-to-0xcd-0xdd-etc-on-malloc-free-new – phuclv Jul 04 '14 at 13:29

1 Answers1

4

Setting the BS_OWNERDRAW style tells Windows that it shall not draw the button itself, but that you are responsible for that. That does the trick.

There is not much you need to change. Just create the button with this style.

tmp = CreateWindow("Button", "Press Me", WS_VISIBLE|WS_CHILD|BS_OWNERDRAW, 0,0,0,0, hwnd, (HMENU)btnId, hInst, NULL);

Then in your invisibleBtnProc you can add

case WM_DRAWITEM:
    ValidateRect(hwndBtn, NULL);
    return TRUE;
Werner Henze
  • 16,404
  • 12
  • 44
  • 69