WinAPI C++:重新编程窗口大小调整

WinAPI C++: Reprogramming Window Resize

本文关键字:编程 窗口大小 调整 C++ 新编程 WinAPI      更新时间:2023-10-16

我有一个窗口,我想像任何其他窗口一样实现边框大小。从评论和答案中采纳建议,我重写了我的代码。对于WM_GETMINMAXINFO,我有:

MINMAXINFO *min_max = reinterpret_cast<MINMAXINFO *>(lparam);
min_max->ptMinTrackSize.x = MINX;
min_max->ptMinTrackSize.y = MINY;

MINX 和 MINY 是我希望窗口的最小大小。对于WM_NCHITTEST,我有:

RECT wnd_rect;
int x, y;
GetWindowRect (window, &wnd_rect);
x = GET_X_LPARAM (lparam) - wnd_rect.left;
y = GET_Y_LPARAM (lparam) - wnd_rect.top;
if (x >= BORDERWIDTH && x <= wnd_rect.right - wnd_rect.left - >BORDERWIDTH && y >= BORDERWIDTH && y <= TITLEBARWIDTH)
    return HTCAPTION;
else if (x < BORDERWIDTH && y < BORDERWIDTH)
    return HTTOPLEFT;
else if (x > wnd_rect.right - wnd_rect.left - BORDERWIDTH && y < BORDERWIDTH)
    return HTTOPRIGHT;
else if (x > wnd_rect.right - wnd_rect.left - BORDERWIDTH && y > wnd_rect.bottom - wnd_rect.top - BORDERWIDTH)
    return HTBOTTOMRIGHT;
else if (x < BORDERWIDTH && y > wnd_rect.bottom - wnd_rect.top - BORDERWIDTH)
    return HTBOTTOMLEFT;
else if (x < BORDERWIDTH)
    return HTLEFT;
else if (y < BORDERWIDTH)
    return HTTOP;
else if (x > wnd_rect.right - wnd_rect.left - BORDERWIDTH)
    return HTRIGHT;
else if (y > wnd_rect.bottom - wnd_rect.top - BORDERWIDTH)
    return HTBOTTOM;
return HTCLIENT;

变量是不言自明的。这段代码给了我一个边框,我可以拖动它来调整窗口大小。当我拖动右下、下和右边框时,它效果很好。对于其他边框,当我尝试拖动它们时,窗口的右下角似乎仍然来回移动。它类似于在Google Chrome或Visual Studio 2012中看到的具有相同边框集的内容,但我在Windows资源管理器中没有看到这一点。

有没有办法使右下角在我调整顶部或左侧边框的大小时不会来回"蠕动",就像在 Windows 资源管理器中一样?

我知道

有点晚了,但我想我已经找到了一种在不"蠕动"的情况下调整大小的方法(内部窗口绘制滞后仍然存在(。

与曼努埃尔所说的不同,WM_NCCALCSIZE是万恶之源。此外,此方法应该适用于任何窗口样式(使用 WS_POPUPWS_OVERLAPPEDWINDOW 进行测试(在保留它们功能的同时,所以我是时候闭嘴并发布带有注释的代码了:

//some sizing border definitions
#define MINX 200
#define MINY 200
#define BORDERWIDTH  5
#define TITLEBARWIDTH  30
//................
HWND TempHwnd = Create(NULL, TEXT("CUSTOM BORDER"), TEXT("CUSTOM BORDER"),
               WS_POPUP | WS_VISIBLE,
               100, 100, 400, 400, NULL, NULL, 
                       GetModuleHandle(NULL), NULL);
//...............
LRESULT CALLBACK WinMsgHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_SIZING: // I use this message to redraw window on sizing (o rly?)
            RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_NOERASE | RDW_INTERNALPAINT);
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
        case WM_PAINT: // Used to draw borders and stuff to test WM_NCHITTEST
            {
                PAINTSTRUCT ps;
                BeginPaint(hWnd, &ps);
                RECT ClientRect;
                GetClientRect(hWnd, &ClientRect);
                RECT BorderRect = { BORDERWIDTH, BORDERWIDTH, ClientRect.right - BORDERWIDTH - BORDERWIDTH, ClientRect.bottom - BORDERWIDTH - BORDERWIDTH },
                     TitleRect = { BORDERWIDTH, BORDERWIDTH, ClientRect.right - BORDERWIDTH - BORDERWIDTH, TITLEBARWIDTH };
                HBRUSH BorderBrush = CreateSolidBrush(0x0000ff);
                FillRect(ps.hdc, &ClientRect, BorderBrush);
                FillRect(ps.hdc, &BorderRect, GetSysColorBrush(2));
                FillRect(ps.hdc, &TitleRect, GetSysColorBrush(1));
                DeleteObject(BorderBrush);
                EndPaint(hWnd, &ps);
            }
            break;
        case WM_GETMINMAXINFO: // It is used to restrict WS_POPUP window size
            {              // I don't know if this works on others
                MINMAXINFO *min_max = reinterpret_cast<MINMAXINFO *>(lParam);
                min_max->ptMinTrackSize.x = MINX;
                min_max->ptMinTrackSize.y = MINY;
            }
            break;
        case WM_CREATE:  // In this message we use MoveWindow to invoke
            {        //WM_NCCALCSIZE msg to remove border
                CREATESTRUCT *WindowInfo = reinterpret_cast<CREATESTRUCT *>(lParam);
                MoveWindow(hWnd, WindowInfo->x, WindowInfo->y, WindowInfo->cx - BORDERWIDTH, WindowInfo->cy - BORDERWIDTH, TRUE); 
//Notice that "- BORDERWIDTH" is recommended on every manually called resize function,
//Because we will add BORDERWIDTH value in WM_NCCALCSIZE message
            }
            break;
        case WM_NCCALCSIZE:
            { // Microsoft mentioned that if wParam is true, returning 0 should be enough, but after MoveWindow or similar functions it would begin to "wriggle"
                if (wParam)
                {
                    NCCALCSIZE_PARAMS *Params = reinterpret_cast<NCCALCSIZE_PARAMS *>(lParam);
                    Params->rgrc[0].bottom += BORDERWIDTH; // rgrc[0] is what makes this work, don't know what others (rgrc[1], rgrc[2]) do, but why not change them all?
                    Params->rgrc[0].right += BORDERWIDTH;
                    Params->rgrc[1].bottom += BORDERWIDTH;
                    Params->rgrc[1].right += BORDERWIDTH;
                    Params->rgrc[2].bottom += BORDERWIDTH;
                    Params->rgrc[2].right += BORDERWIDTH;
                    return 0;
                }
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
            }
            break;
        case WM_NCHITTEST:
            {
                RECT WindowRect;
                int x, y;
                GetWindowRect(hWnd, &WindowRect);
                x = GET_X_LPARAM(lParam) - WindowRect.left;
                y = GET_Y_LPARAM(lParam) - WindowRect.top;
                if (x >= BORDERWIDTH && x <= WindowRect.right - WindowRect.left - BORDERWIDTH && y >= BORDERWIDTH && y <= TITLEBARWIDTH)
                    return HTCAPTION;
                else if (x < BORDERWIDTH && y < BORDERWIDTH)
                    return HTTOPLEFT;
                else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH && y < BORDERWIDTH)
                    return HTTOPRIGHT;
                else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH && y > WindowRect.bottom - WindowRect.top - BORDERWIDTH)
                    return HTBOTTOMRIGHT;
                else if (x < BORDERWIDTH && y > WindowRect.bottom - WindowRect.top - BORDERWIDTH)
                    return HTBOTTOMLEFT;
                else if (x < BORDERWIDTH)
                    return HTLEFT;
                else if (y < BORDERWIDTH)
                    return HTTOP;
                else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH)
                    return HTRIGHT;
                else if (y > WindowRect.bottom - WindowRect.top - BORDERWIDTH)
                    return HTBOTTOM;
                else
                    return HTCLIENT;
            }
            break;
        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
        return 0;
    }

