GetWindowRect возвращает размер, включающий "невидимые" границы

Я работаю над приложением, которое размещает окна на экране в стиле сетки. При запуске этого в Windows 10 между окнами возникает огромный разрыв. Дальнейшее расследование показывает, что GetWindowRect возвращает неожиданные значения, включая невидимую границу, но я не могу заставить его вернуть реальные значения с видимой границей.

1) Эта ветка предполагает, что это сделано по замыслу, и вы можете "исправить" это, связавшись с winver=6. Моя среда не позволяет этого, но я попытался изменить PE MajorOperatingSystemVersion а также MajorSubsystemVersion до 6 без последствий

2) Эта же тема также предлагает использовать DwmGetWindowAttribute с DWMWA_EXTENDED_FRAME_BOUNDS чтобы получить реальные координаты из DWM, который работает, но означает изменение везде, где получаются координаты окна. Это также не позволяет устанавливать значение, оставляя нас в обратном порядке, чтобы иметь возможность установить размер окна.

3) Этот вопрос говорит о недостаточной осведомленности о DPI в процессе. Ни установки флага осведомленности DPI в манифесте, ни вызова SetProcessDpiAwareness был какой-либо результат.

4) Я также попытался добавить флаги совместимости Windows Vista, 7, 8, 8.1 и 10, и темы Windows проявляются без изменений.

Снимок экрана Это окно перемещается в 0x0, 1280x1024, предположительно, чтобы заполнить весь экран, и при запросе координат назад мы получаем те же значения. Однако на самом деле окно на 14 пикселей уже, чтобы учесть границы в старых версиях Windows.

Как я могу убедить Windows разрешить мне работать с реальными координатами окна?

5 ответов

Решение

Windows 10 имеет тонкие невидимые границы слева, справа и снизу, она используется для захвата мыши для изменения размера. Границы могут выглядеть так: 7,0,7,7 (слева, сверху, справа, снизу)

Когда вы звоните SetWindowPos поставить окно по этим координатам:
0, 0, 1280, 1024

Окно выберет эти точные координаты, и GetWindowRect вернет те же координаты. Но визуально окно кажется здесь:
7, 0, 1273, 1017

Вы можете обмануть окно и попросить его перейти сюда:
-7, 0, 1287, 1031

Для этого мы получаем толщину границы Windows 10:

RECT rect, frame;
GetWindowRect(hwnd, &rect);
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));

//rect should be `0, 0, 1280, 1024`
//frame should be `7, 0, 1273, 1017`

RECT border;
border.left = frame.left - rect.left;
border.top = frame.top - rect.top;
border.right = rect.right - frame.right;
border.bottom = rect.bottom - frame.bottom;

//border should be `7, 0, 7, 7`

Затем сместите прямоугольник так:

rect.left -= border.left;
rect.top -= border.top;
rect.right += border.left + border.right;
rect.bottom += border.top + border.bottom;

//new rect should be `-7, 0, 1287, 1031`

Если нет более простого решения!

Как я могу убедить Windows разрешить мне работать с реальными координатами окна?

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

Если вы хотите, чтобы ваши глаза соответствовали тому, что говорит вам Windows, вы можете попробовать выставить эти границы, чтобы они снова были видны, используя тему Aero Lite:

http://winaero.com/blog/enable-the-hidden-aero-lite-theme-in-windows-10/

Я нашел новое решение для тех, кому нужны логические координаты, например, как возвращается, но с правильными значениями (без включения невидимой границы). Использование with даст правильное значение, но в нелогических координатах, и в некоторых случаях это становится проблемой (например, при наличии нескольких мониторов с разными конфигурациями DPI).

Новое решение — использовать ответ из), а затем исправьте ответ, чтобы он был в логических единицах (например, как возвращает GetWindowRect), используя некоторые умные математические приемы и с помощью еще двух вызовов API (чтобы получить дополнительную информацию для крутой математики)

Решение основано на моем последнем открытии из раздела «Получение реального разрешения экрана с помощью Win32 API».

