Partilhar via


Interface do utilizador aninhada nos itens da lista

A interface aninhada é uma interface de utilizador (UI) que expõe controlos acionáveis aninhados dentro de um contentor que também pode assumir foco independente.

Pode usar a interface aninhada para apresentar ao utilizador opções adicionais que ajudam a acelerar a tomada de ações importantes. No entanto, quanto mais ações expões, mais complicada se torna a tua interface. Tem de ter cuidado extra ao escolher usar este padrão de interface. Este artigo fornece orientações para o ajudar a determinar o melhor curso de ação para a sua interface de utilizador em particular.

APIs importantes: classe ListView, classe GridView

Neste artigo, discutimos a criação de interfaces aninhadas nos itens ListView e GridView . Embora esta secção não fale de outros casos de UI aninhados, estes conceitos são transferíveis. Antes de começar, deve estar familiarizado com as orientações gerais para usar os controlos ListView ou GridView na sua interface, que se encontram nos artigos de Listas e Vista de Listas e Vista de grelha .

Neste artigo, usamos os termos lista, item da lista e UI aninhada conforme definido aqui:

  • Lista refere-se a uma coleção de itens contidos numa vista de lista ou vista em grelha.
  • Item de lista refere-se a um item individual sobre o qual um utilizador pode agir numa lista.
  • Interface de Utilizador aninhada refere-se a elementos da interface de utilizador presentes num item de lista, nos quais um utilizador pode interagir de forma independente da interação com o próprio item da lista.

Captura de ecrã que mostra as partes de uma IU aninhada.

NOTA: O ListView e o GridView derivam ambos da classe ListViewBase , pelo que têm a mesma funcionalidade, mas apresentam dados de forma diferente. Neste artigo, quando falamos de listas, a informação aplica-se tanto aos controlos ListView como GridView.

Ações primárias e secundárias

Ao criar uma interface com uma lista, considere que ações o utilizador pode tomar a partir desses itens da lista.

  • Um utilizador pode clicar no item para realizar uma ação?
    • Normalmente, clicar num item da lista inicia uma ação, mas não tem de o fazer.
  • Há mais do que uma ação que o utilizador pode realizar?
    • Por exemplo, tocar num email numa lista abre esse email. No entanto, podem existir outras ações, como apagar o email, que o utilizador gostaria de tomar sem o abrir primeiro. Seria benéfico para o utilizador aceder diretamente a esta ação na lista.
  • Como devem as ações ser expostas ao utilizador?
    • Considere todos os tipos de entrada. Algumas interfaces de utilizador aninhadas funcionam muito bem com um método de entrada, mas podem não funcionar com outras formas.

A ação principal é aquilo que o utilizador espera que aconteça ao carregar no item da lista.

As ações secundárias são tipicamente aceleradores associados a itens de lista. Estes aceleradores podem servir para gestão de listas ou ações relacionadas com o item da lista.

Opções para ações secundárias

Ao criar a interface de lista, primeiro precisa de garantir que considera todos os métodos de entrada que o Windows suporta. Para mais informações sobre diferentes tipos de entrada, veja Guia de Entrada.

Depois de garantir que a sua aplicação suporta todas as entradas que o Windows suporta, deve decidir se as ações secundárias da sua aplicação são suficientemente importantes para serem expostas como aceleradores na lista principal. Lembra-te que quanto mais ações expores, mais complicada se torna a tua interface. Precisas mesmo de expor as ações secundárias na interface principal da lista, ou podes colocá-las noutro sítio?

Podes considerar expor ações adicionais na interface principal da lista quando essas ações precisam de estar acessíveis por qualquer input em qualquer momento.

Se decidir que não é necessário colocar ações secundárias na interface principal da lista, existem várias outras formas de as expor ao utilizador. Aqui estão algumas opções que pode considerar para onde colocar ações secundárias.

Coloque ações secundárias na página de detalhes

Coloca as ações secundárias na página para onde o item da lista navega quando é pressionado. Quando usas o padrão de lista/detalhes, a página de detalhes é muitas vezes um bom sítio para colocar ações secundárias.

