Codice adattivo per la versione

La scrittura di codice adattivo è concettualmente simile alla creazione di un'interfaccia utente adattiva. È possibile progettare l'interfaccia utente di base per l'esecuzione sugli schermi più piccoli e poi spostare o aggiungere elementi quando si rileva che l'app viene eseguita su schermi più grandi. Con il codice adattivo si scrive il codice di base per l'esecuzione nella versione minima del sistema operativo e poi aggiungere funzionalità selezionate manualmente quando si rileva che l'app viene eseguita in una versione successiva in cui sono disponibili nuove funzionalità.

Per informazioni generali importanti su ApiInformation, sui contratti API e sulla configurazione di Visual Studio, vedi App adattive per la versione.

Controlli di runtime delle API

Per testare la presenza dell'API da chiamare, è possibile usare la classe Windows.Foundation.Metadata.ApiInformation in una condizione del codice. Questa condizione viene valutata ovunque venga eseguita l'app, ma restituisce true solo nei dispositivi in cui l'API è presente e quindi disponibile per essere chiamata. Questo ti permette di scrivere codice adattivo per la versione per creare app che usano API disponibili solo in alcune versioni del sistema operativo.

In questo articolo esamineremo alcuni esempi specifici di codice adattivo per nuove funzionalità in Windows Insider Preview. Per una panoramica generale dell'uso di ApiInformation, vedere Programmazione con gli SDK di estensione e Rilevamento dinamico delle funzionalità con i contratti API.

Suggerimento

L'esecuzione di numerosi controlli di runtime delle API può influire sulle prestazioni dell'app. In questi esempi i controlli sono inline. Nel codice di produzione è necessario eseguire il controllo una sola volta e memorizzare il risultato nella cache, quindi usare il risultato nella cache in tutta l'app.

Scenari non supportati

Nella maggior parte dei casi è possibile lasciare impostata la versione minima dell'app sulla versione 10240 dell'SDK e usare i controlli di runtime per abilitare eventuali nuove API quando l'app viene eseguita in una versione successiva. In alcuni casi, tuttavia, è necessario aumentare la versione minima dell'app per usare nuove funzionalità.

È necessario aumentare la versione minima dell'app se si usa:

  • una nuova API che richiede una funzionalità non disponibile in una versione precedente. È necessario aumentare la versione minima supportata a una che include tale funzionalità. Per altre informazioni, vedere Dichiarazioni di funzionalità delle app.
  • qualsiasi nuova chiave di risorsa aggiunta a generic.xaml e non disponibile in una versione precedente. La versione di generic.xaml usata in fase di esecuzione dipende dalla versione del sistema operativo eseguita dal dispositivo. Non è possibile usare i controlli di runtime delle API per determinare la presenza di risorse XAML. È quindi necessario usare solo le chiavi di risorsa disponibili nella versione minima supportata dall'app. In caso contrario XAMLParseException causerà un arresto anomalo dell'app in fase di esecuzione.

Opzioni per il codice adattivo

Esistono due modi per creare codice adattivo. Nella maggior parte dei casi si può scrivere il markup dell'app per l'esecuzione nella versione minima e quindi usare il codice dell'app per sfruttare le funzionalità più recenti del sistema operativo, quando disponibili. Se è necessario aggiornare una proprietà in uno stato di visualizzazione, tuttavia, e tra le versioni del sistema operativo esiste solo una modifica di un valore di proprietà o enumerazione, è possibile creare un trigger di stato estendibile attivato in base alla presenza di un'API.

Ecco un confronto di queste opzioni.

Codice dell'app

Uso consigliato:

  • Consigliato per tutti gli scenari di codice adattivo ad eccezione dei casi specifici definiti di seguito per i trigger estendibili.

Vantaggi:

  • Lo sviluppatore può evitare il maggiore lavoro e le complicazioni correlati all'integrazione delle differenze delle API nel markup.

Svantaggi:

  • Nessun supporto di progettazione.

Trigger di stato

Uso consigliato:

  • Usare quando tra le versioni del sistema operativo è presente solo una modifica di proprietà o enumerazione che non richiede modifiche della logica e il cambiamento è correlato a uno stato di visualizzazione.

Vantaggi:

  • Consente di creare stati di visualizzazione specifici attivati in base alla presenza di un'API.
  • Supporto di progettazione parzialmente disponibile.

