Udostępnij przez


ItemsRepeater

Użyj ItemsRepeater do tworzenia niestandardowych doświadczeń z kolekcjami przy użyciu elastycznego systemu układu, widoków niestandardowych i wirtualizacji.

W przeciwieństwie do elementu ListView funkcja ItemsRepeater nie zapewnia kompleksowego środowiska użytkownika końcowego — nie ma domyślnego interfejsu użytkownika i nie zapewnia żadnych zasad dotyczących koncentracji uwagi, wyboru ani interakcji użytkownika. Zamiast tego jest to blok konstrukcyjny, którego można użyć do tworzenia własnych unikalnych doświadczeń opartych na kolekcjach i niestandardowych kontrolek. Chociaż nie ma żadnych wbudowanych zasad, pozwala na dołączanie zasad w celu zbudowania wymaganego doświadczenia. Można na przykład zdefiniować układ do użycia, zasady klawiatury, zasady wyboru itp.

ItemsRepeater można traktować koncepcyjnie jako panel oparty na danych, a nie jako pełną kontrolkę, taką jak ListView. Należy określić kolekcję elementów danych do wyświetlenia, szablon elementu, który generuje element interfejsu użytkownika dla każdego elementu danych, oraz układ określający, jak elementy mają rozmiar i położenie. Następnie element ItemsRepeater tworzy elementy podrzędne na podstawie źródła danych i wyświetla je zgodnie z szablonem i układem elementu. Wyświetlane elementy nie muszą być homogeniczne, ponieważ element ItemsRepeater może załadować zawartość do reprezentowania elementów danych na podstawie kryteriów podanych w selektorze szablonu danych.

Czy jest to właściwa kontrola?

Użyj elementu ItemsRepeater , aby utworzyć niestandardowe wyświetlacze dla kolekcji danych. Chociaż może służyć do prezentowania podstawowego zestawu elementów, często można użyć go jako elementu wyświetlania w szablonie kontrolki niestandardowej.

Jeśli potrzebujesz gotowej kontrolki do wyświetlania danych na liście lub siatce z minimalnym dostosowaniem, rozważ użycie elementu ListView lub GridView.

ItemsRepeater nie ma wewnętrznej kolekcji Items. Jeśli musisz bezpośrednio dostarczyć kolekcję Items, zamiast wiązać się z oddzielnym źródłem danych, prawdopodobnie potrzebujesz bardziej zaawansowanego doświadczenia i powinieneś użyć ListView lub GridView.

ItemsControl i ItemsRepeater umożliwiają dostosowywanie środowisk kolekcji, ale element ItemsRepeater obsługuje wirtualizację układów interfejsu użytkownika, podczas gdy element ItemsControl nie. Zalecamy używanie elementu ItemsRepeater zamiast ItemsControl, niezależnie od tego, czy służy do prezentowania kilku elementów z danych, czy tworzenia niestandardowej kontrolki kolekcji.

Przewijanie za pomocą elementu ItemsRepeater

ItemsRepeater nie wywodzi się z Control, więc nie ma szablonu elementu sterującego. W związku z tym nie zawiera żadnego wbudowanego mechanizmu przewijania, takiego jak w ListView lub innych kontrolkach kolekcji.

W przypadku korzystania z elementu ItemsRepeater należy zapewnić funkcję przewijania, umieszczając go w kontrolce ScrollViewer.

Uwaga / Notatka

Jeśli aplikacja będzie działać we wcześniejszych wersjach systemu Windows — wydanych przed systemem Windows 10 w wersji 1809 — musisz również hostować Program ScrollViewer wewnątrz elementu ItemsRepeaterScrollHost.

<muxc:ItemsRepeaterScrollHost>
    <ScrollViewer>
        <muxc:ItemsRepeater ... />
    </ScrollViewer>
</muxc:ItemsRepeaterScrollHost>

Jeśli aplikacja będzie działać tylko w ostatnich wersjach systemu Windows 10, w wersji 1809 lub nowszej — nie ma potrzeby używania elementu ItemsRepeaterScrollHost.

Przed systemem Windows 10 w wersji 1809 ScrollViewer nie zaimplementował interfejsu IScrollAnchorProvider wymaganego przez ItemsRepeater. ItemsRepeaterScrollHost umożliwia ItemsRepeater, aby koordynować z ScrollViewer w wcześniejszych wersjach systemu, aby poprawnie zachować widoczną lokalizację elementów przeglądanych przez użytkownika. W przeciwnym razie elementy mogą wydawać się przemieszczać lub nagle znikać, kiedy zmieniane są elementy na liście lub zmieniana jest wielkość aplikacji.

Utwórz element ItemsRepeater

Aplikacja Galeria WinUI 3 zawiera interaktywne przykłady większości kontrolek, funkcji i funkcji interfejsu WinUI 3. Pobierz aplikację ze Sklepu Microsoft lub pobierz kod źródłowy w witrynie GitHub

Aby użyć elementu ItemsRepeater, musisz nadać mu dane do wyświetlenia, ustawiając właściwość ItemsSource . Następnie poinformuj go, jak wyświetlić elementy, ustawiając właściwość ItemTemplate .

Źródło elementów

Aby wypełnić widok, ustaw właściwość ItemsSource na kolekcję elementów danych. W tym miejscu źródło elementów jest ustawiane w kodzie bezpośrednio na wystąpienie kolekcji.

ObservableCollection<string> Items = new ObservableCollection<string>();

ItemsRepeater itemsRepeater1 = new ItemsRepeater();
itemsRepeater1.ItemsSource = Items;

Właściwość ItemsSource można również powiązać z kolekcją w języku XAML. Aby uzyskać więcej informacji na temat powiązania danych, zobacz Omówienie powiązania danych.

