Informazioni sull'uso delle funzionalità delle app di Windows Store nell'esempio Reversi

Applies to Windows only

Nell'esempio Reversi vengono usate diverse funzionalità comuni delle app di Windows Store scritte in XAML e C#. In questo argomento viene descritto in che modo l'esempio usa alcune di queste funzionalità e sono inclusi collegamenti ad argomenti sulle caratteristiche principali.

Ai fini di questo argomento non è necessario comprendere l'intero esempio, ma si presuppongono almeno la conoscenza di XAML e C# e il possesso di nozioni di base su ognuna delle funzionalità o comunque l'intenzione di apprenderle leggendo gli argomenti collegati. Per informazioni sulle nozioni fondamentali relative allo sviluppo di app, vedi Creare la prima app di Windows Store con C# or Visual Basic.

Per un'introduzione generale per l'esempio, vedi Reversi, un gioco di Windows Store scritto in XAML, C# e C++. Per informazioni sull'integrazione tra le diverse funzionalità, vedi Struttura dell'app Reversi. Per informazioni su come è stato convertito il motore di gioco C# originale in C++, vedi Informazioni sul motore di gioco C++ Reversi.

Scarica l'app di esempio Reversi o esplora il codice sorgente.

Riquadro e schermata iniziale

Il riquadro e la schermata iniziale dell'app sono i primi elementi visualizzati all'utente. Puoi usarli per offrire un punto di accesso accattivante e mostrare il tuo marchio. I componenti di base sono estremamente semplici ma puoi realizzare elementi più complessi come descritto nella documentazione.

Risorse essenziali:

Reversi supporta solo schermata iniziale e riquadro di base. Ciò include riquadri quadrati e larghi, nonché una schermata iniziale, illustrati di seguito a dimensioni ridotte.

Riquadri e schermata iniziale di Reversi

I nomi dei file di immagine sono impostati nel file Package.appxmanifest. Puoi fornire immagini con dimensioni diverse per supportare le dimensioni di più schermi. Per questo semplice esempio non sono necessarie altre implementazioni.

Barra dell'app

Le barre delle app offrono una posizione standard in cui inserire comandi. Per impostazione predefinita, gli utenti possono mostrare o nascondere la barra dell'app a seconda delle esigenze, e questo fa di tale barra la scelta ideale per i comandi meno comuni. Ciò consente di incentrare maggiormente l'interfaccia utente principale sulle interazioni dirette con il contenuto.

Risorse essenziali:

Reversi include alcuni comandi secondari adatti alla barra dell'app, ad esempio la possibilità di fermare l'orologio o di annullare e ripetere mosse. Durante la normale modalità di gioco, la barra dell'app rimane nascosta, ma è possibile visualizzarla o nasconderla manualmente scorrendo rapidamente sullo schermo dall'alto verso il basso.

Barra dell'app Reversi

Questo codice, tratto da GamePage.xaml, illustra la definizione della barra dell'app. Sebbene lo sfondo e il bordo siano trasparenti, la proprietà Background è impostata su {x:Null} per impedire che la barra dell'app invisibile blocchi le interazioni di tocco e clic. Questa impostazione è necessaria perché la barra dell'app si estende per l'intero schermo e si sovrappone alla riga inferiore del tavolo di gioco.


<Page.BottomAppBar>
  <CommandBar x:Name="GamePageAppBar" Background="{x:Null}" 
    BorderBrush="Transparent" IsSticky="True" Margin="9,0">
    <CommandBar.SecondaryCommands>
      <AppBarButton Icon="Pause" Label="Pause"
        Command="{Binding Clock.PauseCommand}" Click="DismissAppBar"
        Visibility="{Binding Clock.IsPauseButtonVisible, 
          Converter={StaticResource BooleanToVisibilityConverter}}"/>
      <AppBarButton Icon="Play" Label="Play"
        Command="{Binding Clock.PlayCommand}" Click="DismissAppBar"          
        Visibility="{Binding Clock.IsPauseButtonVisible,
          Converter={StaticResource BooleanToVisibilityConverter}, 
          ConverterParameter=Reverse}"/>
      <AppBarButton Icon="Undo" Label="Undo" Command="{Binding UndoCommand}"/>
      <AppBarButton Icon="Redo" Label="Redo" Command="{Binding RedoCommand}"/>
    </CommandBar.SecondaryCommands>
  </CommandBar>