Para mais informações, consulte a lista/padrão de detalhe.

Coloque ações secundárias num menu de contexto

Coloque as ações secundárias num menu contextual ao qual o utilizador possa aceder com o botão direito ou manter pressionado. Isto oferece a vantagem de permitir ao utilizador realizar uma ação, como apagar um email, sem ter de carregar a página de detalhes. É uma boa prática também disponibilizar estas opções na página de detalhes, pois os menus de contexto destinam-se a ser aceleradores e não interface principal.

Para expor ações secundárias quando a entrada é proveniente de um comando de jogos ou controlo remoto, recomendamos que utilize um menu contextual.

Para mais informações, veja menus contextuais e flyouts.

Colocar ações secundárias na interface de hover para otimizar a entrada de ponteiros

Espera-se que a sua aplicação seja usada frequentemente com entrada de ponteiro, como rato e caneta, e se quiser tornar as ações secundárias disponíveis apenas para essas entradas, então pode mostrar as ações secundárias apenas ao pairar o cursor. Este acelerador só é visível quando é usada uma entrada de apontador, por isso certifique-se de usar as outras opções para suportar outros tipos de entrada também.

Interface aninhada mostrada ao passar o cursor

Para mais informações, veja Interações com rato.

Disposição da Interface de Utilizador para ações primárias e secundárias

Se decidir que ações secundárias devem ser expostas na interface da lista principal, recomendamos as seguintes orientações.

Quando crias um item de lista com ações primárias e secundárias, coloca a ação principal à esquerda e as ações secundárias à direita. Nas culturas de leitura da esquerda para a direita, os utilizadores associam ações no lado esquerdo do item da lista como a ação principal.

Nestes exemplos, falamos da interface de listas onde o item flui mais horizontalmente (é mais largo do que a sua altura). No entanto, pode ter itens de lista que sejam de formato mais quadrado ou mais altos do que largos. Normalmente, estes são itens usados numa grelha. Para estes itens, se a lista não deslocar verticalmente, podes colocar as ações secundárias no fundo do item da lista em vez de no lado direito.

Considerar todas as entradas

Ao decidir usar uma interface aninhada, avalie também a experiência do utilizador com todos os tipos de entrada. Como mencionado anteriormente, a interface aninhada funciona muito bem para alguns tipos de entrada. No entanto, não funciona sempre de forma ideal para algumas outras pessoas. Em particular, entradas de teclado, controlador e entrada remota podem ter dificuldade em aceder aos elementos aninhados da interface. Certifique-se de seguir as orientações abaixo para garantir que o seu Windows funciona com todos os tipos de entrada.

Tratamento de UI aninhada

Quando tiver mais do que uma ação aninhada no item da lista, recomendamos esta orientação para tratar da navegação com teclado, gamepad, controlador remoto ou outra entrada que não seja um apontador.

Interface aninhada onde os itens da lista executam uma ação

Se a sua interface de lista com elementos aninhados suportar ações como invocação, seleção (única ou múltipla) ou arrastar e largar, recomendamos estas técnicas de navegação com setas para navegar pelos elementos da interface de utilizador aninhados.

Captura de ecrã mostrando elementos aninhados de U I rotulados com as letras A, B, C e D.

Comando

Quando a entrada for de um gamepad, proporcione a seguinte experiência de utilizador:

  • A partir de A, a tecla direcional direita coloca o foco em B.
  • A partir de B, a tecla direcional direita focaliza em C.
  • A partir do C, a chave direcional direita é sem op, ou se houver um elemento de interface focado à direita da Lista, coloca o foco aí.
  • A partir de C, a tecla direcional esquerda coloca o foco em B.
  • Partindo de B, a tecla direcional esquerda coloca o foco em A.
  • A partir de A, a tecla direcional esquerda ou não está operacional, ou se houver um elemento de interface focável à direita da Lista, coloca o foco aí.
  • A partir de A, B ou C, a tecla direcional para baixo foca D.
  • Do elemento UI à esquerda do Item da Lista, a tecla direcional direita foca-se em A.
  • Do elemento UI à direita do Item da Lista, a tecla direcional esquerda coloca o foco em A.

