Compartilhar via


Comandos contextuais para coleções e listas

Muitos aplicativos contêm coleções de conteúdo na forma de listas, grades e árvores que os usuários podem manipular. Por exemplo, os usuários podem ser capazes de excluir, renomear, sinalizar ou atualizar itens. Este artigo mostra como usar comandos contextuais para implementar esses tipos de ações de forma a fornecer a melhor experiência possível para todos os tipos de entrada.

APIs importantes: interface ICommand, propriedade UIElement.ContextFlyout, interface INotifyPropertyChanged

Usar uma variedade de entradas para executar o comando Favorito

Criando comandos para todos os tipos de entrada

Como os usuários podem interagir com um aplicativo do Windows usando uma ampla gama de dispositivos e entradas, seu aplicativo deve expor comandos tanto por menus de contexto independentes de entrada quanto por aceleradores específicos de entrada. A inclusão de ambos permite que o usuário invoque rapidamente comandos no conteúdo, independentemente de entrada ou tipo de dispositivo.

Esta tabela mostra alguns comandos de coleção típicos e maneiras de expor esses comandos.

Command Independente de entrada Acelerador de mouse Acelerador de teclado Acelerador de toque
Excluir item Menu de contexto Botão de Hover Tecla DEL Passar o dedo para excluir
Item de sinalizador Menu de contexto Botão Focalizar Ctrl+Shift+G Deslizar para marcar
Atualizar dados Menu de contexto N/A Tecla F5 Deslizar para atualizar
Favorito de um item Menu de contexto Botão de Sobrevoo F, Ctrl+S Deslizar para favoritar
  • Em geral, você deve disponibilizar todos os comandos para um item no menu de contexto do item. Os menus de contexto são acessíveis aos usuários independentemente do tipo de entrada e devem conter todos os comandos contextuais que o usuário pode executar.

  • Para comandos acessados com frequência, considere o uso de aceleradores de entrada. Os aceleradores de entrada permitem que o usuário execute ações rapidamente, com base em seu dispositivo de entrada. Os aceleradores de entrada incluem:

    • Passar o dedo para ação (acelerador de toque)
    • Arraste para atualizar os dados (acelerador de toque)
    • Atalhos de teclado (acelerador de teclado)
    • Teclas de acesso (acelerador de teclado)
    • Botões de sobrevoo de mouse e caneta (acelerador de ponteiro)

Observação

Os usuários devem ser capazes de acessar todos os comandos de qualquer tipo de dispositivo. Por exemplo, se os comandos do aplicativo forem expostos apenas por meio de aceleradores de ponteiro do botão focalizar, os usuários de toque não poderão acessá-los. No mínimo, use um menu de contexto para fornecer acesso a todos os comandos.

Exemplo: o modelo de dados PodcastObject

Para demonstrar nossas recomendações de comando, este artigo cria uma lista de podcasts para um aplicativo de podcast. O código de exemplo demonstra como permitir que o usuário "favorite" um podcast específico de uma lista.

Aqui está a definição para o objeto de podcast com o qual trabalharemos:

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

Observe que o PodcastObject implementa INotifyPropertyChanged para responder a alterações de propriedade quando o usuário alterna a propriedade IsFavorite.

Definindo comandos com a interface ICommand

A interface ICommand ajuda você a definir um comando disponível para vários tipos de entrada. Por exemplo, em vez de escrever o mesmo código para um comando de exclusão em dois manipuladores de eventos diferentes, um para quando o usuário pressionar a tecla Delete e outra para quando o usuário clicar com o botão direito do mouse em "Excluir" em um menu de contexto, você poderá implementar sua lógica de exclusão uma vez, como um ICommand e disponibilizá-la para diferentes tipos de entrada.

Precisamos definir o ICommand que representa a ação "Favorito". Usaremos o método Execute do comando para adicionar um podcast aos favoritos. O podcast específico será fornecido ao método execute por meio do parâmetro do comando, que pode ser associado usando a propriedade 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;
    }
}

Para usar o mesmo comando com várias coleções e elementos, você pode armazenar o comando como um recurso na página ou no aplicativo.

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

Para executar o comando, você chama o método Execute .

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

Criando um UserControl para responder a uma variedade de entradas

Quando você tem uma lista de itens e cada um desses itens deve responder a várias entradas, você pode simplificar seu código definindo um UserControl para o item e usando-o para definir o menu de contexto e os manipuladores de eventos de seus itens.