</Page.BottomAppBar>


Reversi usa i controlli CommandBar e AppBarButton per recuperare il comportamento e lo stile predefiniti. Il comportamento del pulsante e il relativo stato abilitato vengono forniti dai comandi del modello di visualizzazione associati alle proprietà Command del pulsante, come descritto nella sezione Comandi.

I pulsanti Play e Pause funzionano come un singolo pulsante di attivazione. Per ottenere questo effetto, le proprietà Visibility dei pulsanti sono associate alla stessa proprietà del modello di visualizzazione. Entrambe le associazioni usano un BooleanToVisibilityConverter, ma una di esse ha anche un'impostazione della proprietà ConverterParameter che inverte l'effetto dell'associazione stessa. In questo modo, ogni pulsante risulta visibile solo quando non lo è l'altro. Per ulteriori informazioni, vedi la sezione relativa all'associazione dati.

Notifiche di tipo avviso popup

Le notifiche di tipo avviso popup informano gli utenti quando si verifica un evento importante nell'app, anche se è attualmente attiva un'altra app.

Risorse essenziali:

In Reversi, l'esecuzione delle mosse del computer può richiedere tempo. Se si passa a un'altra app durante l'attesa, verrà visualizzata una notifica di tipo avviso popup quando arriva il turno dell'utente.

Notifica di tipo avviso popup di Reversi

In Reversi viene utilizzato il codice minimo necessario per le notifiche di tipo avviso popup e il campo Popup supportati è impostato su nella finestra di progettazione di Package.appxmanifest. Il codice dei popup può essere facilmente riutilizzato, pertanto si trova in una classe helper della cartella Common.

In GameViewModel.cs:


var window = Windows.UI.Xaml.Window.Current;
if (window != null && !window.Visible && !IsCurrentPlayerAi)
{
    Toast.Show("It's your turn!");
}


In Toast.cs:


public static void Show(string text)
{
    const string template = 
        "<toast duration='short'><visual><binding template='ToastText01'>" +
        "<text id='1'>{0}</text></binding></visual></toast>";
    var toastXml = new XmlDocument();
    toastXml.LoadXml(String.Format(template, text));
    var toast = new ToastNotification(toastXml);
    ToastNotificationManager.CreateToastNotifier().Show(toast);
}


Riquadri a comparsa delle impostazioni

Il pulsante di accesso rapido Impostazioni offre accesso standardizzato alle impostazioni dell'app.

Risorse essenziali:

Reversi è provvisto di due riquadri a comparsa Impostazioni, uno per le opzioni di visualizzazione e uno per le opzioni di nuova partita.

Riquadri a comparsa delle impostazioni di Reversi

Questo codice tratto da App.xaml.cs illustra la modalità di gestione dell'evento SettingsPane.CommandsRequested in Reversi per creare oggetti SettingsCommand. Quando è attivo, ogni comando crea e visualizza un controllo SettingsFlyout.


SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;



private void OnCommandsRequested(SettingsPane sender,
    SettingsPaneCommandsRequestedEventArgs args)
{
    args.Request.ApplicationCommands.Add(new SettingsCommand("Display", "Display options", 
        _ => (new DisplaySettings() { DataContext = SettingsViewModel }).Show()));
    args.Request.ApplicationCommands.Add(new SettingsCommand("NewGame", "New game options", 
        _ => (new NewGameSettings() { DataContext = SettingsViewModel }).Show()));
}


Condivisione del contenuto

Il contratto Condivisione consente all'app di condividere dati che gli utenti possono inviare ad altre app. Gli utenti possono ad esempio condividere dati dell'app in un'app per email e creare un nuovo messaggio.

