WinAPI C++: изменение размера окна перепрограммирования

У меня есть окно, и я хочу реализовать границы как изменение размеров границ, как и любое другое окно. Принимая предложения от комментариев и ответов, я переписал свой код. Для 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 Explorer?

5 ответов

Решение

Я знаю, что уже немного поздно, но я думаю, что нашел способ изменить размер без "шевеления" (задержка внутри рисования окна все равно останется).

В отличие от того, что сказал Мануэлл, WM_NCCALCSIZE это корень всего зла. Также этот метод должен работать с любым стилем окна (протестировано с WS_POPUP а также 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;
    }

Этот вид кода становится уродливым в спешке, вы меняете относительную позицию мыши, изменяя положение клиентской области. Для этого необходимо обновить переменную *track_start*, когда вы игнорируете движение мыши, когда окно становится слишком маленьким. В противном случае получается интересный эффект, когда окно прыгает вперед и назад. Да, "извивается".

Только не делайте так, функция, которую вы ищете, уже реализована. Написать обработчик сообщения для WM_GETMINMAXINFO, Сначала вызовите DefWindowProc(), а затем переопределите значение MINMAXINFO.ptMinTrackSize. Если цель состоит в том, чтобы осуществить перетаскивание углов или краев в окне без полей, тогда реализуйте обработчик сообщений для WM_NCHITTEST, Это также позволяет реализовать ваш BORDERWIDTH. Тот же рецепт, сначала вызовите DefWindowProc(), при необходимости переопределите возвращаемое значение.

Увы, это не тот ответ, которого вы ждете. В Windows Seven перемещение и определение размеров одновременно окна верхнего уровня в стиле WS_POPUP действительно нарушены. визуально окно сначала перемещается, а затем измеряется. При изменении размера по левому или верхнему краю операция перемещения кратко выявляет фоновые пиксели, что приводит к очень плохому восприятию пользователем.

Насколько я понимаю, что происходит, это не имеет ничего общего с WM_GETMINMAXINFO или WM_NCCALCSIZE.

Это очень просто увидеть эффект: создать WS_POPUP | Окно WS_VISIBLE с почти пустой процедурой окна, установите таймер и в WM_TIMER используйте SetWindowPos, перемещая окно немного влево, одновременно увеличивая его размер, чтобы оставить правый край в том же месте. Вы увидите фоновые пиксели, которые глупы. Нет такой поломки на Windows XP.

Я перепробовал много хитростей, некоторые из которых были очень извращенными, но конечный результат всегда один и тот же: в данный момент окно, наконец, отображается в новом состоянии, сначала идет операция перемещения, затем размер один...

У вас есть 2 варианта (если вы нацелены на Семь +):

1) Используйте стандартную границу размеров и используйте новые API (например, DwmExtendFrameIntoClientArea) для настройки фрейма в соответствии с вашими потребностями. См. Пользовательскую оконную раму с использованием DWM на http://msdn.microsoft.com/en-us/library/windows/desktop/bb688195.aspx

2) Не используйте WS_POPUP, но WS_BORDER и используйте трюки, которые обманут Windows, чтобы НИКОГДА не отображать границы. Кажется, это то, что делает VS2012.

Не забывайте: мерцание ВНУТРИ окна - это еще одна история, я просто говорю о "стабильности" справа / снизу.

Было бы полезно увидеть ваш код, который меняет размер и положение окна.

Когда вы перемещаете нижнюю или правую сторону, вы изменяете только размер окна (высоту или ширину). Когда вы перемещаете верхнюю или левую сторону, вы должны изменить не только размер, но и верхний / левый угол.

Если кто-то хочет переместить левую границу вправо на 10 пикселей, то вам нужно увеличить угловое положение на 10 и уменьшить ширину на 10 - предпочтительно в одно и то же (например, используя SetWindowPos для обоих изменений одновременно).

Обратите внимание, что изменение этой угловой позиции также меняет интерпретацию экранных координат мыши. Таким образом, любое сохранение старой позиции также должно быть обновлено.

Вам нужно только обработать WM_NCCALCSIZE сообщение, увеличьте свой левый прямоугольник rgrc с шириной вашей границы и увеличьте верх с вашей высотой CaptionBar, и уменьшите ваше правое с шириной границы и вашим низом с вашей высотой CaptionBar. Для угла границы вы должны изменить область окна на WM_SIZE сообщение

Другие вопросы по тегам