Découvrez comment l’exemple Reversi utilise les fonctionnalités d’application du Windows Store

Applies to Windows only

L’exemple Reversi utilise plusieurs fonctionnalités communes des applications du Windows Store en XAML et C#. Cette rubrique montre comment l’exemple utilise certaines de ces fonctionnalités et fournit des liens vers les rubriques des fonctionnalités essentielles.

Cette rubrique ne nécessite pas que vous compreniez la totalité de l’exemple, mais elle suppose que vous connaissez déjà XAML et C# et avez assimilé les notions fondamentales de chaque fonctionnalité ou êtes disposé à les apprendre en lisant les rubriques connexes. Pour des informations sur les concepts fondamentaux du développement des applications, voir Créer votre première application du Windows Store en C# ou Visual Basic.

Pour obtenir une introduction générale à l’exemple, voir Reversi, un jeu du Windows Store en XAML, C# et C++. Pour comprendre comment les diverses fonctionnalités forment un tout, voir Comprendre la structure de l’application Reversi. Pour découvrir comment le moteur de jeu C# d’origine a été porté vers C++, voir Découvrir le moteur de jeu C++ Reversi.

Téléchargez l’exemple d’application Reversi ou parcourez le code source.

Vignette et écran de démarrage

La vignette et l’écran de démarrage de l’application sont les premières choses que l’utilisateur voit de votre application. Vous pouvez vous en servir pour créer un point d’entrée attractif et afficher votre marque. Les bases sont très simples, mais vous pouvez aussi faire des choses plus complexes, comme décrit dans la documentation.

Ressources clés :

Reversi ne fournit qu’une prise en charge de base pour la vignette et l’écran de démarrage. Cela comprend des vignettes carrées et larges, ainsi qu’un écran de démarrage, comme vous pouvez le voir ici en taille réduite.

Vignettes et écran de démarrage de Reversi

Les noms des fichiers image sont définis dans le fichier Package.appxmanifest. Vous pouvez fournir des images dans différentes échelles pour permettre la prise en charge de plusieurs tailles d’écran. Aucune autre implémentation n’est nécessaire pour cette utilisation simple.

barre de l’application

La barre de l’application est un endroit standard où mettre les commandes de l’application. Par défaut, les utilisateurs peuvent afficher ou masquer une barre de l’application selon les besoins, ce qui en fait un bon endroit pour les commandes moins couramment utilisées. Cela contribue à ce que votre interface utilisateur principale reste centrée sur les interactions directes avec le contenu.

Ressources clés :

Reversi inclut quelques commandes secondaires qui sont bien adaptées à la barre de l’application : la possibilité de mettre l’horloge en pause et d’annuler ou rétablir des déplacements. Pendant le déroulement normal du jeu, la barre de l’application est masquée, mais l’utilisateur peut faire un mouvement de balayage à partir du haut ou du bas de l’écran pour l’afficher ou la masquer.

barre de l’application de Reversi

Ce code, extrait de GamePage.xaml, montre la définition de la barre de l’application. Bien que l’arrière-plan et la bordure soient transparents, la propriété Background est définie sur {x:Null} pour empêcher la barre de l’application invisible de bloquer les tapotages et les clics. Ce paramètre est nécessaire car la barre de l’application s’étend sur tout l’écran et se superpose à la rangée du bas du plateau de jeu.


<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 utilise les contrôles CommandBar et AppBarButton pour obtenir le comportement et le style par défaut. Le comportement du bouton et son état activé sont fournis par les commandes du modèle d’affichage liées aux propriétés du bouton Command, comme décrit dans la section Commandes.

Les boutons Lecture et Pause fonctionnent comme un bouton bascule. Pour obtenir cet effet, les propriétés Visibility des boutons sont liées à la même propriété du modèle d’affichage. Ces liaisons utilisent un BooleanToVisibilityConverter, mais l’une d’elles a également un paramètre de propriété ConverterParameter qui inverse l’effet de la liaison. De cette façon, chaque bouton est visible uniquement quand l’autre ne l’est pas. Pour plus d’informations, voir la section Liaison de données.

Notifications toast

Les notifications toast avertissent vos utilisateurs lorsqu’un événement important survient dans votre application, même si une autre application est active.

Ressources clés :

Dans Reversi, l’ordinateur peut prendre un certain temps pour jouer son coup. Si vous basculez vers une autre application pendant ce temps, une notification toast vous alertera quand c’est votre tour.

Notification toast Reversi

Reversi utilise le code minimal requis pour les notifications toast et définit le champ Compatible toast sur Oui dans le concepteur Package.appxmanifest. Le code de toast est facilement réutilisable et figure donc dans une classe d’assistance Helper dans le dossier Common.

Dans GameViewModel.cs :


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


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


Menus volants de paramètres

L’icône Paramètres fournit un accès standardisé aux paramètres de l’application.

Ressources clés :

Reversi a deux contrôles suspendus Paramètres, un pour les options d’affichage et l’autre pour les options de nouvelle partie.

Menus volants de paramètres Reversi

