Condividi tramite


ItemsRepeater

Usa un ItemsRepeater per creare esperienze di raccolte personalizzate tramite un sistema di layout flessibile, visualizzazioni personalizzate e virtualizzazione.

A differenza di ListView, ItemsRepeater non offre un'esperienza per l'utente finale completa: non ha un'interfaccia utente predefinita e non fornisce alcun criterio relativo a stato attivo, selezione o interazione con l'utente. Si tratta invece di un blocco predefinito che puoi 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.

Concettualmente, puoi considerare ItemsRepeater un pannello basato sui dati, anziché 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.

È il controllo giusto?

Usa 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 integrato per visualizzare i dati in un elenco o una griglia con una personalizzazione minima, prendi in considerazione la possibilità di usare un controllo ListView o GridView.

ItemsRepeater non dispone di una raccolta di elementi predefinita. Se devi fornire una raccolta di elementi direttamente, anziché associandoli a un'origine dati distinta, probabilmente hai bisogno di un'esperienza basata su criteri di livello superiore e dovresti usare ListView o GridView.

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

Piattaforma 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, usare un alias in XAML (si usa muxc) per rappresentare le API della libreria dell'interfaccia utente di Windows incluse nel progetto. Per altre informazioni, vedere Attività iniziali di WinUI 2.

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

<muxc:ItemsRepeater />

Scorrimento con ItemsRepeater

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

Quando si usa ItemsRepeater, devi fornire una funzionalità di scorrimento inserendola in un controllo ScrollViewer.

Nota

Se l'app viene eseguita in versioni precedenti di Windows (rilasciate prima di Windows 10, versione 1809), devi anche ospitare ScrollViewer all'interno di ItemsRepeaterScrollHost.

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

Se l'app viene eseguita solo in versioni recenti di Windows 10, versione 1809 o versioni successive, non c'è alcuna necessità di usare ItemsRepeaterScrollHost.

Nelle versioni precedenti a Windows 10, versione 1809 ScrollViewer non implementa l'interfaccia IScrollAnchorProvider richiesta da ItemsRepeater. ItemsRepeaterScrollHost consente a ItemsRepeater di coordinarsi con ScrollViewer nelle versioni precedenti per preservare 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, devi assegnare al controllo i dati da visualizzare impostando la proprietà ItemsSource. Specifica quindi come visualizzare gli elementi impostando la proprietà ItemTemplate.

ItemsSource

Per popolare la visualizzazione, imposta la proprietà ItemsSource su una raccolta di elementi dati. Qui, ItemsSource è impostata 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 altre informazioni sul data binding, vedi Panoramica del data binding.

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

ItemTemplate

Per specificare come viene visualizzato un elemento dati, imposta la proprietà ItemTemplate su un 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 usa la rappresentazione 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 imposta lo stile di TextBlock in modo da visualizzare la stringa in verde acqua.

Nota

