Partager via


Commande contextuelle pour les collections et les listes

De nombreuses applications contiennent des collections de contenu sous la forme de listes, de grilles et d’arborescences que les utilisateurs peuvent manipuler. Par exemple, les utilisateurs peuvent être en mesure de supprimer, renommer, marquer ou actualiser des éléments. Cet article explique comment utiliser des commandes contextuelles pour implémenter ces types d’actions d’une manière qui offre la meilleure expérience possible pour tous les types d’entrée.

API importantes : interface ICommand, propriété UIElement.ContextFlyout, interface INotifyPropertyChanged

Utiliser une variété d’entrées pour exécuter la commande Favorite

Création de commandes pour tous les types d’entrée

Étant donné que les utilisateurs peuvent interagir avec une application Windows à l’aide d'un large éventail d'appareils et d'options d'entrée, votre application doit exposer des commandes à la fois à travers des menus contextuels neutres en termes d’entrée et des accélérateurs spécifiques aux entrées. L’inclusion des deux permet à l’utilisateur d’appeler rapidement des commandes sur le contenu, quel que soit le type d’entrée ou d’appareil.

Ce tableau présente certaines commandes de collection classiques et des moyens d’exposer ces commandes.

Command Indépendant de l’entrée Accélérateur de souris Raccourci clavier Accélérateur tactile
Supprimer un élément Menu contextuel Bouton au survol Touche DEL Balayez pour supprimer
Élément d’indicateur Menu contextuel Bouton de Survol Ctrl+Maj+G Balayez pour marquer
Actualiser les données Menu contextuel N/A Touche F5 Tirer pour actualiser
Mettre un élément en favoris Menu contextuel Bouton au survol F, Ctrl+S Balayez vers le favori
  • En général, vous devez rendre toutes les commandes d’un élément disponibles dans le menu contextuel de l’élément. Les menus contextuels sont accessibles aux utilisateurs quel que soit le type d’entrée et doivent contenir toutes les commandes contextuelles que l’utilisateur peut effectuer.

  • Pour les commandes fréquemment sollicitées, envisagez d’utiliser des accélérateurs d’entrée. Les accélérateurs d’entrée permettent à l’utilisateur d’effectuer rapidement des actions en fonction de son appareil d’entrée. Les accélérateurs d’entrée sont les suivants :

    • Balayage vers action (accélérateur tactile)
    • Tirer pour actualiser les données (accélérateur tactile)
    • Raccourcis clavier (accélérateur de clavier)
    • Touches d’accès (accélérateur de clavier)
    • Boutons de pointage souris et stylet (accélérateur de pointeur)

Note

Les utilisateurs doivent pouvoir accéder à toutes les commandes à partir de n’importe quel type d’appareil. Par exemple, si les commandes de votre application ne sont exposées qu’à l’aide d’accélérateurs de boutons de survol du curseur, les utilisateurs tactiles ne pourront pas y accéder. Au minimum, utilisez un menu contextuel pour fournir l’accès à toutes les commandes.

Exemple : Modèle de données PodcastObject

Pour illustrer nos recommandations imposantes, cet article crée une liste de podcasts pour une application de podcast. L’exemple de code montre comment autoriser l’utilisateur à « favori » un podcast particulier à partir d’une liste.

Voici la définition de l’objet podcast avec lequel nous allons travailler :

public class PodcastObject : INotifyPropertyChanged
{
    // The title of the podcast
    public String Title { get; set; }

    // The podcast's description
    public String Description { get; set; }

    // Describes if the user has set this podcast as a favorite
    public bool IsFavorite
    {
        get
        {
            return _isFavorite;
        }
        set
        {
            _isFavorite = value;
            OnPropertyChanged("IsFavorite");
        }
    }
    private bool _isFavorite = false;

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(String property)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }
}

Notez que PodcastObject implémente INotifyPropertyChanged pour répondre aux modifications de propriété lorsque l’utilisateur bascule la propriété IsFavorite.

Définition de commandes avec l’interface ICommand

L’interface ICommand vous aide à définir une commande disponible pour plusieurs types d’entrée. Par exemple, au lieu d’écrire le même code pour une commande de suppression dans deux gestionnaires d’événements différents, une pour quand l’utilisateur appuie sur la touche Supprimer et l’autre lorsque l’utilisateur clique avec le bouton droit sur « Supprimer » dans un menu contextuel, vous pouvez implémenter votre logique de suppression une fois, en tant que ICommand, puis la rendre disponible pour différents types d’entrée.

