ItemPeater

Gunakan ItemsRepeater untuk membuat pengalaman pengumpulan kustom menggunakan sistem tata letak yang fleksibel, tampilan kustom, dan virtualisasi.

Tidak seperti ListView, ItemsRepeater tidak memberikan pengalaman pengguna akhir yang komprehensif - tidak memiliki UI default dan tidak memberikan kebijakan sekeliling fokus, pemilihan, atau interaksi pengguna. Sebaliknya, ini adalah blok penyusun yang dapat Anda gunakan untuk membuat pengalaman berbasis koleksi unik dan kontrol kustom Anda sendiri. Meskipun tidak memiliki kebijakan bawaan, kebijakan tersebut memungkinkan Anda melampirkan kebijakan untuk membangun pengalaman yang Anda butuhkan. Misalnya, Anda dapat menentukan tata letak yang akan digunakan, kebijakan keyboard, kebijakan pemilihan, dll.

Anda dapat menganggap ItemRepeater secara konseptual sebagai panel berbasis data, daripada sebagai kontrol lengkap seperti ListView. Anda menentukan kumpulan item data yang akan ditampilkan, templat item yang menghasilkan elemen UI untuk setiap item data, dan tata letak yang menentukan ukuran dan posisi elemen. Kemudian, ItemsRepeater menghasilkan elemen turunan berdasarkan sumber data, dan menampilkannya seperti yang ditentukan oleh templat dan tata letak item. Item yang ditampilkan tidak perlu homogen karena ItemsRepeater dapat memuat konten untuk mewakili item data berdasarkan kriteria yang Anda tentukan dalam pemilih templat data.

Apakah ini kontrol yang tepat?

Gunakan ItemsRepeater untuk membuat tampilan kustom untuk kumpulan data. Meskipun dapat digunakan untuk menyajikan sekumpulan item dasar, Anda mungkin sering menggunakannya sebagai elemen tampilan dalam templat kontrol kustom.

Jika Anda memerlukan kontrol out-of-the-box untuk menampilkan data dalam daftar atau kisi dengan kustomisasi minimal, pertimbangkan untuk menggunakan ListView atau GridView.

ItemRepeater tidak memiliki koleksi Item bawaan. Jika Anda perlu menyediakan koleksi Item secara langsung, daripada mengikat ke sumber data terpisah, maka Anda mungkin membutuhkan pengalaman kebijakan yang lebih tinggi dan harus menggunakan ListView atau GridView.

ItemsControl dan ItemsRepeater mengaktifkan pengalaman pengumpulan yang dapat disesuaikan, tetapi ItemsRepeater mendukung virtualisasi tata letak UI, sementara ItemsControl tidak. Sebaiknya gunakan ItemsRepeater alih-alih ItemsControl, baik untuk hanya menyajikan beberapa item dari data atau membangun kontrol pengumpulan kustom.

UWP dan WinUI 2

Penting

Informasi dan contoh dalam artikel ini dioptimalkan untuk aplikasi yang menggunakan SDK Aplikasi Windows dan WinUI 3, tetapi umumnya berlaku untuk aplikasi UWP yang menggunakan WinUI 2. Lihat referensi API UWP untuk informasi dan contoh spesifik platform.

Bagian ini berisi informasi yang Anda butuhkan untuk menggunakan kontrol di aplikasi UWP atau WinUI 2.

ItemsRepeater untuk aplikasi UWP memerlukan Windows UI Library 2. Untuk informasi selengkapnya, termasuk instruksi penginstalan, lihat Pustaka Windows UI. API untuk kontrol ini ada di namespace Microsoft.UI.Xaml.Controls .

Untuk menggunakan kode dalam artikel ini dengan WinUI 2, gunakan alias di XAML (kami menggunakan muxc) untuk mewakili API Pustaka Windows UI yang disertakan dalam proyek Anda. Lihat Mulai menggunakan WinUI 2 untuk informasi selengkapnya.

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

<muxc:ItemsRepeater />

Menggulir dengan ItemsRepeater

ItemRepeater tidak berasal dari Kontrol, sehingga tidak memiliki templat kontrol. Oleh karena itu, ini tidak berisi pengguliran bawaan seperti ListView atau kontrol koleksi lainnya.

