Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Windows (до Windows 10) Написание приложений Windows 8.1 с поддержкой высокого DPI RSS

Написание приложений Windows 8.1 с поддержкой высокого DPI

Текущий рейтинг: 5 (проголосовало 2)
 Посетителей: 1229 | Просмотров: 1547 (сегодня 0)  Шрифт: - +
В Windows 8 введена новая модель программирования для Windows-приложений, основанная на Windows Runtime (WinRT), которая дает возможность пользователям динамически изменять размер экранных элементов через настройки ПК. Как выглядит соответствующий параметр в настройках ПК (PC settings), показано на рис. 1. На моем настольном компьютере доступны два значения: Default и Larger, а на планшете Surface Pro — Smaller и Default. На деле доступные значения зависят от конкретного устройства и, в частности, от разрешения по вертикали подключенных дисплеев. Еще важнее, что приложения Windows Store принимают событие всякий раз, когда изменяется этот параметр, и благодаря этому могут динамически обновлять свой код рендеринга в соответствии с текущим масштабным множителем (scaling factor).

*
Рис. 1. Влияние настроек компьютера в Windows 8 на приложения Windows Store

Однако рабочий стол в Windows 8 остается статическим. Настольные приложения по-прежнему используют системную настройку DPI, и любые изменения в нем вступают в силу только после того, как пользователь сначала выходит из системы, а потом вновь входит в нее. Это в конечном счете вынуждает все приложения завершать свою работу, а затем перезапускаться. Этот параметр по-прежнему присутствует в Windows 8.1, но имеет более детализированные значения: Smaller, Medium, Larger и Extra Large (рис. 2). Здесь тоже доступные значения зависят от подключенных дисплеев. Например, на моем Surface Pro предлагаются только Smaller, Medium и Larger.

*
Рис. 2. Влияние настроек компьютера на настольные приложения в Windows 8.1

Само собой разумеется, это приводит к своего рода раздвоению личности: как и многие другие вещи в Windows 8, это может сбивать с толку не то что пользователей — даже разработчиков. Хотя этот момент в Windows 8.1 толком так и не доработан, теперь настольные приложения наконец-то тоже могут динамически обрабатывать изменение DPI, благодаря чему пользователю больше не нужно закрывать все приложения и выходить из системы, а затем заново входить в нее. Однако в Windows 8.1 все же вдохнули новую жизнь в конфигурации с несколькими мониторами.

Хотя окно на рис. 2 выглядит довольно похоже на то, что было в Windows 8, оно теперь отличается небольшим флажком, добавленным в Windows 8.1. На рис. 2 он установлен, но изначально, по умолчанию сброшен. Флажок Let me choose one scaling level for all my displays намекает на другую новую возможность в Windows 8.1: на разных мониторах могут использоваться разные масштабные множители. Название этого флажка слегка запутывает, потому что после сброса флажка все равно предлагается значение для пользователей, имеющих только один монитор. В этом случае пользователю по-прежнему предоставляется вариант динамического изменения масштабного множителя. Выбор этого варианта в конфигурации с одним монитором на самом деле переключает в режим совместимости с новым поведением DPI. Таким образом, независимо от наличия второго монитора (и вообще от того, сколько мониторов могут обычно использовать ваши потребители) вам придется освоиться с этими новыми вариантами. Хотите вы того или нет — они в любом случае влияют на ваши приложения. Сброс ранее упомянутого флажка приведет к появлению окна, показанного на рис. 3. И вновь в Windows 8.1 это теперь вариант по умолчанию.

*
Рис. 3. Настройки компьютера в Windows 8.1 влияют на динамическое и индивидуальное для каждого монитора масштабирование рабочего стола

Если подумать, особой разницы между рис. 2 и 3 на самом деле нет. На первом из них мы видим четыре переключателя, а на другом — ползунок с четырьмя возможными позициями. Единственное реальное различие в том, что изменения, вносимые с помощью ползунка, вступают в силу моментально или как только вы нажимаете кнопку Apply. Это во многом то же самое, по крайней мере для пользователя, что и параметр масштабирования для приложений Windows Store. Однако изменения, вносимые выбором одного из переключателей, начинают действовать только при следующем входе пользователя в систему.

Четыре возможных значения (в виде ползунка или кнопок-переключателей) соответствуют четырем масштабным множителям DPI и показаны в табл. 1. Как разработчик я предостерегаю вас от вычитывания из конкретных значений DPI того, что в них нет. Подразумевается, что они отражают разрешение или плотность пикселей экрана, но на деле на значение DPI влияют многие факторы, такие как форм-фактор и расстояние до экрана, поэтому действительное значение DPI, с которым вы в конечном счете будете иметь дело, имеет мало общего с настоящими дюймами. Эти четыре значения также представляют полный спектр возможностей, но что именно может предложить конкретный компьютер, может зависеть от вертикального разрешения его дисплеев.