<ItemsRepeater ItemsSource="{x:Bind Items}"/>

SzablonElementu

Aby określić sposób wizualizacji elementu danych, ustaw właściwość ItemTemplate na zdefiniowaną właściwość DataTemplate lub DataTemplateSelector . Szablon danych definiuje sposób wizualizacji danych. Domyślnie element jest wyświetlany w widoku z elementem TextBlock, który używa reprezentacji ciągu obiektów danych.

Zazwyczaj jednak chcesz pokazać bardziej bogatą prezentację danych przy użyciu szablonu definiującego układ i wygląd co najmniej jednej kontrolki, która będzie wyświetlana w celu wyświetlenia pojedynczego elementu. Kontrolki używane w szablonie mogą być powiązane z właściwościami obiektu danych lub mieć zdefiniowaną zawartość statyczną w tekście.

Szablon Danych

W tym przykładzie obiekt danych jest prostym ciągiem. Element DataTemplate zawiera obraz po lewej stronie tekstu i styluje TextBlock, aby wyświetlić ciąg w kolorze seledynowym.

Uwaga / Notatka

W przypadku używania rozszerzenia znaczników x:Bind w elemencie DataTemplate należy określić typ danych x:DataType w elemencie DataTemplate.

<DataTemplate x:DataType="x:String">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="47"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Image Source="Assets/placeholder.png" Width="32" Height="32"
               HorizontalAlignment="Left"/>
        <TextBlock Text="{x:Bind}" Foreground="Teal"
                   FontSize="15" Grid.Column="1"/>
    </Grid>
</DataTemplate>

Poniżej przedstawiono sposób wyświetlania elementów w przypadku wyświetlania za pomocą elementu DataTemplate.

Elementy wyświetlane przy użyciu szablonu danych

Liczba elementów używanych w elemencie DataTemplate może mieć znaczący wpływ na wydajność, jeśli w widoku jest wyświetlana duża liczba elementów. Aby uzyskać więcej informacji i przykładów użycia elementów DataTemplatedo zdefiniowania wyglądu elementów na liście, zobacz Kontenery elementów i szablony.

Wskazówka

Dla wygody, jeśli chcesz zadeklarować szablon bezpośrednio, zamiast odnosić się do niego jako zasobu statycznego, możesz określić DataTemplate lub DataTemplateSelector jako bezpośredniego potomka ItemsRepeater. Zostanie ona przypisana jako wartość właściwości ItemTemplate . Na przykład jest to prawidłowe:

<ItemsRepeater ItemsSource="{x:Bind Items}">
    <DataTemplate>
        <!-- ... -->
    </DataTemplate>
</ItemsRepeater>

Wskazówka

W przeciwieństwie do kontrolki ListView i innych kolekcji element ItemsRepeater nie opakowuje elementów z elementu DataTemplate z dodatkowym kontenerem elementów, który zawiera domyślne zasady, takie jak marginesy, wypełnienie, wizualizacje wyboru lub wskaźnik nad stanem wizualizacji. Zamiast tego element ItemsRepeater przedstawia tylko to, co jest zdefiniowane w elemecie DataTemplate. Jeśli chcesz, aby elementy miały taki sam wygląd jak element widoku listy, możesz jawnie dołączyć kontener, taki jak ListViewItem, w szablonie danych. ItemsRepeater wyświetli wizualizacje ListViewItem, ale nie będzie automatycznie korzystać z innych funkcji, takich jak zaznaczenie lub wyświetlanie pola wyboru wielokrotnego zaznaczenia.

Podobnie, jeśli kolekcja danych jest kolekcją rzeczywistych kontrolek, takich jak Button (List<Button>), możesz umieścić element ContentPresenter w elemecie DataTemplate , aby wyświetlić kontrolkę.

SelektorSzablonuDanych

Elementy wyświetlane w widoku nie muszą być tego samego typu. Możesz podać właściwość ItemTemplate z elementem DataTemplateSelector , aby wybrać różne elementy DataTemplatena podstawie podanych kryteriów.

W tym przykładzie przyjęto założenie, że zdefiniowano element DataTemplateSelector , który decyduje między dwoma różnymi elementami DataTemplatereprezentującymi element duży i mały.

<ItemsRepeater ...>
    <ItemsRepeater.ItemTemplate>
        <local:VariableSizeTemplateSelector Large="{StaticResource LargeItemTemplate}" 
                                            Small="{StaticResource SmallItemTemplate}"/>
    </ItemsRepeater.ItemTemplate>
</ItemsRepeater>

Podczas definiowania DataTemplateSelector do użycia z ItemsRepeater wystarczy zaimplementować przesłonięcie metody SelectTemplateCore(Object). Aby uzyskać więcej informacji i przykładów, zobacz DataTemplateSelector.

Uwaga / Notatka

Alternatywą dla DataTemplate jest zaimplementowanie własnego IElementFactory w celu zarządzania sposobem tworzenia elementów w bardziej zaawansowanych scenariuszach, który będzie używany jako ItemTemplate. Będzie on odpowiedzialny za generowanie zawartości po żądaniu.

Konfigurowanie źródła danych

Użyj właściwości ItemsSource , aby określić kolekcję, która ma być używana do generowania zawartości elementów. Element ItemsSource można ustawić na dowolny typ, który implementuje IEnumerable. Dodatkowe interfejsy kolekcji implementowane przez źródło danych określają, jakie funkcje są dostępne dla ItemsRepeater do interakcji z danymi.