Saat Anda menggunakan ItemsRepeater, Anda harus menyediakan fungsionalitas gulir dengan membungkusnya dalam kontrol ScrollViewer .

Catatan

Jika aplikasi Anda akan berjalan pada versi Windows yang lebih lama - yang dirilis sebelum Windows 10, versi 1809 - maka Anda juga perlu menghosting ScrollViewer di dalam ItemsRepeaterScrollHost.

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

Jika aplikasi Anda hanya akan berjalan pada versi terbaru Windows 10, versi 1809 dan yang lebih baru - maka tidak perlu menggunakan ItemsRepeaterScrollHost.

Sebelum Windows 10, versi 1809, ScrollViewer tidak mengimplementasikan antarmuka IScrollAnchorProvider yang dibutuhkan ItemsRepeater. ItemsRepeaterScrollHost memungkinkan ItemsRepeater untuk berkoordinasi dengan ScrollViewer pada rilis sebelumnya untuk mempertahankan lokasi item yang terlihat dengan benar yang dilihat pengguna. Jika tidak, item mungkin tampak bergerak atau menghilang tiba-tiba saat item dalam daftar diubah atau aplikasi diubah ukurannya.

Membuat ItemPeater

Aplikasi WinUI 3 Gallery mencakup contoh interaktif sebagian besar kontrol, fitur, dan fungsi WinUI 3. Dapatkan aplikasi dari Microsoft Store atau dapatkan kode sumber di GitHub

Untuk menggunakan ItemsRepeater, Anda perlu memberinya data untuk ditampilkan dengan mengatur properti ItemsSource . Kemudian, beri tahu cara menampilkan item dengan mengatur properti ItemTemplate .

ItemsSource

Untuk mengisi tampilan, atur properti ItemsSource ke kumpulan item data. Di sini, ItemsSource diatur dalam kode langsung ke instans koleksi.

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

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

Anda juga dapat mengikat properti ItemsSource ke koleksi di XAML. Untuk informasi selengkapnya tentang pengikatan data, lihat Gambaran umum pengikatan data.

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

ItemTemplate

Untuk menentukan bagaimana item data divisualisasikan, atur properti ItemTemplate ke DataTemplate atau DataTemplateSelector yang telah Anda tentukan. Templat data menentukan bagaimana data divisualisasikan. Secara default, item ditampilkan dalam tampilan dengan TextBlock menggunakan representasi string objek data.

Namun, Anda biasanya ingin menampilkan presentasi data yang lebih kaya dengan menggunakan templat yang menentukan tata letak dan tampilan satu atau beberapa kontrol yang akan Anda gunakan untuk menampilkan item individual. Kontrol yang Anda gunakan dalam templat dapat terikat ke properti objek data, atau memiliki konten statis yang ditentukan sebaris.

DataTemplate

Dalam contoh ini, objek data adalah string sederhana. DataTemplate menyertakan gambar di sebelah kiri teks, dan menata TextBlock untuk menampilkan string dalam warna teal.

Catatan

Saat Anda menggunakan ekstensi markup x:Bind di DataTemplate, Anda harus menentukan DataType (x:DataType) pada 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>

Berikut adalah bagaimana item akan muncul saat ditampilkan dengan DataTemplate ini.

Item yang ditampilkan dengan templat data

Jumlah elemen yang digunakan dalam DataTemplate untuk item dapat berdampak signifikan pada performa jika tampilan Anda menampilkan sejumlah besar item. Untuk informasi selengkapnya dan contoh cara menggunakan DataTemplates untuk menentukan tampilan item dalam daftar Anda, lihat Kontainer dan templat item.

Tip

Untuk kenyamanan ketika Anda ingin mendeklarasikan templat sebaris daripada direferensikan sebagai sumber daya statis, Anda dapat menentukan DataTemplate atau DataTemplateSelector sebagai turunan langsung dari ItemsRepeater. Ini akan ditetapkan sebagai nilai properti ItemTemplate . Misalnya, ini valid:

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

Tip

