Partilhar via


Comandos contextuais para coleções e listas

Muitas aplicações contêm coleções de conteúdos sob a forma de listas, grelhas e árvores que os utilizadores podem manipular. Por exemplo, os utilizadores podem ser capazes de apagar, renomear, sinalizar ou atualizar itens. Este artigo mostra-lhe como usar comandos contextuais para implementar este tipo de ações de uma forma que proporcione a melhor experiência possível para todos os tipos de entrada.

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

Use uma variedade de entradas para realizar o comando Favorito

Criação de comandos para todos os tipos de entrada

Como os utilizadores podem interagir com uma aplicação Windows usando uma vasta gama de dispositivos e entradas, a sua aplicação deverá expor comandos tanto através de menus contextuais independentes de entrada como aceleradores específicos de cada comando. Incluir ambos permite ao utilizador invocar rapidamente comandos sobre o conteúdo, independentemente da entrada ou do tipo de dispositivo.

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

Command Independente da entrada Acelerador de rato Acelerador de teclado Acelerador de toque
Excluir item Menu de contexto Botão de Sobrevoo Tecla DEL Deslizar para apagar
Item da bandeira Menu de contexto Botão de Sobrevoo Ctrl+Shift+G Deslizar para sinalizar
Atualizar dados Menu de contexto N/A Tecla F5 Puxe para atualizar
Adicionar aos favoritos Menu de contexto Botão de Sobrevoo F, Ctrl+S Deslizar para marcar como favorito
  • De um modo geral, deve disponibilizar todos os comandos de um item no menu contextual do item. Os menus de contexto são acessíveis aos utilizadores independentemente do tipo de entrada e devem conter todos os comandos contextuais que o utilizador pode executar.

  • Para comandos frequentemente acedidos, considere usar aceleradores de entrada. Os aceleradores de entrada permitem ao utilizador realizar ações rapidamente, com base no seu dispositivo de entrada. Aceleradores de entrada incluem:

    • Deslizar para acionar (acelerador de toque)
    • Puxar para atualizar dados (acelerador tátil)
    • Atalhos de teclado (acelerador de teclado)
    • Teclas de acesso (acelerador de teclado)
    • Botões de rato e caneta (acelerador de ponteiros)

Observação

Os utilizadores devem conseguir aceder a todos os comandos a partir de qualquer tipo de dispositivo. Por exemplo, se os comandos da sua aplicação só estiverem expostos através de aceleradores de apontadores de botão de navegação, os utilizadores táteis não poderão aceder a eles. No mínimo, use um menu de contexto para dar acesso a todos os comandos.

Exemplo: O modelo de dados PodcastObject

Para demonstrar as nossas recomendações dominantes, este artigo cria uma lista de podcasts para uma aplicação de podcasts. O código de exemplo demonstra como permitir que o utilizador "favoreça" um determinado podcast de uma lista.

Aqui está a definição do objeto podcast com que vamos trabalhar:

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

Note que o PodcastObject implementa INotifyPropertyChanged para responder a alterações de propriedade quando o utilizador alterna a propriedade IsFavorite.

Definição de comandos com a interface ICommand

A interface ICommand ajuda-te a definir um comando disponível para múltiplos tipos de entrada. Por exemplo, em vez de escrever o mesmo código para um comando delete em dois gestores de eventos diferentes, um para quando o utilizador pressiona a tecla Delete e outro para quando o utilizador clica com o botão direito em "Eliminar" num menu contextual, pode implementar a sua lógica de delete uma vez, como ICommand, e depois torná-la disponível para diferentes tipos de entrada.

Precisamos de definir o ICommand que representa a ação "Favorita". Vamos usar o método Executar do comando para marcar um podcast como favorito. O podcast específico será fornecido ao método execute através do parâmetro do comando, que pode ser atribuído 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 múltiplas coleções e elementos, pode armazenar o comando como recurso na página ou na aplicação.

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

Para executar o comando, chama o seu método Executar.

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

Criar um UserControl para responder a uma variedade de entradas

