简体   繁体   English

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

[英]WinAPI C++: Reprogramming Window Resize

I have a window, and I want to implement the borders as resizing borders, like any other window. 我有一个窗口,我希望将边框实现为调整边框,就像任何其他窗口一样。 Taking in suggestions from comments and answers, I have rewritten my code. 从评论和答案中提出建议,我重写了我的代码。 For WM_GETMINMAXINFO I have: 对于WM_GETMINMAXINFO,我有:

MINMAXINFO *min_max = reinterpret_cast<MINMAXINFO *>(lparam);

min_max->ptMinTrackSize.x = MINX;
min_max->ptMinTrackSize.y = MINY;

MINX and MINY are the minimum size I want the window to be. MINX和MINY是我想要窗口的最小尺寸。 For WM_NCHITTEST I have: 对于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;

The variables are pretty self-explanatory. 这些变量非常明显。 This code gives me a border that I can drag to resize the window. 这段代码给了我一个边框,我可以拖动它来调整窗口大小。 It works well when I drag the bottom-right, bottom, and right borders. 拖动右下角,右下角和右边框时效果很好。 With the other borders, the bottom-right corner of the window still seems to move back and forth when I try to drag them. 对于其他边框,当我尝试拖动它们时,窗口的右下角似乎仍然来回移动。 It's similar to what is seen in Google Chrome or Visual Studio 2012 with the same set of borders, but I don't see this in Windows Explorer. 它类似于谷歌Chrome或Visual Studio 2012中使用相同的边框集,但我在Windows资源管理器中看不到这一点。

Is there a way to make the bottom-right corner not "wriggle" back and forth as I'm resizing the top or left borders, like in Windows Explorer? 当我调整顶部或左边框时,有没有办法让右下角不来回“蠕动”,就像在Windows资源管理器中一样?

I know it is a little late, but I think I have found a way to resize without "wriggle" (inside window drawing lag will still remain). 我知道这有点晚了,但我想我找到了一种不用“蠕动”调整大小的方法(内部窗口绘制滞后仍然存在)。

Unlike what manuell has said, WM_NCCALCSIZE is the root of all evil. 与manuell所说的不同, WM_NCCALCSIZE是万恶之源。 Also this method should work with any window style (tested with WS_POPUP and WS_OVERLAPPEDWINDOW ) while preserving their functionality, so it's time for me to shut up and post the code with commentary: 此方法也应该适用于任何窗口样式(使用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;
    }

This kind of code gets ugly in a hurry, you are changing the relative mouse position by changing the client area position. 这种代码匆忙变得丑陋,你通过改变客户区位置来改变相对鼠标位置。 That requires you to update the *track_start* variable when you ignore the mouse move when the window gets too small. 这要求您在窗口太小时忽略鼠标移动时更新* track_start *变量。 Not doing so produces an, ahem, interesting effect with the window jumping back and forth. 不这样做会产生一种讽刺,有趣的效果,窗户来回跳跃。 Yes, "wriggles". 是的,“扭动”。

Just don't do it this way, the feature you are looking for is already implemented. 只是不要这样做,你正在寻找的功能已经实现。 Write a message handler for WM_GETMINMAXINFO . WM_GETMINMAXINFO编写消息处理程序。 Call DefWindowProc() first, then override the MINMAXINFO.ptMinTrackSize value. 首先调用DefWindowProc(),然后覆盖MINMAXINFO.ptMinTrackSize值。 If the intent is to implement corner or edge dragging on a borderless window then implement a message handler for WM_NCHITTEST . 如果目的是在无边框窗口上实现边角或边缘拖动,则为WM_NCHITTEST实现消息处理程序。 That also permits implementing your BORDERWIDTH. 这也允许实现你的BORDERWIDTH。 Same recipe, call DefWindowProc() first, override the return value when appropriate. 相同的配方,首先调用DefWindowProc(),在适当时覆盖返回值。

Alas, this is not the answer you are waiting for. 唉,这不是你要等的答案。 On Windows Seven, moving and sizing at the same time a Top-Level Window with WS_POPUP style is indeed broken. 在Windows 7上,同时移动和调整大小与WS_POPUP样式的顶级窗口确实被破坏了。 visually, the window is first moved, and afterward sized. 在视觉上,首先移动窗口,然后调整大小。 When sizing by the left or top, the move operation briefly reveals backgroud pixels, resulting in very bad user experience. 通过左侧或顶部调整大小时,移动操作会短暂显示背景像素,从而导致非常糟糕的用户体验。