Teclado

Quando a entrada vem de um teclado, esta é a experiência que o utilizador obtém:

  • A partir de A, a tecla tab foca-se em B.
  • A partir de B, a tecla Tab coloca o foco em C.
  • A partir de C, a tecla Tab coloca o foco no próximo elemento de interface focável na ordem de tabulação.
  • A partir de C, a tecla shift+tab coloca o foco em B.
  • A partir de B, shift+tab ou seta esquerda coloca o foco em A.
  • A partir de A, a tecla shift+tab coloca o foco no próximo elemento focável da interface na ordem inversa da tabulação.
  • A partir de A, B ou C, a seta para baixo coloca o foco em D.
  • Do elemento UI à esquerda do Item da Lista, a tecla tab coloca o foco em A.
  • A partir do elemento UI à direita do Item da Lista, a combinação de teclas shift + tab coloca o foco em C.

Para obter esta UI, defina IsItemClickEnabled como true na sua lista. SelectionMode pode ter qualquer valor.

Para o código que implementa isto, consulte a secção Exemplo deste artigo.

Interface aninhada onde os itens da lista não executam uma ação

Pode usar uma vista de lista porque proporciona virtualização e comportamento de scroll otimizado, mas não tem uma ação associada a um item da lista. Estas interfaces de utilizador normalmente usam apenas o item da lista para agrupar elementos e garantir que deslizam em conjunto.

Este tipo de interface tende a ser muito mais complexo do que os exemplos anteriores, com muitos elementos aninhados sobre os quais o utilizador pode agir.

Captura de ecrã de um complexo U I aninhado mostrando muitos elementos aninhados com os quais o utilizador pode interagir.

Para alcançar esta interface, defina as seguintes propriedades na sua lista:

<ListView SelectionMode="None" IsItemClickEnabled="False" >
    <ListView.ItemContainerStyle>
         <Style TargetType="ListViewItem">
             <Setter Property="IsFocusEngagementEnabled" Value="True"/>
         </Style>
    </ListView.ItemContainerStyle>
</ListView>

Quando os itens da lista não executam uma ação, recomendamos esta orientação para lidar com a navegação com um comando ou teclado.

Comando

Quando a entrada for de um gamepad, proporcione a seguinte experiência de utilizador:

  • A partir do Item da Lista, a tecla direcional para baixo coloca o foco no próximo Item da Lista.
  • A partir do Item da Lista, a tecla esquerda/direita ou não realiza nenhuma operação, ou, se houver um elemento da interface de utilizador focável à direita da Lista, coloca o foco aí.
  • Do item de lista, o botão 'A' coloca foco na interface aninhada com prioridade de cima para baixo e esquerda para direita.
  • Enquanto estiver dentro da interface aninhada, siga o modelo de navegação do XY Focus. O Focus só consegue navegar pela interface aninhada contida no Item da Lista atual até o utilizador pressionar o botão 'B', o que coloca o foco de volta no Item da Lista.

Teclado

Quando a entrada vem de um teclado, esta é a experiência que o utilizador obtém:

  • Pressionando a tecla seta para baixo no Item da Lista coloca o foco no próximo Item da Lista.
  • A partir do item da lista, pressionar a tecla esquerda/direita não é uma opção.
  • A partir do Item da Lista, pressionar a tecla tab move o foco para o próximo ponto de tabulação dentro do item de UI aninhado.
  • A partir de um dos itens da interface de utilizador aninhada, ao pressionar a tecla tab, percorrem-se os itens aninhados da interface por ordem de tabulação. Depois de todos os itens da interface aninhada serem explorados, o foco passa ao próximo controlo por ordem de tabulação após a VistaLista.
  • Shift+Tab comporta-se de forma inversa ao comportamento de tabulação.

Example

