Condividi tramite


ItemsRepeater

Usare itemsRepeater per creare esperienze di raccolta personalizzate usando un sistema di layout flessibile, visualizzazioni personalizzate e virtualizzazione.

A differenza di ListView, ItemsRepeater non offre un'esperienza utente finale completa: non ha un'interfaccia utente predefinita e non fornisce criteri per lo stato attivo, la selezione o l'interazione dell'utente. Si tratta invece di un blocco costitutivo che è possibile usare per creare esperienze uniche basate su raccolte e controlli personalizzati. Anche se non dispone di criteri predefiniti, consente di associare criteri per creare l'esperienza richiesta. Ad esempio, puoi definire il layout da usare, i criteri per la tastiera, i criteri di selezione e così via.

È possibile considerare ItemsRepeater concettualmente come un pannello basato sui dati, anziché come un controllo completo come ListView. Puoi specificare una raccolta di elementi dati da visualizzare, un modello di elemento che genera un elemento dell'interfaccia utente per ogni elemento dati e un layout che determina come vengono ridimensionati e posizionati gli elementi. Quindi, ItemsRepeater produce elementi figlio basati sull'origine dati e li visualizza come specificato dal modello di elemento e dal layout. Gli elementi visualizzati non devono essere omogenei, perché ItemsRepeater può caricare contenuto per rappresentare gli elementi dati in base ai criteri specificati in un selettore di modello di dati.

Questo è il controllo giusto?

Usare itemsRepeater per creare visualizzazioni personalizzate per le raccolte di dati. Anche se può essere usato per presentare un set di elementi di base, spesso è utilizzabile come elemento di visualizzazione nel modello di un controllo personalizzato.

Se hai bisogno di un controllo pronto all'uso per visualizzare i dati in un elenco o in una griglia con una personalizzazione minima, considera l'uso di un controllo ListView o GridView.

ItemsRepeater non dispone di una raccolta di elementi predefinita. Se è necessario fornire direttamente una raccolta Items, anziché eseguire il binding a un'origine dati separata, è probabile che sia necessaria un'esperienza più avanzata e si dovrebbe usare ListView o GridView.

ItemsControl e ItemsRepeater consentono entrambe esperienze di raccolta personalizzabili, ma ItemsRepeater supporta la virtualizzazione dei layout dell'interfaccia utente, mentre ItemsControl non lo fa. È consigliabile usare ItemsRepeater invece di ItemsControl, indipendentemente dal fatto che occorra presentare solo alcuni elementi dai dati o creare un controllo raccolta personalizzato.

Scorrimento con ItemsRepeater

ItemsRepeater non deriva da Control, quindi non ha un modello di controllo. Di conseguenza, non include una funzionalità di scorrimento predefinita come fanno ListView o altri controlli raccolta.

Quando si usa ItemsRepeater, è necessario fornire funzionalità di scorrimento inserendolo in un controllo ScrollViewer.

Annotazioni

Se l'app verrà eseguita in versioni precedenti di Windows, quelle rilasciate prima di Windows 10, versione 1809, è anche necessario ospitare il ScrollViewer all'interno del ItemsRepeaterScrollHost.

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

Se l'app verrà eseguita solo nelle versioni recenti di Windows 10, versione 1809 e successive, non è necessario usare ItemsRepeaterScrollHost.

Prima di Windows 10, versione 1809, ScrollViewer non implementava l'interfaccia IScrollAnchorProvider necessaria per ItemsRepeater . ItemsRepeaterScrollHost consente a ItemsRepeater di coordinarsi con ScrollViewer nelle versioni precedenti per mantenere correttamente la posizione visibile degli elementi visualizzati dall'utente. In caso contrario, gli elementi potrebbero spostarsi o scomparire improvvisamente quando si modificano gli elementi nell'elenco o l'app viene ridimensionata.

Creare un ItemsRepeater

L'app Raccolta WinUI 3 include esempi interattivi della maggior parte dei controlli e delle funzionalità di WinUI 3. Scaricare l'app da Microsoft Store od ottenere il codice sorgente su GitHub