Tidak seperti ListView dan kontrol koleksi lainnya, ItemsRepeater tidak membungkus elemen dari DataTemplate dengan kontainer item tambahan yang menyertakan kebijakan default seperti margin, padding, visual pilihan, atau penunjuk di atas status visual. Sebaliknya, ItemsRepeater hanya menyajikan apa yang ditentukan dalam DataTemplate. Jika Anda ingin item Anda memiliki tampilan yang sama dengan item tampilan daftar, Anda dapat secara eksplisit menyertakan kontainer, seperti ListViewItem, di templat data Anda. ItemsRepeater akan menampilkan visual ListViewItem , tetapi tidak secara otomatis menggunakan fungsionalitas lain, seperti pilihan atau memperlihatkan kotak centang multi-pilih.

Demikian pula, jika pengumpulan data Anda adalah kumpulan kontrol aktual, seperti Tombol (List<Button>), Anda dapat meletakkan ContentPresenter di DataTemplate Anda untuk menampilkan kontrol.

DataTemplateSelector

Item yang Anda tampilkan dalam tampilan Anda tidak harus bertipe sama. Anda dapat menyediakan properti ItemTemplate dengan DataTemplateSelector untuk memilih DataTemplateyang berbeda berdasarkan kriteria yang Anda tentukan.

Contoh ini mengasumsikan DataTemplateSelector telah ditentukan yang memutuskan antara dua DataTemplateyang berbeda untuk mewakili item Besar dan Kecil.

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

Saat menentukan DataTemplateSelector untuk digunakan dengan ItemsRepeater, Anda hanya perlu menerapkan penimpaan untuk metode SelectTemplateCore(Object). Untuk informasi dan contoh selengkapnya, lihat DataTemplateSelector.

Catatan

Alternatif untuk DataTemplates untuk mengelola bagaimana elemen dibuat dalam skenario yang lebih canggih adalah dengan menerapkan IElementFactory Anda sendiri untuk digunakan sebagai ItemTemplate. Ini akan bertanggung jawab untuk menghasilkan konten ketika diminta.

Konfigurasikan sumber data

Gunakan properti ItemsSource untuk menentukan koleksi yang akan digunakan untuk menghasilkan konten item. Anda dapat mengatur ItemsSource ke jenis apa pun yang mengimplementasikan IEnumerable. Antarmuka pengumpulan tambahan yang diterapkan oleh sumber data Anda menentukan fungsionalitas apa yang tersedia untuk ItemsRepeater untuk berinteraksi dengan data Anda.

Daftar ini menunjukkan antarmuka yang tersedia dan kapan harus mempertimbangkan untuk menggunakan masing-masing antarmuka.

  • IEnumerable(.NET) / IIterable

    • Dapat digunakan untuk himpunan data statis kecil.

      Minimal, sumber data harus mengimplementasikan antarmuka IEnumerable / IIterable. Jika ini semua yang didukung maka kontrol akan melakukan iterasi melalui semuanya sekali untuk membuat salinan yang dapat digunakan untuk mengakses item melalui nilai indeks.

  • IReadonlyList(.NET) / IVectorView

    • Dapat digunakan untuk himpunan data statis baca-saja.

      Mengaktifkan kontrol untuk mengakses item berdasarkan indeks dan menghindari salinan internal yang berlebihan.

  • IList(.NET) / IVector

    • Dapat digunakan untuk himpunan data statis.

      Mengaktifkan kontrol untuk mengakses item berdasarkan indeks dan menghindari salinan internal yang berlebihan.

      Peringatan: Perubahan pada daftar/vektor tanpa menerapkan INotifyCollectionChanged tidak akan tercermin dalam UI.

  • INotifyCollectionChanged(.NET)

    • Disarankan untuk mendukung pemberitahuan perubahan.

      Memungkinkan kontrol untuk mengamati dan bereaksi terhadap perubahan dalam sumber data dan mencerminkan perubahan tersebut di UI.

  • IObservableVector

    • Mendukung pemberitahuan perubahan

      Seperti antarmuka INotifyCollectionChanged , ini memungkinkan kontrol untuk mengamati dan bereaksi terhadap perubahan dalam sumber data.

      Peringatan: Windows.Foundation.IObservableVector<T> tidak mendukung tindakan 'Pindahkan'. Hal ini dapat menyebabkan UI item kehilangan status visualnya. Misalnya, item yang saat ini dipilih dan/atau memiliki fokus di mana pemindahan dicapai oleh 'Hapus' diikuti dengan 'Tambahkan' akan kehilangan fokus dan tidak lagi dipilih.

      Platform.Collections.Vector<T> menggunakan IObservableVector<T> dan memiliki batasan yang sama ini. Jika dukungan untuk tindakan 'Pindahkan' diperlukan, maka gunakan antarmuka INotifyCollectionChanged . Kelas .NET ObservableCollection<T> menggunakan INotifyCollectionChanged.

  • IKeyIndexMapping

    • Saat pengidentifikasi unik dapat dikaitkan dengan setiap item. Disarankan saat menggunakan 'Reset' sebagai tindakan perubahan koleksi.

      Memungkinkan kontrol untuk memulihkan UI yang ada dengan sangat efisien setelah menerima tindakan 'Reset' keras sebagai bagian dari peristiwa INotifyCollectionChanged atau IObservableVector . Setelah menerima reset, kontrol akan menggunakan ID unik yang disediakan untuk mengaitkan data saat ini dengan elemen yang telah dibuatnya. Tanpa kunci untuk mengindeks pemetaan kontrol harus mengasumsikan perlu memulai kembali dari awal dalam membuat UI untuk data.