Ta lista zawiera dostępne interfejsy i kiedy należy rozważyć użycie każdego z nich.

  • IEnumerable(.NET) / IIterable

    • Może być używany w przypadku małych, statycznych zestawów danych.

      Co najmniej źródło danych musi zaimplementować interfejs IEnumerable/IIterable. Jeśli to wszystko jest obsługiwane, kontrolka będzie iterować przez wszystko raz, aby utworzyć kopię, której może używać do uzyskiwania dostępu do elementów za pośrednictwem wartości indeksu.

  • IReadonlyList(.NET) / IVectorView

    • Może służyć do statycznych zestawów danych tylko do odczytu.

      Umożliwia kontrolę dostępu do elementów według indeksu i unika nadmiarowej kopii wewnętrznej.

  • IList(.NET) / IVector

    • Może służyć do statycznych zestawów danych.

      Umożliwia kontrolę dostępu do elementów według indeksu i unika nadmiarowej kopii wewnętrznej.

      Ostrzeżenie: Zmiany listy/wektora bez implementowania elementu INotifyCollectionChanged nie zostaną odzwierciedlone w interfejsie użytkownika.

  • INotifyCollectionChanged(.NET)

    • Zaleca się wspieranie powiadomień o zmianach.

      Umożliwia kontrolce obserwowanie zmian w źródle danych i reagowanie na nie oraz odzwierciedlanie tych zmian w interfejsie użytkownika.

  • IObservableVector (Wektor Południowy)

    • Obsługuje powiadomienie o zmianie

      Podobnie jak interfejs INotifyCollectionChanged , umożliwia to kontrolce obserwowanie zmian w źródle danych i reagowanie na nie.

      Ostrzeżenie: Windows.Foundation.IObservableVector<nie> obsługuje akcji "Przenieś". Może to spowodować, że interfejs użytkownika dla elementu straci swój stan wizualny. Na przykład element, który jest aktualnie zaznaczony i/lub ma fokus, w którym przeniesienie jest osiągane przez polecenie "Usuń", po którym następuje "Dodaj", utraci fokus i nie zostanie już wybrany.

      Platform.Collections.Vector<T> używa IObservableVector<T> i ma to samo ograniczenie. Jeśli wymagana jest obsługa akcji "Przenieś", użyj interfejsu INotifyCollectionChanged . Klasa .NET ObservableCollection<T> używa klasy INotifyCollectionChanged.

  • IKeyIndexMapping

    • Gdy można skojarzyć unikatowy identyfikator z każdym elementem. Zalecane w przypadku używania opcji "Resetuj" jako akcję zmiany kolekcji.

      Umożliwia bardzo wydajne odzyskiwanie istniejącego interfejsu użytkownika po otrzymaniu zdecydowanej akcji 'Reset' w ramach eventu INotifyCollectionChanged lub IObservableVector. Po zresetowaniu kontrolka użyje podanego unikatowego identyfikatora, aby skojarzyć bieżące dane z elementami, które zostały już utworzone. Bez klucza do mapowania indeksowania kontrolka musiałaby zakładać, że musi zacząć od podstaw podczas tworzenia interfejsu użytkownika dla danych.

Interfejsy wymienione powyżej, inne niż IKeyIndexMapping, zapewniają takie samo zachowanie w elementach ItemsRepeater, jak w ListView i GridView.

Następujące interfejsy w elemecie ItemsSource umożliwiają korzystanie ze specjalnych funkcji w kontrolkach ListView i GridView, ale obecnie nie mają wpływu na element ItemsRepeater:

Wskazówka

Chcemy twoich opinii! Daj nam znać, co myślisz na projekcie WinUI GitHub. Rozważ dodanie przemyśleń dotyczących istniejących propozycji, takich jak #374: Dodawanie obsługi ładowania przyrostowego dla elementu ItemsRepeater.

Alternatywną metodą przyrostowego ładowania danych, gdy użytkownik przewija się w górę lub w dół, jest obserwowanie położenia widoku programu ScrollViewer i załadowanie większej ilości danych w miarę zbliżania się do zakresu.

<ScrollViewer ViewChanged="ScrollViewer_ViewChanged">
    <ItemsRepeater ItemsSource="{x:Bind MyItemsSource}" .../>
</ScrollViewer>
private async void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
    if (!e.IsIntermediate)
    {
        var scroller = (ScrollViewer)sender;
        var distanceToEnd = scroller.ExtentHeight - (scroller.VerticalOffset + scroller.ViewportHeight);

        // trigger if within 2 viewports of the end
        if (distanceToEnd <= 2.0 * scroller.ViewportHeight
                && MyItemsSource.HasMore && !itemsSource.Busy)
        {
            // show an indeterminate progress UI
            myLoadingIndicator.Visibility = Visibility.Visible;

            await MyItemsSource.LoadMoreItemsAsync(/*DataFetchSize*/);

            loadingIndicator.Visibility = Visibility.Collapsed;
        }
    }
}

Zmienianie układu elementów

Elementy wyświetlane przez ItemsRepeater są rozmieszczane przez obiekt Layout, który zarządza ustalaniem rozmiaru i pozycjonowaniem elementów podrzędnych. W przypadku użycia z elementem ItemsRepeater obiekt Layout umożliwia wirtualizację interfejsu użytkownika. Podane układy to StackLayout i UniformGridLayout. ItemsRepeater domyślnie używa układu StackLayout z orientacją pionową.

StackLayout

StackLayout rozmieszcza elementy w jedną linię, którą można zorientować w poziomie lub w pionie.

Możesz ustawić właściwość Odstępy , aby dostosować ilość miejsca między elementami. Odstępy są stosowane w kierunku orientacji układu.

Odstępy w układzie stosu

W tym przykładzie pokazano, jak ustawić właściwość ItemsRepeater.Layout na wartość StackLayout z orientacją poziomą i odstępami 8 pikseli.