Per usare itemsRepeater, è necessario assegnare i dati da visualizzare impostando la proprietà ItemsSource . Quindi, indicare come visualizzare gli elementi impostando la proprietà ItemTemplate .

FonteElementi

Per popolare la visualizzazione, impostare la proprietà ItemsSource su una raccolta di elementi di dati. In questo caso, ItemsSource viene impostato nel codice direttamente su un'istanza di una raccolta.

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

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

Puoi anche associare la proprietà ItemsSource a una raccolta in XAML. Per ulteriori informazioni sul data binding, vedere la panoramica del data binding .

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

Modello di Elemento

Per specificare la modalità di visualizzazione di un elemento di dati, impostare la proprietà ItemTemplate su un Oggetto DataTemplate o DataTemplateSelector definito. Il modello di dati definisce come vengono visualizzati i dati. Per impostazione predefinita, l'elemento viene visualizzato nella visualizzazione con un TextBlock che utilizza la rappresentazione in stringa dell'oggetto dati.

Tuttavia, in genere puoi mostrare una presentazione più avanzata dei dati usando un modello che definisce il layout e l'aspetto di uno o più controlli che userai per visualizzare un singolo elemento. I controlli usati nel modello possono essere associati alle proprietà dell'oggetto dati oppure il loro contenuto statico può essere definito inline.

DataTemplate

In questo esempio l'oggetto dati è una stringa semplice. DataTemplate include un'immagine a sinistra del testo e stile TextBlock per visualizzare la stringa in un colore verde acqua.

Annotazioni

Quando si usa l'estension e di markup x:Bind in un DataTemplate, è necessario specificare DataType () in 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>

Ecco come vengono visualizzati gli elementi quando vengono visualizzati con questo DataTemplate.

Elementi visualizzati con un modello di dati

Il numero di elementi utilizzati in DataTemplate per un elemento può avere un impatto significativo sulle prestazioni se la visualizzazione visualizza un numero elevato di elementi. Per altre info ed esempi su come usare DataTemplates per definire l'aspetto degli elementi nell'elenco, vedi Contenitori e modelli di elementi.

Suggerimento

Per praticità, quando si desidera dichiarare il modello inline anziché fare riferimento come risorsa statica, è possibile specificare DataTemplate o DataTemplateSelector come elemento figlio diretto di ItemsRepeater. Verrà assegnato come valore della proprietà ItemTemplate . Ad esempio, il codice seguente è valido:

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

Suggerimento

A differenza di ListView e di altri controlli raccolta, ItemsRepeater non esegue il wrapping degli elementi da un DataTemplate con un contenitore di elementi aggiuntivo che include criteri predefiniti, ad esempio margini, padding, oggetti visivi di selezione o puntatore su stato visivo. ItemsRepeater presenta invece solo ciò che viene definito in DataTemplate. Se si desidera che gli elementi abbiano lo stesso aspetto di una voce di visualizzazione elenco, è possibile includere in modo esplicito un contenitore, ad esempio ListViewItem, nel modello di dati. ItemsRepeater mostrerà gli oggetti visivi ListViewItem , ma non usa automaticamente altre funzionalità, ad esempio la selezione o la visualizzazione della casella di controllo a selezione multipla.

Analogamente, se la raccolta dati è una raccolta di controlli effettivi, ad esempio Button (List<Button>), è possibile inserire contentPresenter in DataTemplate per visualizzare il controllo.

DataTemplateSelector

Gli elementi mostrati nella visualizzazione non devono essere dello stesso tipo. È possibile specificare la proprietà ItemTemplate con un DataTemplateSelector per selezionare DataTemplate diversi in base ai criteri specificati.

In questo esempio si presuppone che sia stato definito un Oggetto DataTemplateSelector che decide tra due diversi oggetti DataTemplateper rappresentare un elemento Large e Small.

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