Вот код метода, который выдаст вам правильное значение без невидимых границ и в логических координатах:

      bool GetWindowRectNoInvisibleBorders(HWND hWnd, RECT* rect)
{
    // Get the physical coordinates of the window (this is without the additional offsets)
    RECT dwmRect;
    HRESULT hresult = DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &dwmRect, sizeof(RECT));
    // Return false in such case. error handling was done only here because after this check, the rest of the code
    // use the API correctly so error is unlikely 
    if (hresult != S_OK)
        return false;

    // Get information from the monitor where the window located.
    // We need it for getting its RECT in logical coordinates (rcMonitor.*)
    HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    MONITORINFOEX monInfo;
    monInfo.cbSize = sizeof(MONITORINFOEX);
    GetMonitorInfo(monitor, &monInfo);

    // Get additional information from this monitor. we need it for getting the physical
    // coordinates of its position (dmPosition.x and dmPosition.y) and its physical coordinates
    // of its size (dmPelsWidth)
    DEVMODE monDeviceConfig;
    monDeviceConfig.dmSize = sizeof(DEVMODE);
    EnumDisplaySettings(monInfo.szDevice, ENUM_CURRENT_SETTINGS, &monDeviceConfig);

    // Calculate the ratio between the logical size and the physical size of the monitor (part of math to handle DPI changes)
    auto scalingRatio = (monInfo.rcMonitor.right - monInfo.rcMonitor.left) / (double)monDeviceConfig.dmPelsWidth;

    // Calculate the final answer in logical coordinates
    rect->left = (dwmRect.left - monDeviceConfig.dmPosition.x) * scalingRatio + monInfo.rcMonitor.left;
    rect->right = (dwmRect.right - monDeviceConfig.dmPosition.x) * scalingRatio + monInfo.rcMonitor.left;
    rect->top = (dwmRect.top - monDeviceConfig.dmPosition.y) * scalingRatio + monInfo.rcMonitor.top;
    rect->bottom = (dwmRect.bottom - monDeviceConfig.dmPosition.y) * scalingRatio + monInfo.rcMonitor.top;

    return true; // success
}

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

AdjustWindowRectEx (или в Windows 10 и новее AdjustWindowRectExForDpi) может пригодиться. Эти функции преобразуют клиентский прямоугольник в размер окна.

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

Вот небольшой фрагмент из моей кодовой базы, где я успешно использовал их, чтобы установить размер окна, чтобы получить желаемый размер клиента, простите за макросы обработки ошибок:

DWORD window_style = (DWORD)GetWindowLong(global_context->window, GWL_STYLE);
CHECK_CODE(window_style);
CHECK(window_style != WS_OVERLAPPED); // Required by AdjustWindowRectEx

DWORD window_style_ex = (DWORD)GetWindowLong(global_context->window, GWL_EXSTYLE);
CHECK_CODE(window_style_ex);

// XXX: Use DPI aware version?
RECT requested_size = {};
requested_size.right = width;
requested_size.bottom = height;
AdjustWindowRectEx(
    &requested_size,
    window_style,
    false, // XXX: Why always false here?
    window_style_ex
);

UINT set_window_pos_flags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
CHECK_CODE(SetWindowPos(
    global_context->window,
    nullptr,
    0,
    0,
    requested_size.right - requested_size.left,
    requested_size.bottom - requested_size.top,
    set_window_pos_flags
));

В приведенном выше сценарии использования по-прежнему есть две неоднозначности:

  • Мое окно действительно есть меню, но я должен передать в ложном для паров меню или я получаю неправильный размер вне. Я дополню этот ответ объяснением, если выясню, почему это так!
  • Я еще не читал о том, как Windows обрабатывает осведомленность о DPI, поэтому я не уверен, когда вы хотите использовать эту функцию, а не ту, которая не поддерживает DPI.

Вы можете ответить на WM_NCCALCSIZE сообщение, изменить WndProcПоведение по умолчанию для удаления невидимой границы.

Как этот документ и этот документ объясняют, когда wParam > 0, по запросу wParam.Rgrc[0] содержит новые координаты окна и, когда процедура возвращается, Response wParam.Rgrc[0] содержит координаты нового клиентского прямоугольника.

Пример кода Голанга:

case win.WM_NCCALCSIZE:
    log.Println("----------------- WM_NCCALCSIZE:", wParam, lParam)

    if wParam > 0 {
        params := (*win.NCCALCSIZE_PARAMS)(unsafe.Pointer(lParam))
        params.Rgrc[0].Top = params.Rgrc[2].Top
        params.Rgrc[0].Left = params.Rgrc[0].Left + 1
        params.Rgrc[0].Bottom = params.Rgrc[0].Bottom - 1
        params.Rgrc[0].Right = params.Rgrc[0].Right - 1
        return 0x0300
    }
Другие вопросы по тегам