Табл. 1. Системные масштабные множители

DPIПроцент
96100
120125
144150
192200

Это иллюстрирует табл. 2. Эти лимиты предотвращают обрезание UI-элементов по нижней границе экрана. Если ваш экран имеет менее 900 строк вертикального разрешения, у вас не будет никакого выбора — всегда будет действовать только один масштабный множитель (100%). По мере увеличения вертикального разрешения экрана вам становятся доступны дополнительные значения до достижения 1440 строк, и с этого момента вы можете выбрать любое из четырех значений. Однако эти значения не одинаково влияют на масштабирование на всех мониторах. Здесь вступает в силу концепция масштабирования DPI индивидуально для каждого монитора. С первого взгляда это отнюдь не очевидно, поскольку ОС учитывает как вертикальное разрешение, так и «родной» DPI для данного физического экрана.

Табл. 2. Значения масштабирования относительно вертикального разрешения

РазрешениеЗначение масштабирования
… – 900100%
900 – 1079100% – 125%
1080 – 1439100% – 125% – 150%
1440 – ...100% – 125% – 150% – 200%

Разработчику настольных приложений важно понимать, что теперь могут действовать сразу два масштабных множителя. Это системный и индивидуальный для каждого монитора масштабные множители DPI. Системный множитель соответствует одному из значений в табл. 1 — за исключением устройств вроде Windows Phone — и остается постоянным в течение всего сеанса работы. Значение системного DPI связано с изначально выбранным переключателем (рис. 2) или позицией ползунка (рис. 3).

Чтобы получить значение системного DPI, начните с получения аппаратного контекста рабочего стола (desktop device context). Да, это сводится к старому GDI API, но не имеет ничего общего с GDI-рендерингом и является лишь историческим рудиментом. Прежде всего, чтобы получить описатель, представляющий аппаратный контекст рабочего стола, вызовите функцию GetDC с nullptr вместо описателя окна. Это особое значение указывает, что ваш нужен аппаратный контекст для рабочего стола в целом, а не для конкретного окна:

auto dc = GetDC(nullptr);

Естественно, не забудьте освободить этот описатель, когда закончите:

ReleaseDC(nullptr, dc);

Первый параметр — это описатель окна, к которому относится аппаратный контекст. И вновь nullptr представляет рабочий стол. Теперь, зная аппаратный контекст, вы можете использовать функцию GetDeviceCaps, чтобы получить системный масштабный множитель DPI для осей x и y:

auto x = GetDeviceCaps(dc, LOGPIXELSX);
auto y = GetDeviceCaps(dc, LOGPIXELSY);

Разные значения для осей x и y пришли к нам из мрачного средневековья, когда у принтеров обычно были разные масштабные множители по горизонтали и вертикали. Я никогда не видел экран, где были бы неквадратные пиксели, но слышал, что такие действительно есть и применяются в некоторых областях, для которых разработаны специальные графические карты. LOGPIXELSX и LOGPIXELSY представляют число пикселей на логический дюйм по ширине и высоте рабочего стола. И вновь это логические дюймы, которые не отражают реальные. Кроме того, учитывая, что это системные значения DPI, они одинаковы для всех мониторов, которые охватывают рабочий стол, независимо от того, насколько малы или велики они могут быть. Вот тут-то и кроется проблема.

Если подключить планшет Dell Venue 8 Pro с его 8-дюймовым экраном к массиву 30-дюймовых мониторов Dell UltraSharp, придется делать трудный выбор. Я очень часто подключаю два или три сильно различающихся монитора к графической карте своего компьютера. Общесистемный масштабный множитель DPI просто не справляется с этим. Поэтому для каждого монитора должен быть свой масштабный множитель DPI, идеально подходящий к его относительному размеру или разрешению. Именно это и предлагается в Windows 8.1 через поддержку индивидуального для каждого монитора масштабирования DPI.

Windows 8.1 обеспечивает три разных уровня поддержки DPI. Это очевидно, если заглянуть в окно Process Explorer (рис. 4). Видно, что некоторые приложения совершенно не поддерживают распознавание DPI, как в случае окна командной строки. Большинство приложений, написанный для Windows 7 и Windows 8, распознают системный DPI или, по крайней мере, заявляют об этом. Примеры включают Microsoft Outlook и калькулятор. Распознавание DPI, индивидуального для каждого монитора, является третьим и оптимальным уровнем поддержки. Примеры таких приложений на рис. 4 включают Internet Explorer и Microsoft Word. Интересно, что Word на самом деле на данный момент не распознает DPI, индивидуальный для каждого монитора, но я отключил масштабирование экрана для Word, чтобы избежать размытости изображения. Это дает эффект, эквивалентный переопределению поддержки распознавания DPI для процесса, и диспетчер окон рабочего стола не будет масштабировать это окно. Это делается выбором параметра, включающего совместимость.