Quando si definisce un oggetto DataTemplateSelector da usare con ItemsRepeater, è sufficiente implementare un override per il metodo SelectTemplateCore(Object). Per altre info ed esempi, vedi DataTemplateSelector.

Annotazioni

Un'alternativa a DataTemplates per gestire il modo in cui gli elementi vengono creati in scenari più avanzati consiste nell'implementare il proprio IElementFactory da usare come ItemTemplate. Sarà responsabile della generazione del contenuto quando richiesto.

Configurare l'origine dati

Utilizzare la proprietà ItemsSource per specificare la raccolta da utilizzare per generare il contenuto degli elementi. È possibile impostare ItemsSource su qualsiasi tipo che implementa IEnumerable. Le interfacce di raccolta aggiuntive implementate dall'origine dati determinano quali funzionalità sono disponibili per ItemsRepeater per interagire con i dati.

Questo elenco mostra le interfacce disponibili e quando è consigliabile usare ognuna di esse.

  • IEnumerable(.NET) / IIterable

    • Può essere usata per set di dati statici di piccole dimensioni.

      Come minimo, l'origine dati deve implementare l'interfaccia IEnumerable / IIterable. Se questo è tutto ciò che è supportato, il controllo scorrerà tutti gli elementi una volta per creare una copia utilizzabile per accedere agli elementi tramite un valore di indice.

  • IReadonlyList(.NET) / IVectorView

    • Può essere usata per set di dati statici di sola lettura.

      Consente al controllo di accedere agli elementi in base all'indice, evitando la copia interna ridondante.

  • IList(.NET) / IVector

    • Può essere usata per set di dati statici.

      Consente al controllo di accedere agli elementi in base all'indice, evitando la copia interna ridondante.

      Avviso: le modifiche apportate all'elenco/vettore senza implementare INotifyCollectionChanged non verranno riflesse nell'interfaccia utente.

  • INotifyCollectionChanged(.NET) - Notifica delle modifiche nella collezione

    • Consigliata per supportare la notifica delle modifiche.

      Consente al controllo di osservare e reagire alle modifiche nell'origine dati e riflettere tali modifiche nell'interfaccia utente.

  • IObservableVector

    • Supporta la notifica delle modifiche.

      Analogamente all'interfaccia INotifyCollectionChanged , questo consente al controllo di osservare e reagire alle modifiche nell'origine dati.

      Avviso: Windows.Foundation.IObservableVector<T> non supporta un'azione "Sposta". Ciò può far sì che l'interfaccia utente di un elemento perda lo stato di visualizzazione. Ad esempio, un elemento che è attualmente selezionato e/o ha lo stato attivo dove avviene lo spostamento in seguito a un'azione 'Remove' seguita da un'azione 'Add' perderà lo stato attivo e non sarà più selezionato.

      Platform.Collections.Vector<T> usa IObservableVector<T> e ha questa stessa limitazione. Se è necessario il supporto per un'operazione "Sposta", utilizzare l'interfaccia INotifyCollectionChanged. La classe .NET ObservableCollection<T> usa INotifyCollectionChanged.

  • IKeyIndexMapping

    • Quando un identificatore univoco può essere associato a ogni elemento. Consigliata quando si usa 'Reset' come azione di modifica della raccolta.

      Consente al controllo di ripristinare in modo estremamente efficiente l'interfaccia utente esistente dopo aver ricevuto un'azione di reimpostazione forzata come parte di un evento INotifyCollectionChanged o IObservableVector. Dopo aver ricevuto una reimpostazione, il controllo userà l'ID univoco fornito per associare i dati correnti con gli elementi già creati. Senza la chiave per il mapping dell'indice, il controllo dovrebbe presupporre di dover ricominciare da zero a creare l'interfaccia utente per i dati.

Le interfacce elencate in precedenza, ad eccezione di IKeyIndexMapping, forniscono lo stesso comportamento in ItemsRepeater così come in ListView e GridView.

Le interfacce seguenti su un ItemsSource abilitano funzionalità speciali nei controlli ListView e GridView, ma attualmente non hanno effetto su ItemsRepeater:

