Масштабирование неклиентской области (строка заголовка, строка меню) для поддержки высокого разрешения на каждый монитор

В Windows 8.1 появилась возможность иметь разные настройки DPI для разных мониторов. Эта функция известна как «поддержка высокого разрешения на каждый монитор». Он сохраняется, и был дополнительно доработан в Windows 10.

Если приложение не принимает участия (т.е., не поддерживает DPI или поддерживает высокий DPI), оно будет автоматически увеличено DWM до надлежащего DPI. Большинство приложений попадают в одну из этих двух категорий, включая большинство утилит, поставляемых с Windows (например,, Блокнот). В моей тестовой системе для монитора с высоким DPI установлен масштаб 150% (144 DPI), а для обычного монитора - системный DPI (масштаб 100%, 96 DPI). Поэтому, когда вы открываете одно из этих приложений на экране с высоким разрешением (или перетаскиваете его туда), виртуализация срабатывает, увеличивая все, но также делая его невероятно размытым.

С другой стороны, если приложение явно указывает, что оно поддерживает высокое разрешение для каждого монитора, то виртуализация не выполняется, и разработчик несет ответственность за масштабирование. У Microsoft есть довольно подробное объяснение здесь *, но в качестве самостоятельного вопроса я резюмирую. Во-первых, вы указываете поддержку, задав <dpiAware>True/PM</dpiAware> в манифесте. Это позволит вам получать WM_DPICHANGED сообщения, которые сообщают вам как новый параметр DPI, так и предлагаемый новый размер и положение для вашего окна. Он также позволяет вызывать функцию GetDpiForMonitor и получать фактический DPI, без ложных указаний по соображениям совместимости. Кенни Керр также написал подробное руководство.

Я успешно проделал все это в небольшом тестовом приложении на C ++. Здесь много шаблонов и в основном настроек проекта, поэтому я не вижу особого смысла публиковать здесь полный пример. Если вы хотите проверить это, либо следуйте инструкциям Кенни, это руководство в MSDN или загрузите официальный SDK образец. Теперь текст в клиентской области выглядит хорошо (из-за того, что я обработал WM_DPICHANGED), но поскольку виртуализация больше не выполняется, масштабирование неклиентской области отсутствует. В результате строка заголовка / заголовка и строка меню имеют неправильный размер - они не становятся больше на экране с высоким разрешением:

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

Было предложено , что нет ответа - ваш единственный выбор - настроить отрисовку всего окна, включая неклиентскую область. Хотя это, безусловно, возможно, и действительно то, что делают приложения UWP (ранее известные как Metro), такие как калькулятор Windows 10, это не работоспособный вариант для настольных приложений, которые используют множество неклиентских виджетов и надеются выглядеть нативными.

Кроме того, это явно ложь. Настраиваемые строки заголовка не могут быть единственным способом добиться правильного поведения, поскольку команда оболочки Windows сделала это. Скромное диалоговое окно Run ведет себя именно так, как ожидалось, правильно меняя размер как клиентской, так и неклиентской областей, когда вы перетаскиваете его между мониторами с разными DPI:

Исследование с помощью Spy ++ подтверждает, что это просто стандартный диалог Win32 - ничего особенного. Все элементы управления являются стандартными элементами управления Win32 SDK. Это не приложение UWP, и они не отрисовывали строку заголовка на заказ - она ​​по-прежнему имеет стиль WS_CAPTION. Он запускается процессом explorer.exe, который помечен как поддерживающий высокое разрешение для каждого монитора (проверено с помощью Process Explorer и GetProcessDpiAwareness). Это сообщение в блоге подтверждает что и диалоговое окно «Выполнить», и командная строка были переписаны в Windows 10 для правильного масштабирования (см. «Командные оболочки и др.»). Что делает диалоговое окно "Выполнить" для изменения размера строки заголовка?

Common Item Dialog API, отвечающий за диалоги открытия и сохранения нового стиля, также правильно масштабируется при запуске из процесса, поддерживающего высокое разрешение для каждого монитора, что можно увидеть, нажав кнопку «Обзор» в диалоговом окне «Выполнить». То же самое для API диалогового окна задач, создавая странную ситуацию, когда приложение запускает диалоговое окно с заголовком другого размера < / а>. (Устаревший API MessageBox, однако, не обновлялся и демонстрирует то же поведение, что и мое тестовое приложение.)