Antarmuka yang tercantum di atas, selain IKeyIndexMapping, memberikan perilaku yang sama di ItemsRepeater seperti yang mereka lakukan di ListView dan GridView.

Antarmuka berikut pada ItemsSource memungkinkan fungsionalitas khusus di kontrol ListView dan GridView, tetapi saat ini tidak berpengaruh pada ItemsRepeater:

Tip

Kami ingin umpan balik Anda! Beri tahu kami pendapat Anda tentang proyek GitHub Pustaka UI Windows. Pertimbangkan untuk menambahkan pemikiran Anda pada proposal yang ada seperti #374: Menambahkan dukungan pemuatan bertambah bertahap untuk ItemsRepeater.

Pendekatan alternatif untuk memuat data Anda secara bertahap saat pengguna menggulir ke atas atau ke bawah adalah mengamati posisi viewport ScrollViewer dan memuat lebih banyak data saat viewport mendekati sejauh mana.

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

Mengubah tata letak item

Item yang diperlihatkan oleh ItemsRepeater disusun oleh objek Tata Letak yang mengelola ukuran dan posisi elemen turunannya. Saat digunakan dengan ItemsRepeater, objek Tata Letak mengaktifkan virtualisasi UI. Tata letak yang disediakan adalah StackLayout dan UniformGridLayout. Secara default, ItemsRepeater menggunakan StackLayout dengan orientasi vertikal.

StackLayout

StackLayout mengatur elemen ke dalam satu baris yang dapat Anda orientasikan secara horizontal atau vertikal.

Anda dapat mengatur properti Penspasian untuk menyesuaikan jumlah ruang di antara item. Penspasian diterapkan ke arah Orientasi tata letak.

Penspasian tata letak tumpukan

Contoh ini memperlihatkan cara mengatur properti ItemsRepeater.Layout ke StackLayout dengan orientasi horizontal dan penspasian 8 piksel.

<!-- 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

UniformGridLayout memposisikan elemen secara berurutan dalam tata letak pembungkusan. Item diletakkan secara berurutan dari kiri-ke-kanan saat OrientasinyaHorizontal, dan diletakkan dari atas ke bawah saat Orientasinya Vertikal. Setiap item berukuran sama.

Penspasian tata letak kisi seragam

Jumlah item di setiap baris tata letak horizontal dipengaruhi oleh lebar item minimum. Jumlah item di setiap kolom tata letak vertikal dipengaruhi oleh tinggi item minimum.

  • Anda dapat secara eksplisit memberikan ukuran minimum untuk digunakan dengan mengatur properti MinItemHeight dan MinItemWidth .
  • Jika Anda tidak menentukan ukuran minimum, ukuran item pertama yang diukur dianggap sebagai ukuran minimum per item.

Anda juga dapat mengatur penspasian minimum untuk tata letak untuk disertakan antara baris dan kolom dengan mengatur properti MinColumnSpacing dan MinRowSpacing .

