Адаптивный код версии

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

Важные справочные сведения об ApiInformation, контрактах API и настройке Visual Studio доступны в статье Version adaptive apps: Use new APIs while maintaining compatibility with previous versions (Приложения с адаптивным к версии кодом: используйте новые API, сохраняя совместимость с предыдущими версиями).

Проверка API среды выполнения

Класс Windows.Foundation.Metadata.ApiInformation используется в условии в коде, чтобы проверить наличие API, который требуется вызвать. Это условие вычисляется везде, где выполняется приложение, но оно оценивается только на устройствах, где присутствует API и поэтому доступен для вызова. Это позволяет писать адаптивный к версии код, чтобы создавать приложения, использующие API, которые доступны только в определенных версиях ОС.

Рассмотрим конкретные примеры использования новых функций в Windows Insider Preview. Общие сведения об использовании ApiInformation см. в статье "Программирование с помощью пакетов SDK расширений" и динамическое обнаружение функций с помощью контрактов API.

Совет

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

Неподдерживаемые сценарии

В большинстве случаев можно сохранить минимальную версию приложения в пакете SDK версии 10240 и использовать проверка среды выполнения, чтобы включить любые новые API при запуске приложения в более поздней версии. Однако в некоторых случаях необходимо увеличить минимальную версию приложения, чтобы использовать новые функции.

При использовании необходимо увеличить минимальную версию приложения:

  • новый API, требующий возможности, недоступной в более ранней версии. Необходимо увеличить минимальную поддерживаемую версию до той, которая включает эту возможность. Дополнительные сведения см . в объявлениях возможностей приложений.
  • все новые ключи ресурсов, добавленные в generic.xaml и недоступные в предыдущей версии. Версия generic.xaml, используемая во время выполнения, определяется версией ОС, на котором работает устройство. Api среды выполнения нельзя использовать проверка для определения наличия ресурсов XAML. Поэтому необходимо использовать только ключи ресурсов, которые доступны в минимальной версии, поддерживаемой приложением. Иначе в нем возникнет сбой во время выполнения из-за исключения XAMLParseException.

Параметры адаптивного кода

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

Здесь мы сравниваем эти параметры.

Код приложения

Сценарии использования.

  • Рекомендуется для всех сценариев адаптивного кода, за исключением конкретных случаев, определенных ниже для расширяемых триггеров.

Преимущества:

  • Избегает затрат на разработчика и сложности связывания различий API в разметке.

Недостатки:

  • Нет поддержки конструктора.

Триггеры состояния

Сценарии использования.

  • Используйте, если существует только свойство или перечисление между версиями ОС, которые не требуют изменений логики и подключены к визуальному состоянию.

Преимущества:

  • Позволяет создавать определенные визуальные состояния, которые активируются на основе присутствия API.
  • Доступна поддержка некоторых конструкторов.

Недостатки:

  • Использование пользовательских триггеров ограничено визуальными состояниями, что не дает себе сложных адаптивных макетов.
  • Необходимо использовать методы setters для указания изменений значений, поэтому возможны только простые изменения.
  • Пользовательские триггеры состояния довольно подробно настроены и используются.

Примеры адаптивного кода

В этом разделе показано несколько примеров адаптивного кода, использующего API, которые являются новыми в Windows 10 версии 1607 (предварительная версия программы предварительной оценки Windows).

Пример 1. Новое значение перечисления

Windows 10 версии 1607 добавляет новое значение в перечисление InputScopeNameValue : ChatWithoutEmoji. Этот новый входной область имеет то же поведение ввода, что и входные данные чата область (орфографические проверка, автозапись, автозапись), но она сопоставляется с сенсорной клавиатурой без кнопки эмодзи. Это полезно, если вы создаете собственный средство выбора эмодзи и хотите отключить встроенную кнопку эмодзи на сенсорной клавиатуре.

В этом примере показано, как проверка, если значение перечисления ChatWithoutEmoji присутствует и задает свойство InputScope текстового поля, если оно есть. Если оно отсутствует в системе, приложение запущено в ней, вместо него будет задано значение InputScope. Показанный код можно поместить в обработчик событий Page consructor или Page.Loaded.

Совет

При проверка API используйте статические строки вместо использования функций языка .NET, в противном случае приложение может попытаться получить доступ к типу, который не определен и завершается сбоем во время выполнения.

C#