Если команда оболочки делает это, это должно быть возможно. Я просто не могу представить, чтобы группа, ответственная за разработку / реализацию поддержки DPI для каждого монитора, не позаботилась о том, чтобы предоставить разработчикам разумный способ создания совместимых приложений. . Подобные функции требуют поддержки разработчика, в противном случае они выходят из строя. Даже приложения WPF не работают - Per-Monitor Aware WPF Sample не может масштабировать неклиентскую область, в результате чего строка заголовка имеет неправильный размер. Я не сторонник теорий заговора, но это пахнет маркетинговым ходом, препятствующим разработке настольных приложений. Если это так и официального способа не существует, я приму ответы, основанные на недокументированном поведении.

Говоря о недокументированном поведении, запись сообщений окна, когда диалоговое окно «Выполнить» перетаскивается между мониторами с разными настройками DPI, показывает, что он получает недокументированное сообщение, 0x02E1. Это несколько интересно, потому что этот идентификатор сообщения ровно на единицу больше задокументированного WM_DPICHANGED сообщение (0x02E0). Тем не менее, мое тестовое приложение никогда не получает это сообщение, независимо от настроек распознавания DPI. (Любопытно, что внимательный осмотр действительно показывает, что Windows немного увеличивает размер глифов сворачивания / разворачивания / закрытия в строке заголовка, когда окно перемещается на монитор с высоким разрешением DPI. Они все еще не такие большие как они есть, когда они виртуализированы, но они немного больше, чем глифы, которые он использует для немасштабированных приложений с системным DPI.)

До сих пор моей лучшей идеей было обработать _8 _ сообщение для настройки размера неклиентской области. Используя флаг SWP_FRAMECHANGED с функцией SetWindowPos, я может заставить окно изменить размер и перерисовать неклиентскую область в ответ на WM_DPICHANGED. Это отлично работает, чтобы уменьшить высоту строки заголовка или даже полностью удалить ее, но это никогда не сделает он выше. Заголовок, кажется, достигает максимума на высоте, определяемой системным DPI. Даже если бы это сработало, это не было бы идеальным решением, потому что это не помогло бы с системной строкой меню или полосами прокрутки, но, по крайней мере, это было бы началом. Другие идеи?

* Я знаю, что в этой статье говорится: " Обратите внимание, что неклиентская область приложения, поддерживающего разрешение каждого монитора, не масштабируется Windows и будет казаться пропорционально меньше на дисплее с высоким разрешением. " См. выше, почему это (1) неправильно и (2) неудовлетворительно. Я ищу обходной путь, кроме настраиваемого рисования неклиентской области.