Suggerimento

Microsoft vuole conoscere l'opinione degli utenti. Facci sapere cosa ne pensi sul progetto GitHub WinUI. È consigliabile aggiungere i propri pensieri sulle proposte esistenti, ad esempio #374: Aggiungere il supporto per il caricamento incrementale per ItemsRepeater.

Un approccio alternativo per il caricamento incrementale dei dati mentre l'utente scorre verso l'alto o verso il basso consiste nell'osservare la posizione del riquadro di visualizzazione di ScrollViewer e caricare altri dati quando il riquadro di visualizzazione sta per raggiungere le dimensioni massime.

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

Modificare il layout degli elementi

Gli elementi visualizzati da ItemsRepeater sono disposti da un oggetto Layout che gestisce il ridimensionamento e il posizionamento dei relativi elementi figlio. Se usato con ItemsRepeater, l'oggetto Layout consente la virtualizzazione dell'interfaccia utente. I layout forniti sono StackLayout e UniformGridLayout. Per impostazione predefinita, ItemsRepeater usa StackLayout con orientamento verticale.

StackLayout

StackLayout dispone gli elementi in una singola riga che è possibile orientare orizzontalmente o verticalmente.

È possibile impostare la proprietà Spaziatura per regolare la quantità di spazio tra gli elementi. La spaziatura viene applicata nella direzione dell'orientamento del layout.

Spaziatura del layout dello stack

Questo esempio illustra come impostare la proprietà ItemsRepeater.Layout su StackLayout con orientamento orizzontale e una spaziatura di 8 pixel.

<!-- 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 posiziona gli elementi in sequenza in un layout a riempimento. Gli elementi vengono disposti in ordine da sinistra a destra quando l'orientamento è Orizzontale e viene disposti dall'alto verso il basso quando l'orientamento è Verticale. Ogni elemento viene ridimensionato in modo uniforme.

Spaziatura uniforme della griglia di layout

Il numero di elementi in ogni riga di un layout orizzontale è influenzato dalla larghezza minima dell'elemento. Il numero di elementi in ogni colonna di un layout verticale è influenzato dall'altezza minima dell'elemento.

  • È possibile specificare in modo esplicito una dimensione minima da usare impostando le proprietà minItemHeight e MinItemWidth.
  • Se non specifichi una dimensione minima, la dimensione misurata del primo elemento viene considerata la dimensione minima per ogni elemento.

È anche possibile impostare la spaziatura minima per il layout da includere tra righe e colonne impostando le proprietà MinColumnSpacing e MinRowSpacing .

Ridimensionamento e spaziatura uniformi della griglia