<!-- xmlns:muxc="using:Microsoft.UI.Xaml.Controls" -->
<muxc:ItemsRepeater ItemsSource="{x:Bind Items}" ItemTemplate="{StaticResource MyTemplate}">
    <muxc:ItemsRepeater.Layout>
        <muxc:StackLayout Orientation="Horizontal" Spacing="8"/>
    </muxc:ItemsRepeater.Layout>
</muxc:ItemsRepeater>

UniformGridLayout

Element UniformGridLayout umieszcza elementy sekwencyjnie w układzie opakowującym. Elementy są uporządkowane w kolejności od lewej do prawej, gdy orientacji jest poziomai ułożone od góry do dołu, gdy orientacja jest pionowa. Każdy element ma równy rozmiar.

Odstępy w jednolitej siatce

Na liczbę elementów w każdym wierszu układu poziomego wpływa minimalna szerokość elementu. Na liczbę elementów w każdej kolumnie układu pionowego wpływa minimalna wysokość elementu.

  • Można jawnie podać minimalny rozmiar do użycia, ustawiając właściwości MinItemHeight i MinItemWidth .
  • Jeśli nie określisz minimalnego rozmiaru, mierzony rozmiar pierwszego elementu jest uznawany za minimalny rozmiar na element.

Można również ustawić minimalne odstępy w układzie między wierszami i kolumnami, ustawiając właściwości MinColumnSpacing i MinRowSpacing.

Jednolity rozmiar i odstępy siatki

Po określeniu liczby elementów w wierszu lub kolumnie na podstawie minimalnego rozmiaru i odstępu elementu może pozostać nieużywane miejsce po ostatnim elemencie w wierszu lub kolumnie (jak pokazano na poprzedniej ilustracji). Możesz określić, czy jakiekolwiek dodatkowe miejsce jest ignorowane, używane do zwiększania rozmiaru każdego elementu, czy też używane do tworzenia dodatkowego miejsca między elementami. Jest to kontrolowane przez właściwości ItemsStretch i ItemsJustification .

Możesz ustawić właściwość ItemsStretch , aby określić, jak rozmiar elementu jest zwiększany w celu wypełnienia nieużywanego miejsca.

Ta lista zawiera dostępne wartości. W definicjach przyjęto, że domyślną orientacją jest pozioma.

  • Brak: dodatkowe miejsce pozostaje nieużywane na końcu wiersza. Jest to opcja domyślna.
  • Wypełnienie: elementy są przeznaczone na dodatkową szerokość w celu wykorzystania dostępnej przestrzeni (wysokość, jeśli pionowo).
  • Jednolite: elementy mają dodatkową szerokość w celu wykorzystania dostępnego miejsca i są dostosowane wysokościowo, aby zachować współczynnik proporcji (wysokość i szerokość są zamieniane miejscami, jeśli elementy są ustawione pionowo).

Na tym obrazie przedstawiono efekt wartości ItemsStretch w układzie poziomym.

Rozciągnięty jednolity element siatki

Gdy element ItemsStretch ma wartość None, możesz ustawić właściwość ItemsJustification , aby określić, jak dodatkowe miejsce jest używane do wyrównania elementów.

Ta lista zawiera dostępne wartości. W definicjach przyjęto, że domyślną orientacją jest pozioma.

  • Początek: Elementy są wyrównane na początku wiersza. Dodatkowe miejsce pozostaje nieużywane na końcu wiersza. Jest to opcja domyślna.
  • Środek: Przedmioty są wyrównane centralnie w wierszu. Dodatkowe miejsce jest dzielone równomiernie na początku i na końcu wiersza.
  • Koniec: Elementy są wyrównane do końca wiersza. Dodatkowe miejsce pozostaje nieużywane na początku wiersza.
  • SpaceAround: Elementy są dystrybuowane równomiernie. Przed i po każdym elemencie jest dodawana równa ilość miejsca.
  • SpaceBetween: Elementy są równomiernie dystrybuowane. Między poszczególnymi elementami jest dodawana równa ilość miejsca. Na początku i na końcu wiersza nie jest dodawana żadna spacja.
  • SpaceEvenly: Elementy są dystrybuowane równomiernie z równą ilością miejsca zarówno między każdym elementem, jak i na początku i na końcu wiersza.

Na tym obrazie przedstawiono efekt wartości ItemsStretch w układzie pionowym (zastosowanym do kolumn zamiast wierszy).

Uzasadnienie jednolitych elementów siatki

Wskazówka

Właściwość ItemsStretch wpływa na przekazywanie miary układu. Właściwość ItemsJustification wpływa na rozmieszczenie układu.

W tym przykładzie pokazano, jak ustawić właściwość ItemsRepeater.Layout na uniformGridLayout.

<!-- xmlns:muxc="using:Microsoft.UI.Xaml.Controls" -->
<muxc:ItemsRepeater ItemsSource="{x:Bind Items}"
                    ItemTemplate="{StaticResource MyTemplate}">
    <muxc:ItemsRepeater.Layout>
        <muxc:UniformGridLayout MinItemWidth="200"
                                MinColumnSpacing="28"
                                ItemsJustification="SpaceAround"/>
    </muxc:ItemsRepeater.Layout>
</muxc:ItemsRepeater>

Zdarzenia cyklu życia

W przypadku hostowania elementów w elemencie ItemsRepeater może być konieczne wykonanie pewnej akcji po wyświetleniu lub zatrzymaniu wyświetlania elementu, na przykład uruchomienie asynchronicznego pobierania jakiejś zawartości, skojarzenie elementu z mechanizmem śledzenia zaznaczenia lub zatrzymanie zadania w tle.

W kontrolce wirtualizacji nie można polegać na zdarzeniach Załadowano/Rozładowano, ponieważ element może nie zostać usunięty z aktywnego drzewa wizualnego, gdy jest poddawany recyklingowi. Zamiast tego inne zdarzenia są udostępniane do zarządzania cyklem życia elementów. Na tym diagramie przedstawiono cykl życia elementu w elemencie ItemsRepeater oraz moment, w którym wywoływane są powiązane zdarzenia.

