다음을 통해 공유


ItemsRepeater (아이템 반복기)

ItemsRepeater를 사용하여 유연한 레이아웃 시스템, 사용자 지정 보기 및 가상화를 사용하여 사용자 지정 컬렉션 환경을 만듭니다.

ListView와 달리 ItemsRepeater는 포괄적인 최종 사용자 환경을 제공하지 않습니다. 기본 UI는 없으며 포커스, 선택 또는 사용자 상호 작용에 대한 정책을 제공하지 않습니다. 대신 이는 고유한 컬렉션 기반 환경 및 사용자 지정 컨트롤을 만드는 데 사용할 수 있는 문서 블록입니다. 기본 정책은 없지만, 정책을 연결하여 필요한 환경을 구축할 수 있습니다. 예를 들어 사용할 레이아웃, 키보드 정책, 선택 정책 등을 정의할 수 있습니다.

ItemsRepeater를 개념적으로 ListView와 같은 완전한 컨트롤이 아닌 데이터 기반 패널로 생각할 수 있습니다. 개발자는 표시할 데이터 항목 컬렉션, 각 데이터 항목의 UI 요소를 생성하는 항목 템플릿 그리고 요소의 크기와 위치를 결정하는 레이아웃을 지정합니다. 그러면 ItemsRepeater가 데이터 원본에 따라 자식 요소를 생성하고, 항목 템플릿과 레이아웃에 지정된 대로 자식 요소를 표시합니다. ItemsRepeater는 개발자가 데이터 템플릿 선택기에서 지정하는 기준에 따라 데이터 항목을 표시할 콘텐츠를 로드할 수 있으므로 표시되는 항목이 같은 형식일 필요는 없습니다

올바른 컨트롤인가요?

ItemsRepeater를 사용하여 데이터 컬렉션에 대한 사용자 지정 디스플레이를 만듭니다. 기본 항목 세트를 표시하는 데 사용할 수 있지만, 사용자 지정 컨트롤의 템플릿에 표시 요소로 자주 사용됩니다.

최소한의 사용자 지정으로 목록 또는 그리드에 데이터를 표시하기 위해 기본 제공 컨트롤이 필요한 경우 ListView 또는 GridView를 사용하는 것이 좋습니다.

ItemsRepeater는 기본 항목 컬렉션을 제공하지 않습니다. 별도의 데이터 원본에 바인딩하는 대신 Items 컬렉션을 직접 제공해야 하는 경우 더 높은 정책 환경이 필요할 수 있으며 ListView 또는 GridView를 사용해야 합니다.

ItemsControl 과 ItemsRepeater는 모두 사용자 지정 가능한 컬렉션 환경을 사용하도록 설정하지만 ItemsRepeater는 가상화 UI 레이아웃을 지원하지만 ItemsControl은 지원하지 않습니다. 간단하게 데이터의 몇 가지 항목을 표시하든 아니면 사용자 지정 컬렉션 컨트롤을 빌드하든, ItemsControl 대신 ItemsRepeater를 사용하는 것이 좋습니다.

ItemsRepeater와 함께 스크롤하기

ItemsRepeater 는 Control에서 파생되지 않으므로 컨트롤 템플릿이 없습니다. 따라서 ListView 또는 다른 컬렉션 컨트롤처럼 기본 스크롤 기능을 제공하지 않습니다.

ItemsRepeater를 사용하는 경우 ScrollViewer 컨트롤에 래핑하여 스크롤 기능을 제공해야 합니다.

참고

앱이 Windows 10 버전 1809 이전에 릴리스된 이전 버전의 Windows에서 실행되는 경우 ItemsRepeaterScrollHost 내에서 ScrollViewer를 호스트해야 합니다.

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

앱이 최신 버전의 Windows 10 버전 1809 이상에서만 실행되는 경우 ItemsRepeaterScrollHost를 사용할 필요가 없습니다.

