MAUI CollectionView 활용 가이드: 유연한 데이터 목록 구현

.NET MAUI의 CollectionView는 데이터 컬렉션을 유연하게 표시하고 관리하기 위한 강력한 컨트롤입니다. 이 문서는 CollectionView의 기본적인 사용법부터 다양한 시각적 사용자 정의, 데이터 관리 기능까지 심도 있게 다룹니다.

1. 기본적인 사용법 및 데이터 바인딩

CollectionView를 사용하기 위해서는 표시할 데이터 모델과 해당 데이터를 CollectionView에 제공할 뷰 모델이 필요합니다. 여기서는 StaffMember라는 간단한 직원 모델을 사용하겠습니다.

1.1. 데이터 모델 정의 (Models/StaffMember.cs)


public class StaffMember
{
    public int EmployeeId { get; set; }
    public string FullName { get; set; }
    public string DepartmentName { get; set; }
    public string ProfileImage { get; set; } // 예: "kim.png"
    public string ShortDescription { get; set; }
}

1.2. 뷰 모델 구현 (ViewModels/EmployeeListViewModel.cs)

ObservableCollection<StaffMember>을 사용하여 데이터 변경 시 UI가 자동으로 업데이트되도록 합니다. 프로필 이미지는 Resources/Images 폴더에 미리 추가해야 합니다.


using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;

namespace MyMauiApp.ViewModels;

public partial class EmployeeListViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<StaffMember> teamMembers;

    public EmployeeListViewModel()
    {
        TeamMembers = new ObservableCollection<StaffMember>
        {
            new StaffMember { EmployeeId = 1, FullName = "김철수", DepartmentName = "개발부", ProfileImage = "kim.png", ShortDescription = "소프트웨어 개발 담당" },
            new StaffMember { EmployeeId = 2, FullName = "이영희", DepartmentName = "개발부", ProfileImage = "lee.png", ShortDescription = "UX/UI 디자인 담당" },
            new StaffMember { EmployeeId = 3, FullName = "박민준", DepartmentName = "영업부", ProfileImage = "park.png", ShortDescription = "신규 고객 유치" },
            new StaffMember { EmployeeId = 4, FullName = "최지혜", DepartmentName = "마케팅부", ProfileImage = "choi.png", ShortDescription = "온라인 캠페인 기획" },
            new StaffMember { EmployeeId = 5, FullName = "정수민", DepartmentName = "영업부", ProfileImage = "jung.png", ShortDescription = "기존 고객 관리" }
        };
    }
}

1.3. XAML에서 CollectionView 사용 (Views/EmployeeListPage.xaml)

CollectionView의 ItemsSource 속성을 뷰 모델의 TeamMembers에 바인딩하고, ItemTemplate을 사용하여 각 항목의 시각적 요소를 정의합니다.


<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="직원 목록">

    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <!-- ItemsSource 속성으로 데이터 원본 지정 -->
    <CollectionView ItemsSource="{Binding TeamMembers}">
        <!-- ItemTemplate 속성으로 각 데이터 항목의 UI 정의 -->
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="{x:Type vm:StaffMember}">
                <Grid ColumnDefinitions="70,*" RowDefinitions="Auto,Auto,Auto" Padding="10">
                    <Image
                        Grid.RowSpan="3"
                        Source="{Binding ProfileImage}"
                        Aspect="AspectFill"
                        HeightRequest="60"
                        WidthRequest="60"
                        Margin="0,0,10,0" />
                    <Label
                        Grid.Column="1"
                        Text="{Binding FullName}"
                        FontAttributes="Bold"
                        FontSize="16" />
                    <Label
                        Grid.Row="1"
                        Grid.Column="1"
                        Text="{Binding DepartmentName}"
                        FontSize="14"
                        TextColor="Gray" />
                    <Label
                        Grid.Row="2"
                        Grid.Column="1"
                        Text="{Binding ShortDescription}"
                        FontSize="12"
                        FontAttributes="Italic"
                        TextColor="DarkSlateGray" />
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

2. 시각적 요소 사용자 정의

2.1. 조건부 스타일 적용 (DataTemplateSelector)

데이터 항목의 특정 조건에 따라 다른 UI 템플릿을 적용할 수 있습니다. 예를 들어, 부서에 따라 다른 배경색이나 텍스트 색상을 사용할 수 있습니다.

2.1.1. 템플릿 셀렉터 클래스 정의 (Controls/DepartmentTemplateSelector.cs)


using MyMauiApp.ViewModels; // StaffMember 클래스 접근을 위해 추가
using Microsoft.Maui.Controls;
using System.Collections.Generic;

namespace MyMauiApp.Controls;