Quando tem uma lista de itens e cada um deles deve responder a múltiplas entradas, pode simplificar o seu código definindo um UserControl para o item e usando-o para definir o menu de contexto e os gestores de eventos dos seus itens.

Para criar um UserControl no Visual Studio:

  1. No Explorador de Soluções, clique com o botão direito no projeto. Aparece um menu de contexto.
  2. Selecionar Adicionar > Novo Item...
    Aparece o diálogo Adicionar Novo Item .
  3. Selecione UserControl na lista de itens. Dá-lhe o nome que queres e clica em Adicionar. O Visual Studio gera um stub UserControl para ti.

No nosso exemplo de podcast, cada podcast será apresentado numa lista, que expõe várias formas de "Favoritar" um podcast. O utilizador poderá realizar as seguintes ações para "Favoritar" o podcast:

  • Invocar um menu de contexto
  • Executar atalhos de teclado
  • Mostrar um botão de hover
  • Faz um gesto de deslizar

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

O PodcastUserControl mostra os campos do PodcastObject como TextBlocks e responde a várias interações dos utilizadores. Vamos referenciar e expandir 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.
    }
}

Note que o PodcastUserControl mantém uma referência ao PodcastObject como uma DependencyProperty. Isto permite-nos vincular os PodcastObjects ao PodcastUserControl.

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

Criação de menus de contexto

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

Mostrar um menu de contexto no item

O utilizador pode invocar menus de contexto usando estas "ações contextuais":

Entrada Ação contextual
Mouse Clique direito
Keyboard Shift+F10, botão Menu
Touch Mantenha premido o item
Caneta Pressionar o botão lateral, pressionar longamente no item
Gamepad Botão Menu

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

ContextFlyout

A propriedade ContextFlyout, definida pela classe UIElement, facilita a criação de um menu de contexto que funciona com todos os tipos de entrada. Fornece um flyout que representa o seu menu de contexto usando MenuFlyout ou CommandBarFlyout, e quando o utilizador executa uma "ação de contexto" conforme definido acima, o MenuFlyout ou CommandBarFlyout correspondente ao item será exibido.

Consulte menus e menus de contexto para ajuda a identificar cenários de menu vs. menu contextual e orientações sobre quando usar o menu flyout vs. o flyout da barra de comandos.

Para este exemplo, vamos usar o MenuFlyout e começaremos por adicionar um ContextFlyout ao PodcastUserControl. O MenuFlyout especificado como ContextFlyout contém um único item para favoritar um podcast. Note que este MenuFlyoutItem utiliza o favoritoCommand 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>

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

Criação de aceleradores de entrada

Embora cada item da coleção deva ter um menu contextual contendo todos os comandos contextuais, pode querer permitir que os utilizadores executem rapidamente um conjunto mais pequeno de comandos frequentemente executados. Por exemplo, uma aplicação de correio pode ter comandos secundários como Responder, Arquivar, Mover para Pasta, Definir Flag e Delete que aparecem num menu contextual, mas os comandos mais comuns são Delete e Flag. Depois de identificar quais os comandos mais comuns, pode usar aceleradores baseados em entrada para facilitar a execução destes comandos ao utilizador.

Na aplicação do podcast, o comando frequentemente executado é o comando "Favorito".

Aceleradores de teclado

Atalhos e manuseamento direto de teclas

Pressione Ctrl e F para realizar uma ação

Dependendo do tipo de conteúdo, pode identificar certas combinações de teclas que devem realizar uma ação. Numa aplicação de email, por exemplo, a tecla DEL pode ser usada para eliminar o email selecionado. Numa aplicação de podcast, as teclas Ctrl+S ou F podem marcar um podcast como favorito para mais tarde. Embora alguns comandos tenham atalhos de teclado comuns e bem conhecidos, como DEL para eliminar, outros comandos têm atalhos específicos de aplicação ou domínio. Use atalhos bem conhecidos, se possível, ou considere fornecer texto de lembrete numa dica de ferramenta para ensinar o utilizador sobre o comando de atalho.

