简体   繁体   中英

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:

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. For WM_NCHITTEST I have:

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.

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?

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. 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:

//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. 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 . Call DefWindowProc() first, then override the MINMAXINFO.ptMinTrackSize value. If the intent is to implement corner or edge dragging on a borderless window then implement a message handler for WM_NCHITTEST . That also permits implementing your BORDERWIDTH. Same recipe, call DefWindowProc() first, override the return value when appropriate.

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. 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.

It's very simple to see the effect: create a 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. You will see backgroud pixels, which is silly. No such breakage on 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. See Custom Window Frame Using DWM at 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. It seems that's what VS2012 is doing.

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).

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 . For the border corner you should change your window region on the WM_SIZE message .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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