*
Рис. 4. Process Explorer показывает заявленный уровень распознавания DPI

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

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

Прежде чем перейти к обсуждению того, как написать приложение с корректным распознаванием DPI для каждого монитора, рассмотрим, что требуется для соответствующего заявления. В Windows Vista была введена функция SetProcessDPIAware, которая помечает вызывающий процесс как распознающий DPI. Эта функция не принимает никаких аргументов и просто включает поддержку распознавания DPI, если так можно выразиться. Она была написана до появления поддержки распознавания индивидуального для каждого монитора DPI, поэтому возвращаемое значение было простым двоичным состоянием. Либо вы распознаете DPI, либо — нет. В Windows 8.1 введена новая функция, SetProcessDpiAwareness, которая обеспечивает больший контроль над уровнем распознавания:

VERIFY_(S_OK, SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE));

Эта строка выставляет заданный уровень распознавания DPI для процесса. Доступные константы охватывают три возможных состояния: DPI не распознается, поддерживается системный DPI и распознается индивидуальный для каждого монитора DPI. Также есть соответствующая функция GetProcessDpiAwareness, которая запрашивает это значение для указанного процесса. Но использование кода для установки уровня распознавания имеет ряд недостатков. На ранних этапах жизненного цикла приложения вызывать эти функции следует с осторожностью. Иногда это вообще не практично и создает проблемы при смешении исполняемых EXE- и DLL-файлов. Значит, требуется более качественное решение.

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

Эти файлы манифестов являются чисто текстовыми. Они либо встраиваются в исполняемый файл как традиционный Win32-ресурс, либо просто поставляются с исполняемым файлом. Например, вы можете заглянуть в файл манифеста Word, используя утилиту для манифестов, которая включается в Windows SDK:

mt -inputresource:"C:\ ... \WINWORD.EXE" -out:manifest.xml

Я опустил часть пути для экономии места. Если все пройдет успешно, вы будете вознаграждены появлением XML-документа в текущем каталоге. Как он выглядит, показано на рис. 5. В середине информации (которая нас сейчас не интересует) об исполняемом файле Word находится XML-элемент dpiAware со значением true.

*
Рис. 5. Файл манифеста Microsoft Word

Если у приложения нет файла манифеста и не используется программный подход к установке его уровня распознавания, то считается, что оно не распознает DPI. Если у приложения есть файл манифеста, но в нем отсутствует элемент dpiAware, то вновь предполагается, что оно не распознает DPI. В Windows 7 наличия элемента dpiAware достаточно для того, чтобы система считала, что DPI распознается. Значение этого элемента не важно — оно вообще может отсутствовать. В Window 8 подход более специфический. Эта ОС ожидает, что значение начинается с «T» (сокращение от True). Регистр этой буквы не имеет значения.

На этом мы закончим с приложениями, которые либо вообще не распознают DPI, либо распознают только системный DPI. А как насчет приложений, распознающих индивидуальный для каждого монитора DPI? Что ж, когда эта функциональность была введена, для элемента dpiAware было выбрано новое значение: Per Monitor. Желание дать возможность разработчикам создавать единый двоичный файл, способный работать в Windows 7 и Windows 8 с поддержкой системного DPI, а также с распознаванием индивидуального для каждого монитора DPI в Windows 8, привело к выбору нового значения: True/PM. Это позволяет Windows 7 и Windows 8 воспринимать двоичный файл как поддерживающий системный DPI, а Windows 8.1 — как распознающий DPI, индивидуальный для каждого монитора. Конечно, вы можете по-прежнему использовать Per Monitor, если вы поддерживаете только Windows 8.1.

Увы, Visual C++ 2013 пока не поддерживает это новое значение, но есть способ сделать это самостоятельно. Manifest Tool, который участвует в процессе сборки проекта Visual C++, имеет параметр, обеспечивающий включение дополнительных файлов манифеста. Вам нужно лишь создать текстовый файл с правильным элементом dpiAware и значением и включить его в процесс сборки. Следующий код показывает нужный вам XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
  manifestVersion="1.0">
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns=
        "http://schemas.microsoft.com/SMI/2005/WindowsSettings">
        True/PM</dpiAware>
    </windowsSettings>
  </application>
</assembly>

Создав файл манифеста, вы можете просто обновить настройки своего проекта (рис. 6). Манифест задается как дополнительный файл манифеста, а уровень поддержки DPI — как None.

*
Рис. 6. Параметры в Manifest Tool

Когда дело доходит до рендеринга окна вашего приложения, вы должны убедиться в правильности масштабного множителя DPI. Если вы занимаетесь сопровождением устаревшего приложения с глубокими корнями в GDI-графике и элементами управления из подсистемы USER, то скорее всего столкнетесь с массой трудностей, попытавшись ввести в это приложение поддержку DPI, индивидуального для каждого монитора. С этими же трудностями столкнулись группы Windows и Office в Microsoft. Но, если вы следите за моей рубрикой, то знаете, что решением является переход на Direct2D. Direct2D берет на себя все, что связано с масштабированием DPI и предоставляет вам логическую систему координат, независимую от физических пикселей и масштабного множителя DPI. Конечно, чтобы Direct2D мог делать это эффективно, вам нужно сообщить ему, какие значения DPI следует использовать для конкретной мишени рендеринга (render target).

Традиционно Direct2D-приложения просто получали значения DPI от Direct2D-фабрики, которая в свою очередь лишь вызывала GetDeviceCaps, как я демонстрировал ранее. Но этого больше не достаточно. Как я показал, значения DPI теперь могут изменяться «на лету», и значение, предоставленное GetDeviceCaps, полезно, только если вы пытаетесь использовать его в рендеринге приложения, распознающего системный DPI.

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

auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST);

Получив описатель этого монитора, можно вызвать функцию GetDpiForMonitor, чтобы выяснить значения DPI конкретно для этого монитора:

auto x = unsigned {};
auto y = unsigned {};
VERIFY_(S_OK, GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &x, &y));

Действительное значение DPI для данного монитора не обязательно будет точно соответствовать параметрам, представленным в табл. 1. Оно вновь зависит от ряда факторов, в том числе от разрешения, физического DPI экрана и предполагаемого расстояния от поверхности экрана. Наконец, вы можете вызвать метод SetDpi мишени рендеринга Direct2D, и Direct2D позаботится о должном масштабировании вашего контента.

Я упомянул, что все это может происходить динамически. Ваше приложение может выполняться, и вдруг пользователь изменяет масштаб с помощью окна, показанного на рис. 3, или перетаскивает ваше окно на другой монитор с другим масштабным множителем DPI. В таких случаях оболочка Windows будет отправлять окнам приложения, распознающего индивидуальный для каждого монитора DPI, новое оконное сообщение — WM_DPICHANGED.

Внутри своего обработчика сообщений вы можете вновь вызвать функции MonitorFromWindow и GetDpiForMonitor, чтобы получить действительные значения DPI для текущего монитора, а затем просто обновить Direct2D-мишень рендеринга с помощью ее метода SetDpi. В качестве альтернативы в параметр WPARAM сообщения упаковываются значения DPI по осям x и y, что позволяет просто извлечь младшее и старшее слова:

auto x = LOWORD(wparam);
  auto y = HIWORD(wparam);

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

auto rect = *reinterpret_cast<RECT *>(lparam);

В таком случае вы можете просто вызвать функцию SetWindowPos, чтобы обновить позицию и размер вашего окна:

VERIFY(SetWindowPos(window,
                    0, // не относительно окна
                    rect.left,
                    rect.top,
                    rect.right - rect.left,
                    rect.bottom - rect.top,
                    SWP_NOACTIVATE | SWP_NOZORDER));

И это все. Если вы освоили и используете Direct2D, вы всего в нескольких шагах от поддержки DPI индивидуально для каждого монитора. Несколько приложений уже совершили этот скачок, так что ваше приложение обязано чем-то выделяться! Если у вас есть какое-то WPF-приложение, то для него тоже можно найти пример по ссылке bit.ly/IPDN3p, который показывает, как интегрировать те же принципы, обрисованные в этой статье, в кодовую базу для WPF.

На рис. 7 приведен пример приложения с поддержкой DPI, индивидуального для каждого монитора, которое автоматически масштабирует свой контент и размер окна на основе сообщений WM_DPICHANGED. Смайлик указывает, на каком мониторе сейчас находится окно. Более интерактивный пример см. в моем учебном курсе на Pluralsight по ссылке bit.ly/1fgTifi, где вы также сможете получить образец кода для этого приложения.

*
Рис. 7. Приложение, распознающее индивидуальный для каждого монитора DPI

Автор: Кенни Керр  •  Иcточник: MSDN Magazine  •  Опубликована: 05.05.2014
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Windows 8.1.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.