Ce code extrait de App.xaml.cs montre comment Reversi gère l’événement SettingsPane.CommandsRequested pour créer des objets SettingsCommand. Une fois activée, chaque commande crée et affiche un contrôle 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()));
}


Partage de contenu

Le contrat de partage permet à votre application de partager des données que les utilisateurs peuvent envoyer à d’autres applications. Par exemple, les utilisateurs peuvent partager des données de votre application dans une application de messagerie pour créer un message.

Ressources clés :

Windows intègre la prise en charge du partage d’une image de l’application. Reversi n’a pas besoin de fonctionnalités supplémentaires.

Liaison de données

La liaison de données vous permet de connecter des contrôles de l’interface utilisateur aux données qu’ils affichent afin que les modifications dans l’interface mettent à jour les données. La liaison de données est commune avec les formulaires de saisie de données, mais vous pouvez également l’utiliser pour piloter votre interface utilisateur entière et conserver celle-ci séparée de la logique de votre application.

Ressources clés :

Reversi utilise des liaisons de données pour connecter son interface utilisateur (ou couche « affichage ») à sa logique d’application (ou couche « modèle d’affichage »). Cette disposition en couches, qui permet de séparer l’interface utilisateur du reste du code, est appelée modèle Model-View-ViewModel (MVVM). Pour savoir comment Reversi utilise ce modèle, voir Structure de l’application Reversi. Pour une brève présentation de MVVM, voir Utilisation du modèle Model-View-ViewModel.

La plupart des liaisons dans Reversi sont définies en XAML au moyen de l’extension de balisage Binding, bien que du code-behind soit utilisé dans certains cas (par exemple, dans le fichier Board.xaml.cs). Chaque page définit sa propriété DataContext, que tous les éléments de la page utilisent comme source de données pour leurs liaisons.

Mises à jour de l’interface utilisateur

Les liaisons de données pilotent l’interface utilisateur de Reversi. Les interactions de l’interface utilisateur entraînent des modifications de propriétés dans la source de données et les liaisons de données répondent à ces changements en mettant à jour l’interface utilisateur.

Ces mises à jour fonctionnent, car les classes du modèle d’affichage Reversi héritent de la classe BindableBase. Cette classe se trouve dans le fichier Common/BindableBase.cs et fournit une implémentation standard de INotifyPropertyChanged, ainsi que quelques méthodes de prise en charge. La méthode SetProperty met à jour la valeur de stockage d’une propriété et tout élément de l’interface utilisateur lié avec un seul appel de méthode. La méthode OnPropertyChanged met à jour l’interface utilisateur qui est liée aux propriétés spécifiées. Ceci est utile pour contrôler le minutage des mises à jour et pour les propriétés qui obtiennent leurs valeurs à partir d’autres propriétés.

Ce code extrait de GameViewModel.cs montre l’utilisation de base de SetProperty et OnPropertyChanged.


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


Conversion de valeur

Vous pouvez convertir des valeurs de propriétés dans une forme plus adaptée pour la liaison en créant des propriétés calculées, qui obtiennent leurs valeurs à partir d’autres propriétés.

Ce code extrait de GameViewModel.cs montre une simple propriété calculée. L’interface utilisateur qui est liée à cette propriété est mise à jour par l’appel OnPropertyChanged correspondant de l’exemple précédent.


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


Les propriétés calculées sont faciles à créer pour tout type de conversion dont vous pourriez avoir besoin, mais elles ont tendance à encombrer le code. Pour les conversions courantes, il est préférable de placer le code de la conversion dans une implémentation de IValueConverter réutilisable. Reversi utilise les classes NullStateToVisibilityConverter et BooleanToVisibilityConverter dans le dossier Common/Converters pour les liaisons qui affichent et masquent divers éléments de l’interface utilisateur.

Cette liaison extraite de StartPage.xaml affiche ou masque un panneau selon qu’une propriété a une valeur ou non.


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


Cette liaison extraite de NewGameSettings.xaml affiche ou masque un panneau selon l’état d’un contrôle ToggleSwitch.


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


Pour plus d’exemples, voir barre de l’application.

Commandes

Les comportements de Button sont souvent implémentés avec des gestionnaires d’événements Click dans les fichiers code-behind. C’est le cas dans Reversi pour les boutons de navigation, mais pour les autres boutons, l’interface utilisateur du bouton est séparée du code non-IU que le bouton appelle. Pour ce faire, les propriétés Button.Command sont liées aux propriétés du modèle d’affichage que retournent les implémentations de ICommand.

Les propriétés de commande Reversi sont du type DelegateCommand ou DelegateCommand<T>. Ces classes se trouvent dans le fichier Common/DelegateCommand.cs et fournissent des implémentations de ICommand standards et réutilisables. Vous pouvez utiliser ces classes pour simplifier la création des commandes non réutilisables et pour confiner le code nécessaire dans des implémentations de propriété uniques.