Risorse essenziali:

Windows include il supporto per la condivisione di un'immagine dell'app e Reversi non necessita di alcuna funzionalità aggiuntiva.

Associazione dati

L'associazione dati consente di connettere i controlli di interfaccia utente ai dati visualizzati in modo che le modifiche ai primi si riflettano nei secondi. L'associazione dati è comune per i moduli di immissione dati, tuttavia può essere anche utilizzata per controllare l'intera interfaccia utente e tenerla distinta dalla logica dell'app.

Risorse essenziali:

Reversi usa le associazioni dati per connettere l'interfaccia utente (ovvero il livello di "visualizzazione") alla logica dell'app (ovvero il livello del "modello di visualizzazione). La suddivisione su più livelli consente di separare l'interfaccia utente dal resto del codice. Tale approccio è noto come modello MVVM (Model-View-ViewModel). Per informazioni sull'uso di questo modello in Reversi, vedi Struttura dell'app Reversi. Per una breve introduzione a MVVM, vedi Uso del modello Model-View-ViewModel (MVVM).

La maggior parte delle associazioni in Reversi è definita in XAML mediante l'estensione di markup Binding, sebbene in alcuni casi sia usato code-behind (ad esempio nel file Board.xaml.cs). Per ogni pagina è impostata la relativa proprietà DataContext usata da tutti gli elementi della pagina come origine dati per le associazioni.

Aggiornamenti dell'interfaccia utente

Le associazioni dati consentono di controllare l'interfaccia utente di Reversi. Le interazioni con l'interfaccia utente producono modifiche alle proprietà dell'origine dati alle quali le associazioni rispondono aggiornando l'interfaccia stessa.

Questi aggiornamenti funzionano perché le classi del modello di visualizzazione di Reversi ereditano la classe BindableBase. Questa classe si trova nel file Common/BindableBase.cs e fornisce un'implementazione standard di INotifyPropertyChanged oltre ad alcuni metodi di supporto. Il metodo SetProperty aggiorna il valore di supporto di una proprietà nonché tutti gli elementi dell'interfaccia utente associati mediante una singola chiamata al metodo. Il metodo OnPropertyChanged aggiorna l'interfaccia utente associata a proprietà specifiche. Ciò è utile per controllare i tempi di aggiornamento e per le proprietà che ottengono i propri valori da altre proprietà.

Questo codice tratto da GameViewModel.cs illustra l'uso di base di SetProperty e OnPropertyChanged.


public State CurrentPlayer
{
    get { return _currentPlayer; }
    set
    {
        SetProperty(ref _currentPlayer, value);
        OnPropertyChanged("IsCurrentPlayerAi");
        OnPropertyChanged("IsPlayerOneAi");
        OnPropertyChanged("IsPlayerTwoAi");
        OnPropertyChanged("CurrentPlayerAiSearchDepth");
    }
}


Conversione di valori

È possibile convertire qualsiasi valore di proprietà in un formato più idoneo all'associazione creando proprietà calcolate, ovvero proprietà che traggono i propri valori da altre proprietà.

Questo codice tratto da GameViewModel.cs illustra una semplice proprietà calcolata. L'interfaccia utente associata a questa proprietà viene aggiornata dalla chiamata a OnPropertyChanged corrispondente dell'esempio precedente.


public bool IsPlayerOneAi { get { return (int)PlayerOne > 0; } }


Le proprietà calcolate sono facili da creare per qualsiasi tipo di conversione necessaria, tuttavia rischiano di complicare la struttura del codice . Per le conversioni comuni, è preferibile inserire il codice di conversione in un'implementazione riutilizzabile di IValueConverter. Reversi usa le classi NullStateToVisibilityConverter e BooleanToVisibilityConverter nella cartella Common/Converters per le associazioni che mostrano e nascondono vari elementi di interfaccia utente.

Questa associazione tratta da StartPage.xaml mostra o nasconde un pannello a seconda della presenza di un valore in una proprietà.


<StackPanel Visibility="{Binding GameViewModel, 
  Converter={StaticResource NullStateToVisibilityConverter}}">


