【问题标题】:Why does Windows draw its own min-max-close buttons, even if the WM_NCPAINT is correctly(?) reimplemented?即使 WM_NCPAINT 正确(?)重新实现,为什么 Windows 会绘制自己的 min-max-close 按钮​​?
【发布时间】:2013-12-24 18:24:03
【问题描述】:

从我截取的截图中可以看出:

http://www.picpaste.com/pics/skinned_window.1386408792.png

由于某些奇怪的原因,Windows 在标题栏中绘制了自己的(未设置样式的)最小化/最大化/关闭按钮 在我的蒙皮标题栏(黄色矩形)的顶部,如红色箭头所指。 它还在这些按钮的底部绘制了一条恼人的 1 像素大小的线,正如您从屏幕截图中看到的那样。

您可以注意到我正在为窗口设置皮肤:我正在绘制自己的标题栏(黄色矩形),并且 调整边框大小(青色、品红色、红色矩形)。 它们现在只是矩形,但我不明白为什么 Windows 会在我在非客户区绘制的黄色矩形上绘制,只需在 WM_NCPAINT 发生时绘制。 一切都很好,除了这个奇怪的东西。

这并不总是发生,它会在使用蒙皮窗口、调整大小、最大化/最小化 2-3 次后发生。特别是当我在小程序执行的某个时间点在平铺栏上单击鼠标时会发生这种情况。事实上,我认为问题可能出在 WM_NCHITTEST 消息中,但似乎并非如此。出了点问题,可能是某些 Window Style 或 Window Extended Style 标志错误。

我无法解释这一点,我正在正确地重新实现 WM_NCPAINT 消息(我猜),所以 Windows 不应该理解我正在绘制自己的标题栏吗?为什么它会覆盖我的图纸?!它是Windows XP的错误吗? 在 Windows 7 中似乎不会发生这种情况,但我不太确定。

也许我只是错过了为此重新实现 WM_* 消息。 有人可以帮助我吗?这让我发疯了!

注意:我不能使用 WinForms、Qt 或其他有助于为窗口设置外观的库,这是一个旧项目,必须直接在 winapi 中完成,处理正确的 WM_* 消息。无法链接任何库。

注意 2:在 WM_NCACTIVATE 消息中同时使用 SetWindowPos 或 RedrawWindow 会产生相同的结果。

这是代码:

#include <windows.h>
#include <stdio.h>


LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

char szClassName[ ] = "SkinTest";


int left_off;
int right_off;
int top_off;
int bottom_off;


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 */


    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;
    wincl.style = CS_DBLCLKS;
    wincl.cbSize = sizeof (WNDCLASSEX);

    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                
    wincl.cbClsExtra = 0;                     
    wincl.cbWndExtra = 0;                     
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    if (!RegisterClassEx (&wincl))
        return 0;

    DWORD style =  WS_OVERLAPPEDWINDOW;

    hwnd = CreateWindowEx (
       WS_EX_CLIENTEDGE,
       szClassName,
       "Code::Blocks Template Windows App",
       style,
       CW_USEDEFAULT,
       CW_USEDEFAULT,
       500,
       500,
       HWND_DESKTOP,
       NULL,
       hThisInstance,
       NULL
       );

    //
    // This prevent the round-rect shape of the overlapped window.
    //
    HRGN rgn = CreateRectRgn(0,0,500,500);
    SetWindowRgn(hwnd, rgn, TRUE);

    left_off = 4;
    right_off = 4;
    top_off = 23;
    bottom_off = 4;

    ShowWindow (hwnd, nCmdShow);

    while (GetMessage (&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }

    return messages.wParam;
}



#define COLOR_TITLEBAR        0
#define COLOR_LEFT_BORDER     2
#define COLOR_RIGHT_BORDER    4
#define COLOR_BOTTOM_BORDER   6

int win_x, win_y, win_width, win_height;
int win_is_not_active = 0;


