ItemsRepeater

Используйте ItemsRepeater для создания взаимодействия настраиваемых коллекций с помощью системы гибкого макета, пользовательских представлений и виртуализации.

В отличие от ListView, ItemsRepeater не обеспечивает комплексное взаимодействие с конечным пользователем — он не содержит стандартного пользовательского интерфейса и не предоставляет политику в отношении фокуса, выбора или взаимодействия с пользователем. Вместо этого он является стандартным блоком, который можно использовать для создания собственных уникальных интерфейсов, основанных на коллекции, и пользовательских элементов управления. Поскольку он не содержит встроенной политики, то предоставляет возможность подключения политики для создания необходимого взаимодействия. Вы можете определить, например, макет для использования, политику поддержки клавиатуры, политику выбора и т. д.

Концептуально ItemsRepeater можно представить скорее как управляемую данными панель, чем как полное управление, подобное ListView. Вы указываете коллекцию отображаемых элементов данных, шаблон элемента, который создает элемент пользовательского интерфейса для каждого элемента данных, и макет, который определяет размер и положение элементов. Затем ItemsRepeater, на основе источника данных, создает дочерние элементы и отображает их в соответствии с шаблоном элемента и макетом. Поскольку ItemsRepeater может загрузить содержимое для представления элементов данных на основе критериев, указанных в селекторе шаблонов данных, отображаемые элементы не обязательно должны быть однородными.

Выбор правильного элемента управления

Используйте ItemsRepeater, чтобы создать пользовательские представления коллекций данных. Хотя его можно использовать для предоставления базового набора элементов, Вы часто можете использовать его в качестве отображаемого элемента в шаблоне пользовательского элемента управления.

Если требуется, чтобы стандартный элемент управления отображал данные в списке или сетке с минимальной настройкой, рассмотрите возможность использования ListView или GridView.

ItemsRepeater не содержит встроенную коллекцию элементов. Если Вам нужно напрямую предоставить коллекцию элементов, вместо привязки к отдельному источнику данных, то скорее всего, Вам необходимо взаимодействие с более высоким уровнем политики. Поэтому используйте ListView или GridView.

ItemsControl и ItemsRepeater оба позволяют создать настраиваемые взаимодействия с коллекцией, но ItemsRepeater, в отличии от ItemsControl поддерживает виртуализацию макетов пользовательского интерфейса. Мы рекомендуем использовать ItemsRepeater вместо ItemsControl, независимо от того, представляет он несколько элементов данных или создает настраиваемые элементы управления коллекции.

UWP и WinUI 2

Внимание

Сведения и примеры в этой статье оптимизированы для приложений, использующих пакет SDK для приложений Windows и WinUI 3, но обычно применимы к приложениям UWP, использующим WinUI 2. См. справочник по API UWP для конкретных сведений и примеров платформы.

В этом разделе содержатся сведения, необходимые для использования элемента управления в приложении UWP или WinUI 2.

Для приложений UWP itemsRepeater требуется WinUI 2. Дополнительные сведения, включая инструкции по установке, см. в статье WinUI 2. API для этого элемента управления существуют в пространстве имен Microsoft.UI.Xaml.Controls .

Чтобы использовать код в этой статье с WinUI 2, используйте псевдоним в XAML (мы используем muxc), чтобы представить API библиотеки пользовательского интерфейса Windows, включенные в проект. Дополнительные сведения см. в статье "Начало работы с WinUI 2 ".

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

<muxc:ItemsRepeater />

Прокрутка при помощи ItemsRepeater

ItemsRepeater не является разработанным на основе элемента управления, поэтому он не имеет шаблон элемента управления. Таким образом он не содержит любые встроенные режимы прокрутки, подобно ListView или другим элементам управления в коллекции.

При использовании ItemsRepeater, необходимо предоставить функциональные возможности прокрутки, переместив ее в элемент управления ScrollViewer.

Примечание.

Если приложение работает в более ранних версиях Windows, выпущенных перед Windows 10, версии 1809, вам нужно разместить ScrollViewer также внутри ItemsRepeaterScrollHost.

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

Если приложение работает только в последних версиях Windows 10 — версии 1809 и более поздней — нет необходимости в использовании ItemsRepeaterScrollHost.

