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 excluir, renomear, sinalizar ou atualizar os itens. Este artigo mostra como usar os comandos contextuais para implementar esses tipos de ações de maneira que proporcione a melhor experiência possível para todos os tipos de entrada.
APIs importantes: interface ICommand, propriedade UIElement.ContextFlyout, interface INotifyPropertyChanged
Criar comandos para todos os tipos de entrada
Como os usuários podem interagir com um aplicativo do Windows usando uma ampla variedade de dispositivos e entradas, o aplicativo deve expor comandos por meio de menus de contexto independentes do tipo de entrada e aceleradores de entrada específicos. A inclusão de ambos permite que o usuário invoque rapidamente comandos no conteúdo, independentemente do tipo de entrada ou dispositivo.
Esta tabela mostra alguns comandos típicos de coleção e os modos de expô-los.
Comando | Independentes do tipo de entrada | Acelerador de mouse | Acelerador de teclado | Acelerador de toque |
---|---|---|---|---|
Excluir item | Menu de contexto | Botão Focalizar | Tecla DEL | Deslizar o dedo para excluir |
Sinalizar item | Menu de contexto | Botão Focalizar | Ctrl+Shift+G | Deslizar o dedo para sinalizar |
Atualizar dados | Menu de contexto | N/D | Tecla F5 | Puxar para atualizar |
Adicionar item a Favoritos | Menu de contexto | Botão Focalizar | F, Ctrl+S | Deslizar o dedo para adicionar a Favoritos |
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 nos dispositivos de entrada. Os aceleradores de entrada incluem:
- Ação de deslizar o dedo (acelerador de toque)
- Deslizar para atualizar dados (acelerador de toque)
- Atalhos de teclado (acelerador de teclado)
- Teclas de acesso (acelerador de teclado)
- Botões de foco 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 de 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 comandos, este artigo cria uma lista de podcasts para um aplicativo de podcast. O exemplo de código demonstra como permitir que o usuário salve como "favorito" um podcast específico de uma lista.
Esta é a definição do objeto de podcast com a 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 PodcastObject implementa INotifyPropertyChanged para responder às alterações de propriedade quando o usuário alterna a propriedade IsFavorite.
Definição de comandos com a interface ICommand
A interface ICommand ajuda você a definir um comando que está disponível para vários tipos de entrada. Por exemplo, em vez de escrever o mesmo código para um comando de exclusão nos dois manipuladores de eventos diferentes, um para quando o usuário pressiona a tecla Delete e outro para quando o usuário clica com o botão direito do mouse em "Excluir" em um menu de contexto, você pode implementar a lógica de exclusão uma vez como um ICommande, em seguida, torná-lo disponível para diferentes tipos de entrada.
É necessário definir o ICommand que representa a ação "Favoritos". Vamos usar o método Execute do comando para incluir o podcast nos favoritos. O podcast específico será fornecido para o 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ários elementos e coleções, você pode armazená-lo como um recurso na página ou no aplicativo.
<Application.Resources>
<local:FavoriteCommand x:Key="favoriteCommand" />
</Application.Resources>
Para executar o comando, chame o método Execute.
// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);
Criar um UserControl para responder a diversas entradas
Quando você tiver uma lista de itens e cada um deve responder a várias entradas, é possível simplificar o código ao definir um UserControl para o item e usá-lo para definir manipuladores de eventos e o menu de contexto dos itens.
Para criar um UserControl no Visual Studio:
- No Gerenciador de Soluções, clique com o botão direito do mouse no projeto. O menu de contexto é exibido.
- Selecione Adicionar > Novo item...
A caixa de diálogo Adicionar Novo Item aparecerá. - Selecione UserControl na lista de itens. Dê o nome que você deseja e clique em Adicionar. O Visual Studio gerará um stub UserControl para você.
No exemplo do podcast, cada um será exibido em uma lista, o que expõe diversas maneiras de incluir um podcast nos "Favoritos". O usuário poderá executar as seguintes ações para incluir o podcast nos "Favoritos":
- Invocar um menu de contexto
- Executar atalhos de teclado
- Mostrar um botão de foco
- Executar um gesto de deslizar 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 diversas interações do usuário. Abordaremos a referência e explicaremos melhor o PodcastUserControl neste 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 uma DependencyProperty. Isso nos permite associar PodcastObjects ao PodcastUserControl.
Depois de gerar alguns PodcastObjects, você pode criar uma lista de podcasts ao associar PodcastObjects a um ListView. Os objetos PodcastUserControl descrevem a visualização do PodcastObjects e, portanto, 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>
Criar menus de contexto
Os menus de contexto exibem uma lista de comandos ou opções quando o usuário as solicita. Os menus de contexto fornecem comandos contextuais relacionados ao elemento anexado e, em geral, são reservados para ações secundárias específicas desse item.
O usuário pode invocar os menus de contexto usando essas "ações de contexto":
Entrada | Ação de contexto |
---|---|
Mouse | Clicar com o botão direito do mouse |
Teclado | Shift+F10, botão de menu |
Tocar | Pressionamento longo em item |
Caneta | Pressionamento de botão da caneta, um pressionamento longo em item |
Gamepad | Botão de 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 funciona com todos os tipos de entrada. Você fornece um submenu que representa o menu de contexto usando MenuFlyout ou CommandBarFlyout e, quando o usuário executa uma "ação de contexto" conforme definido acima, o MenuFlyout ou CommandBarFlyout correspondente ao item será exibido.
Confira menus e menus de contexto para obter ajuda para diferenciar cenários de menu de cenários de menu de contexto e ver diretrizes sobre quando usar submenu de menu versus submenu de 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 incluir um podcast nos 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 às ações de contexto. O evento ContextRequested não será acionado se um ContextFlyout tiver sido especificado.
Criar aceleradores de entrada
Embora cada item da coleção deva ter um menu de contexto com todos os comandos contextuais, convém permitir que os usuários realizem rapidamente um conjunto menor de comandos realizados 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 os comandos mais comuns, você pode usar aceleradores com base na entrada para facilitar a execução desses comandos por um usuário.
No aplicativo de podcast, o comando executado com frequência é o comando "Favorite".
Aceleradores de teclado
Atalhos e manipulação direta de tecla
Dependendo do tipo de conteúdo, você pode identificar determinadas combinações de teclas que devem realizar uma ação. Por exemplo, em um aplicativo de email, a tecla DEL pode ser usada para excluir o email selecionado. Em um aplicativo de podcast, as teclas Ctrl + S ou F poderiam incluir um podcast como favorito para uso posterior. Embora alguns comandos tenham atalhos de teclado comuns e conhecidos, como DEL para excluir, outros comandos têm atalhos de aplicativo ou domínio específico. Use atalhos conhecidos, se possível, ou considere fornecer texto de lembrete em uma dica de ferramenta para instruir o usuário sobre o comando de atalho.
O aplicativo pode responder quando o usuário pressionar uma tecla usando o evento KeyDown. Em geral, os usuários esperam que o aplicativo responda ao pressionarem a tecla em vez de aguardar até que a soltem.
Este exemplo explica como adicionar o manipulador KeyDown a PodcastUserControl para incluir um podcast nos favoritos quando o usuário pressiona 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
Os usuários estão familiarizados com os menus de contexto ao clicar com o botão direito do mouse, mas você pode desejar capacitá-los para executar comandos comuns com apenas um único clique no mouse. Para habilitar essa experiência, você pode incluir botões dedicados na tela do item da coleção. Para permitir que os usuários atuem rapidamente usando o mouse e reduzir a poluição visual, você pode optar por exibir somente esses botões quando o usuário está com o ponteiro dentro de um item de lista específico.
Neste exemplo, o comando Favorite é 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 VisualStateManager para alternar entre os 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 de mouse, use os eventos PointerEntered e PointerExited em 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 podem ser acessados somente 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 de ponteiro. Se você optar por fazer isso, garanta que o volume do botão seja de pelo menos 20 x 20 px para ser utilizável com caneta e mouse.
Aceleradores de toque
Deslizar
Os comandos efetuados ao deslizar o dedo são aceleradores de toque, que permitem aos usuários em dispositivos sensíveis ao toque executar ações comuns secundárias. Ao deslizar o dedo, os usuários podem interagir com conteúdo de maneira rápida e natural, usando as ações comuns como passar o dedo para excluir ou passar o dedo para invocar. Confira o artigo sobre comandos de deslizar 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, o SwipeItems contém um comando para tornar um item 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 deslizar o dedo. Observe que o SwipeControl contém uma referência aos SwipeItems como seus RightItems. O item Favoritos será exibido quando o usuário deslizar 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 desliza o dedo para invocar o comando Favorite, o método Invoked é 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);
}
Puxar para atualizar
A ação de Deslizar para atualizar permite a um usuário extrair uma coleção de dados com toque para recuperar mais dados. Confira o artigo Deslizar para atualizar para saber mais.
Aceleradores de caneta
O tipo de entrada de caneta fornece a precisão de entrada de ponteiro. Os usuários podem realizar ações comuns, como abrir os menus de contexto com aceleradores baseados em caneta. Para abrir um menu de contexto, os usuários podem tocar na tela com o botão da caneta pressionado ou manter o conteúdo pressionado. Os usuários também podem usar a caneta para focalizar o conteúdo a fim de obter uma compreensão mais profunda da interface do usuário, como exibir dicas de ferramentas ou para revelar ações de foco secundário, semelhante ao mouse.
Para otimizar o aplicativo para entrada de caneta, confira o artigo Interação com caneta.
Recomendações
- Garanta que os usuários possam 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.
Tópicos relacionados
Windows developer