Windows 10 버전 1809 이전에는 ScrollViewerItemsRepeater에 필요한 IScrollAnchorProvider 인터페이스를 구현하지 않았습니다. ItemsRepeaterScrollHost를 사용하면 ItemsRepeater가 이전 릴리스에서 ScrollViewer와 조정하여 사용자가 보고 있는 항목의 표시 위치를 올바르게 유지할 수 있습니다. 그렇지 않으면 목록의 항목이 변경되거나 앱의 크기를 조정할 때 항목이 갑자기 이동하거나 사라지는 것처럼 보일 수 있습니다.

ItemsRepeater 만들기

WinUI 3 갤러리 앱에는 대부분의 WinUI 3 컨트롤, 기능 및 기능의 대화형 예제가 포함되어 있습니다. Microsoft Store에서 앱을 가져오거나 GitHub에서 소스 코드를 가져옵니다.

ItemsRepeater를 사용하려면 ItemsSource 속성을 설정하여 표시할 데이터를 제공해야 합니다. 그런 다음 ItemTemplate 속성을 설정하여 항목을 표시하는 방법을 알려줍니다.

항목 소스

보기를 채웁니다. ItemsSource 속성을 데이터 항목 컬렉션으로 설정합니다. 여기서 ItemsSource 는 코드에서 컬렉션의 인스턴스로 직접 설정됩니다.

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

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

XAML의 컬렉션에 ItemsSource 속성을 바인딩할 수도 있습니다. 데이터 바인딩에 대한 자세한 내용은 데이터 바인딩 개요를 참조하세요.

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

아이템 템플릿

데이터 항목을 시각화하는 방법을 지정하려면 ItemTemplate 속성을 정의한 DataTemplate 또는 DataTemplateSelector로 설정합니다. 데이터 템플릿은 데이터를 시각화하는 방법을 정의합니다. 기본적으로 항목은 데이터 개체의 문자열 표현을 사용하는 TextBlock 을 사용하여 보기에 표시됩니다.

그러나 개별 항목을 표시하는 데 사용할 하나 이상의 컨트롤의 모양과 레이아웃을 정의하는 템플릿을 사용하여 데이터를 보다 풍부하게 표시하는 것이 일반적입니다. 템플릿에 사용하는 컨트롤을 데이터 개체의 속성에 바인딩하거나 정적 콘텐츠를 인라인으로 정의할 수 있습니다.

데이터 템플릿

이 예제의 데이터 개체는 간단한 문자열입니다. DataTemplate에는 텍스트 왼쪽에 이미지가 포함되어 있으며 TextBlock의 스타일을 지정하여 문자열을 청록색으로 표시합니다.

참고

DataTemplate에서 x:Bind 태그 확장을 사용하는 경우 DataTemplate에서 DataType(x:DataType)을 지정해야 합니다.

<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 또는 DataTemplateSelectorItemsRepeater의 직접 자식으로 지정할 수 있습니다. ItemTemplate 속성의 값으로 할당됩니다. 예를 들어 다음과 같이 사용하면 유효합니다.

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

ListView 및 기타 컬렉션 컨트롤과 달리 ItemsRepeater는 여백, 안쪽 여백, 선택 시각적 개체 또는 시각적 상태에 대한 포인터와 같은 기본 정책을 포함하는 추가 항목 컨테이너를 사용하여 DataTemplate의 요소를 래핑하지 않습니다. 대신 ItemsRepeaterDataTemplate에 정의된 항목만 표시합니다. 항목이 목록 보기 항목과 같은 모양을 갖도록 하려면 ListViewItem과 같은 컨테이너를 데이터 템플릿에 명시적으로 포함할 수 있습니다. ItemsRepeaterListViewItem 시각적 개체를 표시하지만 선택 또는 다중 선택 확인란 표시와 같은 다른 기능을 자동으로 사용하지는 않습니다.

마찬가지로 데이터 컬렉션이 Button(List<Button>)처럼 실제 컨트롤의 컬렉션인 경우 DataTemplateContentPresenter를 배치하여 컨트롤을 표시할 수 있습니다.

데이터 템플릿 선택기

