سؤال

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;
}
هل كانت مفيدة؟

المحلول

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;
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top