WM_DPICHANGED вызывается в цикле с размером, не возвращенным из WM_GETDPISCALEDSIZE

Я пытаюсь сделать наше приложение WinAPI зависимым от DPI и столкнулся с очевидно бесконечным циклом, в котором окно постоянно получает WM_DPICHANGED. Программа всегда вызывает SetWindowPos с lParam сообщения WM_DPICHANGED, как сказано в документации.

Моя конфигурация монитора имеет монитор 1 справа, 1920x1080, масштабирование 100%, и монитор 2 слева, выровненный снизу, 3840x2160, масштабирование 150%. Запустив следующую программу, переместите окно так, чтобы оно располагалось между двумя мониторами, но при 100% масштабировании. Теперь возьмите окно на левом мониторе и переместите окно вверх и вниз по вертикали, чтобы оно переключалось между двумя разрешениями. В какой-то момент он входит в цикл, который, кажется, останавливает работу всего оконного менеджера (ctrl-alt-del прервет его - нет необходимости фактически завершать программу).

В этом цикле прямоугольник, переданный в lParam, имеет размер 500x500 для 96 и 144 точек на дюйм! Почему WM_DPICHANGED получает прямоугольник, размер которого отличается от размера, возвращенного из WM_GETDPISCALEDSIZE?

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

Почему возникает этот цикл и как избежать цикла изменения DPI?

#include "stdafx.h"

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_GETDPISCALEDSIZE:
    {
        LPSIZE lpSize = (LPSIZE)lParam;
        int dpi = wParam;
        lpSize->cy = lpSize->cx = (dpi == 96 ? 200 : 500);
        return TRUE;
    }
    case WM_DPICHANGED:
    {
        LPRECT lpRect = (LPRECT)lParam;
        SetWindowPos(hWnd, nullptr, lpRect->left, lpRect->top, lpRect->right - lpRect->left, lpRect->bottom - lpRect->top, SWP_NOZORDER | SWP_NOACTIVATE);
        return 0;
    }
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

WCHAR szTitle[] = L"Test";                  // The title bar text
WCHAR szWindowClass[] = L"TESTCLASS";            // the main window class name

ATOM                MyRegisterClass(HINSTANCE);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    MyRegisterClass(hInstance);

   SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_VISIBLE,
      200, 200, 200, 200, nullptr, nullptr, hInstance, nullptr);

    MSG msg;

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = nullptr;
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = nullptr;
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = nullptr;

    return RegisterClassExW(&wcex);
}

person Max    schedule 05.09.2018    source источник
comment
Не вижу ничего плохого в вашем коде. В Windows есть много ошибок, связанных с DPI каждого монитора, я подозреваю, что вы только что наткнулись на одну из них.   -  person Jonathan Potter    schedule 05.09.2018
comment
Ваше окно может постоянно менять монитор, у которого самая большая его площадь. Для проверки используйте MonitorFromWindow - записать результат в консоль или файл.   -  person Daniel Sęk    schedule 05.09.2018


Ответы (1)


Кажется возможным обойти эту ситуацию, определив, совпадает ли DPI в wParam тому, что возвращается GetDpiForWindow, и игнорируя WM_DPICHANGED в этом случае.

person Max    schedule 25.09.2018