Questa associazione tratta da NewGameSettings.xaml mostra o nasconde un pannello a seconda dello stato di un controllo di ToggleSwitch.


<StackPanel Orientation="Horizontal" 
  Visibility="{Binding IsOn, ElementName=PlayerOneSwitch, 
    Converter={StaticResource BooleanToVisibilityConverter}}">


Per ulteriori esempi, vedi Barra dell'app.

Comandi

I comportamenti di Button vengono spesso implementati con gestori di eventi Click in file code-behind. In Reversi questo approccio è usato per i pulsanti di navigazione, tuttavia per altri pulsanti l'interfaccia utente è separata dal codice non di interfaccia richiamato dal pulsante stesso. A tale scopo, le proprietà Button.Command sono associate a proprietà del modello di visualizzazione che restituiscono implementazioni di ICommand.

Le proprietà dei comandi di Reversi sono di tipo DelegateCommand o DelegateCommand<T>. Queste classi si trovano nel file Common/DelegateCommand.cs e forniscono implementazioni standard riutilizzabili di ICommand. È possibile usare queste classi per semplificare la creazione di comandi per uso singolo e per tenere isolato il codice necessario in singole implementazioni di proprietà.

Questo codice tratto da GameViewModel.cs illustra il comando di una mossa utilizzato dagli spazi del tavolo di gioco che sono pulsanti personalizzati. L'operatore ?? o "null-coalescing" indica che il valore del campo viene restituito solo se non è null; in caso contrario il campo è impostato e viene restituito il nuovo valore. Ciò significa che viene creato un singolo oggetto comando la prima volta che si accede a una proprietà e che lo stesso oggetto viene riutilizzato per tutti gli accessi futuri. L'oggetto comando viene inizializzato chiamando il metodo DelegateCommand<ISpace>.FromAsyncHandler con riferimenti ai metodi MoveAsync e CanMove. Tali metodi forniscono l'implementazione per i metodi ICommand.Execute e CanExecute.


public DelegateCommand<ISpace> MoveCommand 
{ 
    get 
    { 
        return _moveCommand ?? (_moveCommand = 
            DelegateCommand<ISpace>.FromAsyncHandler(MoveAsync, CanMove));
    } 
}


Il metodo CanExecute viene chiamato dall'associazione dati per aggiornare lo stato abilitato del pulsante. Tuttavia, le associazioni dei comandi si basano su notifiche di cambiamenti analoghe a quelle di altre associazioni (illustrate nella sezione sugli aggiornamenti dell'interfaccia utente). Questo codice tratto da GameViewModel.cs illustra come il metodo UpdateView sincronizza lo stato del modello di visualizzazione con lo stato del modello e quindi chiama OnCanExecuteChanged per ogni comando prima di continuare con la mossa successiva.


private void UpdateView()
{
    SyncModelProperties();
    UpdateBoard();
    UndoCommand.RaiseCanExecuteChanged();
    RedoCommand.RaiseCanExecuteChanged();
    MoveCommand.RaiseCanExecuteChanged();
}


Proprietà di dipendenza personalizzate

L'app Reversi usa le proprietà di dipendenza personalizzate nei propri controlli personalizzati, in modo da poter usare gli aggiornamenti dell'associazione dati per controllare i cambiamenti dello stato di visualizzazione. Gli stati di visualizzazione e le transazioni animate sono definiti in XAML mediante la classe VisualStateManager. Tuttavia, non c'è modo di associare direttamente uno stato visivo a una proprietà del modello di visualizzazione. Le proprietà di dipendenza personalizzate forniscono le destinazioni per l'associazione alle proprietà del modello di visualizzazione. Le proprietà di dipendenza includono callback di proprietà modificata che effettuano le chiamate necessarie al metodo VisualStateManager.GoToState.