Ukuran dan penspasian kisi seragam

Setelah jumlah item dalam baris atau kolom ditentukan berdasarkan ukuran dan penspasian minimum item, mungkin ada ruang yang tidak digunakan yang tersisa setelah item terakhir di baris atau kolom (seperti yang diilustrasikan pada gambar sebelumnya). Anda dapat menentukan apakah ada ruang tambahan yang diabaikan, digunakan untuk meningkatkan ukuran setiap item, atau digunakan untuk membuat ruang ekstra di antara item. Ini dikontrol oleh properti ItemsStretch dan ItemsJustification .

Anda dapat mengatur properti ItemsStretch untuk menentukan bagaimana ukuran item ditingkatkan untuk mengisi ruang yang tidak digunakan.

Daftar ini memperlihatkan nilai yang tersedia. Definisi mengasumsikan Orientasi default Horizontal.

  • Tidak ada: Ruang ekstra dibiarkan tidak digunakan di akhir baris. Ini adalah default.
  • Isi: Item diberi lebar ekstra untuk menggunakan ruang yang tersedia (tinggi jika vertikal).
  • Seragam: Item diberi lebar ekstra untuk menggunakan ruang yang tersedia, dan diberikan tinggi ekstra untuk mempertahankan rasio aspek (tinggi dan lebar dialihkan jika vertikal).

Gambar ini memperlihatkan efek nilai ItemsStretch dalam tata letak horizontal.

Peregangan item kisi seragam

Saat ItemsStretchtidak ada, Anda dapat mengatur properti ItemsJustification untuk menentukan bagaimana ruang tambahan digunakan untuk meratakan item.

Daftar ini memperlihatkan nilai yang tersedia. Definisi mengasumsikan Orientasi default Horizontal.

  • Mulai: Item diratakan dengan awal baris. Ruang ekstra dibiarkan tidak digunakan di akhir baris. Ini adalah default.
  • Tengah: Item diratakan di tengah baris. Ruang ekstra dibagi secara merata di awal dan akhir baris.
  • Akhir: Item diratakan dengan akhir baris. Ruang ekstra dibiarkan tidak digunakan di awal baris.
  • SpaceAround: Item didistribusikan secara merata. Jumlah ruang yang sama ditambahkan sebelum dan sesudah setiap item.
  • SpaceBetween: Item didistribusikan secara merata. Jumlah ruang yang sama ditambahkan di antara setiap item. Tidak ada spasi yang ditambahkan di awal dan akhir baris.
  • SpaceEvenly: Item didistribusikan secara merata dengan jumlah ruang yang sama baik di antara setiap item maupun di awal dan akhir baris.

Gambar ini memperlihatkan efek nilai ItemsStretch dalam tata letak vertikal (diterapkan ke kolom alih-alih baris).

Pertimbangan item kisi seragam

Tip

Properti ItemsStretch memengaruhi lulus pengukuran tata letak. Properti ItemsJustification memengaruhi pass tata letak susun .

Contoh ini memperlihatkan cara mengatur properti ItemsRepeater.Layout ke 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>

Peristiwa siklus hidup

Saat Menghosting item di ItemsRepeater, Anda mungkin perlu mengambil beberapa tindakan saat item ditampilkan atau berhenti ditampilkan, seperti memulai pengunduhan asinkron beberapa konten, mengaitkan elemen dengan mekanisme untuk melacak pilihan, atau menghentikan beberapa tugas latar belakang.

Dalam kontrol virtualisasi, Anda tidak dapat mengandalkan peristiwa Yang Dimuat/Dibongkar karena elemen mungkin tidak dihapus dari pohon visual langsung saat didaur ulang. Sebaliknya, peristiwa lain disediakan untuk mengelola siklus hidup elemen. Diagram ini menunjukkan siklus hidup elemen dalam ItemsRepeater, dan kapan peristiwa terkait dinaikkan.