public class DepartmentTemplateSelector : DataTemplateSelector
{
    public DataTemplate DevelopmentTemplate { get; set; }
    public DataTemplate SalesMarketingTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        if (item is StaffMember staff)
        {
            // '개발부' 소속이면 DevelopmentTemplate, 그 외는 SalesMarketingTemplate 사용
            return staff.DepartmentName.Contains("개발부") ? DevelopmentTemplate : SalesMarketingTemplate;
        }
        return base.OnSelectTemplate(item, container);
    }
}

2.1.2. XAML에서 DataTemplateSelector 사용

ContentPage.Resources에 여러 DataTemplate을 정의하고, DepartmentTemplateSelector를 인스턴스화하여 이 템플릿들을 바인딩합니다. 이후 CollectionView의 ItemTemplate 속성에 DepartmentTemplateSelector를 지정합니다.


<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    xmlns:c="clr-namespace:MyMauiApp.Controls"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="조건부 스타일 예제">

    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <!-- 개발부 직원용 DataTemplate -->
        <DataTemplate x:Key="DevelopmentTemplate" x:DataType="{x:Type vm:StaffMember}">
            <Grid ColumnDefinitions="70,*" RowDefinitions="Auto,Auto,Auto" Padding="10" BackgroundColor="#E0F2F7">
                <Image Grid.RowSpan="3" Source="{Binding ProfileImage}" Aspect="AspectFill" HeightRequest="60" WidthRequest="60" Margin="0,0,10,0" />
                <Label Grid.Column="1" Text="{Binding FullName}" FontAttributes="Bold" FontSize="16" />
                <Label Grid.Row="1" Grid.Column="1" Text="{Binding DepartmentName}" FontSize="14" TextColor="DarkCyan" />
                <Label Grid.Row="2" Grid.Column="1" Text="{Binding ShortDescription}" FontSize="12" FontAttributes="Italic" TextColor="DarkSlateGray" />
            </Grid>
        </DataTemplate>

        <!-- 영업/마케팅부 직원용 DataTemplate -->
        <DataTemplate x:Key="SalesMarketingTemplate" x:DataType="{x:Type vm:StaffMember}">
            <Grid ColumnDefinitions="70,*" RowDefinitions="Auto,Auto,Auto" Padding="10" BackgroundColor="#FFF8E1">
                <Image Grid.RowSpan="3" Source="{Binding ProfileImage}" Aspect="AspectFill" HeightRequest="60" WidthRequest="60" Margin="0,0,10,0" />
                <Label Grid.Column="1" Text="{Binding FullName}" FontAttributes="Bold" FontSize="16" />
                <Label Grid.Row="1" Grid.Column="1" Text="{Binding DepartmentName}" FontSize="14" TextColor="DarkOrange" />
                <Label Grid.Row="2" Grid.Column="1" Text="{Binding ShortDescription}" FontSize="12" FontAttributes="Italic" TextColor="DarkSlateGray" />
            </Grid>
        </DataTemplate>

        <!-- DataTemplateSelector 인스턴스화 및 템플릿 할당 -->
        <c:DepartmentTemplateSelector
            x:Key="DepartmentSelector"
            DevelopmentTemplate="{StaticResource DevelopmentTemplate}"
            SalesMarketingTemplate="{StaticResource SalesMarketingTemplate}" />
    </ContentPage.Resources>

    <!-- ItemTemplate에 DataTemplateSelector 적용 -->
    <CollectionView ItemTemplate="{StaticResource DepartmentSelector}" ItemsSource="{Binding TeamMembers}" />
</ContentPage>

2.2. 항목 배열 방식 (ItemsLayout)

CollectionView는 ItemsLayout 속성을 통해 다양한 레이아웃 옵션을 제공합니다. 수직/수평 리스트, 수직/수평 그리드 등을 설정할 수 있습니다.

2.2.1. 수직 리스트 (기본값)


<!-- 간단한 설정 -->
<CollectionView ItemsSource="{Binding TeamMembers}" ItemsLayout="VerticalList">
    <!-- ... ItemTemplate ... -->
</CollectionView>

<!-- 속성 요소 방식 -->
<CollectionView ItemsSource="{Binding TeamMembers}">
    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Vertical" ItemSpacing="5" />
    </CollectionView.ItemsLayout>
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.2.2. 수평 리스트


<!-- 간단한 설정 -->
<CollectionView ItemsSource="{Binding TeamMembers}" ItemsLayout="HorizontalList">
    <!-- ... ItemTemplate ... -->
</CollectionView>

<!-- 속성 요소 방식 -->
<CollectionView ItemsSource="{Binding TeamMembers}">
    <CollectionView.ItemsLayout>
        <LinearItemsLayout Orientation="Horizontal" ItemSpacing="5" />
    </CollectionView.ItemsLayout>
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.2.3. 수직 그리드