보기에 표시하는 항목이 동일한 형식일 필요는 없습니다. ItemTemplate 속성에 DataTemplateSelector를 제공하여 지정한 조건에 따라 다른 DataTemplate을 선택할 수 있습니다.

이 예제에서는 두 개의 서로 다른 DataTemplate중에서 Large 및 Small 항목을 나타내는 DataTemplateSelector가 정의되었다고 가정합니다.

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

ItemsRepeater와 함께 사용할 DataTemplateSelector를 정의할 때 SelectTemplateCore(Object) 메서드에 대한 재정의만 구현하면 됩니다. 자세한 정보 및 예제는 DataTemplateSelector를 참조하세요.

참고

고급 시나리오에서 요소를 만드는 방법을 관리하는 DataTemplate의 대안은 ItemTemplate으로 사용할 고유한 IElementFactory를 구현하는 것입니다. 요청을 받으면 콘텐츠를 생성하는 책임이 있습니다.

데이터 원본 구성

ItemsSource 속성을 사용하여 항목의 콘텐츠를 생성하는 데 사용할 컬렉션을 지정합니다. ItemsSource를 IEnumerable을 구현하는 모든 형식으로 설정할 수 있습니다. 데이터 원본이 구현하는 추가 컬렉션 인터페이스에 따라 ItemsRepeater가 데이터와 상호 작용하는 데 사용할 수 있는 기능이 결정됩니다.

이 목록은 사용 가능한 인터페이스과 각 인터페이스를 언제 사용하는지 보여줍니다.

  • IEnumerable(.NET)/IIterable

    • 작은 정적 데이터 세트에 사용할 수 있습니다.

      적어도 데이터 원본이 IEnumerable/IIterable 인터페이스를 구현해야 합니다. 이 외에는 지원되지 않는 경우 컨트롤은 모든 항목을 한 번씩 반복하여 인덱스 값을 통해 항목에 액세스할 때 사용할 수 있는 복사본을 만듭니다.

  • IReadonlyList(.NET)/IVectorView

    • 정적 읽기 전용 데이터 세트에 사용할 수 있습니다.

      컨트롤이 인덱스를 통해 항목에 액세스할 수 있게 해주고 중복 내부 복사를 방지합니다.

  • IList(.NET) / IVector

    • 정적 데이터 세트에 사용할 수 있습니다.

      컨트롤이 인덱스를 통해 항목에 액세스할 수 있게 해주고 중복 내부 복사를 방지합니다.

      경고: INotifyCollectionChanged 를 구현하지 않고 목록/벡터의 변경 내용은 UI에 반영되지 않습니다.

  • INotifyCollectionChanged(컬렉션 변경 알림, .NET)

    • 변경 알림을 지원하는 것이 좋습니다.

      컨트롤이 데이터 원본의 변경 내용을 관찰하고 그에 따라 대응할 수 있게 해주며 변경 내용을 UI에 반영합니다.

  • IObservableVector

    • 변경 알림을 지원합니다.

      INotifyCollectionChanged 인터페이스와 마찬가지로 이 인터페이스를 사용하면 컨트롤이 데이터 원본의 변경 내용을 관찰하고 대응할 수 있습니다.

      경고: Windows.Foundation.IObservableVector<T> 는 '이동' 작업을 지원하지 않습니다. 이로 인해 항목의 UI가 시각적 상태를 잃게 될 수 있습니다. 예를 들어, 현재 선택되어 있거나 포커스가 있는 항목은 'Remove' 뒤에 'Add'를 사용하여 이동이 수행되면 포커스를 잃고 선택되지 않은 상태가 됩니다.

      Platform.Collections.Vector<T>는 IObservableVector<T>를 사용하며 동일한 제한이 적용됩니다. '이동' 작업에 대한 지원이 필요한 경우 INotifyCollectionChanged 인터페이스를 사용합니다. .NET ObservableCollection<T> 클래스는 INotifyCollectionChanged를 사용합니다.

  • IKeyIndex매핑

    • 고유 식별자를 각 항목에 연결할 수 있는 경우. 컬렉션 변경 작업으로 '다시 설정'을 사용하는 경우에 권장합니다.

      INotifyCollectionChanged 또는 IObservableVector 이벤트의 일부로 하드 'Reset' 작업을 받은 후 컨트롤이 기존 UI를 매우 효율적으로 복구할 수 있도록 합니다. 다시 설정을 수신한 후 컨트롤은 제공된 고유 ID를 사용하여 현재 데이터를 이미 만들어진 요소와 연결합니다. 인덱스 매핑 키가 없으면, 컨트롤은 데이터의 UI를 생성할 때 처음부터 시작해야 한다고 가정해야 합니다.