Dopo che il numero di elementi in una riga o in una colonna è stato determinato in base alle dimensioni e alla spaziatura minime dell'elemento, potrebbe esserci dello spazio inutilizzato dopo l'ultimo elemento nella riga o nella colonna (come illustrato nell'immagine precedente). Puoi specificare se l'eventuale spazio aggiuntivo deve essere ignorato, usato per aumentare le dimensioni di ogni elemento o usato per creare spazio aggiuntivo tra gli elementi. Questo controllo è controllato dalle proprietà ItemsStretch e ItemsJustification .

È possibile impostare la proprietà ItemsStretch per specificare il modo in cui le dimensioni dell'elemento vengono aumentate per riempire lo spazio inutilizzato.

Questo elenco mostra i valori disponibili. Le definizioni presuppongono che l'Orientamento sia Orizzontale predefinito.

  • Nessuno: lo spazio aggiuntivo viene lasciato inutilizzato alla fine della riga. Si tratta dell'impostazione predefinita.
  • Riempimento: agli elementi viene assegnata una larghezza aggiuntiva per usare lo spazio disponibile (altezza se verticale).
  • Uniforme: agli elementi viene assegnata una larghezza aggiuntiva per utilizzare lo spazio disponibile e data altezza aggiuntiva per mantenere le proporzioni (altezza e larghezza vengono cambiate se verticale).

Questa immagine mostra l'effetto dei valori ItemsStretch in un layout orizzontale.

Estensione dell'elemento griglia uniforme

Quando ItemsStretch è Nessuno, è possibile impostare la proprietà ItemsJustification per specificare il modo in cui viene usato spazio aggiuntivo per allineare gli elementi.

Questo elenco mostra i valori disponibili. Le definizioni presuppongono che l'Orientamento sia Orizzontale predefinito.

  • Inizio: gli elementi sono allineati all'inizio della riga. lo spazio aggiuntivo viene lasciato inutilizzato alla fine della riga. Si tratta dell'impostazione predefinita.
  • Centro: gli elementi sono allineati al centro della riga. Lo spazio aggiuntivo viene suddiviso in modo uniforme all'inizio e alla fine della riga.
  • Fine: gli elementi sono allineati alla fine della riga. Lo spazio aggiuntivo viene lasciato inutilizzato all'inizio della riga.
  • SpaceAround: gli elementi vengono distribuiti in modo uniforme. Un'uguale quantità di spazio viene aggiunta prima e dopo ogni elemento.
  • SpaceBetween: gli elementi vengono distribuiti uniformemente. Un'uguale quantità di spazio viene aggiunta tra ogni elemento. Non viene aggiunto alcuno spazio all'inizio e alla fine della riga.
  • SpaceEvenly: gli elementi vengono distribuiti uniformemente con una quantità di spazio uguale tra ogni elemento e all'inizio e alla fine della riga.

Questa immagine mostra l'effetto dei valori ItemsStretch in un layout verticale (applicato alle colonne anziché alle righe).

Giustificazione uniforme degli elementi della griglia

Suggerimento

La proprietà ItemsStretch influisce sul passaggio di misura del layout. La proprietà ItemsJustification influisce sul passaggio di disposizione del layout.

In questo esempio viene illustrato come impostare la proprietà ItemsRepeater.Layout su 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>

Eventi del ciclo di vita

Quando si ospitano elementi in itemsRepeater, potrebbe essere necessario intervenire quando viene visualizzato o arrestato un elemento, ad esempio avviare un download asincrono di alcuni contenuti, associare l'elemento a un meccanismo per tenere traccia della selezione o arrestare alcune attività in background.

In un controllo di virtualizzazione non puoi fare affidamento sugli eventi Loaded/Unloaded perché l'elemento potrebbe non essere rimosso dalla struttura ad albero visuale in tempo reale quando viene riciclato. Invece, vengono forniti altri eventi per gestire il ciclo di vita degli elementi. Questo diagramma mostra il ciclo di vita di un elemento in un ItemsRepeater e quando vengono generati gli eventi correlati.

Diagramma degli eventi del ciclo di vita

  • ElementPrepared si verifica ogni volta che un elemento è pronto per l'uso. Si verifica sia per un elemento appena creato che per uno già esistente che viene riutilizzato dalla coda di riciclo.
  • ElementClearing si verifica immediatamente ogni volta che un elemento è stato inviato alla coda di riciclo, ad esempio quando non rientra nell'intervallo di elementi realizzati.
  • ElementIndexChanged si verifica per ogni UIElement realizzato in cui l'indice per l'elemento rappresentato è stato modificato. Ad esempio, quando viene aggiunto o rimosso un altro elemento nell'origine dati, l'indice per gli elementi successivi nell'ordinamento riceve questo evento.

Questo esempio mostra come usare questi eventi per associare un servizio di selezione personalizzato per tenere traccia della selezione degli elementi in un controllo personalizzato che usa ItemsRepeater per visualizzare gli elementi.

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

Ordinamento, filtro e reimpostazione dei dati

Quando si eseguono azioni come il filtro o l'ordinamento del set di dati, in genere è possibile confrontare il set di dati precedente con i nuovi dati, quindi inviare notifiche di modifica granulari tramite INotifyCollectionChanged. Tuttavia, spesso è più facile sostituire completamente i dati precedenti con i nuovi dati e attivare una notifica di modifica della raccolta usando l'azione Reimposta .

In genere, una reimpostazione fa sì che un controllo rilasci gli elementi figlio esistenti e ricominci da capo, creando l'interfaccia utente dall'inizio alla posizione di scorrimento 0, non sapendo esattamente come sono cambiati i dati durante la reimpostazione.

Tuttavia, se la raccolta assegnata come ItemsSource supporta identificatori univoci implementando l'interfaccia IKeyIndexMapping , ItemsRepeater può identificare rapidamente:

  • UIElement riutilizzabili per i dati esistenti prima e dopo la reimpostazione
  • elementi precedentemente visibili che sono stati rimossi
  • nuovi elementi aggiunti che saranno visibili

In questo modo ItemsRepeater evita di ricominciare dalla posizione di scorrimento 0. Può inoltre ripristinare rapidamente gli elementi UIElement per i dati che non sono stati modificati durante la reimpostazione, assicurando prestazioni migliori.

Questo esempio mostra come visualizzare un elenco di elementi in uno stack verticale in cui MyItemsSource è un'origine dati personalizzata che esegue il wrapping di un elenco sottostante di elementi. Espone una proprietà Data che può essere usata per riassegnare un nuovo elenco da usare come origine degli elementi, che attiva quindi una reimpostazione.

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

Creare un controllo raccolta personalizzato

È possibile utilizzare ItemsRepeater per creare un controllo raccolta personalizzato completo con il proprio tipo di controllo per presentare ogni elemento.

Annotazioni

Questo comportamento è simile all'uso di ItemsControl, ma invece di derivare da ItemsControl e inserire itemsPresenter nel modello di controllo, si deriva da Control e si inserisce itemsRepeater nel modello di controllo. Il controllo raccolta personalizzato "ha un" ItemsRepeater a confronto con "è un" ItemsControl. Ciò implica che dovrai anche scegliere in modo esplicito le proprietà da esporre, anziché quali proprietà ereditate non supportare.

In questo esempio viene illustrato come inserire un ItemsRepeater nel modello di un controllo personalizzato denominato MediaCollectionView ed esporne le proprietà.

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

Visualizzare elementi raggruppati

È possibile annidare un itemsRepeaternell'itemTemplate di un altro itemsRepeater per creare layout di virtualizzazione annidati. Il framework farà un uso efficiente delle risorse riducendo al minimo la realizzazione non necessaria di elementi che non sono visibili o in prossimità del riquadro di visualizzazione corrente.

Questo esempio mostra come visualizzare un elenco di elementi raggruppati in uno stack verticale. L'ItemsRepeater esterno genera ogni gruppo. Nel modello per ogni gruppo un altro ItemsRepeater genera gli elementi.

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

L'immagine seguente mostra il layout di base creato usando l'esempio precedente come linea guida.

Layout annidato con ripetizione di elementi

L'esempio seguente mostra un layout per un'app con diverse categorie che possono essere modificate con le preferenze dell'utente e vengono presentate come elenchi di scorrimento in orizzontale. Il layout di questo esempio è rappresentato anche dall'immagine precedente.

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

Rendere visibile un elemento

Il framework XAML gestisce già il modo per rendere visibile un oggetto FrameworkElement quando 1) riceve lo stato attivo della tastiera o 2) riceve lo stato attivo dell'Assistente vocale. Potrebbero esserci altri casi in cui è necessario rendere visibile in modo esplicito un elemento. Ad esempio, in risposta a un'azione dell'utente o per ripristinare lo stato dell'interfaccia utente dopo lo spostamento tra le pagine.