// Create a TextBox control for sending messages 
// and initialize an InputScope object.
TextBox messageBox = new TextBox();
messageBox.AcceptsReturn = true;
messageBox.TextWrapping = TextWrapping.Wrap;
InputScope scope = new InputScope();
InputScopeName scopeName = new InputScopeName();

// Check that the ChatWithEmoji value is present.
// (It's present starting with Windows 10, version 1607,
//  the Target version for the app. This check returns false on earlier versions.)         
if (ApiInformation.IsEnumNamedValuePresent("Windows.UI.Xaml.Input.InputScopeNameValue", "ChatWithoutEmoji"))
{
    // Set new ChatWithoutEmoji InputScope if present.
    scopeName.NameValue = InputScopeNameValue.ChatWithoutEmoji;
}
else
{
    // Fall back to Chat InputScope.
    scopeName.NameValue = InputScopeNameValue.Chat;
}

// Set InputScope on messaging TextBox.
scope.Names.Add(scopeName);
messageBox.InputScope = scope;

// For this example, set the TextBox text to show the selected InputScope.
messageBox.Text = messageBox.InputScope.Names[0].NameValue.ToString();

// Add the TextBox to the XAML visual tree (rootGrid is defined in XAML).
rootGrid.Children.Add(messageBox);

В предыдущем примере создается TextBox, и все свойства задаются в коде. Однако если у вас есть XAML и просто необходимо изменить свойство InputScope в системах, где поддерживается новое значение, это можно сделать, не изменив XAML, как показано здесь. Значение по умолчанию — Chat в XAML, но переопределяете его в коде, если значение ChatWithoutEmoji присутствует.

XAML

<TextBox x:Name="messageBox"
         AcceptsReturn="True" TextWrapping="Wrap"
         InputScope="Chat"
         Loaded="messageBox_Loaded"/>

C#

private void messageBox_Loaded(object sender, RoutedEventArgs e)
{
    if (ApiInformation.IsEnumNamedValuePresent("Windows.UI.Xaml.Input.InputScopeNameValue", "ChatWithoutEmoji"))
    {
        // Check that the ChatWithEmoji value is present.
        // (It's present starting with Windows 10, version 1607,
        //  the Target version for the app. This code is skipped on earlier versions.)
        InputScope scope = new InputScope();
        InputScopeName scopeName = new InputScopeName();
        scopeName.NameValue = InputScopeNameValue.ChatWithoutEmoji;
        // Set InputScope on messaging TextBox.
        scope.Names.Add(scopeName);
        messageBox.InputScope = scope;
    }

    // For this example, set the TextBox text to show the selected InputScope.
    // This is outside of the API check, so it will happen on all OS versions.
    messageBox.Text = messageBox.InputScope.Names[0].NameValue.ToString();
}

Теперь, когда у нас есть конкретный пример, давайте посмотрим, как к нему применяются параметры целевой и минимальной версии.

В этих примерах можно использовать значение перечисления чата в XAML или в коде без проверка, так как оно присутствует в минимальной поддерживаемой версии ОС.

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

Пример 2. Новый элемент управления

Новая версия Windows обычно предоставляет новые элементы управления в область API UWP, которая предоставляет новые функциональные возможности для платформы. Чтобы использовать новый элемент управления используйте метод ApiInformation.IsTypePresent.

Windows 10 версии 1607 представляет новый элемент управления мультимедиа с именем MediaPlayerElement. Этот элемент управления основан на классе MediaPlayer , поэтому он предоставляет такие функции, как возможность легко связаться с фоновым звуком, и он использует архитектурные улучшения в стеке мультимедиа.

Однако если приложение работает на устройстве с версией Windows 10 старше версии 1607, вместо нового элемента управления MediaPlayerElement необходимо использовать элемент управления MediaElement. Метод ApiInformation.IsTypePresent можно использовать для проверка для присутствия элемента управления MediaPlayerElement во время выполнения и загрузить любой элемент управления, подходящий для системы, в которой выполняется приложение.

В этом примере показано, как создать приложение, которое использует новый MediaPlayerElement или старый MediaElement в зависимости от того, присутствует ли тип MediaPlayerElement. В этом коде вы используете класс UserControl для компонентов элементов управления и связанного с ним пользовательского интерфейса и кода, чтобы можно было переключать их на основе версии ОС. В качестве альтернативы можно использовать пользовательский элемент управления, который обеспечивает больше функциональных возможностей и настраиваемого поведения, чем то, что необходимо для этого простого примера.

MediaPlayerUserControl