COLORREF borders_colors[] =
{
    RGB(255,255,0), RGB(180,180,0),    // Active titlebar - Not active titlebar
    RGB(0,255,255), RGB(0,180,180),    // Active left border - Not active left border
    RGB(255,0,255), RGB(180,0,180),    // Active right border - Not Active right border
    RGB(255,0,0),   RGB(180,0,0)       // Active bottom border - Not active bottom border
};




void draw_titlebar(HDC hdc)
{
    HBRUSH tmp, br = CreateSolidBrush(borders_colors[COLOR_TITLEBAR + win_is_not_active]);
    tmp = (HBRUSH)SelectObject(hdc, br);
    Rectangle(hdc, 0, 0, win_width, top_off);
    SelectObject(hdc, tmp);
    DeleteObject(br);
}

void draw_left_border(HDC hdc)
{
    HBRUSH tmp, br = CreateSolidBrush(borders_colors[COLOR_LEFT_BORDER + win_is_not_active]);
    tmp = (HBRUSH)SelectObject(hdc, br);
    Rectangle(hdc, 0, top_off, left_off, win_height - bottom_off);
    SelectObject(hdc, tmp);
    DeleteObject(br);
}


void draw_right_border(HDC hdc)
{
    HBRUSH tmp, br = CreateSolidBrush(borders_colors[COLOR_RIGHT_BORDER + win_is_not_active]);
    tmp = (HBRUSH)SelectObject(hdc, br);
    Rectangle(hdc, win_width - right_off, top_off, win_width, win_height - bottom_off);
    SelectObject(hdc, tmp);
    DeleteObject(br);
}


void draw_bottom_border(HDC hdc)
{
    HBRUSH tmp, br = CreateSolidBrush(borders_colors[COLOR_BOTTOM_BORDER + win_is_not_active]);
    tmp = (HBRUSH)SelectObject(hdc, br);
    Rectangle(hdc, 0, win_height - bottom_off, win_width, win_height);
    SelectObject(hdc, tmp);
    DeleteObject(br);
}



LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
switch (message)
{
    case WM_DESTROY:
        PostQuitMessage (0);
        break;

    case WM_SIZE:
        {
            RECT rect;
            HRGN rgn;
            GetWindowRect(hwnd, &rect);
            win_x = rect.left;
            win_y = rect.top;
            win_width = rect.right - rect.left;
            win_height = rect.bottom - rect.top;
            //
            // I use this to set a rectangular region for the window, and not a round-rect one.
            //
            rgn = CreateRectRgn(0,0, rect.right, rect.bottom);
            SetWindowRgn(hwnd, rgn, TRUE);
            DeleteObject(rgn);
        }
        break;

    case WM_PAINT:
    {
        printf("WM_PAINT\n");
        PAINTSTRUCT ps;
        HDC hdc;
        HBRUSH hb;
        RECT rect;

        hdc = BeginPaint(hwnd, &ps);
        hb = CreateSolidBrush(RGB(rand()%255,rand()%255,rand()%255));

        GetClientRect(hwnd, &rect);

        FillRect(hdc, &rect, hb);

        DeleteObject(hb);
        EndPaint(hwnd, &ps);
        break;
    }

    case WM_NCPAINT:
    {
        printf("WM_NCPAINT\n");
        HDC hdc;
        HBRUSH br;
        RECT rect;
        HRGN rgn = (HRGN)wparam;

        if ((wparam == 0) || (wparam == 1))
            hdc = GetWindowDC(hwnd);
        else
            hdc = GetDCEx(hwnd, (HRGN)wparam, DCX_WINDOW|DCX_CACHE|DCX_LOCKWINDOWUPDATE|DCX_INTERSECTRGN);

        draw_titlebar(hdc);
        draw_left_border(hdc);
        draw_right_border(hdc);
        draw_bottom_border(hdc);
        ReleaseDC(hwnd, hdc);
        return 0;
    }

    case WM_NCACTIVATE:
            if (wparam)
                win_is_not_active = 0;
            else
                win_is_not_active = 1;
          // Force paint our non-client area otherwise Windows will paint its own.
          SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_FRAMECHANGED);
          //RedrawWindow(hwnd, 0, 0, RDW_FRAME | RDW_UPDATENOW | RDW_NOCHILDREN);
          return 0;

    case WM_NCCALCSIZE:
        {
            if (wparam)
            {
                NCCALCSIZE_PARAMS * ncparams = (NCCALCSIZE_PARAMS *)lparam;
                printf("WM_NCCALCSIZE wparam:True\n");
                ncparams->rgrc[0].left   += left_off;
                ncparams->rgrc[0].top    += top_off;
                ncparams->rgrc[0].right  -= right_off;
                ncparams->rgrc[0].bottom -= bottom_off;
                return 0;
            } else {

                RECT * rect = (RECT *)lparam;
                return 0;
            }
        }


    case WM_NCHITTEST:
        {
            LRESULT result = DefWindowProc(hwnd, message, wparam, lparam);
            switch (result)
            {
                //
                // I have to set this, because i need to draw my own min/max/close buttons
                // in different coordinates where the system draws them, so let's consider
                // all the titlebar just a tilebar for now, ignoring those buttons.
                //
                case HTCLOSE:
                case HTMAXBUTTON:
                case HTMINBUTTON:
                case HTSYSMENU:
                case HTNOWHERE:
                case HTHELP:
                case HTERROR:
                    return HTCAPTION;
                default:
                    return  result;
            };
        }

    case WM_ERASEBKGND:
        return 1;

    default:
        return DefWindowProc (hwnd, message, wparam, lparam);
}