Questo codice mostra come il controllo PlayerStatus usa code-behind per associare le proprie proprietà di dipendenza personalizzate alle proprietà del modello di visualizzazione. Qui viene mostrata una sola delle proprietà di dipendenza, incluso il relativo metodo di callback di proprietà modificata. Il callback e l'override del metodo OnApplyTemplate chiamano entrambi il metodo di aggiornamento. Tuttavia, la chiamata di OnApplyTemplate inizializza il controllo per la prima visualizzazione sullo schermo, pertanto non usa transizioni animate.


public PlayerStatus()
{
    DefaultStyleKey = typeof(PlayerStatus);
    SetBinding(CurrentPlayerProperty, new Binding { 
        Path = new PropertyPath("CurrentPlayer") });
    SetBinding(IsClockShowingProperty, new Binding { 
        Path = new PropertyPath("Settings.IsClockShowing") });
    SetBinding(IsGameOverProperty, new Binding { 
        Path = new PropertyPath("IsGameOver") });
    SetBinding(WinnerProperty, new Binding { 
        Path = new PropertyPath("Winner") });
}

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    UpdatePlayerState(false);
    UpdateClockState(false);
    UpdateGameOverState(false);
}

public bool IsClockShowing
{
    get { return (bool)GetValue(IsClockShowingProperty); }
    set { SetValue(IsClockShowingProperty, value); }
}

public static readonly DependencyProperty IsClockShowingProperty =
    DependencyProperty.Register("IsClockShowing", typeof(bool),
    typeof(PlayerStatus), new PropertyMetadata(true, IsClockShowingChanged));

private static void IsClockShowingChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
{
    (d as PlayerStatus).UpdateClockState(true);
}

private void UpdateClockState(bool useTransitions)
{
    GoToState(IsClockShowing ? "ClockShowing" : "ClockHidden", useTransitions);
}

private void GoToState(string state, bool useTransitions)
{
    VisualStateManager.GoToState(this, state, useTransitions);
}


Codice asincrono

Il codice asincrono permette all'interfaccia utente di continuare a rispondere mentre l'app è occupata con operazioni che richiedono molto tempo.

Risorse essenziali:

L'app Reversi usa codice asincrono per eseguire le mosse in un gioco. Ogni mossa richiede almeno un secondo, inclusa la relativa animazione, e le mosse basate sull'intelligenza artificiale possono richiedere molto più tempo. L'interfaccia utente continua tuttavia a rispondere e le mosse in corso possono essere annullate tramite i comandi utente, ad esempio Annulla.

Questo codice del file GameViewModel.cs mostra come vengono utilizzati i token di annullamento, le parole chiave async e await e la classe Task nell'app Reversi. Nota l'uso di AsTask per consentire l'integrazione con il codice asincrono di Windows Runtime nella classe Game. Per ulteriori informazioni, vedi la sezione successiva.


private async Task MoveAsync(ISpace move)
{
    var cancellationToken = GetNewCancellationToken();
    LastMoveAffectedSpaces = await Game.MoveAsync(move).AsTask(cancellationToken);
    if (cancellationToken.IsCancellationRequested) return;
    await OnMoveCompletedAsync(cancellationToken);
}



private async Task AiMoveAsync()
{
    var cancellationToken = GetNewCancellationToken();

    // Unlike the MoveAsync method, the AiMoveAsync method requires a try/catch 
    // block for cancellation. This is because the AI search checks for 
    // cancellation deep within a recursive, iterative search process
    // that is easiest to halt by throwing an exception. 
    try
    {
        // The WhenAll method call enables the delay and the AI search to 
        // occur concurrently. However, in order to retrieve the return 
        // value of the first task, both tasks must have the same signature,
        // thus requiring the delay task to have a (meaningless) return value.  
        var results = await Task.WhenAll(
            Game.GetBestMoveAsync(CurrentPlayerAiSearchDepth)
                .AsTask(cancellationToken),
            Task.Run(async () =>
            {
                await DelayAsync(MinimumTurnLength, cancellationToken);
                return (ISpace)null;
            })
        );

        // Perform the AI move only after both the 
        // search and the minimum delay have passed.
        LastMoveAffectedSpaces = await Game.MoveAsync(
            results[0]).AsTask(cancellationToken);
        if (cancellationToken.IsCancellationRequested) return;

        await OnMoveCompletedAsync(cancellationToken);
    }
    catch (OperationCanceledException)
    {
        System.Diagnostics.Debug.WriteLine("cancelled with exception");
    }
}