IKeyIndexMapping을 제외하고 위에 나열된 인터페이스는 ListView 및 GridView와 똑같은 동작을 ItemsRepeater에서도 제공합니다.

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에 표시되는 항목은 자식 요소의 크기 조정 및 위치를 관리하는 Layout 개체에 의해 정렬됩니다. 레이아웃 개체는 ItemsRepeater와 함께 사용할 경우 UI 가상화를 지원합니다. 제공된 레이아웃은 StackLayoutUniformGridLayout입니다. 기본적으로 ItemsRepeater는 StackLayout을 세로 방향으로 사용합니다.

스택 레이아웃 (StackLayout)

StackLayout 은 요소를 가로 또는 세로 방향으로 방향을 지정할 수 있는 한 줄로 정렬합니다.

간격 속성을 설정하여 항목 사이의 간격을 조정할 수 있습니다. 간격은 레이아웃 방향 방향으로 적용 됩니다.

스택 레이아웃 간격

이 예제에서는 가로 방향 및 8픽셀 간격을 사용하여 ItemsRepeater.Layout 속성을 StackLayout으로 설정하는 방법을 보여줍니다.

<!-- 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은 요소를 래핑 레이아웃에 순차적으로 배치합니다. 항목은 방향이 가로일 때 왼쪽에서 오른쪽으로 순서대로 배치되고, 방향이 세로일 때 위쪽에서 아래쪽으로 배치됩니다. 모든 항목의 크기가 동일하게 조정됩니다.

균일한 그리드 레이아웃 간격

가로 레이아웃의 각 행에 있는 항목 수는 최소 항목 너비의 영향을 받습니다. 세로 레이아웃의 각 열에 있는 항목 수는 최소 항목 높이의 영향을 받습니다.

  • MinItemHeightMinItemWidth 속성을 설정하여 사용할 최소 크기를 명시적으로 제공할 수 있습니다.
  • 최소 크기를 지정하지 않으면 첫 번째 항목을 측정한 크기가 항목의 최소 크기로 간주됩니다.

MinColumnSpacing 및 MinRowSpacing 속성을 설정하여 행과 열 사이에 레이아웃이 포함되도록 최소 간격을 설정할 수도 있습니다.

균일한 그리드 크기 조정 및 간격

항목의 최소 크기 및 간격에 따라 행 또는 열의 항목이 결정된 경우 이전 이미지처럼 행 또는 열의 마지막 항목 뒤에 사용되지 않는 공간이 남아 있을 수 있습니다. 모든 추가 공간을 무시할 것인지, 각 항목의 크기를 늘리는 데 사용할 것인지 아니면 항목 사이에 추가 공간을 만드는 데 사용할 것인지 지정할 수 있습니다. ItemsStretchItemsJustification 속성에 의해 제어됩니다.

ItemsStretch 속성을 설정하여 사용하지 않는 공간을 채우기 위해 항목 크기를 늘리는 방법을 지정할 수 있습니다.

다음 목록은 사용 가능한 값을 보여줍니다. 정의는 가로의 기본 방향을 가정 합니다.

  • 없음: 추가 공간은 행의 끝에 사용되지 않습니다. 기본값입니다.
  • 채우기: 항목은 사용 가능한 공간(세로인 경우 높이)을 사용하기 위해 추가 너비가 지정됩니다.
  • 균일: 항목은 사용 가능한 공간을 사용하기 위해 추가 너비를 부여하고 가로 세로 비율을 유지하기 위해 추가 높이를 부여합니다(세로인 경우 높이와 너비가 전환됨).