Diagram zdarzeń cyklu życia

  • ElementPrepared występuje za każdym razem, gdy element jest gotowy do użycia. Dzieje się tak zarówno dla nowo utworzonego elementu, jak i elementu, który już istnieje i jest używany ponownie z kolejki recyklingu.
  • ElementClearing występuje natychmiast za każdym razem, gdy element został wysłany do kolejki recyklingu, na przykład gdy znajduje się poza zakresem zrealizowanych elementów.
  • ElementIndexChanged występuje dla każdego zrealizowanego elementu interfejsu użytkownika, w którym zmieniono indeks dla reprezentowanego elementu. Na przykład, gdy w źródle danych dodawany lub usuwany jest inny element, indeksy elementów, które są później w kolejności, otrzymują to zdarzenie.

W tym przykładzie pokazano, jak można użyć tych zdarzeń, aby przypiąć niestandardową usługę selekcji do śledzenia wyboru elementów w kontrolce niestandardowej, która używa kontrolki ItemsRepeater do wyświetlania elementów.

<!-- xmlns:muxc="using:Microsoft.UI.Xaml.Controls" -->
<UserControl ...>
    ...
    <ScrollViewer>
        <muxc:ItemsRepeater ItemsSource="{x:Bind Items}"
                            ItemTemplate="{StaticResource MyTemplate}"
                            ElementPrepared="OnElementPrepared"
                            ElementIndexChanged="OnElementIndexChanged"
                            ElementClearing="OnElementClearing">
        </muxc:ItemsRepeater>
    </ScrollViewer>
    ...
</UserControl>
interface ISelectable
{
    int SelectionIndex { get; set; }
    void UnregisterSelectionModel(SelectionModel selectionModel);
    void RegisterSelectionModel(SelectionModel selectionModel);
}

private void OnElementPrepared(ItemsRepeater sender, ElementPreparedEventArgs args)
{
    var selectable = args.Element as ISelectable;
    if (selectable != null)
    {
        // Wire up this item to recognize a 'select' and listen for programmatic
        // changes to the selection model to know when to update its visual state.
        selectable.SelectionIndex = args.Index;
        selectable.RegisterSelectionModel(this.SelectionModel);
    }
}

private void OnElementIndexChanged(ItemsRepeater sender, ElementIndexChangedEventArgs args)
{
    var selectable = args.Element as ISelectable;
    if (selectable != null)
    {
        // Sync the ID we use to notify the selection model when the item
        // we represent has changed location in the data source.
        selectable.SelectionIndex = args.NewIndex;
    }
}

private void OnElementClearing(ItemsRepeater sender, ElementClearingEventArgs args)
{
    var selectable = args.Element as ISelectable;
    if (selectable != null)
    {
        // Disconnect handlers to recognize a 'select' and stop
        // listening for programmatic changes to the selection model.
        selectable.UnregisterSelectionModel(this.SelectionModel);
        selectable.SelectionIndex = -1;
    }
}

Sortowanie, filtrowanie i resetowanie danych

W przypadku wykonywania akcji, takich jak filtrowanie lub sortowanie zestawu danych, tradycyjnie można porównać poprzedni zestaw danych z nowymi danymi, a następnie wydać szczegółowe powiadomienia o zmianie za pośrednictwem elementu INotifyCollectionChanged. Jednak często łatwiej jest całkowicie zastąpić stare dane nowymi danymi i wyzwolić powiadomienie o zmianie kolekcji przy użyciu akcji Resetuj .

Zazwyczaj resetowanie powoduje, że kontrolka zwalnia istniejące elementy podrzędne i zaczyna od nowa, budując interfejs użytkownika od zera w pozycji początkowej przewijania, ponieważ nie jest świadoma, jak dokładnie dane zmieniły się podczas resetowania.

Jeśli jednak kolekcja przypisana jako element ItemsSource obsługuje unikatowe identyfikatory przez zaimplementowanie interfejsu IKeyIndexMapping , element ItemsRepeater może szybko zidentyfikować:

  • elementy interfejsu użytkownika wielokrotnego użytku dla danych, które istniały zarówno przed, jak i po zresetowaniu
  • wcześniej widoczne elementy, które zostały usunięte
  • dodano nowe elementy, które będą widoczne

Dzięki temu ItemsRepeater może wyeliminować konieczność rozpoczynania od pozycji przewijania 0. Umożliwia również szybkie przywracanie elementów interfejsu użytkownika dla danych, które nie uległy zmianie podczas resetu, co prowadzi do lepszej wydajności.

W tym przykładzie pokazano, jak wyświetlić listę elementów w stosie pionowym, w którym element MyItemsSource jest niestandardowym źródłem danych, które opakowuje podstawową listę elementów. Uwidacznia właściwość Dane, która może służyć do przypisania nowej listy jako źródła elementów, co powoduje wyzwolenie resetowania.

<ScrollViewer x:Name="sv">
    <ItemsRepeater x:Name="repeater"
                ItemsSource="{x:Bind MyItemsSource}"
                ItemTemplate="{StaticResource MyTemplate}">
       <ItemsRepeater.Layout>
           <StackLayout ItemSpacing="8"/>
       </ItemsRepeater.Layout>
   </ItemsRepeater>