Para criar um UserControl no Visual Studio:

  1. No Gerenciador de Soluções, clique com o botão direito do mouse no projeto. O menu de contexto é exibido.
  2. Selecione Adicionar > Novo Item...
    A caixa de diálogo Adicionar Novo Item é exibida.
  3. Selecione UserControl na lista de itens. Dê a ele o nome desejado e clique em Adicionar. O Visual Studio gerará um UserControl stub para você.

Em nosso exemplo de podcast, cada podcast será exibido em uma lista, que apresentará várias maneiras de 'favoritar' um podcast. O usuário poderá executar as seguintes ações para favoritar o podcast:

  • Invocar um menu de contexto
  • Executar atalhos de teclado
  • Mostrar um botão de focalização
  • Executar um gesto de passar o dedo

Para encapsular esses comportamentos e usar o FavoriteCommand, vamos criar um novo UserControl chamado "PodcastUserControl" para representar um podcast na lista.

O PodcastUserControl exibe os campos do PodcastObject como TextBlocks e responde a várias interações do usuário. Vamos referenciar e expandir sobre o PodcastUserControl ao longo deste artigo.

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.
    }
}

Observe que o PodcastUserControl mantém uma referência ao PodcastObject como dependencyProperty. Isso nos permite associar PodcastObjects ao PodcastUserControl.

Depois de gerar alguns PodcastObjects, você pode criar uma lista de podcasts associando os PodcastObjects a um ListView. Os objetos PodcastUserControl descrevem a visualização dos PodcastObjects e, portanto, são definidos usando ItemTemplate do 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>

Criando menus de contexto

Os menus de contexto exibem uma lista de comandos ou opções quando o usuário os solicita. Os menus de contexto fornecem comandos contextuais relacionados ao elemento anexado e geralmente são reservados para ações secundárias específicas a esse item.

Mostrar um menu de contexto no item

O usuário pode invocar menus de contexto usando essas "ações de contexto":

Input Ação de contexto
Rato Clique com o botão direito do mouse
Teclado Shift+F10, botão Menu
Toque Pressionamento longo no item
Caneta Pressione o botão Barrel, pressione o item por muito tempo
Controle de jogo Botão Menu

Como o usuário pode abrir um menu de contexto independentemente do tipo de entrada, o menu de contexto deve conter todos os comandos contextuais disponíveis para o item de lista.

ContextFlyout

A propriedade ContextFlyout, definida pela classe UIElement, facilita a criação de um menu de contexto que funcione com todos os tipos de entrada. Você fornece um menu contextual usando MenuFlyout ou CommandBarFlyout, e, quando o usuário executa uma "ação de contexto" conforme definido anteriormente, o MenuFlyout ou CommandBarFlyout correspondente ao item será exibido.

Consulte menus e menus de contexto para obter ajuda para identificar cenários de menus versus menus de contexto e diretrizes sobre quando usar sobreposição de menu versus menu flutuante da barra de comandos.

Para este exemplo, usaremos MenuFlyout e começaremos adicionando um ContextFlyout ao PodcastUserControl. O MenuFlyout especificado como ContextFlyout contém um único item para adicionar um podcast aos favoritos. Observe que este MenuFlyoutItem usa o favoriteCommand definido acima, com o CommandParameter associado ao 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>

Observe que você também pode usar o evento ContextRequested para responder a ações de contexto. O evento ContextRequested não será acionado se um ContextFlyout tiver sido especificado.

Criando aceleradores de entrada

Embora cada item da coleção deva ter um menu de contexto contendo todos os comandos contextuais, convém permitir que os usuários executem rapidamente um conjunto menor de comandos executados com frequência. Por exemplo, um aplicativo de endereçamento pode ter comandos secundários como Responder, Arquivar, Mover para Pasta, Definir Sinalizador e Excluir que aparecem em um menu de contexto, mas os comandos mais comuns são Excluir e Sinalizar. Depois de identificar quais comandos são mais comuns, você pode usar aceleradores baseados em entrada para facilitar a execução desses comandos para um usuário.

No aplicativo de podcast, o comando executado com frequência é o comando "Favorito".

Aceleradores de teclado

Atalhos e manipulação direta de teclas

Pressione Ctrl e F para executar uma ação

Dependendo do tipo de conteúdo, você pode identificar determinadas combinações de chaves que devem executar uma ação. Em um aplicativo de email, por exemplo, a chave DEL pode ser usada para excluir o email selecionado. Em um aplicativo de podcast, as teclas Ctrl+S ou F poderiam favoritar um podcast para mais tarde. Embora alguns comandos tenham atalhos de teclado comuns e conhecidos, como DEL para excluir, outros comandos têm atalhos específicos do aplicativo ou do domínio. Use atalhos conhecidos, se possível, ou considere fornecer texto de lembrete em uma dica de ferramenta para ensinar o usuário sobre o comando de atalho.