Nous devons définir l’ICommand qui représente l’action « Favorite ». Nous allons utiliser la méthode Execute de la commande pour favoriser un podcast. Le podcast particulier sera fourni à la méthode d’exécution via le paramètre de la commande, qui peut être lié à l’aide de la propriété CommandParameter.

public class FavoriteCommand: ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        // Perform the logic to "favorite" an item.
        (parameter as PodcastObject).IsFavorite = true;
    }
}

Pour utiliser la même commande avec plusieurs collections et éléments, vous pouvez stocker la commande en tant que ressource sur la page ou sur l’application.

<Application.Resources>
    <local:FavoriteCommand x:Key="favoriteCommand" />
</Application.Resources>

Pour exécuter la commande, vous appelez sa méthode Execute .

// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);

Création d’un UserControl pour répondre à une variété d’entrées

Lorsque vous disposez d’une liste d’éléments et que chacun de ces éléments doit répondre à plusieurs entrées, vous pouvez simplifier votre code en définissant un UserControl pour l’élément et en l’utilisant pour définir le menu contextuel et les gestionnaires d’événements de vos éléments.

Pour créer un UserControl dans Visual Studio :

  1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet. Un menu contextuel s’affiche.
  2. Sélectionnez Ajouter un > nouvel élément...
    La boîte de dialogue Ajouter un nouvel élément s’affiche.
  3. Sélectionnez UserControl dans la liste des éléments. Donnez-lui le nom souhaité, puis cliquez sur Ajouter. Visual Studio génère un userControl stub pour vous.

Dans notre exemple de podcast, chaque podcast sera affiché dans une liste, qui présentera diverses façons de mettre un podcast en favori. L’utilisateur pourra effectuer les actions suivantes pour « Favori » le podcast :

  • Appeler un menu contextuel
  • Effectuer des raccourcis clavier
  • Afficher un bouton de pointage
  • Effectuer un mouvement de balayage

Pour encapsuler ces comportements et utiliser FavoriteCommand, nous allons créer un UserControl nommé « PodcastUserControl » pour représenter un podcast dans la liste.

PodcastUserControl affiche les champs du PodcastObject en tant que TextBlocks et répond à différentes interactions utilisateur. Nous allons référencer et développer le PodcastUserControl tout au long de cet article.

PodcastUserControl.xaml

<UserControl
    x:Class="ContextCommanding.PodcastUserControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    IsTabStop="True" UseSystemFocusVisuals="True"
    >
    <Grid Margin="12,0,12,0">
        <StackPanel>
            <TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
            <TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
            <TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
        </StackPanel>
    </Grid>
</UserControl>

PodcastUserControl.xaml.cs

public sealed partial class PodcastUserControl : UserControl
{
    public static readonly DependencyProperty PodcastObjectProperty =
        DependencyProperty.Register(
            "PodcastObject",
            typeof(PodcastObject),
            typeof(PodcastUserControl),
            new PropertyMetadata(null));

    public PodcastObject PodcastObject
    {
        get { return (PodcastObject)GetValue(PodcastObjectProperty); }
        set { SetValue(PodcastObjectProperty, value); }
    }

    public PodcastUserControl()
    {
        this.InitializeComponent();

        // TODO: We will add event handlers here.
    }
}

Notez que PodcastUserControl conserve une référence au PodcastObject en tant que DependencyProperty. Cela nous permet de lier PodcastObjects au PodcastUserControl.

Une fois que vous avez généré certains PodcastObjects, vous pouvez créer une liste de podcasts en liant podcastObjects à un ListView. Les objets PodcastUserControl décrivent la visualisation des PodcastObjects et sont donc définis à l’aide de l’élément ItemTemplate de ListView.

MainPage.xaml

<ListView x:Name="ListOfPodcasts"
            ItemsSource="{x:Bind podcasts}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:PodcastObject">
            <local:PodcastUserControl PodcastObject="{x:Bind Mode=OneWay}" />
        </DataTemplate>
    </ListView.ItemTemplate>
    <ListView.ItemContainerStyle>
        <!-- The PodcastUserControl will entirely fill the ListView item and handle tabbing within itself. -->
        <Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemRevealStyle}">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <Setter Property="Padding" Value="0"/>
            <Setter Property="IsTabStop" Value="False"/>
        </Style>
    </ListView.ItemContainerStyle>