ScrollViewer для Windows 10, версия 1809, и предыдущих версий не внедрил интерфейс IScrollAnchorProvider, требуемый для ItemsRepeater. ItemsRepeaterScrollHost позволяет ItemsRepeater координировать с ScrollViewer более ранние версии, чтобы правильно сохранить отображаемое расположение элементов при просмотре пользователем. В противном случае элементы могут отображаться при перемещении, или внезапно исчезать после изменения элементов в списке или размера приложения.

Создание ItemsRepeater

Приложение коллекции WinUI 3 включает интерактивные примеры большинства элементов управления, функций и функций WinUI 3. Получение приложения из Microsoft Store или получение исходного кода на GitHub

Чтобы использовать ItemsRepeater, необходимо предоставить ему данные для отображения, задав свойство ItemsSource. Затем, определите порядок отображения элементов, задав свойство ItemTemplate.

ItemsSource

Чтобы заполнить представление, задайте свойство ItemsSource в коллекцию элементов данных. В этом коде ItemsSource задается непосредственно экземпляру коллекции.

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

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

Свойство ItemsSource можно также связать с коллекцией в среде XAML. См.сведения о привязке данных.

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

ItemTemplate

Чтобы указать, как визуализируются элемент данных, задайте свойство ItemTemplate в DataTemplate или DataTemplateSelector, определенный вами. Шаблон данных определяет способ визуализации данных. По умолчанию элемент данных отображается в представлении с TextBlock, который представляет объект данных в виде строки.

Тем не менее обычно требуется отобразить более широкое представление данных с помощью шаблона, который определяет макет и внешний вид одного или нескольких элементов управления, используемых для отображения отдельного элемента. Элементы управления, используемые в шаблоне, могут быть привязаны к свойствам объекта данных или иметь статическое содержимое, задаваемое внутри кода.

DataTemplate

В этом примере объект данных является простой строкой. DataTemplate включает изображения слева от текста и стили TextBlock для отображения строки в сине-зеленом цвете.

Примечание.

При использовании расширения разметки x:Bind в DataTemplate необходимо задать DataType (x:DataType) в 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>

Вот как будут выглядеть элементы при отображении с помощью DataTemplate.

Элементы, отображаемые с помощью шаблона данных

Число компонентов для элемента, используемое в DataTemplate, может иметь значительное влияние на производительность, если представление отображает большое количество элементов. Дополнительные сведения и примеры использования DataTemplateдля определения внешнего вида элементов в списке см. в разделе Контейнеры элементов и шаблоны.

Совет

Для удобства при объявлении встроенного шаблона, вместо того, чтобы ссылаться на статический ресурс, DataTemplate или DataTemplateSelectorможно указать как непосредственный дочерний элемент ItemsRepeater. Он будет назначен в качестве значения свойства ItemTemplate. Например это действительно для:

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

Совет

В отличие от ListView и других элементов управления коллекции, ItemsRepeater не переносит элементы из DataTemplate с помощью дополнительного контейнера элемента, который содержит стандартную политику, как например поля, заполнение, выбор визуальных элементов или указатель на визуальное состояние. Вместо этого ItemsRepeater отображает только данные, определенные в DataTemplate. Если требуется, чтобы элементы выглядели как элемент представления списка, можно явно включить контейнер в шаблон данных, как ListViewItem. ItemsRepeater отображает визуальные элементы ListViewItem, но не использует автоматически другие функции, такие как выбор или отображение нескольких флажков.

Аналогично, если коллекция данных является коллекцией фактических элементов управления, на подобие кнопки (List<Button>),ContentPresenter можно поместить в ваш DataTemplate, чтобы отобразить элемент управления.

DataTemplateSelector

Отображаемые в представлении элементы не обязательно должны быть одного типа. Свойство ItemTemplate можно предоставить с DataTemplateSelector, чтобы выбрать разные DataTemplate, основываясь на указанной критерии.

В этом примере предполагается, что DataTemplateSelector определен для выбора между двумя разными DataTemplate, чтобы представлять большой и малый элемент.

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

При определении DataTemplateSelector для использования с ItemsRepeater необходимо только реализовать переопределение для метода SelectTemplateCore(Object). Дополнительные сведения и примеры см. в разделеDataTemplateSelector.

