Адаптивный код версии
Вы можете подумать о написании адаптивного кода аналогично тому, как вы думаете о создании адаптивного пользовательского интерфейса. Вы можете разработать базовый пользовательский интерфейс для запуска на самом маленьком экране, а затем переместить или добавить элементы при обнаружении того, что приложение работает на большом экране. С помощью адаптивного кода вы записываете базовый код для запуска в самой низкой версии ОС, и вы можете добавить выбранные вручную функции при обнаружении того, что приложение работает в более поздней версии, где доступна новая функция.
Важные справочные сведения об 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.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
Инкапсулирует MediaPlayerUserControl
MediaPlayerElement и несколько кнопок, которые используются для пропуска через кадр мультимедиа по кадру. 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
только объект либо MediaPlayerUserControl
MediaElementUserControl
. Эти условные проверки необходимо выполнить в любом месте кода, которые необходимо определить, следует ли использовать 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>