</ListView>

Création de menus contextuels

Les menus contextuels affichent une liste de commandes ou d’options lorsque l’utilisateur les demande. Les menus contextuels fournissent des commandes contextuelles liées à leur élément attaché et sont généralement réservées aux actions secondaires spécifiques à cet élément.

Afficher un menu contextuel sur l’élément

L’utilisateur peut appeler des menus contextuels à l’aide de ces « actions contextuelles » :

Input Action de contexte
Souris Cliquer avec le bouton droit
Clavier Maj+F10, bouton Menu
Touchez Appuyez longuement sur l’élément
Stylet Appuyez sur le bouton molette, appuyez longuement sur l’élément
Manette de jeu Bouton Menu

Étant donné que l’utilisateur peut ouvrir un menu contextuel quel que soit le type d’entrée, votre menu contextuel doit contenir toutes les commandes contextuelles disponibles pour l’élément de liste.

ContextFlyout

La propriété ContextFlyout, définie par la classe UIElement, facilite la création d’un menu contextuel qui fonctionne avec tous les types d’entrée. Vous fournissez un menu volant représentant votre menu contextuel à l’aide de MenuFlyout ou CommandBarFlyout, et lorsque l’utilisateur effectue une « action contextuelle » telle que définie ci-dessus, menuFlyout ou CommandBarFlyout correspondant à l’élément s’affiche.

Consultez les menus et les menus contextuels pour obtenir de l'aide sur l'identification des scénarios de menu et de menu contextuel, ainsi que des conseils sur l'utilisation du menu volant et de la barre de commandes volante.

Pour cet exemple, nous allons utiliser MenuFlyout et commencerons par ajouter un ContextFlyout au PodcastUserControl. Le MenuFlyout spécifié comme ContextFlyout contient un seul élément permettant de mettre un podcast en favori. Notez que ce MenuFlyoutItem utilise le favoriteCommand défini ci-dessus, avec commandParameter lié au PodcastObject.

PodcastUserControl.xaml

<UserControl>
    <UserControl.ContextFlyout>
        <MenuFlyout>
            <MenuFlyoutItem Text="Favorite" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" />
        </MenuFlyout>
    </UserControl.ContextFlyout>
    <Grid Margin="12,0,12,0">
        <!-- ... -->
    </Grid>
</UserControl>

Notez que vous pouvez également utiliser l’événement ContextRequested pour répondre aux actions de contexte. L’événement ContextRequested ne se déclenche pas si un ContextFlyout a été spécifié.

Création d’accélérateurs d’entrée

Bien que chaque élément de la collection ait un menu contextuel contenant toutes les commandes contextuelles, vous pouvez permettre aux utilisateurs d’effectuer rapidement un plus petit ensemble de commandes fréquemment effectuées. Par exemple, une application de publipostage peut avoir des commandes secondaires telles que Reply, Archive, Move to Folder, Set Flag et Delete qui apparaissent dans un menu contextuel, mais les commandes les plus courantes sont Delete et Flag. Une fois que vous avez identifié les commandes les plus courantes, vous pouvez utiliser des accélérateurs basés sur les entrées pour faciliter l’exécution de ces commandes par un utilisateur.

Dans l’application podcast, la commande fréquemment effectuée est la commande « Favorite ».

Raccourcis clavier

Raccourcis et gestion directe des touches

Appuyez sur Ctrl et F pour effectuer une action

Selon le type de contenu, vous pouvez identifier certaines combinaisons de touches qui doivent effectuer une action. Dans une application de messagerie, par exemple, la clé DEL peut être utilisée pour supprimer l’e-mail sélectionné. Dans une application de podcast, les touches Ctrl+S ou F peuvent favoriser un podcast pour l'écouter ultérieurement. Bien que certaines commandes aient des raccourcis clavier courants connus comme DEL à supprimer, d’autres commandes ont des raccourcis spécifiques à l’application ou au domaine. Utilisez des raccourcis connus si possible, ou envisagez de fournir du texte de rappel dans une info-bulle pour enseigner à l’utilisateur la commande de raccourci.