<!-- 2열 수직 그리드 -->
<CollectionView ItemsSource="{Binding TeamMembers}" ItemsLayout="VerticalGrid, 2">
    <!-- ... ItemTemplate ... -->
</CollectionView>

<!-- 속성 요소 방식 -->
<CollectionView ItemsSource="{Binding TeamMembers}">
    <CollectionView.ItemsLayout>
       <GridItemsLayout Orientation="Vertical" Span="2" VerticalItemSpacing="10" HorizontalItemSpacing="10" />
    </CollectionView.ItemsLayout>
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.2.4. 수평 그리드


<!-- 2행 수평 그리드 -->
<CollectionView ItemsSource="{Binding TeamMembers}" ItemsLayout="HorizontalGrid, 2">
    <!-- ... ItemTemplate ... -->
</CollectionView>

<!-- 속성 요소 방식 -->
<CollectionView ItemsSource="{Binding TeamMembers}">
    <CollectionView.ItemsLayout>
       <GridItemsLayout Orientation="Horizontal" Span="2" VerticalItemSpacing="10" HorizontalItemSpacing="10" />
    </CollectionView.ItemsLayout>
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.3. 헤더 및 푸터 설정 (Header/Footer)

CollectionView의 시작과 끝에 고정된 콘텐츠를 표시할 수 있습니다.

2.3.1. 문자열 헤더/푸터


<CollectionView ItemsSource="{Binding TeamMembers}"
                Header="우리 회사 팀원 목록"
                Footer="Copyright © 2024">
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.3.2. 뷰를 이용한 헤더/푸터


<CollectionView ItemsSource="{Binding TeamMembers}">
    <CollectionView.Header>
        <StackLayout BackgroundColor="#E0E0E0" Padding="8">
            <Label Text="직원 현황" FontAttributes="Bold" FontSize="15" HorizontalOptions="Center" />
        </StackLayout>
    </CollectionView.Header>
    <CollectionView.Footer>
        <StackLayout BackgroundColor="#F0F0F0" Padding="8">
            <Label Text="MAUI CollectionView 예제" FontAttributes="Italic" FontSize="11" TextColor="Gray" HorizontalOptions="Center" />
        </StackLayout>
    </CollectionView.Footer>
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.3.3. DataTemplate을 이용한 헤더/푸터

더 복잡한 UI를 구성하거나 데이터 바인딩이 필요한 경우 DataTemplate을 사용할 수 있습니다.


<CollectionView
    Header="{Binding .}" <!-- 현재 BindingContext를 헤더 템플릿으로 전달 -->
    Footer="{Binding .}"
    ItemsSource="{Binding TeamMembers}">
    <CollectionView.HeaderTemplate>
        <DataTemplate>
            <StackLayout BackgroundColor="#D0D0D0" Padding="10">
                <Label Text="전체 팀원 목록" FontAttributes="Bold" FontSize="16" TextColor="DarkBlue" HorizontalOptions="Center" />
            </StackLayout>
        </DataTemplate>
    </CollectionView.HeaderTemplate>
    <CollectionView.FooterTemplate>
        <DataTemplate>
            <StackLayout BackgroundColor="#E8E8E8" Padding="10">
                <Label Text="데이터 제공: 회사 인트라넷" FontAttributes="Italic" FontSize="12" TextColor="Gray" HorizontalOptions="Center" />
            </StackLayout>
        </DataTemplate>
    </CollectionView.FooterTemplate>
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.4. 데이터 항목 간 간격 설정

ItemsLayout 속성 요소를 사용하여 항목 간의 간격을 조절할 수 있습니다.

2.4.1. 수직/수평 리스트의 항목 간격


<CollectionView ItemsSource="{Binding TeamMembers}">
    <CollectionView.ItemsLayout>
        <LinearItemsLayout ItemSpacing="10" Orientation="Vertical" />
    </CollectionView.ItemsLayout>
    <!-- ... ItemTemplate ... -->
</CollectionView>

2.4.2. 수직/수평 그리드의 항목 간격


<CollectionView ItemsSource="{Binding TeamMembers}">
    <CollectionView.ItemsLayout>
       <GridItemsLayout Orientation="Vertical"
                        Span="2"
                        VerticalItemSpacing="15"
                        HorizontalItemSpacing="25" />
    </CollectionView.ItemsLayout>
    <!-- ... ItemTemplate ... -->
</CollectionView>>

2.5. 데이터 항목 크기 조정 전략 (ItemSizingStrategy)

CollectionView가 항목의 크기를 계산하는 방식을 지정합니다.