Svantaggi:

  • L'uso dei trigger personalizzati è limitato agli stati di visualizzazione, quindi non si presta a layout adattivi complicati.
  • Per specificare le modifiche dei valori è necessario usare i setter, quindi sono possibili solo modifiche semplici.
  • L'impostazione e l'uso dei trigger di stato personalizzati sono piuttosto laboriosi.

Esempi di codice adattivo

Questa sezione illustra vari esempi di codice adattivo che usano API nuove di Windows 10, versione 1607 (Windows Insider Preview).

Esempio 1: nuovo valore di enumerazione

In Windows 10 versione 1607 è stato aggiunto un nuovo valore per l'enumerazione InputScopeNameValue: ChatWithoutEmoji. Questo nuovo ambito di input ha lo stesso comportamento di input dell'ambito Chat (controllo ortografia, completamento automatico, iniziale maiuscola automatica), ma è mappato a una tastiera virtuale senza pulsante emoji. Può essere utile se si crea una selezione personalizzata per gli emoji e si vuole disabilitare il pulsante predefinito emoji nella tastiera virtuale.

Questo esempio mostra come verificare se il valore dell'enumerazione ChatWithoutEmoji è presente e imposta la proprietà InputScope di un controllo TextBox in caso affermativo. Se non è presente nel sistema in cui viene eseguita l'app, la proprietà InputScope viene invece impostata su Chat. Questo codice potrebbe essere inserito in un costruttore Page o nel gestore dell'evento Page.Loaded.

Suggerimento

Quando si esegue un controllo per un'API, usare stringhe statiche anziché affidarsi alle funzionalità del linguaggio .NET. In caso contrario, l'app potrebbe tentare di accedere a un tipo non definito e subire un arresto anomalo in fase di esecuzione.

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);

Nell'esempio precedente viene creato il controllo TextBox e tutte le proprietà vengono impostate nel codice. Tuttavia, se si ha codice XAML esistente ed è necessario semplicemente modificare la proprietà InputScope nei sistemi in cui è supportato il nuovo valore, è possibile farlo senza modificare il codice XAML, come illustrato qui. Si imposta il valore predefinito su Chat in XAML, ma si esegue l'override nel codice se è presente il valore 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();
}

Ora che abbiamo un esempio concreto, vediamo gli effetti dell'impostazione della versione di destinazione e minima.

In questi esempi possiamo usare il valore di enumerazione Chat in XAML o nel codice senza un controllo, perché è presente nella versione minima del sistema operativo supportata.

Se si usa il valore ChatWithoutEmoji in XAML o nel codice senza un controllo, la compilazione verrà eseguita senza errori perché è presente nel sistema operativo di destinazione. L'esecuzione sarà senza errori anche in un sistema con la versione del sistema operativo di destinazione. Se l'app viene eseguita in un sistema con un sistema operativo con la versione minima, tuttavia, si verificherà un arresto anomalo in fase di esecuzione perché il valore dell'enumerazione ChatWithoutEmoji non è presente. Pertanto, è necessario usare questo valore solo nel codice ed eseguirne il wrapping in un controllo di runtime dell'API in modo che venga chiamato solo se è supportato nel sistema corrente.

Esempio 2: nuovo controllo

In genere, una nuova versione di Windows introduce nuovi controlli nella superficie delle API UWP per le nuove funzionalità della piattaforma. Per sfruttare la presenza di un nuovo controllo, usa il metodo ApiInformation.IsTypePresent.

In Windows 10 versione 1607 è stato introdotto un nuovo controllo multimediale denominato MediaPlayerElement. Questo controllo si basa sulla classe MediaPlayer, quindi introduce funzionalità come la possibilità di collegare facilmente audio di sottofondo e sfrutta i miglioramenti dell'architettura nello stack multimediale.

Se l'app viene eseguita in un dispositivo che esegue una versione di Windows 10 precedente alla versione 1607, tuttavia, è necessario usare il controllo MediaElement e non il nuovo controllo MediaPlayerElement. È possibile usare il metodo ApiInformation.IsTypePresent per verificare la presenza del controllo MediaPlayerElement in fase di esecuzione e caricare il controllo adatto per il sistema in cui l'app è in esecuzione.

Questo esempio mostra come creare un'app che usa il nuovo controllo MediaPlayerElement o il controllo MediaElement precedente in base alla presenza o meno del tipo MediaPlayerElement. In questo codice è possibile usare la classe UserControl per componentizzare i controlli e l'interfaccia utente e il codice correlati, in modo da poterli usare o meno a seconda della versione del sistema operativo. In alternativa si può usare un controllo personalizzato, che fornisce più funzionalità e comportamenti personalizzati rispetto a quanto necessario per questo semplice esempio.