Примечание.

Альтернатива DataTemplate для управления тем, как элементы создаются в более сложных сценариях, заключается в реализации собственного IElementFactory для использования в качестве ItemTemplate. Он будет отвечать за создание содержимого по запросу.

Настройка источника данных

Используйте свойство ItemsSource, чтобы указать коллекцию, используемую для создания содержимого элементов. ItemsSource можно задать любому типу, реализующему IEnumerable. Дополнительные интерфейсы коллекции, реализованные источником данных, определяют доступную функцию для ItemsRepeater, чтобы взаимодействовать с данными.

Этот список содержит доступные интерфейсы, а также информацию о том, когда следует использовать каждый из них.

  • IEnumerable(.NET) / IIterable

    • Можно использовать для небольших, статических наборов данных.

      Источник данных должен, как минимум, реализовывать интерфейс IEnumerable/IIterable. Если это все поддерживаемые интерфейсы, то элемент управления будет перебирать все один раз, чтобы создать копию, которую он может использовать для доступа к элементам через значение индекса.

  • IReadonlyList(.NET) / IVectorView

    • Можно использовать для статических, нередактируемых наборов данных.

      Включает элемент управления для доступа к элементам с помощью индекса и позволяет избежать избыточных внутренних копий.

  • IList(.NET) / IVector

    • Можно использовать для статических наборов данных.

      Включает элемент управления для доступа к элементам с помощью индекса и позволяет избежать избыточных внутренних копий.

      Предупреждение. Изменения списка или вектора без реализации INotifyCollectionChanged не будут отражены в пользовательском интерфейсе.

  • INotifyCollectionChanged(.NET)

    • Рекомендуется для поддержки уведомления об изменениях.

      Позволяет элементу управления наблюдать за изменениями в источнике данных и реагировать на них, а также отображать эти изменения в пользовательском интерфейсе.

  • IObservableVector

    • Поддерживает уведомление об изменениях

      Как и интерфейс INotifyCollectionChanged, он позволяет элементу управления наблюдать за изменениями в источнике данных и реагировать на них.

      Предупреждение: Windows.Foundation.IObservableVector<T> не поддерживает действие Move. Это может вызвать потерю визуального состояния пользовательского интерфейса для элемента. Например, элемент, выбранный в данный момент и/или который фокусируется на месте, куда выполнено перемещение, при использовании команды "Удалить", а затем "Добавить" теряет фокус и не поддается выбору.

      Платформа.Collections.Vector<T> использует IObservableVector<T> и имеет то же ограничение. Если необходима поддержка для действия "Перемещение", используйте интерфейс INotifyCollectionChanged. Класс T .NET ObservableCollection использует> INotifyCollectionChanged<.

  • IKeyIndexMapping

    • Уникальный идентификатор можно связать с каждым элементом. Рекомендуется при использовании действия "Сброс" в качестве действия изменения коллекции.

      Позволяет элементу управления очень эффективно восстановить существующий пользовательский интерфейс после сложного действия "Сброс", как части события INotifyCollectionChanged или IObservableVector. После сброса элемент управления будет использовать указанный уникальный идентификатор, чтобы сопоставить текущие данные с уже созданными элементами. Без ключа для сопоставления индекса элементу управления будет необходимо начать с нуля создание пользовательского интерфейса для данных.

Интерфейсы, перечисленные выше, отличные от IKeyIndexMapping, ведут себя в ItemsRepeater также, как в ListView и GridView.

Следующие интерфейсы ItemsSource позволяют использовать специальные функции в элементах управления ListView и GridView, но в настоящее время они не оказывают влияния на ItemsRepeater:

Совет

Ждем ваших отзывов! Сообщите нам, что вы думаете о проекте WinUI GitHub. Попробуйте добавить свои мысли о существующих предложениях, таких как #374: добавление добавочной поддержки загрузки для ItemsRepeater.

Альтернативный метод для инкрементной загрузки данных при прокручивании пользователем вверх или вниз заключается в наблюдении за положением окна просмотра ScrollViewer и загрузке большего количества данных по мере приближения области просмотра к экстенту.

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

