【问题标题】:WinAPI C++: Reprogramming Window ResizeWinAPI C++:重新编程窗口调整大小
【发布时间】:2013-10-07 00:20:48
【问题描述】:

我有一个窗口,我想将边框实现为调整边框大小,就像任何其他窗口一样。接受 cmets 和答案的建议,我重写了我的代码。对于 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_NCHITTEST 消息并在适当的时候返回HTTOPLEFT,您仍然可以利用操作系统提供的窗口大小调整。
  • 您可能需要处理 WM_NCCALCSIZE 消息。调整顶部边框大小时,DefWindowProc 会为该消息返回什么?
  • 当 wParam 为 TRUE 时,Yoy 可以尝试为 WM_NCCALCSIZE 返回 WVR_REDRAW。如果闪烁过多,您可以返回 WVR_VALIDRECTS(如果 wParam 为 TRUE),但您必须更新 NCCALCSIZE_PARAMS 结构,这可能很棘手,具体取决于窗口的内容。简而言之(如果您返回 WVR_VALIDRECTS):不要保留底部和/或右对齐的内容(边框)。
  • 当“左上角”调整弹出窗口的大小时(通过 DefWindowProc),有两种类型的闪烁。首先,在窗口“内部”闪烁,这是由右下对齐像素(边框)的“bitblit”引起的。 WM_NCCALCSIZE 和 WVR_VALIDRECTS 应该解决这个问题。其次,由 DefWindowProc/Windows7 引起的闪烁首先将窗口向左上方移动,然后再调整窗口大小:右下角的边框似乎“蠕动”。在 Windows XP 上不会发生这种情况!我(也许)会尝试找到一个修复(通过不将 WM_NCLBUTTONDOWN 委托给 DefWindowProc,或者通过使用一些用于 Vista/7 (DWM) 的新 API)

标签: c++ windows winapi resize paint


【解决方案1】:

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

与 manuell 所说的不同,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;
    }

【讨论】:

  • 哇,它确实有效!稍后我需要进一步研究您的代码,但暂时有一些声誉。
  • 我已下载您的应用程序,但无法启动。我总是收到错误,它不是有效的 win32 应用程序。有什么想法吗?最好的问候。
  • 我只能猜测您使用的是 Windows XP。 Visual Studio 2012/2013 为 XP 和 Vista++ 提供了不同的工具集。这就是为什么您不能在 XP 上运行 Vista++ 工具集的原因(这只是我的猜测)。我可以为你重新编译它,但不幸的是我删除了我的代码。此外,除非您想创建自定义框架,否则它不会消除这种“滞后”的绘图,有时它会使其更加引人注目。所以我并没有真正使用它,因为它是死胡同。无论如何回到你的问题。我编译了 2 个示例程序,它们使用不同的工具集什么都不做。它们中的任何一个都有效吗? putlocker.com/file/ADD95C258262DD11
  • @Helix:你说得对,我有Windows XPXP 版本有效。这是一个有趣的问题,也是一个有趣的解决方案——你们都得到了我的 +1。最好的问候。
  • 这对我不起作用,我是否必须启用某些东西才能通过拖动边框来调整窗口大小?
【解决方案2】:

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

据我了解,这与 WM_GETMINMAXINFO 或 WM_NCCALCSIZE 无关。

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

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

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

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

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

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

【讨论】:

  • 感谢您对这个问题的持续支持。虽然这个问题没有一个非常直接的解决方案,但我认为你已经提供了最全面的答案。我会投票给所有在这个问题上帮助我的人,任何人都将你标记为答案。
  • 谢谢!如果您打算实施某些事情并遇到问题,请告诉我,我会尽力提供帮助。
  • 您能否详细说明#2 - 我很好奇如何使用 WS_BORDER 以及如何“愚弄 Windows 从不渲染边框”(例如 VS2012+ 效果)
  • @holtavolt 不,抱歉,我当时做了很多实验,但我手头没有代码了。批准的答案对您不起作用?
  • @manuell 不,我通过 WM_NCCALCSIZE 的自定义框架导致了一些 WM_LBUTTON* 消息到达我的应用程序中的顶级窗口或弹出窗口的问题,所以我正在寻求基于 WS_POPUP(或 WS_BORDER)的解决方案.
【解决方案3】:

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

【讨论】:

  • 这个我也试过了,也没用。 Windows 似乎在允许 WM_PAINT 通过之前将现有窗口 bitblt 到其新位置,这导致左上角大小闪烁。
【解决方案4】:

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

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

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

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

【讨论】:

  • 所有相关内容都已发布在我的问题中。我以前用过你的方法,但它也会使窗口结结巴巴。此外,它不像直接使用 WM_NCHITTEST 那样标准。
【解决方案5】:

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

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

【讨论】:

  • 我执行了你的建议,但是当我用左上角调整大小时,窗口似乎仍然在蠕动,当我用上边框调整大小时,窗口不会重新绘制!
猜你喜欢
  • 2011-02-25
  • 2012-10-29
  • 1970-01-01
  • 1970-01-01
  • 2018-09-25
  • 2016-10-25
  • 1970-01-01
  • 2020-01-16
  • 1970-01-01
相关资源
最近更新 更多