person Cody Gray    schedule 26.04.2016    source источник
comment
Думаю, это не так просто, как не передать WM_DPICHANGED DefWindowProc?   -  person Jonathan Potter    schedule 24.05.2016
comment
@Jonathan К сожалению, нет. Согласно обычному обычаю, мое тестовое приложение передает все сообщения DefWindowProc (кроме WM_PAINT), так что это позаботится даже о недокументированных сообщениях.   -  person Cody Gray    schedule 24.05.2016
comment
@Barmak Я предполагаю, что он не рисует строку заголовка, потому что имеет стиль WS_CAPTION. Я предполагаю, что он мог использовать WM_NCCALCSIZE для удаления строки заголовка, нарисованной системой, и рисования своей собственной. Да, я пробовал все это, скачав последнюю версию набора инструментов Visual Studio. Определенно установлено на v140 и, в частности, нацелено на последнюю версию Windows 10 (10586).   -  person Cody Gray    schedule 26.05.2016
comment
На самом деле я ошибался. Я не знаю, как сделать панель заголовка с настраиваемым отрисовкой, которая увеличивает размер кнопок закрытия / максимума / минимума в Windows 10. Поэтому строка заголовка Run-Dialog, вероятно, не является настраиваемой (это было бы еще одной загадкой, если бы это был кейс)   -  person Barmak Shemirani    schedule 28.05.2016
comment
Я начал диалог выполнения с помощью этого метода. Кажется, что Run-Dialog использует манифест из родительского процесса. Если мой процесс поддерживает per-monitor-dpi, то Run-Dialog также поддерживает per-monitor-dpi, но с той же ошибкой в ​​строке заголовка, что и мой собственный процесс. Если вы запускаете Run-Dialog из меню «Пуск», то Explorer.exe становится родительским процессом, а Run-Dialog использует манифест из Explorer.exe. Глядя на код Explorer.exe, кажется, что у него есть собственный специальный флаг DPI: ‹dpiAware› Explorer ‹/dpiAware› (он не работает для других программ)   -  person Barmak Shemirani    schedule 29.05.2016
comment
@barmak Ах, очень интересно! Спасибо, что поделились этим наблюдением. Естественно, диалоговое окно унаследует свойства своего процесса, но я перепробовал все настройки поддержки DPI. Я понятия не имел, что Explorer использует свой специальный флаг. Process Explorer просто сообщает, что он поддерживает высокое разрешение для каждого монитора.   -  person Cody Gray    schedule 30.05.2016
comment
Ой. Ложная сигнализация. Я был в восторге от того, что настройка ‹dpiAware› Explorer ‹/dpiAware›, похоже, работает для моего небольшого тестового приложения, но оказывается, что оно просто рассматривается как приложение, не поддерживающее DPI. Мой собственный код масштабирования портил меня.   -  person Cody Gray    schedule 30.05.2016
comment
@CodyGray Можно ли поймать WM_NCPAINT, затем сделать что-нибудь (возможно, как-то изменить DPI HDC / HWND?) И попытаться передать его DefWindowProc, а затем установить старый DPI?   -  person sashoalm    schedule 30.05.2016
comment
@sas Захват WM_NCPAINT не является проблемой, проблема заключается в изменении DPI HDC / HWND. Раньше DPI был общесистемным; теперь это настройка для каждого процесса. По-прежнему не для каждого окна или для контекста устройства. Единственный способ, которым WM_NCPAINT поможет мне, - это использовать его для полного рисования владельцем неклиентской области, чего я бы предпочел не делать по причинам, обсуждаемым в вопросе.   -  person Cody Gray    schedule 31.05.2016


Ответы (2)


