Определить, было ли масштабировано / виртуализировано приложение, не поддерживающее DPI

Я пытаюсь определить в приложении WinForms, запущено ли оно в масштабированном / виртуализированном режиме из-за того, что ОС имеет высокий DPI. В настоящее время в системе с разрешением 3840x2400 с масштабированием 200% приложение видит разрешение 1920x1200, DPI- 96, а коэффициент масштабирования - 1.

Мы находимся в процессе создания приложения с учетом DPI, но до этого нам нужно "быстрое исправление", которое позволит нам обнаруживать масштабирование. Причина этого заключается в том, что это нарушает функциональность приложения, которое делает снимок экрана. Мы используем масштабированные размеры в Graphics.CopyFromScreen, он делает снимок экрана неправильного размера, так как он ожидает немасштабированных размеров.

Мне известно о настройке DPI-осведомленности, но на данный момент мы все еще хотим, чтобы приложение масштабировалось, но при этом мы можем обнаружить, что мы масштабированы, и получить немасштабированные измерения, если это возможно.

1 ответ

Решение

Приложение, которое явно не помечено как осведомленное о высоком разрешении, будет обмануто системой и сообщит, что имеется 96 точек на дюйм с коэффициентом масштабирования 100%. Чтобы получить реальные настройки DPI и избежать автоматической виртуализации с помощью DWM, вам необходимо включить <dpiAware>True/PM</dpiAware> в манифесте вашего приложения. Более подробная информация доступна здесь.

В вашем случае это звучит так, как будто вы ищете LogicalToPhysicalPointForPerMonitorDPI а также PhysicalToLogicalPointForPerMonitorDPI пара функций. Как объясняется в связанной документации, по умолчанию система будет возвращать информацию о других окнах, основываясь на осведомленности вызывающего абонента о DPI. Таким образом, если приложение без поддержки DPI пытается получить границы окна процесса с высоким разрешением, оно получит границы, которые были переведены в свое собственное координатное пространство без поддержки DPI. Это было бы, в просторечии этих функций, "логическими" координатами. Вы можете преобразовать их в "физические" координаты, которые фактически используются операционной системой (и другими процессами с высоким разрешением).

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

  1. Позвоните GetScaleFactorForMonitor функция. Если в результате DEVICE_SCALE_FACTOR значение - это что-то кроме SCALE_100_PERCENT тогда вы масштабируете. Если ваше приложение не поддерживает DPI, вы виртуализированы.

    Это быстрое и грязное решение, так как простое определение P/Invoke - это все, что вам нужно, чтобы вызвать его из приложения WinForms. Тем не менее, вы не должны полагаться на его результаты для чего-то большего, чем логическое "мы масштабированы / виртуализированы?" индикатор. Другими словами, не верьте масштабному коэффициенту, который он возвращает!

    В системе Windows 10, где системное DPI составляет 96, а монитор с высоким DPI имеет 144 DPI (масштабирование 150%), GetScaleFactorForMonitor функция возвращает SCALE_140_PERCENT когда это должно было бы вернуться SCALE_150_PERCENT (144/96 == 1,5). Я не очень понимаю, почему это так. Единственное, что я могу понять, это то, что он был разработан для приложений Metro/Modern/UWP в Windows 8.1, где 150% - это не действительный коэффициент масштабирования, а 140%. С тех пор коэффициенты масштабирования были унифицированы в Windows 10, но эта функция, по-видимому, не была обновлена ​​и по-прежнему возвращает ненадежные результаты для настольных приложений.

  2. Рассчитайте коэффициент масштабирования самостоятельно, исходя из логической и физической ширины монитора.

    Во-первых, конечно, вам нужно получить HMONITOR (обращаться к конкретному физическому монитору). Вы можете сделать это, позвонив MonitorFromWindow, передавая дескриптор в окно WinForms и указывая MONITOR_DEFAULTTONEAREST, Это даст вам представление о мониторе, на котором отображается интересующее вас окно.

    Затем вы будете использовать этот дескриптор монитора, чтобы получить логическую ширину этого монитора, вызвав GetMonitorInfo функция. Это заполняет MONITORINFOEX структура, которая содержит в качестве одного из своих членов, RECT состав (rcMonitor), который содержит координаты виртуального экрана этого монитора. (Помните, что, в отличие от.NET, Windows API представляет прямоугольники с точки зрения их левого, верхнего, правого и нижнего экстентов. Ширина - это правый экстент минус левый экстент, а высота - нижний экстент минус верхний экстент.)

    MONITORINFOEX структура заполнена GetMonitorInfo также даст вам имя этого монитора (szDevice членом). Затем вы можете использовать это имя для вызова EnumDisplaySettings функция, которая будет заполнять DEVMODE структура с кучей информации о физических режимах отображения для этого монитора. Члены, которые вас интересуют dmPelsWidth а также dmPelsHeight, которые дают вам количество физических пикселей на ширину и высоту соответственно.

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

    Пример кода, протестированный и работающий в Windows 10 (написан на C++, потому что это то, что мне пригодится; извините, вам придется сделать свой собственный перевод на.NET):

    // Get the monitor that the window is currently displayed on
    // (where hWnd is a handle to the window of interest).
    HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    
    // Get the logical width and height of the monitor.
    MONITORINFOEX miex;
    miex.cbSize = sizeof(miex);
    GetMonitorInfo(hMonitor, &miex);
    int cxLogical = (miex.rcMonitor.right  - miex.rcMonitor.left);
    int cyLogical = (miex.rcMonitor.bottom - miex.rcMonitor.top);
    
    // Get the physical width and height of the monitor.
    DEVMODE dm;
    dm.dmSize        = sizeof(dm);
    dm.dmDriverExtra = 0;
    EnumDisplaySettings(miex.szDevice, ENUM_CURRENT_SETTINGS, &dm);
    int cxPhysical = dm.dmPelsWidth;
    int cyPhysical = dm.dmPelsHeight;
    
    // Calculate the scaling factor.
    double horzScale = ((double)cxPhysical / (double)cxLogical);
    double vertScale = ((double)cyPhysical / (double)cyLogical);
    ASSERT(horzScale == vertScale);
    
Другие вопросы по тегам