<!-- 기본값: 모든 항목의 크기를 개별적으로 측정 -->
<CollectionView ItemsSource="{Binding TeamMembers}"
                ItemSizingStrategy="MeasureAllItems">
    <!-- ... -->
</CollectionView>

<!-- 첫 번째 항목 크기를 기준으로 나머지 항목 크기를 결정 -->
<CollectionView ItemsSource="{Binding TeamMembers}"
                ItemSizingStrategy="MeasureFirstItem">
    <!-- ... -->
</CollectionView>

2.6. 데이터 항목 정렬 (FlowDirection)

ContentPage 또는 상위 레이아웃의 FlowDirection 속성을 사용하여 항목의 정렬 방향을 변경할 수 있습니다.


<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    FlowDirection="RightToLeft" <!-- 오른쪽에서 왼쪽으로 정렬 -->
    Title="방향 정렬 예제">
    <StackLayout Padding="20">
        <CollectionView ItemsSource="{Binding TeamMembers}">
            <!-- ... ItemTemplate ... -->
        </CollectionView>
    </StackLayout>
</ContentPage>

2.7. 데이터 항목 동적 크기 조정

사용자 상호작용에 따라 항목의 크기를 동적으로 변경하는 것은 코드 비하인드에서 처리할 수 있습니다.


<!-- EmployeeListPage.xaml -->
<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="동적 크기 조정 예제">
    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>
    <Grid RowDefinitions="*,50">
        <CollectionView
            Grid.Row="0"
            ItemsSource="{Binding TeamMembers}"
            SelectionMode="None">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="{x:Type vm:StaffMember}">
                    <Grid ColumnDefinitions="*,Auto" Padding="10">
                        <StackLayout Grid.Column="0" VerticalOptions="Center">
                            <Label Text="{Binding FullName}" FontAttributes="Bold" FontSize="16" />
                            <Label Text="{Binding DepartmentName}" FontSize="14" TextColor="Gray" />
                        </StackLayout>
                        <ImageButton
                            Grid.Column="1"
                            Clicked="ProfileImage_Tapped" <!-- 클릭 이벤트 핸들러 -->
                            HeightRequest="70"
                            WidthRequest="70"
                            Aspect="AspectFill"
                            Source="{Binding ProfileImage}"
                            VerticalOptions="Center" />
                    </Grid>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </Grid>
</ContentPage>

// EmployeeListPage.xaml.cs (코드 비하인드)
namespace MyMauiApp.Views;

public partial class EmployeeListPage : ContentPage
{
    public EmployeeListPage()
    {
        InitializeComponent();
    }

    private void ProfileImage_Tapped(object sender, EventArgs e)
    {
        if (sender is ImageButton imgButton)
        {
            // 클릭 시 이미지 버튼의 크기를 토글합니다.
            imgButton.HeightRequest = imgButton.WidthRequest = imgButton.HeightRequest.Equals(70) ? 110 : 70;
        }
    }
}

2.8. 스와이프 컨텍스트 메뉴 (SwipeView)

SwipeView 컨트롤을 사용하여 CollectionView 항목에 스와이프 동작 기반의 컨텍스트 메뉴를 추가할 수 있습니다.


<!-- EmployeeListPage.xaml -->
<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    x:Name="employeeListPage" <!-- 뷰 모델에 접근하기 위한 x:Name -->
    Title="스와이프 메뉴 예제">
    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <CollectionView
        x:Name="staffCollectionView"
        ItemsSource="{Binding TeamMembers}"
        SelectionMode="None">
        <CollectionView.ItemsLayout>
            <LinearItemsLayout Orientation="Vertical" ItemSpacing="1" />
        </CollectionView.ItemsLayout>
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="{x:Type vm:StaffMember}">
                <!-- SwipeView로 DataTemplate 내용 감싸기 -->
                <SwipeView>
                    <!-- 왼쪽 스와이프 메뉴 정의 -->
                    <SwipeView.LeftItems>
                        <SwipeItems Mode="Reveal">
                            <SwipeItem
                                BackgroundColor="LightGreen"
                                Command="{Binding Source={x:Reference employeeListPage}, Path=BindingContext.ToggleFavoriteCommand}"
                                CommandParameter="{Binding .}" <!-- 현재 항목을 명령 파라미터로 전달 -->
                                Text="즐겨찾기"
                                IconImageSource="star.png" />
                            <SwipeItem
                                BackgroundColor="LightCoral"
                                Command="{Binding Source={x:Reference employeeListPage}, Path=BindingContext.RemoveStaffMemberCommand}"
                                CommandParameter="{Binding .}"
                                Text="삭제"
                                IconImageSource="delete.png" />
                        </SwipeItems>
                    </SwipeView.LeftItems>
                    <!-- 실제 항목 내용 -->
                    <Grid ColumnDefinitions="70,*" RowDefinitions="Auto,Auto,Auto" Padding="10" BackgroundColor="White">
                        <Image Grid.RowSpan="3" Source="{Binding ProfileImage}" Aspect="AspectFill" HeightRequest="60" WidthRequest="60" Margin="0,0,10,0" />
                        <Label Grid.Column="1" Text="{Binding FullName}" FontAttributes="Bold" FontSize="16" />
                        <Label Grid.Row="1" Grid.Column="1" Text="{Binding DepartmentName}" FontSize="14" TextColor="Gray" />
                        <Label Grid.Row="2" Grid.Column="1" Text="{Binding ShortDescription}" FontSize="12" FontAttributes="Italic" TextColor="DarkSlateGray" />
                    </Grid>
                </SwipeView>
            </DataTemplate>>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