Quando usi l'estensione di markup x:Bind in un oggetto DataTemplate, devi specificare l'oggetto DataType (x: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 appaiono gli elementi quando vengono visualizzati con questo DataTemplate.

Elementi visualizzati con un modello di dati

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

Suggerimento

Per praticità quando vuoi dichiarare il modello inline anziché farvi riferimento come risorsa statica, puoi 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 altri controlli raccolta, ItemsRepeater non esegue il wrapping di elementi da DataTemplate con un contenitore di elementi aggiuntivo che include criteri predefiniti, come margini, spaziatura interna, elementi visivi di selezione o un puntatore su uno stato di visualizzazione. Al contrario, ItemsRepeater presenta solo quello che è stato definito in DataTemplate. Se vuoi che gli elementi abbiano lo stesso aspetto di un elemento della visualizzazione elenco, puoi includere in modo esplicito un contenitore, come ListViewItem, nel modello di dati. ItemsRepeater mostrerà gli elementi visivi di ListViewItem, ma usa automaticamente altre funzionalità, come la selezione o la visualizzazione della casella di controllo a seleziona multipla.

Analogamente, se la raccolta dati è una raccolta di controlli effettivi, come Button (List<Button>), puoi inserire ContentPresenter in DataTemplate per visualizzare il controllo.

DataTemplateSelector

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

In questo esempio si presuppone che sia stato definito un DataTemplateSelector che decide tra due diversi DataTemplate per rappresentare un elemento grande e piccolo.

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

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

Nota

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

Configurare l'origine dati

Usa la proprietà ItemsSource per specificare la raccolta da usare per generare il contenuto degli elementi. Puoi 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.

      Attenzione: le modifiche apportate all'elenco/vettore senza implementare INotifyCollectionChanged non si rifletteranno nell'interfaccia utente.

  • INotifyCollectionChanged(.NET)

    • 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.

      Come l'interfaccia INotifyCollectionChanged, consente al controllo di osservare e reagire alle modifiche nell'origine dati.

      Attenzione: Windows.Foundation.IObservableVectorT<> non supporta un'azione 'Move'. 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 è richiesto il supporto per un'azione 'Move', usa 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 molto efficiente l'interfaccia utente esistente dopo aver ricevuto un'azione hard 'Reset' 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. Comunicaci cosa ne pensi del progetto WinUI GitHub. È 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 in base a un oggetto Layout che gestisce il ridimensionamento e il posizionamento degli elementi figlio. Se usato con ItemsRepeater, l'oggetto Layout consente la virtualizzazione dell'interfaccia utente. I layout disponibili sono StackLayout e UniformGridLayout. Per impostazione predefinita, ItemsRepeater usa StackLayout con orientamento verticale.

StackLayout

StackLayout dispone gli elementi su una singola riga che puoi orientare orizzontalmente o verticalmente.

Puoi impostare la proprietà Spacing in modo da regolare la quantità di spazio tra gli elementi. La spaziatura viene applicata nella direzione della proprietà Orientation del layout.

Spaziatura di StackLayout

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 di disposizione testo. Gli elementi vengono disposti in ordine da sinistra a destra quando Orientation è Horizontal e dall'alto verso il basso quando l'orientamento è Vertical. Ogni elemento viene ridimensionato in modo uniforme.

Spaziatura del layout griglia uniforme

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.

  • Puoi 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.

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

Dimensioni e spaziatura della griglia uniformi

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. Queste opzioni sono controllate dalle proprietà ItemsStretch e ItemsJustification.

Puoi impostare la proprietà ItemsStretch per specificare in che modo le dimensioni dell'elemento vengono aumentate fino a riempire lo spazio inutilizzato.

Questo elenco mostra i valori disponibili. Le definizioni presuppongono per Orientation un valore predefinito di Horizontal.

  • Nessuno: lo spazio aggiuntivo viene lasciato inutilizzato alla fine della riga. Si tratta dell'impostazione predefinita.
  • Riempire: gli elementi dispongono di maggiore larghezza per usare lo spazio disponibile (altezza per l'orientamento verticale).
  • Uniformare: gli elementi dispongono di maggiore larghezza per usare lo spazio disponibile e di maggiore altezza per mantenere le proporzioni (altezza e larghezza si invertono per l'orientamento verticale).

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

Estensione degli elementi della griglia uniforme

Quando ItemsStretch è None, puoi impostare la proprietà ItemsJustification per specificare come usare lo spazio aggiuntivo per allineare gli elementi.

Questo elenco mostra i valori disponibili. Le definizioni presuppongono per Orientation un valore predefinito di Horizontal.

  • Inizio: gli elementi sono allineati con l'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 con la 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 sono distribuiti in modo uniforme con un'uguale quantità di spazio tra ogni elemento e all'inizio e alla fine della riga.

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

Giustificazione degli elementi della griglia uniforme

Suggerimento

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

Questo esempio mostra 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 ospiti gli elementi in un ItemsRepeater, potresti dover eseguire alcune operazioni quando un elemento viene visualizzato o smette di essere visualizzato, come l'avvio di un download asincrono di alcuni contenuti, l'associazione dell'elemento con un meccanismo per tenere traccia della selezione o l'arresto di 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 degli elementi realizzati.
  • ElementIndexChanged si verifica per ogni UIElement realizzato in cui è stato modificato l'indice per l'elemento che rappresenta. 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 di un set di dati, di norma potresti confrontare il set di dati precedente con i nuovi dati e quindi emettere le notifiche di modifica granulare tramite INotifyCollectionChanged. Tuttavia, spesso è più facile sostituire completamente i dati precedenti con i nuovi dati e attivare una notifica di modifica della raccolta usando invece l'azione Reset.

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 gli identificatori univoci con l'implementazione dell'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 di elementi sottostante. 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 usare ItemsRepeater per creare un controllo raccolta personalizzato completo con il proprio tipo di controllo per presentare ogni elemento.

Nota

È come usare ItemsControl, ma invece di derivare da ItemsControl e inserire ItemsPresenter nel modello del controllo, si deriva da Control e si inserisce ItemsRepeater nel modello del controllo. Il controllo raccolta personalizzato "ha un" ItemsRepeater e non "è un" ItemsControl. Ciò implica che dovrai anche scegliere in modo esplicito le proprietà da esporre, anziché quali proprietà ereditate non supportare.

Questo esempio mostra come inserire 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

Puoi annidare ItemsRepeater nell'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 ItemsRepeater

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 fornisce un'esperienza di accessibilità predefinita. La documentazione sull'usabilità per le app di Windows offre una vasta gamma di informazioni che ti consentono di garantire un'esperienza utente inclusiva nella tua app. Se usi ItemsRepeater per creare un controllo personalizzato, assicurati pertanto di consultare la documentazione relativa ai peer di automazione personalizzati.

Tastiera

Il supporto minimo per la tastiera per lo spostamento dello stato attivo fornito da ItemsRepeater è basato sugli spostamenti direzionali 2D per tastiera di XAML.

Spostamenti direzionali

Per impostazione predefinita la modalità XYFocusKeyboardNavigation di ItemsRepeater è impostata su Enabled. A seconda dell'esperienza desiderata, prendi in considerazione la possibilità di aggiungere il supporto per le interazioni tramite tastiera più comuni, come Home, Fine, Pag Su e Pag Giù.

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 invece del valore predefinito comune di Local.

Nota

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.

Annuncio di un "elemento X di Y" nelle utilità per la lettura dello schermo

È necessario gestire l'impostazione delle proprietà di automazione appropriate, come i valori per PositionInSet e SizeOfSet, e garantire che rimangano aggiornati quando gli elementi 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 nel fare in modo che il peer di automazione per il controllo dell'elemento implementi i metodi GetPositionInSetCore e GetSizeOfSetCore e riporti 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.

Questo esempio mostra come farlo quando si presenta un controllo personalizzato chiamato 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();
    }
}