Kapslat användargränssnitt i listobjekt

Kapslat användargränssnitt är ett användargränssnitt (UI) som exponerar kapslade åtgärdsbara kontroller som omges av en container som också kan fokusera oberoende.

Du kan använda kapslat användargränssnitt för att presentera ytterligare alternativ för en användare som hjälper till att påskynda genomförandet av viktiga åtgärder. Men ju fler åtgärder du exponerar, desto mer komplicerat blir användargränssnittet. Du måste vara extra försiktig när du väljer att använda det här användargränssnittsmönstret. Den här artikeln innehåller riktlinjer som hjälper dig att fastställa det bästa sättet att vidta åtgärder för ditt specifika användargränssnitt.

Viktiga API:er: ListView-klass, GridView-klass

I den här artikeln diskuterar vi skapandet av kapslat användargränssnitt i ListView - och GridView-objekt . Även om det här avsnittet inte handlar om andra kapslade användargränssnittsfall kan dessa begrepp överföras. Innan du börjar bör du känna till den allmänna vägledningen för att använda ListView- eller GridView-kontroller i användargränssnittet, som finns i artiklarna list- och listvy och rutnätsvy .

I den här artikeln använder vi termerna lista, listelement och nästlad UI enligt definitionen här:

  • Listan refererar till en samling objekt som finns i en listvy eller rutnätsvy.
  • Listobjekt refererar till ett enskilt objekt som en användare kan vidta åtgärder för i en lista.
  • Kapslat användargränssnitt refererar till gränssnittselement i ett listobjekt som en användare kan vidta åtgärder på separat från att vidta åtgärder för själva listobjektet.

Skärmbild som visar delarna i ett kapslat U I.

Note ListView och GridView härleds båda från klassen ListViewBase , så de har samma funktioner, men visar data på olika sätt. I den här artikeln gäller informationen för kontrollerna ListView och GridView när vi pratar om listor.

Primära och sekundära åtgärder

När du skapar användargränssnittet med en lista bör du överväga vilka åtgärder användaren kan vidta från dessa listobjekt.

  • Kan en användare klicka på objektet för att utföra en åtgärd?
    • Vanligtvis initieras en åtgärd när man klickar på ett listobjekt, men det är inte alltid nödvändigt.
  • Finns det fler än en åtgärd som användaren kan vidta?
    • Om du till exempel trycker på ett e-postmeddelande i en lista öppnas e-postmeddelandet. Det kan dock finnas andra åtgärder, till exempel att ta bort e-postmeddelandet, som användaren vill vidta utan att öppna det först. Det skulle vara till nytta för användaren att komma åt den här åtgärden direkt i listan.
  • Hur ska åtgärderna exponeras för användaren?
    • Överväg alla indatatyper. Vissa former av kapslat användargränssnitt fungerar bra med en indatametod, men kanske inte fungerar med andra metoder.

Den primära åtgärden är vad användaren förväntar sig att hända när de trycker på listobjektet.

Sekundära åtgärder är vanligtvis acceleratorer som är associerade med listobjekt. Dessa acceleratorer kan vara för listhantering eller åtgärder som är relaterade till listobjektet.

Alternativ för sekundära åtgärder

När du skapar listgränssnittet måste du först se till att du tar hänsyn till alla indatametoder som Windows stöder. Mer information om olika typer av indata finns i Indataprimör.

När du har kontrollerat att din app stöder alla indata som Windows stöder bör du bestämma om appens sekundära åtgärder är tillräckligt viktiga för att exponeras som acceleratorer i huvudlistan. Kom ihåg att ju fler åtgärder du exponerar, desto mer komplicerat blir användargränssnittet. Behöver du verkligen exponera de sekundära åtgärderna i huvudlistegränssnittet, eller kan du placera dem någon annanstans?

Du kan överväga att exponera ytterligare åtgärder i huvudlistans användargränssnitt när dessa åtgärder alltid måste vara tillgängliga med indata.