В любых современных сборках Windows Insider (сборка> = 14342, версия SDK #> = 14332) есть EnableNonClientDpiScaling API (который принимает HWND в качестве аргумента), который будет включать масштабирование DPI неклиентского уровня для HWND верхнего уровня. . Эта функция требует, чтобы окно верхнего уровня работало в режиме с учетом DPI для каждого монитора. Этот API должен вызываться из обработчика WM_NCCREATE для окна. Когда этот API вызывается в окне верхнего уровня, его панель заголовков, полосы прокрутки верхнего уровня, системное меню и строка меню будут масштабироваться при изменении DPI приложения (это может произойти, когда приложение перемещается на дисплей с другим масштабированием дисплея). значение или когда DPI изменяется по другим причинам, например, когда пользователь меняет настройки или когда RDP-соединение меняет масштабный коэффициент).

Этот API не поддерживает масштабирование неклиентской области для дочерних окон, например полос прокрутки в дочернем окне.

Насколько мне известно, без этого API невозможно автоматическое масштабирование DPI вне клиентской области.

Обратите внимание, что этот API еще не доработан и может измениться до того, как будет выпущен в обновлении Windows 10 Anniversary. Следите за официальной документацией MSDN, когда она станет окончательной.

person peterfelts    schedule 03.06.2016
comment
Спасибо, отлично работает! Заголовки, системные меню и т. Д. Масштабируются правильно, единственная проблема заключается в том, что контекстные меню (CreatePopupMenu) не изменяются и выглядят слишком большими на мониторах с низким разрешением. Вы знаете, есть ли уловка, чтобы их тоже масштабировать? - person jdm; 15.09.2016
comment
Предварительная документация для EnableNonClientDpiScaling есть в наличии. - person Barmak Shemirani; 15.09.2016
comment
Есть ли какая-то причина использовать этот подход в настоящее время, а не более новый ответ Мартина? - person ToolmakerSteve; 13.08.2017

С помощью осведомленности о разрешении на каждый монитор V2 в Windows 10 Creators Update (сборка 15063) вы можете легко решить эту проблему без EnableNonClientDpiScaling.

Чтобы включить осведомленность о разрешении на каждый монитор V2, при этом поддерживая старую осведомленность о разрешении на каждый монитор в более старых сборках Windows 10 и Windows 8.1 и осведомленность о разрешении на дюйм в еще Более старые версии Windows, сделайте ваше приложение таким:

<assembly ...>
    <!-- ... --->
    <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
            <dpiAware>True/PM</dpiAware>
            <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
        </asmv3:windowsSettings>
    </asmv3:application>
</assembly>

Использованная литература:

person Martin Prikryl    schedule 05.04.2017
comment
для PerMonitorV2 вам больше не следует использовать манифест, вместо этого используйте app.config: добавьте <add key="DpiAwareness" value="PerMonitorV2" /> в теги <System.Windows.Forms.ApplicationConfigurationSection> - person magicandre1981; 12.04.2017
comment
Кстати, это для WinForms, ориентированного на .net 4.7. если вы используете другие технологии, вы должны использовать манифест - person magicandre1981; 12.04.2017
comment
Я предполагаю, что тег <add> в app.config фактически изменяет манифест в конце - этот вопрос и ответ являются общими - они не имеют ничего общего с .NET. - person Martin Prikryl; 12.04.2017
comment
Я понятия не имею. Я также играю со всеми новыми настройками и проверяю, лучше ли наши старые WinForms работают на HighDPI. - person magicandre1981; 12.04.2017
comment
@ magicandre1981 - чтобы уточнить, WinForms .Net 4.7 High DPI, приложения WinForms, ориентированные на более старые версии .Net автоматически, получают поддержку HIGH DPI - у меня есть приложение .Net 3.5, которое масштабируется соответствующим образом на Win 10, учитывая два монитора с разными DPI, один установлен на масштабирование 100%, а другой - на масштабирование 200% в настройках дисплея. (Однако, согласно этой статье, используется более старый алгоритм масштабирования.) Начиная с версии 4.7, поддержка предоставляется по подписке, поэтому необходим этот ключ конфигурации. - person ToolmakerSteve; 13.08.2017
comment
@ToolmakerSteve Вы можете выбрать масштабирование GDI, чтобы получить его. Приложение 3.5 не получило этого улучшения - person magicandre1981; 13.08.2017
comment
@ magicandre1981 - Не совсем так. Старое приложение WinForms + .Net 3.5 автоматически масштабируется в текущих сборках Windows 10. Он даже меняет масштаб, когда вы перемещаете форму на другой монитор с другим масштабом. Ни разработчик, ни пользователь не должны ничего указывать. Проверено сегодня в приложении, которое я написал. Это работа Microsoft по обратной совместимости. ОДНАКО это правда, что это автоматическое масштабирование не всегда работает так же хорошо, как более новая версия. Я упоминаю, чтобы другие знали, что нет необходимости перестраивать старое приложение для версии 4.7, если нет сообщений о проблемах масштабирования. - person ToolmakerSteve; 13.08.2017
comment
@ToolmakerSteve снова, это похоже на масштабирование GDI, которое MSFT также использует для окон MMC, таких как диспетчер устройств - person magicandre1981; 14.08.2017
comment
Правильный. Приложения WinForms используют GDI +, поэтому, как и любое другое решение на основе GDI, приложения WinForms до .NET 4.7 автоматически масштабируются для WIndows 10. Я отвечал на ваше утверждение Возможно, вы выбрал масштабирование GDI .... Нет, до .NET 4.7 такой настройки не было. Просто так получилось. Это единственное, что я пытался сказать. - person ToolmakerSteve; 17.08.2017
comment
@MartiniBianco Рассмотрите возможность размещения нового вопроса с минимальным воспроизводимым примером. - person Martin Prikryl; 18.10.2019