Rendere visibile un elemento virtualizzato comporta le operazioni seguenti:

  1. Realizzare un oggetto UIElement per un elemento
  2. Eseguire il layout per assicurarsi che l'elemento abbia una posizione valida
  3. Avviare una richiesta per rendere visibile l'elemento realizzato

L'esempio seguente illustra questi passaggi nell'ambito del ripristino della posizione di scorrimento di un elemento in un elenco verticale semplice dopo uno spostamento tra le pagine. Nel caso di dati gerarchici che usano ItemsRepeater annidati l'approccio è essenzialmente lo stesso, ma deve essere eseguito a ogni livello della gerarchia.

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

Abilitare l'accessibilità

ItemsRepeater non offre un'esperienza di accessibilità predefinita. La documentazione sull'usabilità per le app di Windows offre un'ampia gamma di informazioni che consentono di garantire che l'app offra un'esperienza utente inclusiva. Se stai usando ItemsRepeater per creare un controllo personalizzato, assicurati di consultare la documentazione sui peer di automazione personalizzate.

Tastiera

Il supporto minimo per la messa a fuoco tramite tastiera fornito da ItemsRepeater è basato sulla navigazione direzionale 2D di XAML per la tastiera.

Navigazione direzionale

Il modalità XYFocusKeyboardNavigation di ItemsRepeater è Abilitato per impostazione predefinita. A seconda dell'esperienza prevista, è consigliabile aggiungere il supporto per le comuni interazioni da tastiera come Home, End, PageUp e PageDown.