Инкапсулирует MediaPlayerUserControlMediaPlayerElement и несколько кнопок, которые используются для пропуска через кадр мультимедиа по кадру. UserControl позволяет рассматривать эти элементы управления как единую сущность и упрощает переключение с помощью MediaElement в старых системах. Этот пользовательский элемент управления следует использовать только в системах, где присутствует MediaPlayerElement, поэтому в коде в этом элементе управления не используется apiInformation проверка.

Примечание.

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

XAML

<UserControl
    x:Class="MediaApp.MediaPlayerUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MediaApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid x:Name="MPE_grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" 
                    HorizontalAlignment="Center" Grid.Row="1">
            <RepeatButton Click="StepBack_Click" Content="Step Back"/>
            <RepeatButton Click="StepForward_Click" Content="Step Forward"/>
        </StackPanel>
    </Grid>
</UserControl>

C#

using System;
using Windows.Media.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace MediaApp
{
    public sealed partial class MediaPlayerUserControl : UserControl
    {
        public MediaPlayerUserControl()
        {
            this.InitializeComponent();
            
            // The markup code compiler runs against the Minimum OS version so MediaPlayerElement must be created in app code
            MPE = new MediaPlayerElement();
            Uri videoSource = new Uri("ms-appx:///Assets/UWPDesign.mp4");
	        MPE.Source = MediaSource.CreateFromUri(videoSource);
	        MPE.AreTransportControlsEnabled = true;
            MPE.MediaPlayer.AutoPlay = true;

            // Add MediaPlayerElement to the Grid
            MPE_grid.Children.Add(MPE);

        }

        private void StepForward_Click(object sender, RoutedEventArgs e)
        {
            // Step forward one frame, only available using MediaPlayerElement.
            MPE.MediaPlayer.StepForwardOneFrame();
        }

        private void StepBack_Click(object sender, RoutedEventArgs e)
        {
            // Step forward one frame, only available using MediaPlayerElement.
            MPE.MediaPlayer.StepForwardOneFrame();
        }
    }
}

MediaElementUserControl

Инкапсулирует MediaElementUserControlэлемент управления MediaElement .

XAML

<UserControl
    x:Class="MediaApp.MediaElementUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MediaApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <Grid>
        <MediaElement AreTransportControlsEnabled="True" 
                      Source="Assets/UWPDesign.mp4"/>
    </Grid>
</UserControl>

Примечание.

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

Инициализация элемента управления на основе IsTypePresent

Во время выполнения вы вызываете ApiInformation.IsTypePresent для проверка для MediaPlayerElement. Если он присутствует, вы загружаете MediaPlayerUserControl, если это не так, загружаетесь MediaElementUserControl.

C#

public MainPage()
{
    this.InitializeComponent();

    UserControl mediaControl;

    // Check for presence of type MediaPlayerElement.
    if (ApiInformation.IsTypePresent("Windows.UI.Xaml.Controls.MediaPlayerElement"))
    {
        mediaControl = new MediaPlayerUserControl();
    }
    else
    {
        mediaControl = new MediaElementUserControl();
    }

    // Add mediaControl to XAML visual tree (rootGrid is defined in XAML).
    rootGrid.Children.Add(mediaControl);
}

Важно!

Помните, что этот проверка задает mediaControl только объект либо MediaPlayerUserControlMediaElementUserControl. Эти условные проверка в любом месте кода необходимо определить, следует ли использовать API MediaPlayerElement или MediaElement. Вы должны выполнить проверка один раз и кэшировать результат, а затем использовать кэшированный результат во всем приложении.

Примеры триггеров состояния

Расширяемые триггеры состояния позволяют использовать разметку и код вместе для активации изменений визуального состояния в зависимости от условия, которое вы проверка в коде; в данном случае наличие определенного API. Не рекомендуется запускать триггеры состояния для распространенных сценариев адаптивного кода из-за накладных расходов и ограничения только визуальных состояний.

Триггеры состояния следует использовать для адаптивного кода только при небольших изменениях пользовательского интерфейса между разными версиями ОС, которые не влияют на оставшийся пользовательский интерфейс, например свойство или значение перечисления в элементе управления.

Пример 1. Новое свойство

Первым шагом в настройке расширяемого триггера состояния является подкласс класса StateTriggerBase для создания настраиваемого триггера, который будет активным на основе присутствия API. В этом примере показан триггер, который активирует, если присутствие свойства соответствует переменной, заданной _isPresent в XAML.