Votre application peut répondre lorsque l’utilisateur appuie sur une touche à l’aide de l’événement KeyDown . En général, les utilisateurs s’attendent à ce que l’application réponde lorsqu’elle appuie pour la première fois sur la touche vers le bas, plutôt que d’attendre qu’elle relâche la touche.

Cet exemple montre comment ajouter le gestionnaire KeyDown à PodcastUserControl pour favoris un podcast lorsque l’utilisateur appuie sur Ctrl+S ou F. Elle utilise la même commande que précédemment.

PodcastUserControl.xaml.cs

// Respond to the F and Ctrl+S keys to favorite the focused item.
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
    var ctrlState = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Control);
    var isCtrlPressed = (ctrlState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down || (ctrlState & CoreVirtualKeyStates.Locked) == CoreVirtualKeyStates.Locked;

    if (e.Key == Windows.System.VirtualKey.F || (e.Key == Windows.System.VirtualKey.S && isCtrlPressed))
    {
        // Favorite the item using the defined command
        var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
        favoriteCommand.Execute(PodcastObject);
    }
}

Accélérateurs de souris

Pointez la souris sur un élément pour afficher un bouton

Les utilisateurs sont familiarisés avec les menus contextuels en cliquant avec le bouton droit, mais vous souhaiterez peut-être permettre aux utilisateurs d’effectuer des commandes courantes à l’aide d’un seul clic de la souris. Pour activer cette expérience, vous pouvez inclure des boutons dédiés sur le canevas de votre élément de collection. Pour permettre aux utilisateurs d’agir rapidement à l’aide de la souris et de réduire l’encombrement visuel, vous pouvez choisir de ne révéler ces boutons que lorsque l’utilisateur a son pointeur dans un élément de liste particulier.

Dans cet exemple, la commande Favorite est représentée par un bouton défini directement dans PodcastUserControl. Notez que le bouton de cet exemple utilise la même commande, FavoriteCommand, comme précédemment. Pour désactiver la visibilité de ce bouton, vous pouvez utiliser VisualStateManager pour basculer entre les états visuels lorsque le pointeur entre et quitte le contrôle.

PodcastUserControl.xaml

<UserControl>
    <UserControl.ContextFlyout>
        <!-- ... -->
    </UserControl.ContextFlyout>
    <Grid Margin="12,0,12,0">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="HoveringStates">
                <VisualState x:Name="HoverButtonsShown">
                    <VisualState.Setters>
                        <Setter Target="hoverArea.Visibility" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="HoverButtonsHidden" />
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <StackPanel>
            <TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
            <TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
            <TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
        </StackPanel>
        <Grid Grid.Column="1" x:Name="hoverArea" Visibility="Collapsed" VerticalAlignment="Stretch">
            <AppBarButton Icon="OutlineStar" Label="Favorite" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" IsTabStop="False" VerticalAlignment="Stretch"  />
        </Grid>
    </Grid>
</UserControl>

Les boutons de pointage doivent apparaître et disparaître lorsque la souris entre et quitte l’élément. Pour répondre aux événements de souris, vous pouvez utiliser les événements PointerEntered et PointerExited sur PodcastUserControl.

PodcastUserControl.xaml.cs

protected override void OnPointerEntered(PointerRoutedEventArgs e)
{
    base.OnPointerEntered(e);

    // Only show hover buttons when the user is using mouse or pen.
    if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse || e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Pen)
    {
        VisualStateManager.GoToState(this, "HoverButtonsShown", true);
    }
}

protected override void OnPointerExited(PointerRoutedEventArgs e)
{
    base.OnPointerExited(e);

    VisualStateManager.GoToState(this, "HoverButtonsHidden", true);
}

Les boutons affichés dans l’état du pointage seront accessibles uniquement via le type d’entrée du pointeur. Étant donné que ces boutons sont limités à l’entrée de pointeur, vous pouvez choisir de réduire ou de supprimer le remplissage autour de l’icône du bouton pour optimiser l’entrée du pointeur. Si vous choisissez de le faire, assurez-vous que l’empreinte du bouton est au moins 20x20px pour rester utilisable avec le stylet et la souris.

Accélérateurs tactiles

Glisser

Balayez un élément pour afficher la commande

La commande de balayage est un accélérateur tactile qui permet aux utilisateurs sur les appareils tactiles d’effectuer des actions secondaires courantes à l’aide de l’interaction tactile. Balayez permet aux utilisateurs tactiles d’interagir rapidement et naturellement avec du contenu, en utilisant des actions courantes comme Balayer pour supprimer ou Balayer pour invoquer. Pour plus d’informations, consultez l’article sur les commandes de balayage .