Diagram peristiwa siklus hidup

  • ElementPrepared terjadi setiap kali elemen siap digunakan. Ini terjadi untuk elemen yang baru dibuat serta elemen yang sudah ada dan sedang digunakan kembali dari antrean daur ulang.
  • ElementClearing terjadi segera setiap kali elemen telah dikirim ke antrean daur ulang, seperti ketika berada di luar rentang item yang direalisasikan.
  • ElementIndexChanged terjadi untuk setiap UIElement yang direalisasikan di mana indeks untuk item yang diwakilinya telah berubah. Misalnya, ketika item lain ditambahkan atau dihapus di sumber data, indeks untuk item yang datang setelah dalam urutan menerima peristiwa ini.

Contoh ini memperlihatkan bagaimana Anda dapat menggunakan peristiwa ini untuk melampirkan layanan pemilihan kustom untuk melacak pilihan item dalam kontrol kustom yang menggunakan ItemsRepeater untuk menampilkan item.

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

Mengurutkan, Memfilter, dan Mengatur Ulang Data

Saat Anda melakukan tindakan seperti memfilter atau mengurutkan himpunan data, Anda mungkin telah membandingkan kumpulan data sebelumnya dengan data baru, lalu mengeluarkan pemberitahuan perubahan terperinci melalui INotifyCollectionChanged. Namun, seringkali lebih mudah untuk sepenuhnya mengganti data lama dengan data baru dan memicu pemberitahuan perubahan koleksi menggunakan tindakan Reset sebagai gantinya.

Biasanya, reset menyebabkan kontrol untuk merilis elemen anak yang ada dan memulai kembali, membangun UI dari awal pada posisi gulir 0 karena tidak memiliki kesadaran tentang bagaimana data telah berubah selama reset.

Namun, jika koleksi yang ditetapkan sebagai ItemsSource mendukung pengidentifikasi unik dengan menerapkan antarmuka IKeyIndexMapping , maka ItemsRepeater dapat dengan cepat mengidentifikasi:

  • UIElements yang dapat digunakan kembali untuk data yang ada baik sebelum dan sesudah reset
  • item yang sebelumnya terlihat yang dihapus
  • item baru ditambahkan yang akan terlihat

Ini memungkinkan ItemsRepeater menghindari memulai dari posisi gulir 0. Ini juga memungkinkannya dengan cepat memulihkan UIElements untuk data yang tidak berubah dalam reset, menghasilkan performa yang lebih baik.

Contoh ini memperlihatkan cara menampilkan daftar item dalam tumpukan vertikal di mana MyItemsSource adalah sumber data kustom yang membungkus daftar item yang mendasar. Ini mengekspos properti Data yang dapat digunakan untuk menetapkan ulang daftar baru untuk digunakan sebagai sumber item, yang kemudian memicu reset.

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

Membuat kontrol koleksi kustom

Anda dapat menggunakan ItemsRepeater untuk membuat kontrol koleksi kustom lengkap dengan jenis kontrolnya sendiri untuk menyajikan setiap item.

Catatan

Ini mirip dengan menggunakan ItemsControl, tetapi alih-alih berasal dari ItemsControl dan menempatkan ItemsPresenter di templat kontrol, Anda berasal dari Kontrol dan menyisipkan ItemsRepeater di templat kontrol. Kontrol pengumpulan kustom "memiliki" ItemsRepeater versus "is an" ItemsControl. Ini menyiratkan bahwa Anda juga harus secara eksplisit memilih properti mana yang akan diekspos, daripada properti warisan mana yang tidak didukung.

Contoh ini memperlihatkan cara menempatkan ItemsRepeater dalam templat kontrol kustom bernama MediaCollectionView dan mengekspos propertinya.

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

Tampilkan item yang dikelompokkan

Anda dapat menumpuk ItemRepeater di ItemTemplate dari ItemsRepeater lain untuk membuat tata letak virtualisasi berlapis. Kerangka kerja akan memanfaatkan sumber daya secara efisien dengan meminimalkan realisasi elemen yang tidak perlu yang tidak terlihat atau di dekat viewport saat ini.

Contoh ini memperlihatkan bagaimana Anda bisa menampilkan daftar item yang dikelompokkan dalam tumpukan vertikal. ItemRepeater luar menghasilkan setiap grup. Dalam templat untuk setiap grup, ItemRepeater lain menghasilkan item.

<!-- 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>

Gambar di bawah ini menunjukkan tata letak dasar yang dibuat menggunakan sampel di atas sebagai pedoman.