// EmployeeListViewModel.cs (뷰 모델)
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using Microsoft.Maui.Controls; // DisplayAlert를 위해 추가

namespace MyMauiApp.ViewModels;

public partial class EmployeeListViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<StaffMember> teamMembers;

    public EmployeeListViewModel()
    {
        TeamMembers = new ObservableCollection<StaffMember>
        {
            new StaffMember { EmployeeId = 1, FullName = "김철수", DepartmentName = "개발부", ProfileImage = "kim.png", ShortDescription = "소프트웨어 개발 담당" },
            new StaffMember { EmployeeId = 2, FullName = "이영희", DepartmentName = "개발부", ProfileImage = "lee.png", ShortDescription = "UX/UI 디자인 담당" },
            new StaffMember { EmployeeId = 3, FullName = "박민준", DepartmentName = "영업부", ProfileImage = "park.png", ShortDescription = "신규 고객 유치" },
            new StaffMember { EmployeeId = 4, FullName = "최지혜", DepartmentName = "마케팅부", ProfileImage = "choi.png", ShortDescription = "온라인 캠페인 기획" },
            new StaffMember { EmployeeId = 5, FullName = "정수민", DepartmentName = "영업부", ProfileImage = "jung.png", ShortDescription = "기존 고객 관리" }
        };
    }

    [RelayCommand]
    private void RemoveStaffMember(StaffMember member)
    {
        if (member != null)
        {
            TeamMembers.Remove(member);
            Application.Current.MainPage?.DisplayAlert("삭제 완료", $"{member.FullName} 님이 목록에서 제거되었습니다.", "확인");
        }
    }

    [RelayCommand]
    private void ToggleFavorite(StaffMember member)
    {
        if (member != null)
        {
            // 즐겨찾기 상태를 토글하는 로직 구현
            Application.Current.MainPage?.DisplayAlert("알림", $"{member.FullName} 님을 즐겨찾기 상태 변경했습니다.", "확인");
        }
    }
}

3. 데이터 원본 관리

3.1. 데이터 항목 선택 제어

CollectionView는 항목 선택 방식을 제어하는 다양한 옵션을 제공합니다.

3.1.1. 단일 선택 및 초기 선택


<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="단일 선택 예제">
    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <Label Text="{Binding SelectedStaffMember.FullName, StringFormat='선택된 직원: {0}'}" FontSize="18" Margin="10" />
        <CollectionView
            ItemsSource="{Binding TeamMembers}"
            SelectedItem="{Binding SelectedStaffMember}" <!-- 단일 선택 항목 바인딩 -->
            SelectionChanged="OnStaffSelectionChanged" <!-- 선택 변경 이벤트 -->
            SelectionMode="Single"> <!-- 단일 선택 모드 -->
            <!-- ... ItemTemplate ... -->
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>

// EmployeeListViewModel.cs (뷰 모델)
public partial class EmployeeListViewModel : ObservableObject
{
    // ... 기존 코드 ...

    [ObservableProperty]
    private StaffMember selectedStaffMember; // 단일 선택된 항목

    public EmployeeListViewModel()
    {
        // ... TeamMembers 초기화 ...
        SelectedStaffMember = TeamMembers.Skip(1).FirstOrDefault(); // 두 번째 항목을 초기 선택
    }
}

// EmployeeListPage.xaml.cs (코드 비하인드 - 선택 이벤트 예시)
namespace MyMauiApp.Views;

public partial class EmployeeListPage : ContentPage
{
    public EmployeeListPage()
    {
        InitializeComponent();
    }

    private void OnStaffSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.CurrentSelection.FirstOrDefault() is StaffMember selected)
        {
            System.Diagnostics.Debug.WriteLine($"새로 선택된 직원: {selected.FullName}");
        }
    }
}