Ce code extrait de GameViewModel.cs montre la commande de déplacement utilisée par les espaces du plateau, qui sont des boutons personnalisés. L’opérateur ?? signifie que la valeur du champ est retournée uniquement si elle n’est pas null ; sinon, le champ est défini et la nouvelle valeur est retournée. Cela signifie qu’un objet de commande unique est créé au premier accès à une propriété, et que le même objet est réutilisé pour tous les futurs accès. L’objet de commande est initialisé via l’appel de la méthode DelegateCommand<ISpace>.FromAsyncHandler avec des références aux méthodes MoveAsync et CanMove. Ces méthodes fournissent l’implémentation des méthodes ICommand.Execute et CanExecute.


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


La méthode CanExecute est appelée par la liaison de données pour mettre à jour l’état activé du bouton. Toutefois, les liaisons de commande dépendent des notifications de changement comme les autres liaisons (discuté dans Mises à jour de l’interface utilisateur). Ce code extrait de GameViewModel.cs montre comment la méthode UpdateView synchronise l’état du modèle d’affichage avec l’état du modèle, puis appelle OnCanExecuteChanged pour chaque commande avant de continuer avec le prochain déplacement.


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


Propriétés de dépendance personnalisées

Reversi utilise des propriétés de dépendance personnalisées dans ses contrôles personnalisés afin d’utiliser les mises à jour de liaison de données pour provoquer des changements d’état visuel. Les états visuels et les transitions animées sont définis en XAML en utilisant la classe VisualStateManager. Toutefois, il n’y a aucun moyen de lier un état visuel directement à une propriété du modèle d’affichage. Les propriétés de dépendance personnalisées fournissent des cibles de liaison aux propriétés du modèle d’affichage. Les propriétés de dépendance comprennent les rappels de propriété modifiée qui font les appels de méthode VisualStateManager.GoToState nécessaires.

Ce code montre comment le contrôle PlayerStatus utilise du code-behind pour lier ses propriétés de dépendance personnalisées aux propriétés du modèle d’affichage. Seulement une des propriétés de dépendance est représentée ici, y compris sa méthode de rappel de propriété modifiée. Le rappel et la substitution de méthode OnApplyTemplate appellent tous deux la méthode de mise à jour. Toutefois, l’appel à OnApplyTemplate initialise le contrôle pour sa première apparition à l’écran, donc il n’utilise pas de transitions animées.


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


Code asynchrone

Le code asynchrone permet à votre interface utilisateur de rester réactive pendant que votre application exécute des opérations fastidieuses.

Ressources clés :

Reversi utilise du code asynchrone pour exécuter les déplacements dans une partie. Si chaque déplacement prend au moins une seconde (animation de déplacement comprise), les déplacements d’IA peuvent prendre beaucoup plus de temps. Toutefois, l’interface utilisateur reste toujours réactive et les commandes utilisateur (p.ex., annuler) ont pour effet d’annuler un déplacement en cours.

Ce code tiré de GameViewModel.cs montre comment Reversi utilise les mots clés async et await, la classe Task et les jetons d’annulation. Notez l’utilisation de AsTask pour assurer une intégration au code asynchrone Windows Runtime dans la classe Game. (Pour plus d’informations, voir la prochaine section.)


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


Utilisation d’un composant Windows Runtime

Le fait d’implémenter une partie de votre code en tant que composant Windows Runtime vous permet de réutiliser ce code dans des applications différentes, sur des plateformes différentes ou avec des langages différents. Vous pouvez également remplacer plus facilement le composant par une implémentation alternative dans un autre langage.

Ressource clé :

Reversi implémente sa logique de base du jeu en tant que composant Windows Runtime de façon à la découpler entièrement de l’application. Cela lui permet de prendre en charge une extensibilité future et une réutilisation du code. Reversi comprend également une version C++ du moteur de jeu comme alternative haute performances à la version C# d’origine. Pour plus d’informations, voir Découvrir le moteur de jeu C++ Reversi.

Ce code tiré de Game.cs montre comment Reversi utilise le code asynchrone basé sur Task (mots clés async et await compris). Cependant, les résultats sont exposés via des interfaces asynchrones Windows Runtime. Ce code montre également comment le jeton d’annulation du code GameViewModel est utilisé par la classe Game.

La première et la troisième méthodes dans l’exemple de code appellent la méthode AsyncInfo.Run pour retourner un IAsyncOperation<T>. La valeur de retour est incluse dans un wrapper et l’annulation autorisée. Dans le deuxième exemple, la méthode WindowsRuntimeSystemExtensions.AsAsyncAction est appelée pour retourner un IAsyncAction. Ceci s’avère utile pour les tâches qui n’ont pas de valeurs de retour et qui n’ont pas besoin d’être annulées.


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


Rubriques associées

Exemple d’application Reversi
Reversi, un jeu du Windows Store en XAML, C# et C++
Utiliser le modèle Model-View-ViewModel (MVVM)
Découvrez comment l’exemple Reversi utilise les fonctionnalités d’application du Windows Store
Comprendre la structure de l’application Reversi
Découvrir le moteur de jeu C++ Reversi
Créer votre première application du Windows Store en C# ou Visual Basic
Feuille de route pour les applications Windows Runtime en C# ou Visual Basic
Liaison de données

 

 

Afficher:
© 2014 Microsoft