ItemsRepeater garantisce automaticamente che l'ordine di tabulazione predefinito per i relativi elementi (indipendentemente dal fatto che siano virtualizzati o meno) segua lo stesso ordine degli elementi nei dati. Per impostazione predefinita, ItemsRepeater ha la proprietà TabFocusNavigation impostata su Once anziché l'impostazione predefinita comune di Local.

Annotazioni

ItemsRepeater non ricorda automaticamente l'ultimo elemento con lo stato attivo. Ciò significa che quando un utente usa MAIUSC+TAB, può essere riportato all'ultimo elemento realizzato.

Annunciando "Elemento X di Y" nei screen reader

È necessario gestire l'impostazione delle proprietà di automazione appropriate, ad esempio i valori per PositionInSet e SizeOfSet, e assicurarsi che rimangano up-to-date quando vengono aggiunti, spostati, rimossi e così via.

In alcuni layout personalizzati potrebbe non esserci una sequenza ovvia all'ordine visivo. Gli utenti si aspettano almeno che i valori delle proprietà PositionInSet e SizeOfSet usate dalle utilità per la lettura dello schermo corrispondano all'ordine in cui gli elementi vengono visualizzati nei dati (offset di 1 per la corrispondenza con il conteggio naturale rispetto a quello in base 0).

Il modo migliore per ottenere questo risultato consiste nell'avere il peer di automazione per l'elemento di controllo implementare i metodi GetPositionInSetCore e GetSizeOfSetCore, segnalando la posizione dell'elemento nel set di dati rappresentato dal controllo. Il valore viene calcolato solo in fase di esecuzione quando vi si accede tramite assistive technology e mantenerlo aggiornato non costituisce un problema. Il valore corrisponde all'ordine dei dati.

In questo esempio viene illustrato come eseguire questa operazione quando si presenta un controllo personalizzato denominato 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 e WinUI 2

Importante

Le informazioni e gli esempi in questo articolo sono ottimizzati per le app che usano Windows App SDK e WinUI 3, ma sono generalmente applicabili alle app UWP che usano WinUI 2. Per informazioni ed esempi specifici della piattaforma, consultare le indicazioni di riferimento sulle API UWP.

Questa sezione contiene informazioni necessarie per usare il controllo in un'app UWP o WinUI 2.

ItemsRepeater per le app UWP richiede WinUI 2. Per maggiori informazioni, incluse le istruzioni per l'installazione, vedere WinUI 2. Le API per questo controllo esistono nello spazio dei nomi Microsoft.UI.Xaml.Controls.

Per usare il codice in questo articolo con WinUI 2, usa un alias in XAML (usiamo muxc) per rappresentare le API della libreria dell'interfaccia utente di Windows incluse nel progetto. Per altre informazioni, vedi Introduzione a WinUI 2 .

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

<muxc:ItemsRepeater />