3.1.2. 다중 선택 및 초기 선택


<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="다중 선택 예제">
    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <Label Text="{Binding SelectedStaffMembers.Count, StringFormat='선택된 직원 수: {0}'}" FontSize="18" Margin="10" />
        <CollectionView
            ItemsSource="{Binding TeamMembers}"
            SelectedItems="{Binding SelectedStaffMembers}" <!-- 다중 선택 항목 바인딩 -->
            SelectionMode="Multiple"> <!-- 다중 선택 모드 -->
            <!-- ... ItemTemplate ... -->
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>

// EmployeeListViewModel.cs (뷰 모델)
public partial class EmployeeListViewModel : ObservableObject
{
    // ... 기존 코드 ...

    [ObservableProperty]
    private ObservableCollection<object> selectedStaffMembers; // SelectedItems는 ObservableCollection<object>로 바인딩

    public EmployeeListViewModel()
    {
        // ... TeamMembers 초기화 ...
        SelectedStaffMembers = new ObservableCollection<object> { TeamMembers[0], TeamMembers[2] }; // 첫 번째와 세 번째 항목을 초기 선택
    }
}

3.1.3. 선택된 항목의 배경색 변경

VisualStateManager를 사용하여 항목이 선택되었을 때의 시각적 상태를 정의할 수 있습니다. ItemTemplate의 루트 요소에 스타일을 적용합니다.


<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="선택 색상 예제">
    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <Style TargetType="Grid"> <!-- ItemTemplate의 루트 요소에 적용 -->
            <Setter Property="VisualStateManager.VisualStateGroups">
                <VisualStateGroupList>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal" />
                        <VisualState x:Name="Selected">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="LightBlue" /> <!-- 선택 시 배경색 -->
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateGroupList>
            </Setter>
        </Style>
    </ContentPage.Resources>

    <StackLayout Margin="10">
        <CollectionView ItemsSource="{Binding TeamMembers}" SelectionMode="Multiple">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="{x:Type vm:StaffMember}">
                    <Grid Padding="10"> <!-- Style이 적용될 루트 Grid -->
                        <Label Text="{Binding FullName}" FontSize="16" />
                        <!-- ... 다른 항목 내용 ... -->
                    </Grid>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </StackLayout>
</ContentPage>

3.2. 빈 뷰 (EmptyView)

CollectionView의 ItemsSource가 비어 있거나 null일 때 표시될 콘텐츠를 정의할 수 있습니다.

3.2.1. 문자열 EmptyView


<!-- EmptyTeamMembers가 비어있으면 "표시할 직원 데이터가 없습니다." 문자열이 표시됩니다. -->
<CollectionView ItemsSource="{Binding EmptyTeamMembers}" EmptyView="표시할 직원 데이터가 없습니다.">
    <!-- ... ItemTemplate ... -->
</CollectionView>

// EmployeeListViewModel.cs (뷰 모델)
public partial class EmployeeListViewModel : ObservableObject
{
    // ... 기존 코드 ...

    [ObservableProperty]
    private ObservableCollection<StaffMember> emptyTeamMembers = new ObservableCollection<StaffMember>(); // 빈 컬렉션
}

3.2.2. 뷰를 이용한 EmptyView

더 복잡한 UI를 표시하려면 ContentView를 사용하여 EmptyView를 정의할 수 있습니다.


<CollectionView ItemsSource="{Binding EmptyTeamMembers}">
    <CollectionView.EmptyView>
        <!-- ContentView는 생략 가능 -->
        <ContentView>
            <StackLayout HorizontalOptions="Center" VerticalOptions="Center" Padding="20">
                <Label
                    Text="데이터를 찾을 수 없습니다."
                    FontAttributes="Bold"
                    FontSize="18"
                    HorizontalTextAlignment="Center" />
                <Image Source="no_data_icon.png" HeightRequest="100" Margin="0,15,0,0" />
                <Label
                    Text="나중에 다시 시도하거나 데이터를 추가해주세요."
                    FontSize="14"
                    TextColor="Gray"
                    HorizontalTextAlignment="Center" />
            </StackLayout>
        </ContentView>
    </CollectionView.EmptyView>
    <!-- ... ItemTemplate ... -->
</CollectionView>

3.2.3. 런타임에 동적으로 EmptyView 변경

ContentPage.Resources에 여러 EmptyView 템플릿을 정의하고, 코드 비하인드에서 필요에 따라 변경할 수 있습니다.