이 이미지는 가로 레이아웃에서 ItemsStretch 값의 효과를 보여 줍니다.

균일 그리드 항목 늘리기

ItemsStretchNone이면 ItemsJustification 속성을 설정하여 항목을 정렬하는 데 추가 공간을 사용하는 방법을 지정할 수 있습니다.

다음 목록은 사용 가능한 값을 보여줍니다. 정의는 가로의 기본 방향을 가정 합니다.

  • 시작: 항목이 행의 시작 부분에 맞춰집니다. 추가 공간이 행의 끝에 사용되지 않은 상태로 남아 있습니다. 기본값입니다.
  • 가운데: 항목이 행의 가운데에 맞춰집니다. 추가 공간이 행의 시작 부분과 끝부분에 균등하게 분배됩니다.
  • : 항목이 행의 끝에 맞춰집니다. 추가 공간은 행의 시작 부분에 사용되지 않은 상태로 남아 있습니다.
  • SpaceAround: 항목이 균등하게 분산됩니다. 각 항목 앞뒤에 동일한 양의 공간이 추가됩니다.
  • 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 는 나타내는 항목의 인덱스가 변경된 각 실현된 UIElement에 대해 발생합니다. 예를 들어 데이터 원본에서 다른 항목이 추가 또는 제거되면 순서가 늦은 항목의 인덱스가 이 이벤트를 수신합니다.

다음 예제에서는 이러한 이벤트를 사용하여 사용자 지정 선택 서비스를 연결하고, 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를 통해 세분화된 변경 알림을 실행했을 수 있습니다. 그러나 이전 데이터를 새 데이터로 완전히 바꾸고 대신 다시 설정 작업을 사용하여 컬렉션 변경 알림을 트리거하는 것이 더 쉬운 경우가 많습니다.

일반적으로 다시 설정하면 컨트롤은 다시 설정하는 동안 데이터가 어떻게 변경되었는지 정확하게 알지 못하기 때문에 기존 자식 요소를 해제한 후 다시 시작하고, 스크롤 위치 0에서 UI를 처음부터 새로 빌드합니다.

그러나 ItemsSource로 할당된 컬렉션이 IKeyIndexMapping 인터페이스를 구현하여 고유 식별자를 지원하는 경우 ItemsRepeater는 다음을 신속하게 식별할 수 있습니다.

  • 재설정 전후의 데이터에 대해 재사용 가능한 UI 요소들
  • 이전에 표시되다가 제거된 항목
  • 표시할 새로 추가된 항목

이렇게 하면 ItemsRepeater가 스크롤 위치 0에서 다시 시작하지 않습니다. 또한 다시 설정할 때 변경되지 않은 데이터의 UIElement를 신속하게 복원할 수 있으므로 성능이 향상됩니다.

이 예제에서는 MyItemsSource 가 기본 항목 목록을 래핑하는 사용자 지정 데이터 원본인 세로 스택에 항목 목록을 표시하는 방법을 보여 줍니다. 항목 원본으로 사용할 새 목록을 다시 할당하는 데 사용할 수 있는 데이터 속성을 노출한 다음 다시 설정을 트리거합니다.

<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이라는 것과의 차이를 나타냅니다. 즉, 상속한 속성 중 지원하지 않을 속성보다는 노출할 속성을 명시적으로 선택해야 합니다.

이 예제에서는 MediaCollectionView라는 사용자 지정 컨트롤의 템플릿에 ItemsRepeater를 배치하고 해당 속성을 노출하는 방법을 보여줍니다.

<!-- 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의 ItemTemplateItemsRepeater를 중첩하여 중첩된 가상화 레이아웃을 만들 수 있습니다. 이 프레임워크는 표시되지 않거나 현재 뷰포트 가까이에 있는 요소를 불필요하게 구현하는 일을 최소화하여 리소스를 효율적으로 사용합니다.

