Поделиться через


Mixed-Mode Масштабирование DPI и API, поддерживающие DPI

поддержка Sub-Process DPI для повышения осведомленности

SetThreadDpiAwarenessContext позволяет использовать различные режимы масштабирования DPI в рамках одного процесса. До юбилейного обновления Windows 10 осведомленность о DPI окна была привязана к процессуальному режиму осведомленности о DPI (без DPI, с системной осведомленностью о DPI, или с осведомленностью о DPI Per-Monitor). Но теперь с SetThreadDpiAwarenessContextокна верхнего уровня могут иметь режим осведомленности о DPI, отличный от режима осведомленности о DPI на уровне процесса. Это также влияет на дочерние окна, так как они всегда будут иметь тот же режим осведомленности о DPI, что и их родительское окно.

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

Контекст осведомленности о DPI

До появления SetThreadDpiAwarenessContext DPI-осведомленность процесса определялась в манифесте двоичного файла приложения или посредством вызова SetProcessDpiAwareness при инициализации процесса. При SetThreadDpiAwarenessContextкаждый поток может иметь отдельный контекст осведомленности о DPI, который может отличаться от режима осведомленности о DPI на уровне процесса. Контекст осведомленности о DPI потока представлен типом DPI_AWARENESS_CONTEXT и ведет себя следующим образом:

  • В любой момент поток может изменить контекст осведомленности о DPI.
  • Все вызовы API, выполненные после изменения контекста, будут выполняться в соответствующем контексте DPI (и могут быть виртуализированы).
  • При создании окна его восприятие DPI определяется как восприятие DPI вызывающего потока на момент создания.
  • При вызове процедуры окна поток автоматически переключается в контекст осведомленности DPI, который использовался при создании окна.

Распространенный сценарий использования SetThreadDpiAwarenessContext выглядит следующим образом: начните с потока, выполняющегося с одним контекстом (например, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) временно переключитесь в другой контекст (DPI_AWARENESS_CONTEXT_UNAWARE), создайте окно, а затем немедленно переключите контекст потока обратно в предыдущее состояние. Созданное окно будет иметь контекст DPI DPI_AWARENESS_CONTEXT_UNAWARE, в то время как контекст вызывающего потока будет восстановлен до DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE с последующим вызовом SetThreadDpiAwarenessContext. В этом сценарии окно, связанное с вызывающим потоком, будет работать с контекстом для каждого монитора (и, следовательно, операционная система не будет растягивать его в растровом формате), в то время как только что созданное окно не будет учитывать DPI (и поэтому автоматически будет растягиваться растровым методом на дисплее, установленном на масштабирование >100%).

На рисунке 1 показано, как выполняется основной поток процесса с DPI_AWARENESS_CONTEXT_PER_MONITOR, переключает контекст на DPI_AWARENESS_CONTEXT_UNAWAREи создает новое окно. Затем созданное окно выполняется с контекстом DPI-осведомленности DPI_AWARENESS_CONTEXT_UNAWARE всякий раз, когда сообщение отправляется в него или когда выполняются вызовы API из него. Сразу после создания нового окна основной поток возвращается к своему предыдущему контексту DPI_AWARENESS_CONTEXT_PER_MONITOR.

схема , показывающая осведомленность о dpi на уровне каждого монитора в действии

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

EnableNonClientDpiScaling

Заметка

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

Вызов EnableNonClientDpiScaling из обработчика WM_NCCREATE окна приведет к тому, что не клиентская область окна верхнего уровня будет автоматически масштабироваться для DPI. Если окно верхнего уровня поддерживает DPI для каждого монитора (будь то потому, что сам процесс поддерживает DPI для каждого монитора, или потому, что окно было создано в потоке с такой поддержкой), строка заголовка, полосы прокрутки, меню и панели меню этих окон будут масштабироваться всякий раз при изменении DPI окна.

Обратите внимание, что неклиентские области дочернего окна, такие как неклиентские полосы прокрутки дочернего элемента управления типа "редактор", не будут автоматически подстраиваться под масштаб DPI при использовании этого API.

Заметка

EnableNonClientDpiScaling должна быть вызвана из обработчика WM_NCCREATE.

API *ForDpi
  • Несколько часто используемых API, таких как GetSystemMetrics, не имеют никакого контекста HWND и поэтому не имеют способа определения правильной осведомлённости по поводу DPI для их возвращаемых значений. Вызов этих API из потока, работающего в другом режиме осведомленности о DPI или контексте, может возвращать значения, которые не масштабируются для контекста вызывающего потока. GetSystemMetricForDpi, SystemParametersInfoForDpi, а AdjustWindowRectExForDpi будут выполнять те же функции, что и их аналогов без учета DPI, но принимать DPI в качестве аргумента и определять DPI-настроенность из контекста текущего потока.

  • GetSystemMetricForDpi и SystemParametersInfoForDpi возвращают значения системных метрик и параметры системы, масштабированные по DPI, в соответствии с этим уравнением:

    GetSystemMetrics(...) @ dpi == GetSystemMetricsForDpi(..., dpi)

    Таким образом, вызов GetSystemMetrics (или SystemParametersInfoForDpi) на устройстве с определенным значением системного DPI будет возвращать то же значение, что и их версии, учитывающие DPI (например,GetSystemMetricsForDpi и SystemParametersInfoForDpi), при условии, что на вход подается то же значение DPI.

  • AdjustWindowRectExForDpi принимает HWND и вычисляет требуемый размер прямоугольника окна с учетом DPI.

GetDpiForWindow
GetDpiForWindow вернет DPI, связанный с предоставленным HWND. Ответ будет зависеть от режима DPI-осведомленности для HWND.
Режим учета DPI для HWND Возвращаемое значение
Не подозревающий 96
Система Системный DPI
Per-Monitor DPI дисплея, на котором преимущественно находится связанное окно верхнего уровня.
(Если предоставлено дочернее окно, будет возвращено значение DPI верхнего уровня соответствующего родительского окна)
GetDpiForSystem

Вызов GetDpiForSystem является более эффективным, чем вызов GetDC и GetDeviceCaps для получения DPI системы.

Любой компонент, который может выполняться в приложении с осведомленностью о DPI в подпроцессах, не должен предполагать, что системный DPI статичен во время жизненного цикла процесса. Например, если поток, выполняющийся в DPI_AWARENESS_CONTEXT_UNAWARE контексте осведомленности, запрашивает DPI системы, ответ будет равен 96. Однако если этот же поток переключился на DPI_AWARENESS_CONTEXT_SYSTEM контекст осведомленности и запросил системный DPI еще раз, ответ может отличаться. Чтобы избежать использования кэшированного (и, возможно, устаревшего) значения DPI системы, используйте GetDpiForSystem для получения системного DPI относительно режима учета DPI вызывающего потока.