<!-- EmployeeListPage.xaml -->
<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="동적 EmptyView 예제">
    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ContentView x:Key="NoDataViewA">
            <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
                <Label FontSize="18" Text="데이터 없음 (옵션 A)" />
            </StackLayout>
        </ContentView>
        <ContentView x:Key="NoDataViewB">
            <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
                <Label FontSize="18" Text="데이터 없음 (옵션 B)" />
            </StackLayout>
        </ContentView>
    </ContentPage.Resources>

    <VerticalStackLayout>
        <Switch Toggled="OnEmptyViewToggle" IsToggled="False" Margin="10" />
        <Label Text="토글하여 EmptyView 변경" HorizontalOptions="Center" Margin="0,0,0,10" />
        <CollectionView x:Name="dynamicEmptyCollectionView" ItemsSource="{Binding EmptyTeamMembers}">
            <CollectionView.ItemTemplate>
                <!-- ... ItemTemplate ... -->
            </CollectionView.ItemTemplate>
        </CollectionView>
    </VerticalStackLayout>
</ContentPage>

// EmployeeListPage.xaml.cs (코드 비하인드)
namespace MyMauiApp.Views;

public partial class EmployeeListPage : ContentPage
{
    public EmployeeListPage()
    {
        InitializeComponent();
    }

    private void OnEmptyViewToggle(object sender, ToggledEventArgs e)
    {
        // 스위치 상태에 따라 다른 EmptyView를 할당
        dynamicEmptyCollectionView.EmptyView = e.Value ? Resources["NoDataViewA"] : Resources["NoDataViewB"];
    }
}

3.3. 풀 투 리프레시 (Pull-to-refresh)

CollectionView는 RefreshView 컨트롤과 함께 사용하여 사용자가 목록을 아래로 당겨 데이터를 새로 고치도록 할 수 있습니다. RefreshView의 Command와 IsRefreshing 속성을 뷰 모델에 바인딩합니다.

3.4. 점진적 로딩 (Incremental Loading)

방대한 데이터 세트를 처리할 때, 모든 데이터를 한 번에 로드하는 대신 사용자가 목록을 스크롤할 때 추가 데이터를 로드하도록 구성할 수 있습니다. CollectionView는 스크롤 임계값과 관련된 이벤트를 제공하여 이를 구현할 수 있습니다.

4. 목록 스크롤 제어

4.1. 스크롤 이벤트

Scrolled 이벤트를 사용하여 CollectionView의 스크롤 위치 변화를 감지할 수 있습니다. 이벤트 인자 ItemsViewScrolledEventArgs는 스크롤 양, 오프셋, 보이는 항목의 인덱스 등 다양한 정보를 제공합니다.


<CollectionView Scrolled="OnCollectionViewScrolled" ItemsSource="{Binding TeamMembers}">
    <!-- ... -->
</CollectionView>

// 코드 비하인드
private void OnCollectionViewScrolled(object sender, ItemsViewScrolledEventArgs e)
{
    System.Diagnostics.Debug.WriteLine($"수평 이동: {e.HorizontalDelta}, 수직 이동: {e.VerticalDelta}");
    System.Diagnostics.Debug.WriteLine($"수평 오프셋: {e.HorizontalOffset}, 수직 오프셋: {e.VerticalOffset}");
    System.Diagnostics.Debug.WriteLine($"첫 번째 가시 항목 인덱스: {e.FirstVisibleItemIndex}, 마지막: {e.LastVisibleItemIndex}");
    // 필요한 경우 추가 로직 구현 (예: 특정 위치에 도달했을 때 데이터 로드)
}

4.2. 코드를 통한 스크롤 제어

ScrollTo 메서드를 사용하여 특정 인덱스 또는 특정 데이터 항목으로 CollectionView를 프로그래밍 방식으로 스크롤할 수 있습니다.

4.2.1. 인덱스로 스크롤


// 인덱스 3의 항목으로 스크롤하고, 항목을 뷰포트 시작 부분에 배치하며, 애니메이션 사용
staffCollectionView.ScrollTo(3, position: ScrollToPosition.Start, animate: true);

// 그룹화된 데이터의 경우, 특정 그룹의 특정 인덱스로 스크롤
// staffCollectionView.ScrollTo(그룹인덱스, 항목인덱스, ...);

4.2.2. 특정 데이터 항목으로 스크롤


// 뷰 모델에서 특정 직원을 찾아 해당 직원으로 스크롤
var viewModel = BindingContext as EmployeeListViewModel;
var targetMember = viewModel?.TeamMembers.FirstOrDefault(m => m.FullName == "정수민");

if (targetMember != null)
{
    // '정수민' 팀원으로 스크롤하고, 항목을 뷰포트 중앙에 배치하며, 애니메이션은 사용하지 않음
    staffCollectionView.ScrollTo(targetMember, position: ScrollToPosition.Center, animate: false);
}