이 예제에서는 그룹화된 항목의 목록을 세로 스택에 표시하는 방법을 보여줍니다. 외부 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>

요소를 화면에 보이게 하기

XAML 프레임워크는 FrameworkElement가 1) 키보드 포커스를 받거나 2) 내레이터 포커스를 받을 때 화면에 나타나도록 이미 처리하고 있습니다. 명시적으로 요소를 화면에 표시해야 하는 경우가 있을 수 있습니다. 사용자 작업에 응답할 때 또는 페이지 탐색 후 UI 상태를 복원하는 경우를 예로 들 수 있습니다.

가상화된 요소를 보기로 가져오려면 다음을 수행해야 합니다.

  1. 항목의 UIElement 구현
  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의 키보드용 2D 방향 탐색을 기반으로 합니다.

방향 탐색

ItemsRepeater의 XYFocusKeyboardNavigation 모드 는 기본적으로 사용하도록 설정되어 있습니다. 의도한 환경에 따라 홈, 엔드, PageUp 및 PageDown과 같은 일반적인 키보드 상호 작용 에 대한 지원을 추가하는 것이 좋습니다.

ItemsRepeater는 항목(가상화 여부에 관계 없이)의 기본 탭 순서가 데이터에서 항목에 제공되는 순서와 동일하도록 자동으로 확인합니다. 기본적으로 ItemsRepeater에는 로컬의 일반적인 기본값 대신 TabFocusNavigation 속성이 Once로 설정됩니다.

참고

ItemsRepeater는 마지막으로 포커스가 있었던 항목을 자동으로 기억하지 않습니다. 즉, 사용자가 Shift+Tab을 사용할 때 마지막으로 실현된 항목으로 이동될 수 있습니다.

화면 읽기 프로그램에서 "YX 항목" 안내

PositionInSetSizeOfSet에 대한 값과 같은 적절한 자동화 속성 설정을 관리하고 항목이 추가, 이동, 제거된 날짜 등과 up-to유지되는지 확인해야 합니다.

명확한 시각적 순서가 없는 사용자 지정 레이아웃도 있습니다. 사용자는 적어도 화면 읽기 프로그램에 사용되는 PositionInSet 및 SizeOfSet 속성의 값이 데이터에 항목이 나타나는 순서와 일치할 것으로 예상합니다(자연 집계와 일치하도록 1로 오프셋하거나 0부터 시작).

이를 수행하는 가장 좋은 방법은 항목 컨트롤에 대한 자동화 피어가 GetPositionInSetCoreGetSizeOfSetCore 메서드를 구현하고 컨트롤이 나타내는 데이터 집합에서 항목의 위치를 보고하도록 하는 것입니다. 보조 기술이 액세스할 때 런타임에만 값이 컴퓨팅되므로 최신 상태를 유지하는 것은 문제가 되지 않습니다. 이 값은 데이터 순서와 일치합니다.

이 예제에서는 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 및 WinUI 2

중요한

이 문서의 정보 및 예제는 Windows 앱 SDKWinUI 3을 사용하는 앱에 최적화되어 있지만 일반적으로 WinUI 2를 사용하는 UWP 앱에 적용됩니다. 플랫폼별 정보 및 예제는 UWP API 참조를 확인하세요.

이 섹션에는 UWP 또는 WinUI 2 앱에서 컨트롤을 사용하는 데 필요한 정보를 다룹니다.

UWP 앱을 위한 ItemsRepeater에는 WinUI 2가 필요합니다. 설치 지침을 비롯한 자세한 내용은 WinUI 2를 참조하세요. 이 컨트롤에 대한 API는 Microsoft.UI.Xaml.Controls 네임스페이스에 있습니다.

이 문서의 코드를 WinUI 2와 함께 사용하려면 XAML의 별칭(여기서는 muxc를 사용)을 사용하여 프로젝트에 포함된 Windows UI 라이브러리 API를 표현합니다. 자세한 내용은 WinUI 2 시작 을 참조하세요.

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

<muxc:ItemsRepeater />