Изменение макета элементов

Отображаемые с помощью ItemsRepeater элементы, упорядочены по объектумакета, который управляет определением размеров и положения его дочерних элементов. При использовании с ItemsRepeater объект макета позволяет выполнять виртуализацию пользовательского интерфейса. Предоставленными макетами являются StackLayout и UniformGridLayout. По умолчанию ItemsRepeater использует StackLayout с вертикальной ориентацией.

StackLayout

StackLayout выравнивает элементы в одну линию, которую можно размещать по горизонтали или вертикали.

Вы можете задать свойство интервала, чтобы отрегулировать величину пространства между элементами. Интервал применяется в направлении ориентации макета.

Интервал макета стека

В этом примере показано, как присвоить свойство ItemsRepeater.Layout для StackLayout с горизонтальной ориентацией и интервалом 8 пикселей.

<!-- 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 размещает элементы последовательно в макете переноса. Элементы располагаются в порядке слева направо, если значение параметра Orientation — равно Horizontal (по горизонтали), и сверху вниз, если значение равно Vertical (по вертикали). Все элементы имеют одинаковый размер.

Универсальный интервал макета сетки

Число элементов в каждой строке горизонтального макета зависит от минимальной ширины элемента. Число элементов в каждом столбце вертикального макета зависит от минимальной высоты элемента.

  • Минимальный размер можно явно указать с помощью свойств MinItemHeight и MinItemWidth.
  • Если не указать минимальный размер, размер первого элемента станет минимальным размером каждого элемента.

Вы можете также задать минимальный интервал для макета, чтобы включить его между строками и столбцами, задав свойства MinColumnSpacing и MinRowSpacing.

Универсальный интервал и размер сетки

После определения количества элементов в строке или столбце в зависимости от минимального размера и интервалов, может возникнуть неиспользуемое место после последнего элемента в строке или столбце (как показано на предыдущем рисунке). Вы можете указать,что нужно сделать с оставшимся пространством — игнорировать его, использовать для увеличения размера каждого элемента или создать дополнительное пространство между элементами. Этим управляют свойства ItemsStretch и ItemsJustification.

Чтобы указать, как увеличивается размер элемента для заполнения всего неиспользуемого пространства, можно задать свойство ItemsStretch.

В списке содержатся доступные значения. Определения предполагают, что для параметра Orientation задано по умолчанию Horizontal (по горизонтали).

  • Нет: дополнительное пространство остается неиспользуемо в конце строки. Это значение по умолчанию.
  • Заливка: элементы получают дополнительную ширину, чтобы использовать доступное пространство (высота, если вертикали).
  • Единообразие: элементы получают дополнительную ширину, чтобы использовать доступное пространство, и с учетом дополнительной высоты для поддержания пропорции (высота и ширина переключаются, если вертикали).

На этом изображении показано результат влияние значения ItemsStretch в горизонтальном макете.

Универсальное растяжение элемента сетки

Если для параметра ItemsStretch задано значение None (Нет), можно задать свойство ItemsJustification, чтобы указать, как используется дополнительное пространство для выравнивания элементов.

В списке содержатся доступные значения. Определения предполагают, что для параметра Orientation задано по умолчанию Horizontal (по горизонтали).

  • Начало. Элементы выровнены по началу строки. Неиспользованное дополнительное пространство содержится в конце строки. Это значение по умолчанию.
  • Центр: элементы выровнены в центре строки. Дополнительное пространство делится поровну в начало и конец строки.
  • Конец: элементы выровнены с концами строки. Неиспользованное дополнительное пространство содержится в начале строки.
  • Пространство. Элементы распределяются равномерно. Равное количество места добавляется до и после каждого элемента.
  • SpaceBetween: элементы распределяются равномерно. Равное количество места добавляется между каждым элементом. Пространство не добавляется в начале и конце строки.
  • SpaceEvenly: элементы распределяются равномерно с равным объемом пространства между каждым элементом и в начале и конце строки.

На этом изображении показано результат влияния значения ItemsStretch, в макете по вертикали (применяется к столбцам, а не строкам).

Универсальное обоснование элемента сетки

Совет

Свойство ItemsStretch влияет на этапизмерения макета. Свойство ItemsJustification влияет на этапупорядочивания макета.

