Kode adaptif versi
Anda dapat berpikir tentang menulis kode adaptif mirip dengan bagaimana Anda berpikir tentang membuat antarmuka pengguna adaptif. Anda mungkin merancang antarmuka pengguna dasar untuk berjalan di layar terkecil, lalu memindahkan atau menambahkan elemen saat mendeteksi bahwa aplikasi berjalan di layar yang lebih besar. Dengan kode adaptif, Anda menulis kode dasar untuk dijalankan pada versi OS terendah, dan Anda dapat menambahkan fitur yang dipilih tangan saat mendeteksi bahwa aplikasi Anda berjalan pada versi yang lebih tinggi tempat fitur baru tersedia.
Untuk informasi latar belakang penting tentang ApiInformation, kontrak API, dan konfigurasi Visual Studio, lihat Aplikasi adaptif versi.
Pemeriksaan API runtime
Anda menggunakan kelas Windows.Foundation.Metadata.ApiInformation dalam kondisi dalam kode Anda untuk menguji keberadaan API yang ingin Anda panggil. Kondisi ini dievaluasi di mana pun aplikasi Anda berjalan, tetapi mengevaluasi ke true hanya pada perangkat tempat API ada dan oleh karena itu tersedia untuk dipanggil. Ini memungkinkan Anda menulis kode adaptif versi untuk membuat aplikasi yang menggunakan API yang hanya tersedia pada versi OS tertentu.
Di sini, kita melihat contoh spesifik untuk menargetkan fitur baru di Pratinjau Windows Insider. Untuk gambaran umum penggunaan ApiInformation, lihat Pemrograman dengan SDK ekstensi dan Mendeteksi fitur secara dinamis dengan kontrak API.
Tip
Banyak pemeriksaan API runtime dapat memengaruhi performa aplikasi Anda. Kami menunjukkan pemeriksaan sebaris dalam contoh ini. Dalam kode produksi, Anda harus melakukan pemeriksaan sekali dan menyimpan hasilnya, lalu menggunakan hasil cache di seluruh aplikasi Anda.
Skenario yang tidak didukung
Dalam kebanyakan kasus, Anda dapat mengatur Versi Minimum aplikasi ke SDK versi 10240 dan menggunakan pemeriksaan runtime untuk mengaktifkan API baru saat aplikasi Anda berjalan di versi yang lebih baru. Namun, ada beberapa kasus di mana Anda harus meningkatkan Versi Minimum aplikasi Anda untuk menggunakan fitur baru.
Anda harus meningkatkan Versi Minimum aplikasi jika menggunakan:
- API baru yang memerlukan kemampuan yang tidak tersedia di versi sebelumnya. Anda harus meningkatkan versi minimum yang didukung menjadi versi yang menyertakan kemampuan tersebut. Untuk informasi selengkapnya, lihat Deklarasi kemampuan aplikasi.
- kunci sumber daya baru apa pun yang ditambahkan ke generic.xaml dan tidak tersedia di versi sebelumnya. Versi generic.xaml yang digunakan pada runtime ditentukan oleh versi OS yang dijalankan perangkat. Anda tidak dapat menggunakan pemeriksaan API runtime untuk menentukan keberadaan sumber daya XAML. Jadi, Anda hanya boleh menggunakan kunci sumber daya yang tersedia dalam versi minimum yang didukung aplikasi Anda atau XAMLParseException akan menyebabkan aplikasi Anda mengalami crash saat runtime.
Opsi kode adaptif
Ada dua cara untuk membuat kode adaptif. Dalam kebanyakan kasus, Anda menulis markup aplikasi untuk dijalankan pada versi Minimum, lalu menggunakan kode aplikasi Anda untuk memanfaatkan fitur OS yang lebih baru saat ada. Namun, jika Anda perlu memperbarui properti dalam status visual, dan hanya ada perubahan nilai properti atau enumerasi antara versi OS, Anda dapat membuat pemicu status yang dapat diperluas yang diaktifkan berdasarkan keberadaan API.
Di sini, kami membandingkan opsi ini.
Kode aplikasi
Kapan menggunakan:
- Direkomendasikan untuk semua skenario kode adaptif kecuali untuk kasus tertentu yang ditentukan di bawah ini untuk pemicu yang dapat diperluas.
Keuntungan:
- Menghindari overhead/kompleksitas pengembang untuk mengikat perbedaan API menjadi markup.
Halangan-halangan:
- Tidak ada dukungan Desainer.
Pemicu Status
Kapan menggunakan:
- Gunakan saat hanya ada properti atau perubahan enum antara versi OS yang tidak memerlukan perubahan logika, dan tersambung ke status visual.
Keuntungan:
- Memungkinkan Anda membuat status visual tertentu yang dipicu berdasarkan keberadaan API.
- Beberapa dukungan desainer tersedia.
Halangan-halangan:
- Penggunaan pemicu kustom dibatasi untuk status visual, yang tidak meminjamkan dirinya ke tata letak adaptif yang rumit.
- Harus menggunakan Setters untuk menentukan perubahan nilai, jadi hanya perubahan sederhana yang dimungkinkan.
- Pemicu status kustom cukup verbose untuk disiapkan dan digunakan.
Contoh kode adaptif
Di bagian ini, kami menunjukkan beberapa contoh kode adaptif yang menggunakan API yang baru di Windows 10, versi 1607 (Pratinjau Windows Insider).
Contoh 1: Nilai enum baru
Windows 10, versi 1607 menambahkan nilai baru ke enumerasi InputScopeNameValue : ChatWithoutEmoji. Cakupan input baru ini memiliki perilaku input yang sama dengan cakupan input Obrolan (pemeriksaan ejaan, lengkap otomatis, kapitalisasi otomatis), tetapi memetakan ke keyboard sentuh tanpa tombol emoji. Ini berguna jika Anda membuat pemilih emoji Anda sendiri dan ingin menonaktifkan tombol emoji bawaan di keyboard sentuh.
Contoh ini menunjukkan cara memeriksa apakah nilai enum ChatWithoutEmoji ada dan mengatur properti InputScope dari TextBox jika ada. Jika tidak ada di sistem tempat aplikasi dijalankan, InputScope diatur ke Obrolan sebagai gantinya. Kode yang ditampilkan dapat ditempatkan di konstruktor Halaman atau penanganan aktivitas Page.Loaded.
Tip
Saat Anda memeriksa API, gunakan string statis alih-alih mengandalkan fitur bahasa .NET, jika tidak, aplikasi Anda mungkin mencoba mengakses jenis yang tidak ditentukan dan crash saat runtime.
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);
Dalam contoh sebelumnya, TextBox dibuat dan semua properti diatur dalam kode. Namun, jika Anda memiliki XAML yang ada, dan hanya perlu mengubah properti InputScope pada sistem di mana nilai baru didukung, Anda dapat melakukannya tanpa mengubah XAML Anda, seperti yang ditunjukkan di sini. Anda mengatur nilai default ke Obrolan di XAML, tetapi Anda mengambil alih dalam kode jika nilai ChatWithoutEmoji ada.
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();
}
Sekarang setelah kita memiliki contoh konkret, mari kita lihat bagaimana pengaturan versi Target dan Minimum berlaku untuk itu.
Dalam contoh ini, Anda dapat menggunakan nilai enum Obrolan di XAML, atau dalam kode tanpa pemeriksaan, karena ada dalam versi OS minimum yang didukung.
Jika Anda menggunakan nilai ChatWithoutEmoji di XAML, atau dalam kode tanpa pemeriksaan, itu akan dikompilasi tanpa kesalahan karena ada dalam versi OS Target. Ini juga akan berjalan tanpa kesalahan pada sistem dengan versi OS Target. Namun, ketika aplikasi berjalan pada sistem dengan OS menggunakan versi Minimum, aplikasi akan mengalami crash saat runtime karena nilai enum ChatWithoutEmoji tidak ada. Oleh karena itu, Anda harus menggunakan nilai ini hanya dalam kode, dan membungkusnya dalam pemeriksaan API runtime sehingga hanya dipanggil jika didukung pada sistem saat ini.
Contoh 2: Kontrol baru
Versi baru Windows biasanya membawa kontrol baru ke permukaan API UWP yang membawa fungsionalitas baru ke platform. Untuk memanfaatkan keberadaan kontrol baru, gunakan metode ApiInformation.IsTypePresent .
Windows 10, versi 1607 memperkenalkan kontrol media baru yang disebut MediaPlayerElement. Kontrol ini dibangun pada kelas MediaPlayer , sehingga membawa fitur seperti kemampuan untuk dengan mudah mengikat ke audio latar belakang, dan memanfaatkan peningkatan arsitektur di tumpukan media.
Namun, jika aplikasi berjalan pada perangkat yang menjalankan versi Windows 10 yang lebih lama dari versi 1607, Anda harus menggunakan kontrol MediaElement alih-alih kontrol MediaPlayerElement baru. Anda dapat menggunakan metode ApiInformation.IsTypePresent untuk memeriksa keberadaan kontrol MediaPlayerElement saat runtime, dan memuat kontrol mana pun yang cocok untuk sistem tempat aplikasi berjalan.
Contoh ini menunjukkan cara membuat aplikasi yang menggunakan MediaPlayerElement baru atau MediaElement lama tergantung pada apakah jenis MediaPlayerElement ada. Dalam kode ini, Anda menggunakan kelas UserControl untuk mengklasifikasikan kontrol dan UI dan kode terkaitnya sehingga Anda dapat mengalihkannya masuk dan keluar berdasarkan versi OS. Sebagai alternatif, Anda dapat menggunakan kontrol kustom, yang menyediakan lebih banyak fungsionalitas dan perilaku kustom daripada yang diperlukan untuk contoh sederhana ini.
MediaPlayerUserControl
Merangkum MediaPlayerUserControl
MediaPlayerElement dan beberapa tombol yang digunakan untuk melewati bingkai media menurut bingkai. UserControl memungkinkan Anda memperlakukan kontrol ini sebagai satu entitas dan membuatnya lebih mudah untuk beralih dengan MediaElement pada sistem yang lebih lama. Kontrol pengguna ini harus digunakan hanya pada sistem tempat MediaPlayerElement ada, sehingga Anda tidak menggunakan pemeriksaan ApiInformation dalam kode di dalam kontrol pengguna ini.
Catatan
Untuk menjaga contoh ini tetap sederhana dan terfokus, tombol langkah bingkai ditempatkan di luar pemutar media. Untuk pengalaman pengguna yang lebih baik, Anda harus menyesuaikan MediaTransportControls untuk menyertakan tombol kustom Anda. Lihat Kontrol transportasi kustom untuk informasi selengkapnya.
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
Merangkum MediaElementUserControl
kontrol 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>
Catatan
Halaman kode untuk MediaElementUserControl
hanya berisi kode yang dihasilkan, sehingga tidak ditampilkan.
Menginisialisasi kontrol berdasarkan IsTypePresent
Pada runtime, Anda memanggil ApiInformation.IsTypePresent untuk memeriksa MediaPlayerElement. Jika ada, Anda memuat MediaPlayerUserControl
, jika tidak, Anda memuat 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);
}
Penting
Ingatlah bahwa pemeriksaan ini hanya mengatur mediaControl
objek ke atau MediaPlayerUserControl
MediaElementUserControl
. Anda perlu melakukan pemeriksaan kondisional ini di tempat lain dalam kode yang perlu Anda tentukan apakah akan menggunakan API MediaPlayerElement atau MediaElement. Anda harus melakukan pemeriksaan sekali dan menyimpan hasilnya, lalu menggunakan hasil cache di seluruh aplikasi Anda.
Contoh pemicu status
Pemicu status yang dapat diperluas memungkinkan Anda menggunakan markup dan kode bersama-sama untuk memicu perubahan status visual berdasarkan kondisi yang Anda cek masuk kode; dalam hal ini, kehadiran API tertentu. Kami tidak merekomendasikan pemicu status untuk skenario kode adaptif umum karena overhead yang terlibat, dan pembatasan hanya untuk status visual.
Anda harus menggunakan pemicu status untuk kode adaptif hanya ketika Anda memiliki perubahan UI kecil antara versi OS yang berbeda yang tidak akan memengaruhi UI yang tersisa, seperti properti atau perubahan nilai enum pada kontrol.
Contoh 1: Properti baru
Langkah pertama dalam menyiapkan pemicu status yang dapat diperluas adalah mensubkelas kelas StateTriggerBase untuk membuat pemicu kustom yang akan aktif berdasarkan keberadaan API. Contoh ini menunjukkan pemicu yang diaktifkan jika kehadiran properti cocok dengan variabel yang _isPresent
diatur dalam 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);
}
}
}
Langkah selanjutnya adalah menyiapkan pemicu status visual di XAML sehingga dua hasil status visual yang berbeda berdasarkan keberadaan API.
Windows 10, versi 1607 memperkenalkan properti baru pada kelas FrameworkElement yang disebut AllowFocusOnInteraction yang menentukan apakah kontrol mengambil fokus saat pengguna berinteraksi dengannya. Ini berguna jika Anda ingin tetap fokus pada kotak teks untuk entri data (dan menjaga keyboard sentuh tetap ditampilkan) saat pengguna mengklik tombol.
Pemicu dalam contoh ini memeriksa apakah properti ada. Jika properti ada, properti AllowFocusOnInteraction diatur pada Tombol ke false; jika properti tidak ada, Tombol mempertahankan status aslinya. TextBox disertakan untuk mempermudah melihat efek properti ini saat Anda menjalankan kode.
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>
Contoh 2: Nilai enum baru
Contoh ini menunjukkan cara mengatur nilai enumerasi yang berbeda berdasarkan apakah ada nilai. Ini menggunakan pemicu status kustom untuk mencapai hasil yang sama dengan contoh obrolan sebelumnya. Dalam contoh ini, Anda menggunakan cakupan input ChatWithoutEmoji baru jika perangkat menjalankan Windows 10, versi 1607, jika tidak , cakupan input obrolan digunakan. Status visual yang menggunakan pemicu ini disiapkan dalam gaya if-else tempat cakupan input dipilih berdasarkan keberadaan nilai enum baru.
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>