Pour intégrer le balayage dans votre collection, vous avez besoin de deux composants : SwipeItems, qui héberge les commandes ; et un SwipeControl, qui encapsule l’élément et permet une interaction de balayage.

Les swipeItems peuvent être définis comme une ressource dans PodcastUserControl. Dans cet exemple, SwipeItems contient une commande pour favoriser un élément.

<UserControl.Resources>
    <SymbolIconSource x:Key="FavoriteIcon" Symbol="Favorite"/>
    <SwipeItems x:Key="RevealOtherCommands" Mode="Reveal">
        <SwipeItem IconSource="{StaticResource FavoriteIcon}" Text="Favorite" Background="Yellow" Invoked="SwipeItem_Invoked"/>
    </SwipeItems>
</UserControl.Resources>

SwipeControl encapsule l’élément et permet à l’utilisateur d’interagir avec lui à l’aide du mouvement de balayage. Notez que SwipeControl contient une référence aux SwipeItems comme RightItems. L’élément favori s’affiche lorsque l’utilisateur effectue un mouvement de balayage de droite à gauche.

<SwipeControl x:Name="swipeContainer" RightItems="{StaticResource RevealOtherCommands}">
   <!-- The visual state groups moved from the Grid to the SwipeControl, since the SwipeControl wraps the Grid. -->
   <VisualStateManager.VisualStateGroups>
       <VisualStateGroup x:Name="HoveringStates">
           <VisualState x:Name="HoverButtonsShown">
               <VisualState.Setters>
                   <Setter Target="hoverArea.Visibility" Value="Visible" />
               </VisualState.Setters>
           </VisualState>
           <VisualState x:Name="HoverButtonsHidden" />
       </VisualStateGroup>
   </VisualStateManager.VisualStateGroups>
   <Grid Margin="12,0,12,0">
       <Grid.ColumnDefinitions>
           <ColumnDefinition Width="*" />
           <ColumnDefinition Width="Auto" />
       </Grid.ColumnDefinitions>
       <StackPanel>
           <TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
           <TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
           <TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
       </StackPanel>
       <Grid Grid.Column="1" x:Name="hoverArea" Visibility="Collapsed" VerticalAlignment="Stretch">
           <AppBarButton Icon="OutlineStar" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" IsTabStop="False" LabelPosition="Collapsed" VerticalAlignment="Stretch"  />
       </Grid>
   </Grid>
</SwipeControl>

Lorsque l’utilisateur effectue un mouvement de balayage pour appeler la commande Favoris, la méthode Invoked est exécutée.

private void SwipeItem_Invoked(SwipeItem sender, SwipeItemInvokedEventArgs args)
{
    // Favorite the item using the defined command
    var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
    favoriteCommand.Execute(PodcastObject);
}

Tirer pour actualiser

Tirer pour actualiser permet à un utilisateur de faire défiler une collection de données vers le bas à l'aide du toucher pour récupérer davantage de données. Consultez l’article pull to refresh pour en savoir plus.

Accélérateurs de stylet

Le type d’entrée de stylet fournit la précision de l’entrée de pointeur. Les utilisateurs peuvent effectuer des actions courantes telles que l’ouverture de menus contextuels à l’aide d’accélérateurs basés sur le stylet. Pour ouvrir un menu contextuel, les utilisateurs peuvent appuyer sur l’écran à l’aide du bouton baril enfoncé ou appuyer longuement sur le contenu. Les utilisateurs peuvent également utiliser le stylet pour survoler le contenu afin de mieux comprendre l'interface utilisateur, par exemple en affichant des info-bulles, ou pour révéler des actions secondaires de survol, similaires à la souris.

Pour optimiser votre application pour l’entrée de stylo et de stylet, consultez l’article sur l'interaction entre stylo et stylet.

Recommendations

  • Assurez-vous que les utilisateurs peuvent accéder à toutes les commandes de tous les types d’appareils Windows.
  • Incluez un menu contextuel qui fournit l’accès à toutes les commandes disponibles pour un élément de collection.
  • Fournissez des accélérateurs d’entrée pour les commandes fréquemment utilisées.
  • Utilisez l’interface ICommand pour implémenter des commandes.