MediaPlayerUserControl

MediaPlayerUserControl incapsula un controllo MediaPlayerElement e diversi pulsanti che vengono usati per spostarsi nell'elemento multimediale, fotogramma per fotogramma. UserControl consente di gestire questi controlli come una singola entità e semplifica il passaggio a un controllo MediaElement nei sistemi precedenti. Questo controllo utente deve essere usato solo nei sistemi in cui è presente MediaPlayerElement, quindi non occorre usare controlli ApiInformation nel codice all'interno di questo controllo utente.

Nota

Per mantenere questo esempio semplice e calzante, i pulsanti per spostarsi tra i fotogrammi sono posizionati di fuori del lettore multimediale. Per una migliore esperienza utente, è consigliabile personalizzare i controlli MediaTransportControls in modo da includere i pulsanti personalizzati. Per altre informazioni, vedere Controlli di trasporto personalizzati.

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 incapsula un controllo 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>

Nota

La pagina del codice per MediaElementUserControl contiene solo codice generato, quindi non è mostrata.

Inizializzare un controllo in base a IsTypePresent

In fase di esecuzione, chiamare ApiInformation.IsTypePresent per controllare se è presente MediaPlayerElement. In caso affermativo, caricare MediaPlayerUserControl. In caso negativo, caricare 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);
}

Importante

Tenere presente che questo controllo imposta solo l'oggetto mediaControl su MediaPlayerUserControl o MediaElementUserControl. È necessario eseguire questi controlli condizionali in tutte le altre posizioni nel codice in cui è necessario stabilire se usare le API MediaPlayerElement o MediaElement. Occorre eseguire il controllo una sola volta e memorizzare il risultato nella cache, quindi usare il risultato nella cache in tutta l'app.

Esempi di trigger di stato

I trigger di stato estendibili consentono di usare markup e codice insieme per attivare modifiche dello stato di visualizzazione in base a una condizione verificata nel codice, in questo caso la presenza di un'API specifica. Non consigliamo l'uso dei trigger di stato in scenari comuni per il codice adattivo, a causa del maggiore impegno richiesto e del fatto che sono limitati solo agli stati di visualizzazione.

È consigliabile usare i trigger di stato per il codice adattivo solo in presenza di piccole modifiche dell'interfaccia utente tra diverse versioni del sistema operativo senza effetti sulle altre parti dell'interfaccia utente, ad esempio la modifica del valore di una proprietà o un'enumerazione per un controllo.

Esempio 1: nuova proprietà

Il primo passaggio per la configurazione di un trigger di stato estendibile prevede la creazione di una sottoclasse della classe StateTriggerBase per creare un trigger personalizzato che verrà attivato in base alla presenza di un'API. Questo esempio mostra un trigger che viene attivato se la presenza della proprietà corrisponde alla variabile _isPresent impostata in 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);
        }
    }
}

Il passaggio successivo prevede la configurazione del trigger dello stato di visualizzazione in XAML, in modo che risultino due diversi stati di visualizzazione in base alla presenza dell'API.

In Windows 10 versione 1607 è stata introdotta una nuova proprietà nella classe FrameworkElement denominata AllowFocusOnInteraction che determina se un controllo assume lo stato attivo quando un utente interagisce con esso. Può essere utile se si vuole mantenere lo stato attivo in una casella di testo per l'immissione dei dati (e mantenere visualizzata la tastiera virtuale) mentre l'utente fa clic su un pulsante.

Il trigger in questo esempio controlla se la proprietà è presente. Se la proprietà è presente imposta la proprietà AllowFocusOnInteraction di un controllo Button su false. Se la proprietà non è presente, il controllo Button mantiene lo stato originale. Il controllo TextBox è incluso nell'esempio per vedere più facilmente l'effetto di questa proprietà quando si esegue il codice.

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>

Esempio 2: nuovo valore di enumerazione

Questo esempio mostra come impostare valori di enumerazione diversi in base alla presenza o meno di un valore. Usa un trigger di stato personalizzato per ottenere lo stesso risultato dell'esempio precedente per Chat. In questo esempio si usa il nuovo ambito di input ChatWithoutEmoji se il dispositivo esegue Windows 10 versione 1607. In caso contrario, viene usato l'ambito di input Chat. Gli stati di visualizzazione che usano questo trigger sono configurati in uno stile if-else, in cui l'ambito di input viene scelto in base alla presenza del nuovo valore di enumerazione.

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>