// 그룹화된 데이터의 경우, 특정 그룹 내의 특정 항목으로 스크롤
// staffCollectionView.ScrollTo(특정항목, 특정그룹, ...);

5. 데이터 그룹화 표시

CollectionView는 데이터를 그룹으로 묶어 표시하는 기능을 지원합니다. 이는 일반적으로 두 가지 단계로 이루어집니다: 그룹화된 데이터 원본 설정 및 XAML에서 그룹 헤더 템플릿 정의.

5.1. 그룹화된 데이터 원본 설정

먼저, 그룹을 나타내는 클래스를 정의하고 뷰 모델에서 데이터를 그룹화합니다. 예를 들어, StaffMember를 DepartmentName별로 그룹화할 수 있습니다.


// Models/StaffGroup.cs
using System.Collections.ObjectModel;
using System.Collections.Generic;

public class StaffGroup : ObservableCollection<StaffMember>
{
    public string GroupTitle { get; private set; } // 그룹 헤더에 표시될 제목

    public StaffGroup(string title, IEnumerable<StaffMember> staffMembers) : base(staffMembers)
    {
        GroupTitle = title;
    }
}

// ViewModels/EmployeeListViewModel.cs (수정된 뷰 모델)
public partial class EmployeeListViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<StaffGroup> groupedTeamMembers; // 그룹화된 데이터 컬렉션

    public EmployeeListViewModel()
    {
        var allMembers = new List<StaffMember>
        {
            new StaffMember { EmployeeId = 1, FullName = "김철수", DepartmentName = "개발부", ProfileImage = "kim.png", ShortDescription = "소프트웨어 개발 담당" },
            new StaffMember { EmployeeId = 2, FullName = "이영희", DepartmentName = "개발부", ProfileImage = "lee.png", ShortDescription = "UX/UI 디자인 담당" },
            new StaffMember { EmployeeId = 3, FullName = "박민준", DepartmentName = "영업부", ProfileImage = "park.png", ShortDescription = "신규 고객 유치" },
            new StaffMember { EmployeeId = 4, FullName = "최지혜", DepartmentName = "마케팅부", ProfileImage = "choi.png", ShortDescription = "온라인 캠페인 기획" },
            new StaffMember { EmployeeId = 5, FullName = "정수민", DepartmentName = "영업부", ProfileImage = "jung.png", ShortDescription = "기존 고객 관리" }
        };

        // 부서 이름으로 그룹화
        GroupedTeamMembers = new ObservableCollection<StaffGroup>(
            allMembers.GroupBy(m => m.DepartmentName)
                      .OrderBy(g => g.Key) // 그룹을 부서 이름으로 정렬
                      .Select(g => new StaffGroup(g.Key, g))
        );
    }
}

5.2. XAML에서 그룹화된 데이터 표시

CollectionView의 IsGrouped 속성을 True로 설정하고 GroupHeaderTemplate을 정의하여 각 그룹의 헤더를 표시합니다. ItemTemplate은 그룹 내 각 항목을 표시하는 데 사용됩니다.


<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:MyMauiApp.ViewModels"
    x:Class="MyMauiApp.Views.EmployeeListPage"
    Title="그룹화된 직원 목록">

    <ContentPage.BindingContext>
        <vm:EmployeeListViewModel />
    </ContentPage.BindingContext>

    <CollectionView
        IsGrouped="True" <!-- 그룹화 사용 설정 -->
        ItemsSource="{Binding GroupedTeamMembers}">

        <!-- 그룹 헤더 템플릿 정의 -->
        <CollectionView.GroupHeaderTemplate>
            <DataTemplate x:DataType="{x:Type vm:StaffGroup}">
                <Grid BackgroundColor="#DDF" Padding="10">
                    <Label Text="{Binding GroupTitle}" FontAttributes="Bold" FontSize="18" TextColor="DarkBlue" VerticalOptions="Center" />
                </Grid>
            </DataTemplate>
        </CollectionView.GroupHeaderTemplate>

        <!-- 개별 항목 템플릿 정의 -->
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="{x:Type vm:StaffMember}">
                <Grid Padding="10" BackgroundColor="White" Margin="0,1,0,0">
                    <Label Text="{Binding FullName}" FontSize="16" />
                    <!-- ... 다른 항목 세부 정보 (예: 프로필 이미지, 설명) ... -->
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

이 외에도 GroupFooterTemplate을 사용하여 각 그룹의 푸터를 정의하거나, EmptyView와 유사하게 그룹이 비어있을 때 표시할 뷰를 설정하는 등 다양한 고급 기능을 활용할 수 있습니다.

태그: .NET MAUI collectionview XAML MVVM data binding

10월 1일 20:17에 게시됨