C#

class IsPropertyPresentTrigger : StateTriggerBase
{
    public string TypeName { get; set; }
    public string PropertyName { get; set; }

    private Boolean _isPresent;
    private bool? _isPropertyPresent = null;

    public Boolean IsPresent
    {
        get { return _isPresent; }
        set
        {
            _isPresent = value;
            if (_isPropertyPresent == null)
            {
                // Call into ApiInformation method to determine if property is present.
                _isPropertyPresent =
                ApiInformation.IsPropertyPresent(TypeName, PropertyName);
            }

            // If the property presence matches _isPresent then the trigger will be activated;
            SetActive(_isPresent == _isPropertyPresent);
        }
    }
}

Следующий шаг — настройка триггера визуального состояния в XAML, чтобы два разных визуальных состояния были результатом на основе присутствия API.

В Windows 10 версии 1607 добавлено новое свойство для класса FrameworkElement, AllowFocusOnInteraction, которое определяет, получает ли элемент управления фокус, когда пользователь взаимодействует с ним. Это полезно, если вы хотите сосредоточиться на текстовом поле для записи данных (и сохранить сенсорной клавиатуре), пока пользователь нажимает кнопку.

Триггер в этом примере проверка, если свойство присутствует. Если свойство присутствует, оно задает свойству AllowFocusOnInteraction значение false; если свойство отсутствует, кнопка сохраняет исходное состояние. TextBox включается, чтобы упростить просмотр эффекта этого свойства при запуске кода.

XAML

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel>
        <TextBox Width="300" Height="36"/>
        <!-- Button to set the new property on. -->
        <Button x:Name="testButton" Content="Test" Margin="12"/>
    </StackPanel>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="propertyPresentStateGroup">
            <VisualState>
                <VisualState.StateTriggers>
                    <!--Trigger will activate if the AllowFocusOnInteraction property is present-->
                    <local:IsPropertyPresentTrigger 
                        TypeName="Windows.UI.Xaml.FrameworkElement" 
                        PropertyName="AllowFocusOnInteraction" IsPresent="True"/>
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="testButton.AllowFocusOnInteraction" 
                            Value="False"/>
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

Пример 2. Новое значение перечисления

В этом примере показано, как задать различные значения перечисления в зависимости от того, присутствует ли значение. Он использует настраиваемый триггер состояния для достижения того же результата, что и в предыдущем примере чата. В этом примере используется новый входной область ChatWithoutEmoji, если устройство работает под управлением Windows 10 версии 1607, в противном случае используется входной область чата. Визуальные состояния, использующие этот триггер, настраиваются в стиле if-else, где входные область выбираются на основе наличия нового значения перечисления.

C#

class IsEnumPresentTrigger : StateTriggerBase
{
    public string EnumTypeName { get; set; }
    public string EnumValueName { get; set; }

    private Boolean _isPresent;
    private bool? _isEnumValuePresent = null;

    public Boolean IsPresent
    {
        get { return _isPresent; }
        set
        {
            _isPresent = value;

            if (_isEnumValuePresent == null)
            {
                // Call into ApiInformation method to determine if value is present.
                _isEnumValuePresent =
                ApiInformation.IsEnumNamedValuePresent(EnumTypeName, EnumValueName);
            }

            // If the property presence matches _isPresent then the trigger will be activated;
            SetActive(_isPresent == _isEnumValuePresent);
        }
    }
}

XAML

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <TextBox x:Name="messageBox"
     AcceptsReturn="True" TextWrapping="Wrap"/>


    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="EnumPresentStates">
            <!--if-->
            <VisualState x:Name="isPresent">
                <VisualState.StateTriggers>
                    <local:IsEnumPresentTrigger 
                        EnumTypeName="Windows.UI.Xaml.Input.InputScopeNameValue" 
                        EnumValueName="ChatWithoutEmoji" IsPresent="True"/>
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="messageBox.InputScope" Value="ChatWithoutEmoji"/>
                </VisualState.Setters>
            </VisualState>
            <!--else-->
            <VisualState x:Name="isNotPresent">
                <VisualState.StateTriggers>
                    <local:IsEnumPresentTrigger 
                        EnumTypeName="Windows.UI.Xaml.Input.InputScopeNameValue" 
                        EnumValueName="ChatWithoutEmoji" IsPresent="False"/>
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="messageBox.InputScope" Value="Chat"/>
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>