A sua aplicação pode responder quando o utilizador pressiona uma tecla usando o evento KeyDown . De um modo geral, os utilizadores esperam que a aplicação responda quando pressionam a tecla pela primeira vez, em vez de esperar até a largarem.

Este exemplo explica como adicionar o handler KeyDown ao PodcastUserControl para marcar um podcast como favorito quando o utilizador pressiona Ctrl+S ou F. 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 rato

Passa o rato sobre um item para revelar um botão

Os utilizadores estão familiarizados com os menus contextuais do botão direito, mas pode querer capacitar os utilizadores a executar comandos comuns usando apenas um clique do rato. Para permitir esta experiência, pode incluir botões dedicados na tela do seu item de coleção. Para capacitar os utilizadores a agir rapidamente com o rato e minimizar a desordem visual, pode optar por revelar estes botões apenas quando o utilizador tiver o ponteiro dentro de um determinado item da lista.

Neste exemplo, o comando Favorito é representado por um botão definido diretamente no PodcastUserControl. Note que o botão neste exemplo usa o mesmo comando, FavoritosComando, de antes. Para alternar a visibilidade deste botão, pode usar o VisualStateManager para alternar entre estados visuais quando o ponteiro entra e sai do controlo.

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 hover devem aparecer e desaparecer quando o rato entra e sai do item. Para responder a eventos de rato, 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 hover só serão acessíveis através do tipo de entrada do ponteiro. Como estes botões estão limitados à entrada do ponteiro, pode optar por minimizar ou remover o enchimento em redor do ícone do botão para otimizar a entrada do apontador. Se optar por isso, certifique-se de que a área do botão tem pelo menos 20x20px para se manter utilizável com caneta e rato.

Aceleradores tácteis

Passar o dedo

Desliza um item para revelar o comando

O comando de deslizar é um acelerador tátil que permite aos utilizadores em dispositivos táteis realizar ações secundárias comuns usando o toque. O Swipe permite aos utilizadores por toque interagir rápida e naturalmente com o conteúdo, utilizando ações comuns como Deslizar para-Apagar ou Deslizar para Invocar. Consulte o artigo sobre comandos de deslizar para saber mais.

Para integrar o "swipe" na sua coleção, precisa de dois componentes: SwipeItems, que aloja os comandos, e um SwipeControl, que envolve o item e permite a interação através de deslizar.

Os SwipeItems podem ser definidos como um Recurso no PodcastUserControl. Neste exemplo, o SwipeItems contém um comando para Favoritar um item.

<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 envolve o item e permite ao utilizador interagir com ele usando o gesto de deslizar. Note que o SwipeControl contém uma referência aos SwipeItems como os seus RightItems. O item Favorito será mostrado quando o utilizador desliza 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 utilizador desliza para invocar o comando Favorito, é chamado o método Invocado.

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

Puxe para atualizar

O pull to refresh permite ao utilizador descer uma coleção de dados usando o toque para recuperar mais dados. Consulte o artigo de atualização para saber mais.

Aceleradores de caneta

O tipo de entrada da caneta proporciona a precisão da entrada do apontador. Os utilizadores podem realizar ações comuns, como abrir menus de contexto usando aceleradores baseados em caneta. Para abrir um menu contextual, os utilizadores podem tocar no ecrã com o botão do bastão pressionado, ou pressionar prolongadamente no conteúdo. Os utilizadores também podem usar a caneta para pairar sobre os conteúdos e compreender melhor a interface, como mostrar dicas de ferramenta, ou para revelar ações secundárias ao pairar, semelhante ao uso do rato.

Para otimizar a sua aplicação para entrada com caneta, consulte o artigo sobre interação caneta e caneta .

Recommendations

  • Certifique-se de que os utilizadores podem aceder a todos os comandos de todos os tipos de dispositivos Windows.
  • Inclua um menu de contexto que dê acesso a todos os comandos disponíveis para um item da coleção.
  • Forneça aceleradores de entrada para comandos usados frequentemente.
  • Use a interface ICommand para implementar comandos.