Este exemplo mostra como implementar uma interface aninhada onde os itens da lista realizam uma ação.

<ListView SelectionMode="None" IsItemClickEnabled="True"
          ChoosingItemContainer="listview1_ChoosingItemContainer"/>
private void OnListViewItemKeyDown(object sender, KeyRoutedEventArgs e)
{
    // Code to handle going in/out of nested UI with gamepad and remote only.
    if (e.Handled == true)
    {
        return;
    }

    var focusedElementAsListViewItem = FocusManager.GetFocusedElement() as ListViewItem;
    if (focusedElementAsListViewItem != null)
    {
        // Focus is on the ListViewItem.
        // Go in with Right arrow.
        Control candidate = null;

        switch (e.OriginalKey)
        {
            case Windows.System.VirtualKey.GamepadDPadRight:
            case Windows.System.VirtualKey.GamepadLeftThumbstickRight:
                var rawPixelsPerViewPixel = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
                GeneralTransform generalTransform = focusedElementAsListViewItem.TransformToVisual(null);
                Point startPoint = generalTransform.TransformPoint(new Point(0, 0));
                Rect hintRect = new Rect(startPoint.X * rawPixelsPerViewPixel, startPoint.Y * rawPixelsPerViewPixel, 1, focusedElementAsListViewItem.ActualHeight * rawPixelsPerViewPixel);
                candidate = FocusManager.FindNextFocusableElement(FocusNavigationDirection.Right, hintRect) as Control;
                break;
        }

        if (candidate != null)
        {
            candidate.Focus(FocusState.Keyboard);
            e.Handled = true;
        }
    }
    else
    {
        // Focus is inside the ListViewItem.
        FocusNavigationDirection direction = FocusNavigationDirection.None;
        switch (e.OriginalKey)
        {
            case Windows.System.VirtualKey.GamepadDPadUp:
            case Windows.System.VirtualKey.GamepadLeftThumbstickUp:
                direction = FocusNavigationDirection.Up;
                break;
            case Windows.System.VirtualKey.GamepadDPadDown:
            case Windows.System.VirtualKey.GamepadLeftThumbstickDown:
                direction = FocusNavigationDirection.Down;
                break;
            case Windows.System.VirtualKey.GamepadDPadLeft:
            case Windows.System.VirtualKey.GamepadLeftThumbstickLeft:
                direction = FocusNavigationDirection.Left;
                break;
            case Windows.System.VirtualKey.GamepadDPadRight:
            case Windows.System.VirtualKey.GamepadLeftThumbstickRight:
                direction = FocusNavigationDirection.Right;
                break;
            default:
                break;
        }

        if (direction != FocusNavigationDirection.None)
        {
            Control candidate = FocusManager.FindNextFocusableElement(direction) as Control;
            if (candidate != null)
            {
                ListViewItem listViewItem = sender as ListViewItem;

                // If the next focusable candidate to the left is outside of ListViewItem,
                // put the focus on ListViewItem.
                if (direction == FocusNavigationDirection.Left &&
                    !listViewItem.IsAncestorOf(candidate))
                {
                    listViewItem.Focus(FocusState.Keyboard);
                }
                else
                {
                    candidate.Focus(FocusState.Keyboard);
                }
            }

            e.Handled = true;
        }
    }
}

private void listview1_ChoosingItemContainer(ListViewBase sender, ChoosingItemContainerEventArgs args)
{
    if (args.ItemContainer == null)
    {
        args.ItemContainer = new ListViewItem();
        args.ItemContainer.KeyDown += OnListViewItemKeyDown;
    }
}
// DependencyObjectExtensions.cs definition.
public static class DependencyObjectExtensions
{
    public static bool IsAncestorOf(this DependencyObject parent, DependencyObject child)
    {
        DependencyObject current = child;
        bool isAncestor = false;

        while (current != null && !isAncestor)
        {
            if (current == parent)
            {
                isAncestor = true;
            }

            current = VisualTreeHelper.GetParent(current);
        }

        return isAncestor;
    }
}