В этом показано, как задать свойство ItemsRepeater.Layout для 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>

События жизненного цикла

При размещении элементов в ItemsRepeater, может потребоваться выполнение определенного действия при отображении элемента или остановке его отображения, например, запустить асинхронную загрузку содержания, провести связку элемента с механизмом для отслеживания трека или остановить некоторые фоновые задачи.

В элементе управления виртуализации нельзя полагаться на события Loaded/Unloaded, поскольку элемент может быть не удален из динамического визуального дерева после его повторного запуска. Чтобы управлять жизненным циклом элементов, вместо них предоставляются другие события. На этой схеме показан жизненный цикл элемента в ItemsRepeater, а также при создании связанных событий.

Схема событий жизненного цикла

  • ElementPrepared возникает каждый раз, когда элемент становится готов к использованию. Эта операция выполняется для только что созданного элемента , а также элемента, который уже существует, и используется повторно из очереди повторного применения.
  • ElementClearing возникает каждый раз непосредственно когда элемент был отправлен в очередь для повторного запуска, например когда она находится за пределами диапазона элементов.
  • ElementIndexChanged возникает для каждого выполненного элемента пользовательского интерфейса с измененным индексом представляемого элемента. Например при добавлении другого элемента в источник данных или его удалении, индекс элементов, порядком ниже, получает это событие.

В этом примере показано, как можно использовать эти события, чтобы присоединить пользовательскую службу выбора для отслеживания выбора элементов в пользовательском элементе управления, который использует ItemsRepeater для отображения элементов.

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

Сортировка, фильтрация и сброс данных

Выполняя фильтрацию или сортировку набора данных, традиционно существовала возможность сравнивать предыдущий набор данных с новыми данными, а затем выдавать детальные уведомления об изменениях с помощью INotifyCollectionChanged. Тем не менее, вместо этого зачастую проще полностью заменить старые данные новыми данными и запустить уведомление об изменении коллекции с помощью действия Сброс.

Как правило, сброс приводит к тому, что элемент управления освобождает существующие дочерние элементы и выполняет все повторно, создавая пользовательский интерфейс заново с нулевой позицией прокрутки, поскольку он не имеет точной информации об изменении данных во время сброса.

Тем не менее, если коллекция, назначенная в качестве ItemsSource, поддерживает уникальные идентификаторы путем реализации интерфейса IKeyIndexMapping, ItemsRepeater может быстро определить:

  • многократно используемые элементы интерфейса пользователя для данных, которые существовали до и после сброса;
  • ранее видимые элементы, которые были удалены;
  • новые видимые элементы, которые были добавлены.

Это позволяет ItemsRepeater избежать повторного запуска с нулевой позиции прокрутки. Он также позволяет быстро восстановить элементы пользовательского интерфейса для неизменяемых во время сброса данных, что приводит к повышению производительности.

В этом примере показано, как отобразить список элементов в вертикальном столбце, в котором MyItemsSource является пользовательским источником данных, служащим в качестве оболочки базового списка элементов. Он предоставляет свойство Data, которое можно использовать , чтобы повторно назначить новый список для использования в качестве источника элементов, который затем активирует сброс.

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

Создание настраиваемого элемента управления коллекцией

Вы можете использовать ItemsRepeater, чтобы создать настраиваемый элемент управления коллекцией с собственным типом элемента управления для представления каждого элемента.

Примечание.

Он используется аналогично ItemsControl, но вместо вывода из ItemsControl и помещения ItemsPresenter в шаблон элемента управления, производить нужно из Control и помещать ItemsRepeater в шаблон элемента управления. Пользовательский элемент управления коллекцией содержит ItemsRepeater, а не является ItemsControl. Это подразумевает, что необходимо также явно выбрать представляемые свойства, вместо того, чтобы выбирать, какие унаследованные свойства не поддерживать.

В этом примере показано, как разместить ItemsRepeater в шаблоне пользовательского элемента управления с именем MediaCollectionView и как предоставить его свойства.

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

Отображение сгруппированных элементов