return 0;
}

【问题讨论】:

  • 好问题,我赞成。对于这个问题,也许我不正确,但 Windows 可能会在更深层次上处理这个问题,等等 Win32k.sys。可能是。 :-)
  • 一个典型的解决方案是在客户区而不是非客户区绘制你的自定义标题栏,然后隐藏默认的非客户区,比如用SetWindowRgn(),所以它不会不管 Windows 决定放入什么。
  • 但在这种情况下,我必须重新实现所有窗口大小调整工具,例如左上角,右上角调整大小等。我选择不故意使用客户区以避免所有这些混乱,这更难解决!
  • @MarcoPagliaricci:嘿。我知道已经有一段时间了,但我仍然很好奇,您是如何通过捕获 wParam 为 1 或 0 并调用 GetWindowDC 而不是 GetDCEx 来了解如何处理 WM_NCPAINT 的?这似乎有效,但不是WM_NCPAINT suggests
  • 对不起,很多年前......我不记得我是如何解决这个问题的! :-)

标签: windows winapi user-interface skins


【解决方案1】:

您需要适当地处理 WM_NCHITTEST,因为 Windows 也喜欢从该消息中绘制非客户端元素(在默认窗口过程中)。

在我的一些自定义窗口中处理此消息后,您遇到的问题就消失了。

编辑: 我看到您已经在处理 WM_NCHITTEST,如果您正在处理特定的点击,您不会想调用 DefWindowProc,因为它会尝试绘制这些标题按钮。

【讨论】:

  • 微软谁认为响应WM_NCHITTEST绘图是个好主意?感谢这个不直观的答案。
  • 我也发现有必要处理WM_SETCURSOR
  • @MarkRansom 你是如何处理 WM_SETCURSOR 的?我发现这也是我出错的原因。如果处理 WM_SETCURSOR 并阻止默认行为,问题就会消失,但调整游标大小的更改也会消失。
  • @TGasdf 当我说“句柄”时,我的意思是执行 Windows 通常会执行的相同操作,即查看命中测试代码并设置正确的光标。遗憾的是没有捷径。
  • @MarkRansom 感谢您的快速回复。我已经自己处理过了,但希望有一条捷径。最后一个问题,WM_SETCURSOR 非常简单,WM_NCHITTEST 似乎是另一回事。你有什么处理 WM_NCHITTEST 的正确方法的例子吗?
猜你喜欢
  • 2014-07-24
  • 1970-01-01
  • 2017-09-05
  • 1970-01-01
  • 2022-06-12
  • 2013-01-01
  • 2023-03-19
  • 2023-01-28
相关资源
最近更新 更多