Om du bestämmer dig för att det inte är nödvändigt att placera sekundära åtgärder i huvudlistegränssnittet finns det flera andra sätt att exponera dem för användaren. Här är några alternativ som du kan överväga för var du ska placera sekundära åtgärder.

Placera sekundära åtgärder på informationssidan

Placera de sekundära åtgärderna på sidan som listobjektet navigerar till när det trycks ned. När du använder list-/informationsmönstret är detaljsidan ofta ett bra ställe att placera sekundära åtgärder på.

Mer information finns i list-/detaljmönstret.

Placera sekundära åtgärder i en snabbmeny

Placera de sekundära åtgärderna i en snabbmeny som användaren kan komma åt via högerklicka eller håll ned. Detta ger fördelen med att låta användaren utföra en åtgärd, till exempel att ta bort ett e-postmeddelande, utan att behöva läsa in informationssidan. Det är en bra idé att även göra dessa alternativ tillgängliga på detaljsidan, eftersom snabbmenyer är avsedda att vara acceleratorer i stället för primärt användargränssnitt.

Om du vill exponera sekundära åtgärder när indata kommer från en spelplatta eller fjärrstyrning rekommenderar vi att du använder en snabbmeny.

Mer information finns i kontextmenyer och popupmenyer.

Placera sekundära åtgärder i hovringsgränssnittet för att optimera för pekarindata

Om du förväntar dig att din app ska användas ofta med pekarindata som mus och penna, och vill göra sekundära åtgärder lättillgängliga endast för dessa indata, kan du visa de sekundära åtgärderna endast vid hovring. Den här acceleratorn visas bara när en pekarinmatning används, så se till att även använda de andra alternativen för att stödja andra indatatyper.

Användargränssnitt med kapslad struktur visas vid mouseöver

Mer information finns i Musinteraktioner.

UI-placering för primära och sekundära åtgärder

Om du bestämmer dig för att sekundära åtgärder ska exponeras i huvudlistegränssnittet rekommenderar vi följande riktlinjer.

När du skapar ett listobjekt med primära och sekundära åtgärder placerar du den primära åtgärden till vänster och sekundära åtgärder till höger. I läskulturer från vänster till höger associerar användare åtgärder till vänster i listobjektet som den primära åtgärden.

I de här exemplen pratar vi om listgränssnitt där objektet flödar mer horisontellt (det är bredare än dess höjd). Du kan dock ha listobjekt som är mer kvadratiska i form eller högre än deras bredd. Det här är vanligtvis objekt som används i ett rutnät. Om listan inte rullar lodrätt för dessa objekt kan du placera de sekundära åtgärderna längst ned i listobjektet i stället för till höger.

Överväg alla indata

När du bestämmer dig för att använda kapslat användargränssnitt utvärderar du även användarupplevelsen med alla indatatyper. Som tidigare nämnts fungerar det kapslade användargränssnittet bra för vissa indatatyper. Men det fungerar inte alltid bra för vissa andra. I synnerhet kan tangentbord, styrenhet och fjärrindata ha svårt att komma åt kapslade gränssnittselement. Följ anvisningarna nedan för att se till att Windows fungerar med alla indatatyper.

Kapslad användargränssnittshantering

När du har fler än en åtgärd kapslad i listobjektet rekommenderar vi den här vägledningen för att hantera navigering med tangentbord, spelplatta, fjärrstyrning eller andra indata som inte är pekare.

Kapslat användargränssnitt där listobjekt utför en åtgärd

Om ditt listgränssnitt med kapslade element stöder åtgärder som att anropa, välja (en eller flera) eller dra och släpp-åtgärder rekommenderar vi de här pilteknikerna för att navigera genom dina kapslade gränssnittselement.

Skärmbild som visar kapslade U I-element märkta med bokstäverna A, B, C och D.

Gamepad