As far as I understand what's happening, it has nothing to do with WM_GETMINMAXINFO or WM_NCCALCSIZE. 据我了解发生了什么,它与WM_GETMINMAXINFO或WM_NCCALCSIZE无关。

It's very simple to see the effect: create a WS_POPUP | 看到效果非常简单:创建一个WS_POPUP | WS_VISIBLE window with an almost empty window procedure, set a timer and in the WM_TIMER use SetWindowPos, moving the window a little to the left while sizing it larger, in order to let the right edge at the same place. WS_VISIBLE窗口带有一个几乎为空的窗口程序,设置一个计时器并在WM_TIMER中使用SetWindowPos,将窗口稍微向左移动,同时调整它的大小,以便让右边缘在同一个地方。 You will see backgroud pixels, which is silly. 你会看到背景像素,这是愚蠢的。 No such breakage on Windows XP. 在Windows XP上没有这样的破坏。

I tried lots of tricks, some of them very twisted, but the end result is always the same: at the moment the window is finally rendered in its new state, there is first a move operation, then a size one... 我尝试了很多技巧,其中一些非常扭曲,但最终结果总是一样的:当窗口最终呈现在新状态时,首先是一个移动操作,然后是一个尺寸...

You are left with 2 options (if you target Seven+): 你有两个选择(如果你的目标是七+):

1) Use standard sizing border and take advantage of the new APIs (eg: DwmExtendFrameIntoClientArea) for customizing the frame to fit your needs. 1)使用标准大小边框并利用新API(例如:DwmExtendFrameIntoClientArea)自定义框架以满足您的需求。 See Custom Window Frame Using DWM at http://msdn.microsoft.com/en-us/library/windows/desktop/bb688195.aspx 请参阅使用DWM自定义窗口框架, 网址http://msdn.microsoft.com/en-us/library/windows/desktop/bb688195.aspx

2) Do not use WS_POPUP, but WS_BORDER and use tricks that will fool Windows to NEVER renders the borders. 2)不要使用WS_POPUP,而是使用WS_BORDER并使用欺骗Windows的技巧来永久渲染边框。 It seems that's what VS2012 is doing. 这似乎是VS2012正在做的事情。

Don't forget: flickering INSIDE the window is another story, I am just talking about the right/bottom edge "stability" here. 不要忘记:闪烁在窗口内是另一个故事,我只是在谈论右边/底边“稳定”这里。

It would be helpful to see your code that is changing the window size and position. 看到正在改变窗口大小和位置的代码会很有帮助。

When you move the bottom or right sides, you're only changing the size of the window (height or width). 当您移动底部或右侧时,您只需更改窗口的大小(高度或宽度)。 When you move the top or left sides, you have to change not only the size but also the top/left corner position. 移动顶部或左侧时,您不仅要更改尺寸,还要更改上/左角位置。

If someone wants to move the left border to the right by 10 pixels, then you have to increase the corner position by 10 and reduce the width by 10 - preferably at the same (eg using SetWindowPos for both changes at the same time). 如果有人想要将左边框向右移动10个像素,则必须将角位置增加10并将宽度减小10 - 最好相同(例如,同时使用SetWindowPos进行两次更改)。

Note that changing the that corner position also changes how the screen coordinates of the mouse are interpreted. 请注意,更改该角位置也会更改鼠标的屏幕坐标的解释方式。 So any storing of the old position would also have to be updated. 因此,任何旧仓位的存储也必须更新。

You only need is to process the WM_NCCALCSIZE message , increase your left rgrc rectangle with your border width and increase the top with your CaptionBar height , and decrease your right with border width and your bottom with your CaptionBar height . 您只需处理WM_NCCALCSIZE消息,使用边框宽度增加左边的rgrc矩形,并使用CaptionBar高度增加顶部,使用边框宽度减少右边,使用CaptionBar高度减小底部。 For the border corner you should change your window region on the WM_SIZE message . 对于边界角,您应该在WM_SIZE消息上更改窗口区域。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM