Режим Snap View в WinRT

125

Для запуска приложений Windows Store компьютер с Windows 8 должен быть оснащен экраном с размером не менее 1024 x 768 пикселов. Соотношение сторон такого экрана равно 4:3 - как у фильмов до перехода на широкоэкранную съемку в начале 1950-х, а также классических телевизорах и компьютерных мониторах. На планшете экран может переключаться между книжной и альбомной ориентацией, поэтому многие запускаемые приложения столкнутся с тем, что они запущены на экране 768 x 1024. Но в этом случае приложению Windows Store приходится поддерживать всего два возможных размера.

Следующим шагом становится экран 1366 x 768, пропорции которого составляют приблизительно 16:9, как у HD-телевизора. У такого экрана в альбомной ориентации размер равен 768 x 1366. Кроме того, размер 1366 х 768 является наименьшим размером, поддерживающим компактный режим (snap mode). В компактном режиме две программы могут одновременно находиться на экране, но этот режим доступен только в альбомной ориентации.

Пространство имен Windows.UI.ViewManagement содержит класс ApplicationView со статическим свойством Value типа ApplicationViewState (перечисление, обозначающее текущий компактный режим приложения). События, соответствующего этой информации, нет. Если ваша программа должна оповещаться об изменении представления, проверьте значение в обработчике SizeChanged.

Программа WhatSnap похожа на WhatRes, но в нее добавлен вывод свойства ApplicationView.Value:

private void UpdateDisplay()
{
    double dpi = DisplayProperties.LogicalDpi;
    int realWidth = (int)Math.Round(dpi * ActualWidth / 96);
    int realHeight = (int)Math.Round(dpi * ActualHeight / 96);

    txb.Text =
        String.Format("Режим приложения: {0}\r\n" +
                      "Размер окна = {1} x {2}\r\n" +
                      "Масштаб разрешения = {3}\r\n" +
                      "DPI = {4}\r\n" +
                      "Фактический размер = {5} x {6}",
                      Windows.UI.ViewManagement.ApplicationView.Value,
                      ActualWidth, ActualHeight,
                      DisplayProperties.ResolutionScale,
                      DisplayProperties.LogicalDpi,
                      realWidth, realHeight);
}

Кроме того, элемент TextBlock размещается в элементе Viewbox, так что будет виден даже в том случае, если экран станет слишком узким:

<Grid Background="#FF1D1D1D">
        <Viewbox VerticalAlignment="Center" HorizontalAlignment="Center"
                 Margin="32" StretchDirection="DownOnly">
            <TextBlock Name="txb"
                       FontSize="32" Foreground="LimeGreen" />
        </Viewbox>
</Grid>

Перечисление ApplicationViewState состоит из четырех членов. В книжном режиме единственным допустимым значением является FullScreenPortrait:

Приложение в книжном режиме

Компактные режимы актуальны только в альбомной ориентации. Если приложение занимает весь экран, то используется режим FullScreenLandscape:

Приложение в альбомном режиме

Если провести пальцем туда-обратно от левого края экрана, появляется столбцовый перечень других приложений. Если продолжить перетаскивание, не возвращая палец, можно вызвать другую программу в режиме совместного использования экрана. На этой стадии используется режим Filled из перечисления ApplicationViewState:

Приложение в режиме Filled

Учтите, что для минимального размера экрана, поддерживающего компактные режимы - 1366 x 768, - размер Filled равен 1024 x 768, что соответствует минимальному размеру экрана для запуска приложений Windows Store.

Если продолжить перетаскивание вправо, ApplicationViewState переходит в состояние Snapped. Таковы четыре имеющихся возможности. Если ваше приложение находится слева, а не справа, вы получите то же значение Snapped.

В режиме Snapped ширина всегда равна 320 единицам. В режиме Filled она равна общей ширине экрана за вычетом 320 единиц для другого приложения и 22 единиц для полосы перетаскивания.

Например, если запустить эту программу на экране 2560 х 1440 пикселов с диагональю 10,6 дюйма, то общая ширина экрана составит 1422 единицы. Она делится на 1080 единиц для режима Filled, 320 единиц для режима Snapped и 22 единицы для разделителя.

Если запустить программу на 27-дюймовом экране 2560 х 1440 пикселов, аппаратно-независимые единицы будут соответствовать пикселам. Общая ширина экрана составляет 2560 единиц и делится на 2218 единиц для режима Filled, 320 единиц для режима Snapped и 22 единицы для разделителя. В режиме Filled программа может определить полный размер экрана в аппаратно-независимых единицах, увеличив ширину на 320 и 22. А используя значение LogicalDpi, программа может определить полный размер экрана в пикселах.

Так как количество экранных режимов очень ограничено (прежде всего, из-за того, что режим Snapped всегда имеет ширину 320 единиц), приложения должны делать что-то осмысленное в каждом режиме. Например, приложение Bing Weather в режиме Snapped изменяет ориентацию своего прогноза погоды. Впрочем, для режима Filled, скорее всего, делать ничего другого не придется.

Изменение ориентации StackPanel - простой способ адаптации программы к режиму Snapped. Жонглировать строками и столбцами Grid несколько сложнее. И все же решения, подходящего для всех приложений, не существует. Эту проблему приходится решать для каждой конкретной ситуации.

Класс ApplicationView содержит статический метод TryUnsnap(), который пытается вывести активное приложение из компактного режима, но использовать этот метод не рекомендуется, и найти причину для его вызова будет непросто.

Изменения ориентации

Наряду с адаптацией приложения для режимов Filled и Snapped вы также можете адаптировать его для книжной и альбомной ориентации. Даже если вы уверены, что ваше приложение будет работать только на настольной системе и никогда не будет запускаться на планшете, следует учитывать, что некоторые настольные мониторы способны переключаться в книжный режим. Такие мониторы удобны для пользователей, которые много работают с текстом.

Ранее говорилось о том, что свойство ApplicationView.Value обозначает книжный режим с членом перечисления ApplicationViewState.FullScreenPortrait, но если вам понадобится дополнительная информация (и если вы предпочитаете получать информацию об изменении ориентации в виде события), используйте класс DisplayProperties из пространства имен Windows.Graphics.Display. Это тот же класс, который предоставляет информацию масштабирования логических DPI.

Пространство имен Windows.Graphics.Display определяет перечисление DisplayOrientations из пяти членов, приведенных ниже (в круглых скобках указываются фактические значения):

Значения перечисления DisplayOrientations
Значение Описание
None (0)

Используется только для DisplayProperties.

Landscape (1)

Поворот на 90° по часовой стрелке из PortraitFlipped.

Portrait (2)

Поворот на 90° по часовой стрелке из Landscape.

LandscapeFlipped (4)

Поворот на 90° по часовой стрелке из Portrait.

PortraitFlipped (8)

Поворот на 90° по часовой стрелке из LandscapeFlipped.

Под «поворотом на 90° по часовой стрелке» понимается поворот планшета (или экрана монитора), выполняемый пользователем. Как упоминалось ранее, Windows 8 автоматически реагирует на поворот вращением содержимого экрана в противоположном направлении, чтобы оно сохранило прежнюю ориентацию.<,/

Статическое свойство DisplayProperties.NativeOrientation обозначает «исходную», или «наиболее естественную» ориентацию экрана. Оно может принимать значение Landscape или Portrait - как правило, в зависимости от местонахождения кнопок или логотипов на устройстве. Статическое свойство DisplayProperties.CurrentOrientation может принимать любые ненулевые значения.

Событие DisplayProperties.OrientationChanged инициируется при изменении CurrentOrientation (в результате поворота экрана пользователем) или NativeOrientation, что происходит реже (при перемещении приложения на другой монитор). Событие OrientationChanged не инициируется при запуске приложения независимо от исходной ориентации, поэтому обработку OrientationChanged стоит продублировать в фазе инициализации.

Файл XAML в программе NativeUp рисует стрелку, направленную вверх:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <StackPanel VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    RenderTransformOrigin="0.5,0.5">
            <Path Data="M 150 100 L 150 500, 50 500, 50 100, 0 100, 100 0, 200 100 Z"
                  Stroke="DarkOrange"
                  StrokeThickness="14"
                  Fill="LimeGreen"
                  RenderTransformOrigin="0.5,0.5"
                  HorizontalAlignment="Center" />

            <TextBlock Text="Ориентация экрана" FontSize="64" />

            <StackPanel.RenderTransform>
                <RotateTransform x:Name="rotateTransform" />
            </StackPanel.RenderTransform>
        </StackPanel>
    </Grid>
</Page>/code>

Обычно, если вы запустите такую программу на планшете и повернете его в руках» Windows 8 распознает изменение ориентации экрана, так что стрелка всегда будет указывать вверх или почти вверх (с учетом 90-градусных приращений угла поворота). Однако в файле фонового кода этой конкретной программы для противодействий повороту используется событие OrientationChanged. В результате стрелка всегда указывает на верхнюю сторону экрана, как если бы ориентация оставалась неизменной:

using System;
using Windows;
using Windows.Graphics.Display;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            InitializeComponent();

            SetRotation();
            DisplayProperties.OrientationChanged += Display_OrientationChanged;
        }

        private void Display_OrientationChanged(object s)
        {
            SetRotation();
        }

        private void SetRotation()
        {
            rotateTransform.Angle = (Log2(DisplayProperties.CurrentOrientation) -
                                 Log2(DisplayProperties.NativeOrientation)) * 90;
        }

        private int Log2(DisplayOrientations orientation)
        {
            int log = 0;
            int value = (int)orientation;

            while (value > 0 && (value & 1) == 0)
            {
                value >>= 1;
                log += 1;
            }

            return log;
        }
    }
}

Предположим, программа запускается в исходной ориентации. Стрелка направлена вверх. Затем планшет поворачивается на 90° по часовой стрелке. Windows переориентирует программу на 90° против часовой стрелки, но обработчик OrientationChanged поворачивает текст и стрелку на 90° по часовой стрелке. Изменение ориентации можно заметить по небольшому мерцанию экрана, но ориентация стрелки относительно экрана остается неизменной.

Обрабатываем смену ориентации экрана

Работа программы зависит от того, что перечисление DisplayOrientations состоит из значений 1, 2, 4 и 8 в порядке вращения по часовой стрелке. Двоичные логарифмы этих значений равны 0, 1, 2 и 3, так что каждое увеличение на единицу эквивалентно повороту по часовой стрелке на 90°. Приложение может указать конкретную ориентацию, которая ему нужна. Это можно сделать двумя способами. Первый способ: откройте файл Package.appmanifest в Visual Studio, перейдите на вкладку Application UI и установите флажки одной или нескольких ориентаций:

Смена ориентации экрана через свойства проекта

Ваш выбор станет исходным значением статического свойства DisplayProperties.AutoRotationPreferences, но во время инициализации программы этому свойству можно задать одно или несколько значений из перечисления DisplayOrientations объединенных поразрядным оператором OR языка C# (|).

Учтите, что Windows 8 может проигнорировать ваш запрос. Например, если вы указываете, что приложение запускается только в книжном режиме, а программа работает на настольном компьютере с горизонтально ориентированным экраном, то программа запустится в альбомном режиме. Даже если приложение работает на планшете, а планшет установлен в док-станции горизонтально, оно будет работать только в альбомном режиме.

Иначе говоря, Windows 8 отменяет предпочтения программы, если они невыполнимы в текущих условиях. И это разумно: чего бы ни хотела программа, нельзя заставлять пользователя смотреть на экран, склонив голову набок.

Я рекомендую избегать определения предпочтительной ориентации; вместо этого лучше написать программу так, чтобы она адаптировалась к любым ориентациями. Единственно возможными исключениями из этого правила могут быть игры, использующие растровые изображения, которые должны быть ориентированы определенным образом, или программы, использующие датчики ориентации.

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

Пройди тесты