这种代码匆忙变得丑陋,您正在通过更改工作区位置来更改相对鼠标位置。 这要求您在窗口变得太小时忽略鼠标移动时更新 *track_start* 变量。 不这样做会产生一个,咳咳,有趣的效果,窗口来回跳跃。 是的,"蠕动"。

只是不要这样做,您正在寻找的功能已经实现。 为 WM_GETMINMAXINFO 编写消息处理程序。 首先调用 DefWindowProc((,然后覆盖 MINMAXINFO.ptMinTrackSize 值。 如果目的是在无边框窗口上实现角或边缘拖动,则实现 WM_NCHITTEST 的消息处理程序。 这也允许实现你的边框宽度。 相同的配方,首先调用 DefWindowProc((,在适当的时候覆盖返回值。

唉,这不是你正在等待的答案。在 Windows Seven 上,同时移动和调整大小WS_POPUP具有样式的顶级窗口确实被破坏了。在视觉上,首先移动窗口,然后调整大小。按左侧或顶部调整大小时,移动操作会短暂显示背景像素,从而导致非常糟糕的用户体验。

据我所知,这与WM_GETMINMAXINFO或WM_NCCALCSIZE无关。

看到效果非常简单:创建一个WS_POPUP |WS_VISIBLE窗口过程几乎为空的窗口中,请设置一个计时器,并在WM_TIMER中使用 SetWindowPos,将窗口向左移动一点,同时放大窗口,以便让右边缘在同一位置。你会看到背景像素,这很愚蠢。在Windows XP上没有这样的破损。

我尝试了很多技巧,其中一些非常扭曲,但最终结果总是相同的:在窗口最终呈现为新状态的那一刻,首先是移动操作,然后是大小操作......

您剩下 2 个选项(如果您的目标是 Seven+(:

1(使用标准大小边框并利用新的API(例如:DwmExtendFrameIntoClientArea(来自定义框架以满足您的需求。请参阅在 http://msdn.microsoft.com/en-us/library/windows/desktop/bb688195.aspx 使用 DWM 的自定义窗口框架

2(不要使用WS_POPUP,而是WS_BORDER并使用欺骗Windows的技巧,以永不渲染边框。似乎这就是VS2012正在做的事情。

不要忘记:在窗口内闪烁是另一回事,我只是在这里谈论右/底边缘"稳定性"。

查看正在更改窗口大小和位置的代码会很有帮助。

当您移动底部或右侧时,您只是更改窗口的大小(高度或宽度(。当您移动顶部或左侧时,您不仅要更改大小,还要更改上角/左角位置。

如果有人想将左边框向右移动 10 像素,那么您必须将角位置增加 10 并将宽度减少 10 - 最好同时(例如,同时使用 SetWindowPos 进行两次更改(。

请注意,更改该角位置也会更改鼠标屏幕坐标的解释方式。因此,旧仓位的任何存储也必须更新。

你只需要处理WM_NCCALCSIZE消息,用边框宽度增加左rgrc矩形,用标题栏高度增加顶部,用边框宽度减少右边,用标题栏高度减少底部。对于边框角,您应该更改WM_SIZE消息上的窗口区域。