3

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;
}
4

1 回答 1

4

设置BS_OWNERDRAW样式告诉 Windows 它不应绘制按钮本身,但您对此负责。这就是诀窍。

你不需要改变太多。只需使用这种样式创建按钮。

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

然后在你的 invisibleBtnProc 你可以添加

case WM_DRAWITEM:
    ValidateRect(hwndBtn, NULL);
    return TRUE;
于 2014-07-04T12:23:24.333 回答