ItemsRepeater можно вложить в ItemTemplate из другого ItemsRepeater, чтобы создать вложенные макеты виртуализации. Платформа будет эффективно использовать ресурсы, сводя к минимуму ненужную реализацию элементов, которые не отображаются или не находятся рядом с текущим окном просмотра.

В этом примере показано, как можно отобразить список со сгруппированными элементами в вертикальном столбце. Внешний ItemsRepeater создает каждую группу. Другой ItemsRepeater создает элементы в шаблоне каждой группы.

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

На следующем рисунке показан базовый макет, созданный с использованием приведенного выше примера в качестве рекомендации.

Вложенный макет с повторением элементов

В следующем примере показан макет для приложения, содержащий разные категории, которые можно изменить с помощью предпочтений пользователя и которые представлены в виде горизонтальных списков с прокруткой. Макет этого примера также представлен на рисунке выше.

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

Перенос элемента в представление

Framework XAML уже обрабатывает перенос FrameworkElement в представление получении фокуса 1) клавиатуры или 2) экранного диктора. Возможны и другие ситуации, когда элемент необходимо явно сделать отображаемым. Например в ответ на действия пользователя или чтобы восстановить состояние пользовательского интерфейса после перехода между страницами.

Перенос виртуализованного элемента в представление включает следующее:

  1. установка элемента управления для элемента;
  2. выполнение макета, чтобы убедиться, что элемент имеет допустимую позицию;
  3. создание запроса для отображения реализованного элемента в представлении.

В приведенном ниже примере эти действия показаны в процессе восстановления позиции прокрутки элемента в плоском вертикальном списке после перехода между страницами. В случае с иерархическими данными, использующими вложенный ItemsRepeaters, подход, по существу, является прежним, но при этом он должен выполняться на каждом уровне иерархии.

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

Включение специальных возможностей

ItemsRepeater не поддерживает стандартные специальные возможности взаимодействия. Документация по удобству использования приложений Windows предоставляет широкий набор сведений, которые помогут вам обеспечить инклюзивное взаимодействие с пользователем в приложении. Если вы используете ItemsRepeater, чтобы создать настраиваемый элемент управления, ознакомьтесь с документацией по настраиваемым одноранговым элементам автоматизации.

Поддержка клавиатуры

Минимальная поддержка клавиатуры для перемещения фокуса, которую предоставляет ItemsRepeater, основана на двухмерной направленной навигации для клавиатуры в XAML.

Направление навигации

Режим XYFocusKeyboardNavigation для ItemsRepeater включен по умолчанию. В зависимости от предполагаемого взаимодействия рассмотрите возможность добавления поддержки общих взаимодействий с клавиатурой, таких как Home, End, PageUp, и PageDown.

ItemsRepeater автоматически гарантирует, что стандартный порядок вкладок для его элементов (виртуализированных или нет) имеет тот же порядок, что и элементы, заданные в данных. По умолчанию ItemsRepeater имеет свое свойство TabFocusNavigation, заданное однократно, вместо общего стандартного значения локально.

Примечание.

ItemsRepeater не запоминает последний выбранный элемент автоматически. Это означает, что при использовании клавиш Shift + Tab, их можно использовать для последнего реализованного элемента.

Объявление "Элемент X из Y" в устройствах чтения с экрана

Необходимо управлять настройкой соответствующих свойств автоматизации, таких как значения для PositionInSet и SizeOfSet, и обеспечивать их актуальность при добавлении, перемещении, удалении и т. д.

В некоторых пользовательских макетах может отсутствовать очевидная последовательность визуального порядка. Как минимум, пользователи ожидают, что значения свойств PositionInSet и SizeOfSet, используемые устройствами чтения с экрана, будут соответствовать порядку, в котором элементы отображаются в данных (смещение на 1 для соответствия естественному счету, вместо выполнения на основе 0).

Лучший способ добиться этого — использовать одноранговый узел автоматизации для элемента управления, который реализует методы GetPositionInSetCore и GetSizeOfSetCore и сообщает о положении элемента в наборе данных, представленном элементом управления. Значение вычисляется только во время обращения к нему с помощью специальных возможностей, и его постоянное обновление становится несущественным. Значение совпадает с порядком данных.

В этом примере показано, как это можно сделать при вызове представления пользовательского элемента управления 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();
    }
}