</ScrollViewer>
public MainPage()
{
    this.InitializeComponent();

    // Similar to an ItemsControl, a developer sets the ItemsRepeater's ItemsSource.
    // Here we provide our custom source that supports unique IDs which enables
    // ItemsRepeater to be smart about handling resets from the data.
    // Unique IDs also make it easy to do things apply sorting/filtering
    // without impacting any state (i.e. selection).
    MyItemsSource myItemsSource = new MyItemsSource(data);

    repeater.ItemsSource = myItemsSource;

    // ...

    // We can sort/filter the data using whatever mechanism makes the
    // most sense (LINQ, database query, etc.) and then reassign
    // it, which in our implementation triggers a reset.
    myItemsSource.Data = someNewData;
}

// ...


public class MyItemsSource : IReadOnlyList<ItemBase>, IKeyIndexMapping, INotifyCollectionChanged
{
    private IList<ItemBase> _data;

    public MyItemsSource(IEnumerable<ItemBase> data)
    {
        if (data == null) throw new ArgumentNullException();

        this._data = data.ToList();
    }

    public IList<ItemBase> Data
    {
        get { return _data; }
        set
        {
            _data = value;

            // Instead of tossing out existing elements and re-creating them,
            // ItemsRepeater will reuse the existing elements and match them up
            // with the data again.
            this.CollectionChanged?.Invoke(
                this,
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }

    #region IReadOnlyList<T>

    public ItemBase this[int index] => this.Data != null
        ? this.Data[index]
        : throw new IndexOutOfRangeException();

    public int Count => this.Data != null ? this.Data.Count : 0;
    public IEnumerator<ItemBase> GetEnumerator() => this.Data.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();

    #endregion

    #region INotifyCollectionChanged

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion

    #region IKeyIndexMapping

    private int lastRequestedIndex = IndexNotFound;
    private const int IndexNotFound = -1;

    // When UniqueIDs are supported, the ItemsRepeater caches the unique ID for each item
    // with the matching UIElement that represents the item.  When a reset occurs the
    // ItemsRepeater pairs up the already generated UIElements with items in the data
    // source.
    // ItemsRepeater uses IndexForUniqueId after a reset to probe the data and identify
    // the new index of an item to use as the anchor.  If that item no
    // longer exists in the data source it may try using another cached unique ID until
    // either a match is found or it determines that all the previously visible items
    // no longer exist.
    public int IndexForUniqueId(string uniqueId)
    {
        // We'll try to increase our odds of finding a match sooner by starting from the
        // position that we know was last requested and search forward.
        var start = lastRequestedIndex;
        for (int i = start; i < this.Count; i++)
        {
            if (this[i].PrimaryKey.Equals(uniqueId))
                return i;
        }

        // Then try searching backward.
        start = Math.Min(this.Count - 1, lastRequestedIndex);
        for (int i = start; i >= 0; i--)
        {
            if (this[i].PrimaryKey.Equals(uniqueId))
                return i;
        }

        return IndexNotFound;
    }

    public string UniqueIdForIndex(int index)
    {
        var key = this[index].PrimaryKey;
        lastRequestedIndex = index;
        return key;
    }

    #endregion
}

Utwórz niestandardową kontrolę kolekcji

Możesz użyć kontrolki ItemsRepeater, aby utworzyć niestandardową kontrolkę kolekcji z własnym typem kontrolki do prezentowania każdego elementu.

Uwaga / Notatka

Jest to podobne do użycia elementu ItemsControl, ale zamiast wyprowadzać z ItemsControl i umieszczać ItemsPresenter w szablonie kontrolki, wyprowadzasz z Control i wstawiasz ItemsRepeater w szablonie kontrolki. Niestandardowa kontrolka kolekcji "zawiera ItemsRepeater", w odróżnieniu od "jest ItemsControl". Oznacza to, że należy również jawnie wybrać właściwości do uwidocznienia, zamiast wybierać, których właściwości dziedziczonych nie obsługiwać.

W tym przykładzie pokazano, jak umieścić element ItemsRepeater w szablonie niestandardowej kontrolki o nazwie MediaCollectionView i uwidocznić jego właściwości.

<!-- xmlns:muxc="using:Microsoft.UI.Xaml.Controls" -->
<Style TargetType="local:MediaCollectionView">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MediaCollectionView">
                <Border
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                    <ScrollViewer x:Name="ScrollViewer">
                        <muxc:ItemsRepeater x:Name="ItemsRepeater"
                                            ItemsSource="{TemplateBinding ItemsSource}"
                                            ItemTemplate="{TemplateBinding ItemTemplate}"
                                            Layout="{TemplateBinding Layout}"
                                            TabFocusNavigation="{TemplateBinding TabFocusNavigation}"/>
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
public sealed class MediaCollectionView : Control
{
    public object ItemsSource
    {
        get { return (object)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ItemsSource.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(MediaCollectionView), new PropertyMetadata(0));

    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate)GetValue(ItemTemplateProperty); }
        set { SetValue(ItemTemplateProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ItemTemplate.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemTemplateProperty =
        DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(MediaCollectionView), new PropertyMetadata(0));

    public Layout Layout
    {
        get { return (Layout)GetValue(LayoutProperty); }
        set { SetValue(LayoutProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Layout.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty LayoutProperty =
        DependencyProperty.Register(nameof(Layout), typeof(Layout), typeof(MediaCollectionView), new PropertyMetadata(0));

    public MediaCollectionView()
    {
        this.DefaultStyleKey = typeof(MediaCollectionView);
    }
}

Wyświetlanie grupowanych elementów

Element ItemsRepeater można zagnieżdżać w elemencie ItemTemplate innego ItemsRepeater, aby utworzyć zagnieżdżone układy wirtualizacji. Struktura będzie korzystać wydajnie z zasobów, minimalizując niepotrzebne renderowanie elementów, które nie są widoczne lub znajdują się w pobliżu bieżącego widoku.

W tym przykładzie pokazano, jak można wyświetlić listę zgrupowanych elementów w stosie pionowym. Zewnętrzny element ItemsRepeater generuje każdą grupę. W szablonie dla każdej grupy inny element ItemsRepeater generuje elementy.

<!-- xmlns:muxc="using:Microsoft.UI.Xaml.Controls" -->

<Page.Resources>
    <muxc:StackLayout x:Key="MyGroupLayout"/>
    <muxc:StackLayout x:Key="MyItemLayout" Orientation="Horizontal"/>
</Page.Resources>

<ScrollViewer>
  <muxc:ItemsRepeater ItemsSource="{x:Bind AppNotifications}"
                      Layout="{StaticResource MyGroupLayout}">
    <muxc:ItemsRepeater.ItemTemplate>
      <DataTemplate x:DataType="ExampleApp:AppNotifications">
        <!-- Group -->
        <StackPanel>
          <!-- Header -->
          <TextBlock Text="{x:Bind AppTitle}"/>
          <!-- Items -->
          <muxc:ItemsRepeater ItemsSource="{x:Bind Notifications}"
                              Layout="{StaticResource MyItemLayout}"
                              ItemTemplate="{StaticResource MyTemplate}"/>
          <!-- Footer -->
          <Button Content="{x:Bind FooterText}"/>
        </StackPanel>
      </DataTemplate>
    </muxc:ItemsRepeater.ItemTemplate>
  </muxc:ItemsRepeater>
</ScrollViewer>

Na poniższej ilustracji przedstawiono podstawowy układ utworzony przy użyciu powyższego przykładu jako wskazówki.

Układ zagnieżdżony z powtarzaczem elementów

W następnym przykładzie pokazano układ aplikacji z różnymi kategoriami, które mogą ulec zmianie z preferencjami użytkownika i są prezentowane jako listy przewijania poziomego. Układ tego przykładu jest również reprezentowany przez powyższy obraz.

<!-- xmlns:muxc="using:Microsoft.UI.Xaml.Controls" -->
<!-- Include the <muxc:ItemsRepeaterScrollHost> if targeting Windows 10 versions earlier than 1809. -->
<ScrollViewer>
  <muxc:ItemsRepeater ItemsSource="{x:Bind Categories}"
                      Background="LightGreen">
    <muxc:ItemsRepeater.ItemTemplate>
      <DataTemplate x:DataType="local:Category">
        <StackPanel Margin="12,0">
          <TextBlock Text="{x:Bind Name}" Style="{ThemeResource TitleTextBlockStyle}"/>
          <!-- Include the <muxc:ItemsRepeaterScrollHost> if targeting Windows 10 versions earlier than 1809. -->
          <ScrollViewer HorizontalScrollMode="Enabled"
                                          VerticalScrollMode="Disabled"
                                          HorizontalScrollBarVisibility="Auto" >
            <muxc:ItemsRepeater ItemsSource="{x:Bind Items}"
                                Background="Orange">
              <muxc:ItemsRepeater.ItemTemplate>
                <DataTemplate x:DataType="local:CategoryItem">
                  <Grid Margin="10"
                        Height="60" Width="120"
                        Background="LightBlue">
                    <TextBlock Text="{x:Bind Name}"
                               Style="{StaticResource SubtitleTextBlockStyle}"
                               Margin="4"/>
                  </Grid>
                </DataTemplate>
              </muxc:ItemsRepeater.ItemTemplate>
              <muxc:ItemsRepeater.Layout>
                <muxc:StackLayout Orientation="Horizontal"/>
              </muxc:ItemsRepeater.Layout>
            </muxc:ItemsRepeater>
          </ScrollViewer>
        </StackPanel>
      </DataTemplate>
    </muxc:ItemsRepeater.ItemTemplate>
  </muxc:ItemsRepeater>
</ScrollViewer>

Wprowadzanie elementu do widoku

Platforma XAML obsługuje już wprowadzanie elementu FrameworkElement do widoku, gdy albo 1) otrzymuje fokus klawiatury, albo 2) otrzymuje fokus narratora. Mogą istnieć inne przypadki, w których należy jawnie wprowadzić element do widoku. Na przykład w odpowiedzi na akcję użytkownika lub przywrócenie stanu interfejsu użytkownika po nawigacji strony.

Wprowadzenie zwirtualizowanego elementu do widoku obejmuje następujące elementy:

  1. Implementacja elementu UI dla obiektu
  2. Uruchom układ, aby upewnić się, że element ma prawidłową pozycję
  3. Inicjowanie żądania przeniesienia zrealizowanego elementu do widoku

W poniższym przykładzie zademonstrowano te kroki jako część procesu przywracania pozycji przewijania elementu na płaskiej, pionowej liście, po zakończeniu nawigacji między stronami. W przypadku danych hierarchicznych przy użyciu zagnieżdżonych ItemsRepeater podejście jest zasadniczo takie samo, ale należy je wykonywać na każdym poziomie hierarchii.

<ScrollViewer x:Name="scrollviewer">
  <ItemsRepeater x:Name="repeater" .../>
</ScrollViewer>
public class MyPage : Page
{
    // ...

     protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);

        // retrieve saved offset + index(es) of the tracked element and then bring it into view.
        // ... 
        
        var element = repeater.GetOrCreateElement(index);

        // ensure the item is given a valid position
        element.UpdateLayout();

        element.StartBringIntoView(new BringIntoViewOptions()
        {
            VerticalOffset = relativeVerticalOffset
        });
    }

    protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
    {
        base.OnNavigatingFrom(e);

        // retrieve and save the relative offset and index(es) of the scrollviewer's current anchor element ...
        var anchor = this.scrollviewer.CurrentAnchor;
        var index = this.repeater.GetElementIndex(anchor);
        var anchorBounds = anchor.TransformToVisual(this.scrollviewer).TransformBounds(new Rect(0, 0, anchor.ActualSize.X, anchor.ActualSize.Y));
        relativeVerticalOffset = this.scrollviewer.VerticalOffset - anchorBounds.Top;
    }
}

Włączanie ułatwień dostępu

Element ItemsRepeater nie zapewnia domyślnego doświadczenia dostępności. Dokumentacja dotycząca użyteczności aplikacji systemu Windows zawiera wiele informacji, które ułatwiają zapewnienie, że aplikacja zapewnia inkluzywne środowisko użytkownika. Jeśli używasz ItemsRepeater do stworzenia niestandardowej kontrolki, koniecznie zapoznaj się z dokumentacją dotyczącą niestandardowych automatycznych peerów.

Klawiatura

Minimalna obsługa klawiatury do przenoszenia fokusu, którą zapewnia ItemsRepeater, jest oparta na nawigacji kierunkowej 2D w języku XAML dla obsługi klawiatury.

Nawigacja kierunkowa

Tryb XYFocusKeyboardNavigation w elemencie ItemsRepeater jest domyślnie włączony. W zależności od zamierzonego doświadczenia rozważ dodanie obsługi typowych interakcji z klawiaturą , takich jak Home, End, PageUp i PageDown.

ItemsRepeater automatycznie zapewnia, że domyślna kolejność tabulacji dla jego elementów (niezależnie od tego, czy są zwirtualizowane, czy nie) jest zgodna z kolejnością, w jakiej elementy są podane w danych. Domyślnie właściwość ItemsRepeater ma właściwość TabFocusNavigation ustawioną na Raz, a nie na typową wartość domyślną Local.

Uwaga / Notatka

Element ItemsRepeater nie zapamiętuje automatycznie ostatniego elementu z fokusem. Oznacza to, że gdy użytkownik korzysta z Shift+Tab, może zostać przeniesiony do ostatniego zrealizowanego elementu.

Ogłaszanie "Element X z Y" w czytnikach ekranu

Należy zarządzać ustawieniem odpowiednich właściwości automatyzacji, takich jak wartości PozycjiInSet i SizeOfSet, i upewnić się, że pozostają one up-to— data dodania, przeniesienia, usunięcia itp.

W niektórych układach niestandardowych nie może istnieć oczywista sekwencja kolejności wizualizacji. Użytkownicy z minimalnym wyprzedzeniem oczekują, że wartości właściwości PositionInSet i SizeOfSet używane przez czytniki zawartości ekranu będą zgodne z kolejnością wyświetlania elementów w danych (przesunięcie o 1 w celu dopasowania do naturalnego zliczania w porównaniu z wartością 0).

Najlepszym sposobem osiągnięcia tego celu jest posiadanie elementu automatyzacji równorzędnego do kontrolki elementu, który zaimplementuje metody GetPositionInSetCore i GetSizeOfSetCore oraz zgłosi pozycję elementu w zestawie danych reprezentowanym przez kontrolkę. Wartość jest obliczana tylko w czasie wykonywania, gdy uzyskuje się do niej dostęp za pomocą technologii pomocniczej i utrzymuje ją up-to-date staje się problemem. Wartość jest zgodna z kolejnością danych.

W tym przykładzie pokazano, jak można to zrobić podczas prezentowania niestandardowej kontrolki o nazwie CardControl.

<ScrollViewer >
    <ItemsRepeater x:Name="repeater" ItemsSource="{x:Bind MyItemsSource}">
       <ItemsRepeater.ItemTemplate>
           <DataTemplate x:DataType="local:CardViewModel">
               <local:CardControl Item="{x:Bind}"/>
           </DataTemplate>
       </ItemsRepeater.ItemTemplate>
   </ItemsRepeater>
</ScrollViewer>
internal sealed class CardControl : CardControlBase
{
    protected override AutomationPeer OnCreateAutomationPeer() => new CardControlAutomationPeer(this);

