4

我有一个窗口,我想将边框实现为调整边框大小,就像任何其他窗口一样。从评论和答案中获取建议,我重写了我的代码。对于 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 资源管理器中一样?

4

5 回答 5

6

我知道这有点晚了,但我想我已经找到了一种无需“蠕动”即可调整大小的方法(内部窗口绘图延迟仍然存在)。

与曼努埃尔所说的不同,WM_NCCALCSIZE它是万恶之源。此外,此方法适用于任何窗口样式(使用WS_POPUPand测试WS_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;
    }
于 2013-12-26T22:15:55.943 回答
2

这种代码很快就会变得丑陋,您正在通过更改客户区位置来更改相对鼠标位置。这需要您在窗口变得太小时忽略鼠标移动时更新 *track_start* 变量。不这样做会产生一种有趣的效果,窗口来回跳跃。是的,“蠕动”。

只是不要这样做,您正在寻找的功能已经实现。为WM_GETMINMAXINFO. 首先调用 DefWindowProc(),然后覆盖 MINMAXINFO.ptMinTrackSize 值。如果意图是在无边框窗口上实现角或边缘拖动,则为WM_NCHITTEST. 这也允许实施您的 BORDERWIDTH。相同的配方,首先调用 DefWindowProc(),在适当的时候覆盖返回值。

于 2013-10-01T00:53:09.020 回答
2

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

据我了解发生了什么,它与 WM_GETMINMAXINFO 或 WM_NCCALCSIZE 无关。

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

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

您有 2 个选项(如果您的目标是 7+):

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

2) 不要使用 WS_POPUP,而是使用 WS_BORDER 并使用欺骗 Windows 永远不会呈现边框的技巧。看来这就是VS2012正在做的事情。

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

于 2013-10-09T10:36:25.770 回答
1

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

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

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

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

于 2013-10-05T06:27:47.567 回答
1

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

于 2013-10-07T16:01:16.307 回答