Uso di un componente Windows Runtime

Se implementi parte del codice come componente Windows Runtime, puoi riutilizzarlo in app diverse, su piattaforme diverse o con linguaggi diversi. Puoi anche sostituire più facilmente il componente con un'implementazione alternativa in un altro linguaggio.

Risorse essenziali:

La logica di base del gioco dell'app Reversi viene implementata come componente Windows Runtime, in modo da separarla completamente dall'app. Questo permette di riutilizzare il codice e di estendere l'app in futuro. Reversi include anche una versione C++ del motore di gioco come alternativa ad alte prestazioni rispetto alla versione C# originale. Per altre informazioni, vedi Informazioni sul motore di gioco C++ Reversi.

Da questo codice del file Game.cs puoi vedere che l'app Reversi usa il codice asincrono basato su Task (incluse le parole chiave async e await), ma espone i risultati tramite le interfacce asincrone di Windows Runtime. Il codice mostra anche come il token di annullamento dal codice di GameViewModel viene usato dalla classe Game.

Il primo e il terzo metodo nel codice di esempio chiamano il metodo AsyncInfo.Run per restituire un IAsyncOperation<T>, che esegue il wrapping del valore restituito dall'attività e consente l'annullamento. Nel secondo esempio viene chiamato il metodo WindowsRuntimeSystemExtensions.AsAsyncAction per restituire un IAsyncAction, utile per le attività che non restituiscono valori e non richiedono l'annullamento.


public IAsyncOperation<IList<ISpace>> MoveAsync(ISpace move)
{
    // Use a lock to prevent the ResetAsync method from modifying the game 
    // state at the same time that a different thread is in this method.
    lock (_lockObject)
    {
        return AsyncInfo.Run(cancellationToken => Task.Run(() =>
        {
            if (cancellationToken.IsCancellationRequested) return null;
            var changedSpaces = Move(move);
            SyncMoveStack(move);
            return changedSpaces;
        }, cancellationToken));
    }
}



public IAsyncAction AiMoveAsync(int searchDepth)
{
    return Task.Run(async () => 
    {
        // If it is the AI's turn and we're not at the end of the move stack,
        // just use the next move in the stack. This is necessary to preserve
        // the forward stack, but it also prevents the AI from having to search again. 
        var bestMove = Moves.Count < MoveStack.Count ? 
            MoveStack[Moves.Count] : await GetBestMoveAsync(searchDepth);
        await MoveAsync(bestMove);
    }).AsAsyncAction();
}

public IAsyncOperation<ISpace> GetBestMoveAsync(int searchDepth)
{
    if (searchDepth < 1) throw new ArgumentException(
        "must be 1 or greater.", "searchDepth");

    return AsyncInfo.Run(cancellationToken => Task.Run(() => 
    {
        return (ISpace)reversiAI.GetBestMove(Board, 
            CurrentPlayer == State.One, searchDepth, cancellationToken);
    }, cancellationToken));
}


Argomenti correlati

App di esempio di Reversi
Reversi, un gioco di Windows Store scritto in XAML, C# e C++
Usare il modello Model-View-ViewModel (MVVM)
Informazioni sull'uso delle funzionalità delle app di Windows Store nell'esempio Reversi
Informazioni sulla struttura dell'app Reversi
Informazioni sul motore di gioco C++ Reversi
Creare la prima app di Windows Store con C# o Visual Basic
Roadmap per app di Windows Runtime scritte in C# o Visual Basic
Associazione dati

 

 

Mostra:
© 2014 Microsoft