    private sealed class CardControlAutomationPeer : FrameworkElementAutomationPeer
    {
        private readonly CardControl owner;

        public CardControlAutomationPeer(CardControl owner) : base(owner) => this.owner = owner;

        protected override int GetPositionInSetCore()
          => ((ItemsRepeater)owner.Parent)?.GetElementIndex(this.owner) + 1 ?? base.GetPositionInSetCore();

        protected override int GetSizeOfSetCore()
          => ((ItemsRepeater)owner.Parent)?.ItemsSourceView?.Count ?? base.GetSizeOfSetCore();
    }
}

UwP i WinUI 2

Ważne

Informacje i przykłady w tym artykule są zoptymalizowane dla aplikacji korzystających z Windows App SDK oraz WinUI 3, ale generalnie mają zastosowanie także w aplikacjach UWP używających WinUI 2. Zobacz dokumentację interfejsu API platformy UWP, aby uzyskać informacje i przykłady dotyczące platformy.

Ta sekcja zawiera informacje potrzebne do używania kontrolki w aplikacji platformy UWP lub WinUI 2.

ItemsRepeater dla aplikacji UWP wymaga WinUI 2. Aby uzyskać więcej informacji, w tym instrukcje dotyczące instalacji, zobacz WinUI 2. Interfejsy API dla tej kontrolki istnieją w przestrzeni nazw Microsoft.UI.Xaml.Controls .

Aby użyć kodu z tego artykułu w WinUI 2, użyj aliasu w XAML (używamy muxc), aby reprezentować API Biblioteki Interfejsu Użytkownika Windows, które są uwzględnione w Twoim projekcie. Aby uzyskać więcej informacji, zobacz Wprowadzenie do interfejsu WinUI 2 .

xmlns:muxc="using:Microsoft.UI.Xaml.Controls"

<muxc:ItemsRepeater />