WinAPI C++: Reprogramming Window Resize
Asked Answered
M

5

4

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?

Mink answered 1/10, 2013 at 0:24 Comment(10)
Is there any reason you're implementing this yourself rather than letting the system do it for you?Aborigine
It's true that instead of using the WS_OVERLAPPED style I'm using the WS_POPUP style for my main window, and that is because I want some custom features around the border and the title bar of my window, just like Microsoft Word, Visual Studio 2012, and Google Chrome.Mink
If you handle the WM_NCHITTEST message and return HTTOPLEFT when appropriate you can still leverage the OS-provided window resizing.Aborigine
Will try that, and get back to you ASAP.Mink
I tried what you suggested, but when I am resizing the window with the top border, the window does not repaint!Mink
You may have to process the WM_NCCALCSIZE message. What DefWindowProc returns for that message when resizing the top border?Decapod
I looked into the WM_NCCALCSIZE message; DefWindowProc seems to return 0 regardless of which border I size. While exploring the message, though, I found the CS_VREDRAW style, which, when I specified with my window class, fixed the redrawing problem. My second problem still exists though: whenever I size with the bottom-left, left, top-left, top, or top-right borders the bottom-right corner seems to freak out. I can see this in Visual Studio 2012, or Google Chrome, when resizing the same borders, but not in Windows applications, such as Windows Explorer.Mink
Yoy could try to return WVR_REDRAW for WM_NCCALCSIZE when wParam is TRUE. If there is too much flickering, you could return WVR_VALIDRECTS (if wParam is TRUE), but you will have to update the NCCALCSIZE_PARAMS struct, which may be tricky, depending on the content of your window. In short (if you return WVR_VALIDRECTS): don't preserve bottom and/or right aligned content (the borders).Decapod
Hmm, I tried both of your suggestions, but none of them seemed to work. I have determined that the reason for the flickering is because WM_NCCALCSIZE will always copy the original screen onto the new screen, and then send a WM_PAINT message, which will cause flickering. I guess there's probably no solution to this problem then; that's probably why it happens in larger programs such as Visual Studio too. Thank you for your help nonetheless.Mink
When "top-left" sizing a popup (via DefWindowProc), there is two types of flickering. First, flickering "inside" the window, caused by "bitblit" of bottom-right aligned pixel (borders). WM_NCCALCSIZE and WVR_VALIDRECTS should address that one. Second, flickering caused by DefWindowProc/Windows7 FIRST moving the window up-left, and THEN sizing the window afterward: the bottom-right borders seem to "wriggle". That doesn't happen on Windows XP!. I (perhaps) will try to find a fix (by NOT delegating the WM_NCLBUTTONDOWN to DefWindowProc, or by using some new API for Vista/7 (DWM)Decapod
U
7

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;
    }
Urga answered 26/12, 2013 at 22:15 Comment(5)
Wow, it does work! I will need to look further into your code later, but have some reputation for the time being.Mink
I have downloaded your application but it can not start. I always get the error it is not valid win32 application. Any thoughts? Best regards.Comer
I can only guess that you are using Windows XP. Visual Studio 2012/2013 has different toolsets for XP and Vista++. That is why you can't run Vista++ toolset on XP (it's only my guess). I could recompile it for you, but unfortunately I erased my code. Also unless you want to create your custom frame, it won't eliminate this "laggy" drawing and sometimes it makes it more noticeable. So I don't really use it since it's dead end. Anyway back to your problem. I compiled 2 sample programs that do nothing using different toolsets. Do any of them work? putlocker.com/file/ADD95C258262DD11Urga
@Helix: You are right, I have Windows XP. The XP version works. This is an interesting problem and an interesting solution-you both have +1 from me. Best regards.Comer
Nothing of this works for me, do I have to enable something to be able to resize the window by dragging the border?Apogee
C
2

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.

Cubature answered 1/10, 2013 at 0:53 Comment(1)
I implemented your suggestions, but the window still seems to wriggle when I resize with the top-left corner, and also when I resize with the top border, the window does not repaint!Mink
D
2

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.

Decapod answered 9/10, 2013 at 10:36 Comment(5)
Thank you for your continued support on this problem. Although there is not a very straightforward solution for this problem, I think you have provided the most comprehensive answer. I'll upvote all who helped me on this problem, any mark you as answer.Mink
Thanks! Let me know if you intend to implement something and face problems, I will try to help.Decapod
Could you elaborate on #2 - I'm curious about how to use WS_BORDER and how to "fool Windows to never render borders" (e.g. the VS2012+ effect)Ashleighashlen
@Ashleighashlen No, sorry, I did a lot of experiments at the time, but I don't have the code at hand anymore. The approved answer is not working for you?Decapod
@Decapod No, my custom frame via WM_NCCALCSIZE caused a problem with some WM_LBUTTON* messages getting to top-level windows or popups in my app, so I was pursuing a WS_POPUP (or WS_BORDER) based solution instead.Ashleighashlen
R
1

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 (e.g. 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.

Rasberry answered 5/10, 2013 at 6:27 Comment(1)
All relevant has been posted in my question. I have used your method before, but it will stutter the window too. Also, it isn't as standard as directly using WM_NCHITTEST.Mink
E
1

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 .

Excommunication answered 7/10, 2013 at 16:1 Comment(1)
I also tried this, and it didn't work either. Windows seems to bitblt the existing window to its new position before it allows WM_PAINT through, which results in the flickering in top-left sizing.Mink

© 2022 - 2024 — McMap. All rights reserved.