Tata letak berlapis dengan pengulang item

Contoh berikutnya menunjukkan tata letak untuk aplikasi yang memiliki berbagai kategori yang dapat berubah dengan preferensi pengguna dan disajikan sebagai daftar pengguliran horizontal. Tata letak contoh ini juga diwakili oleh gambar di atas.

<!-- 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>

Menampilkan Elemen

Kerangka kerja XAML sudah menangani tampilan FrameworkElement ketika 1) menerima fokus keyboard atau 2) menerima fokus Narator. Mungkin ada kasus lain di mana Anda perlu secara eksplisit menampilkan elemen. Misalnya, sebagai respons terhadap tindakan pengguna atau untuk memulihkan status UI setelah navigasi halaman.

Menampilkan elemen virtual melibatkan hal berikut:

  1. Mewujudkan UIElement untuk item
  2. Jalankan tata letak untuk memastikan elemen memiliki posisi yang valid
  3. Memulai permintaan untuk menampilkan elemen yang direalisasikan

Contoh di bawah ini menunjukkan langkah-langkah ini sebagai bagian dari memulihkan posisi gulir item dalam daftar vertikal datar setelah navigasi halaman. Dalam kasus data hierarkis menggunakan ItemsRepeaters berlapis, pendekatan pada dasarnya sama, tetapi harus dilakukan di setiap tingkat hierarki.

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

Aktifkan Aksesibilitas

ItemsRepeater tidak memberikan pengalaman aksesibilitas default. Dokumentasi tentang Kegunaan untuk aplikasi Windows menyediakan banyak informasi untuk membantu Anda memastikan aplikasi Anda memberikan pengalaman pengguna yang inklusif. Jika Anda menggunakan ItemsRepeater untuk membuat kontrol kustom, pastikan untuk melihat dokumentasi pada Rekan otomatisasi kustom.

Keyboarding

Dukungan keyboard minimal untuk gerakan fokus yang disediakan ItemsRepeater didasarkan pada Navigasi Arah 2D XAML untuk Keyboarding.

Navigasi Arah

Mode XYFocusKeyboardNavigation ItemsRepeater Diaktifkan secara default. Bergantung pada pengalaman yang dimaksudkan, pertimbangkan untuk menambahkan dukungan untuk Interaksi Keyboard umum seperti Beranda, Akhir, PageUp, dan PageDown.

ItemsRepeater secara otomatis memastikan bahwa urutan tab default untuk itemnya (baik divirtualisasi atau tidak) mengikuti urutan yang sama dengan item yang diberikan dalam data. Secara default ItemRepeater memiliki properti TabFocusNavigation yang diatur ke Sekali alih-alih default umum Lokal.

Catatan

ItemsRepeater tidak secara otomatis mengingat item terakhir yang difokuskan. Ini berarti bahwa ketika pengguna menggunakan Shift+Tab, mereka dapat dibawa ke item terakhir yang direalisasikan.

Mengumumkan "Item X dari Y" di Pembaca Layar

Anda perlu mengelola pengaturan properti otomatisasi yang sesuai, seperti nilai untuk PositionInSet dan SizeOfSet, dan memastikannya tetap terbaru saat item ditambahkan, dipindahkan, dihapus, dll.

Dalam beberapa tata letak kustom mungkin tidak ada urutan yang jelas untuk urutan visual. Pengguna minimal mengharapkan bahwa nilai untuk properti PositionInSet dan SizeOfSet yang digunakan oleh pembaca layar akan cocok dengan urutan item muncul dalam data (diimbangi dengan 1 agar sesuai dengan penghitungan alami versus berbasis 0).

Cara terbaik untuk mencapainya adalah dengan meminta peer otomatisasi untuk kontrol item mengimplementasikan metode GetPositionInSetCore dan GetSizeOfSetCore dan melaporkan posisi item dalam himpunan data yang diwakili oleh kontrol. Nilai hanya dihitung pada run-time ketika diakses oleh teknologi bantuan dan menjaganya tetap terbaru menjadi non-masalah. Nilai cocok dengan urutan data.

Contoh ini menunjukkan bagaimana Anda dapat melakukan ini saat menyajikan kontrol kustom yang disebut 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();
    }
}