Seu aplicativo pode responder quando o usuário pressiona uma tecla usando o evento KeyDown . Em geral, os usuários esperam que o aplicativo responda quando pressionarem a tecla pela primeira vez, em vez de esperarem até liberarem a chave.

Este exemplo explica como adicionar o manipulador KeyDown ao PodcastUserControl para fazer o favorito de um podcast quando o usuário pressionar Ctrl+S ou F. Ele usa o mesmo comando de antes.

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

Aceleradores de mouse

Passe o mouse sobre um item para revelar um botão

Os usuários estão familiarizados com menus de contexto com o botão direito do mouse, mas talvez você queira capacitar os usuários a executar comandos comuns usando apenas um único clique do mouse. Para habilitar essa experiência, você pode incluir botões dedicados na tela do item de coleção. Para capacitar os usuários a agir rapidamente usando o mouse e minimizar a desordem visual, você pode optar por revelar esses botões apenas quando o usuário tiver o ponteiro dentro de um item de lista específico.

Neste exemplo, o comando Favorito é representado por um botão definido diretamente no PodcastUserControl. Observe que o botão neste exemplo usa o mesmo comando, FavoriteCommand, como antes. Para alternar a visibilidade desse botão, você pode usar o VisualStateManager para alternar entre estados visuais quando o ponteiro entra e sai do controle.

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>

Os botões de foco devem aparecer e desaparecer quando o mouse entra e sai do item. Para responder a eventos do mouse, você pode usar os eventos PointerEntered e PointerExited no 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);
}

Os botões exibidos no estado de foco só estarão acessíveis por meio do tipo de entrada de ponteiro. Como esses botões são limitados à entrada de ponteiro, você pode optar por minimizar ou remover o preenchimento ao redor do ícone do botão para otimizar a entrada do ponteiro. Se você optar por fazer isso, verifique se o tamanho do botão é pelo menos 20x20px para permanecer utilizável com caneta e mouse.

Aceleradores de toque

Swipe

Passar o dedo em um item para revelar o comando

O comando de passar o dedo é um acelerador de toque que permite que os usuários em dispositivos touch executem ações secundárias comuns usando o toque. Deslizar permite que os usuários de toque interajam de forma rápida e natural com o conteúdo, usando ações comuns como Deslizar para Excluir ou Deslizar para Executar. Consulte o artigo sobre comandos de passar o dedo para saber mais.

Para integrar o swipe à sua coleção, você precisa de dois componentes: SwipeItems, que hospeda os comandos; e um SwipeControl, que encapsula o item e permite a interação de passar o dedo.

Os SwipeItems podem ser definidos como um recurso no PodcastUserControl. Neste exemplo, SwipeItems contém um comando para marcar um item como favorito.

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

O SwipeControl encapsula o item e permite que o usuário interaja com ele usando o gesto de passar o dedo. Observe que o SwipeControl contém uma referência aos SwipeItems como seus RightItems. O item Favorito será mostrado quando o usuário passar o dedo da direita para a esquerda.

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

Quando o usuário passa o dedo para invocar o comando Favorito, o método Invocado é chamado.

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

Deslizar para atualizar

A funcionalidade de "puxar para atualizar" permite que um usuário deslize para baixo em uma coleção de dados usando o toque para obter mais informações. Consulte o artigo puxar para atualizar para saber mais.

Aceleradores de stylus

O tipo de entrada de caneta fornece a precisão da entrada do ponteiro. Os usuários podem executar ações comuns, como abrir menus de contexto usando aceleradores baseados em caneta. Para abrir um menu de contexto, os usuários podem tocar na tela com o botão de barril pressionado ou pressionar o conteúdo por muito tempo. Os usuários também podem usar a caneta para passar o mouse sobre o conteúdo para obter uma compreensão mais profunda da interface do usuário, como exibir dicas de ferramentas ou para revelar ações de foco secundárias, semelhantes ao mouse.

Para otimizar seu aplicativo para entrada de caneta, consulte o artigo sobre interação de caneta e estilete.

Recommendations

  • Verifique se os usuários podem acessar todos os comandos de todos os tipos de dispositivos Windows.
  • Inclua um menu de contexto que fornece acesso a todos os comandos disponíveis para um item de coleção.
  • Forneça aceleradores de entrada para comandos usados com frequência.
  • Use a interface ICommand para implementar comandos.