När indata kommer från en spelplatta anger du den här användarupplevelsen:

  • Från A fokuserar höger riktningsnyckel på B.
  • Från B lägger höger riktningsnyckel fokus på C.
  • Från C är höger riktningsknapp antingen ingen åtgärd, eller om det finns ett fokuserbart UI-element till höger om List lägger du fokus där.
  • Från C lägger vänster riktningsnyckel fokus på B.
  • Från B flyttar vänsterpil fokus till A.
  • Från A är den vänstra riktningstangenten antingen ingen operation, eller om det finns ett fokuserbart användargränssnittselement till höger om listan, sätts fokuset där.
  • Från A, B eller C, nedåtriktad nyckel sätter fokus på D.
  • Från UI-element till vänster om listobjekt sätter den högra piltangenten fokus på A.
  • Från användargränssnittselementet till höger om Listobjekt fokuserar vänsterriktad nyckel på A.

tangentbord

När indata kommer från ett tangentbord är det här upplevelsen som användaren får:

  • Från A fokuserar tabbtangenten på B.
  • Från B fokuserar tabbtangenten på C.
  • Från C fokuserar tabbtangenten på nästa fokuserbara gränssnittselement i tabbordningen.
  • Från C fokuserar skift+tabbtangenten på B.
  • Från B fokuserar skift+tabb- eller vänsterpiltangenten på A.
  • Från A fokuserar skift+tabbtangenten på nästa fokuserbara gränssnittselement i omvänd flikordning.
  • Nedpilen från A, B eller C fokuserar på D.
  • Från användargränssnittselementet till vänster om Listobjekt fokuserar tabbtangenten på A.
  • Från användargränssnittselementet till höger om Listobjekt fokuserar skiftflikstangenten på C.

För att uppnå det här användargränssnittet anger du IsItemClickEnabled till true i listan. SelectionMode kan vara valfritt värde.

Koden som ska implementeras finns i avsnittet Exempel i den här artikeln.

Kapslat användargränssnitt där listobjekt inte utför någon åtgärd

Du kan använda en listvy eftersom den tillhandahåller virtualisering och optimerat rullningsbeteende, men inte har någon åtgärd associerad med ett listobjekt. Dessa UIs använder vanligtvis listobjektet endast för att gruppera element och se till att de rullar som en uppsättning.

Den här typen av användargränssnitt brukar vara mycket mer komplicerat än föregående exempel, med många kapslade element som användaren kan vidta åtgärder på.

Skärmbild av ett komplext kapslat U I som visar många kapslade element som användaren kan interagera med.

För att uppnå det här användargränssnittet anger du följande egenskaper i listan:

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

När listobjekten inte utför någon åtgärd rekommenderar vi den här vägledningen för att hantera navigering med en spelplatta eller ett tangentbord.

Gamepad

När indata kommer från en spelplatta anger du den här användarupplevelsen:

  • Från Listobjekt fokuserar nedåtriktad nyckel på nästa listobjekt.
  • Från Listobjekt är vänster/höger nyckel antingen ingen åtgärd, eller om det finns ett fokuserbart gränssnittselement till höger om Listan, så placera fokus där.
  • Från listobjekt, sätter knappen "A" fokus på det kapslade UI:t med prioritet från toppen/nedtill vänster/höger.
  • I kapslat användargränssnitt följer du navigeringsmodellen XY Focus. Fokus kan bara navigera runt kapslat användargränssnitt i det aktuella listobjektet tills användaren trycker på B-knappen, vilket placerar fokus tillbaka på listobjektet.

tangentbord

När indata kommer från ett tangentbord är det här upplevelsen som användaren får:

  • Från Listobjekt fokuserar nedåtpilen på nästa listobjekt.
  • Från Listobjekt är det ingen åtgärd att trycka på vänster/höger nyckel.
  • När du trycker på tabbtangenten i Listobjekt fokuserar du på nästa tabbstopp bland det kapslade användargränssnittsobjektet.
  • Från ett av de kapslade användargränssnittsobjekten kan man med tabbtangenten navigera mellan dessa objekt i tabbordning. När alla kapslade användargränssnittsobjekt har navigerats till, placeras fokus på nästa kontroll i tabbordningen efter ListView.
  • Skift+Tab beter sig i omvänd riktning från flikbeteende.

Example

Det här exemplet visar hur du implementerar kapslat användargränssnitt där listobjekt utför en åtgärd.

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