Data and templates

Build reusable CollectionView cells and connect flat or grouped item sources.

CollectionView<TItem> renders a flat item source with native cell reuse and diffable updates. It is the normal choice for lists that can grow beyond a small, fixed set of views.

The item bindings below use the same property tracking described in MVVM and binding.

Content = new CollectionView<Contact>
{
    ItemsSource = viewModel.Contacts,
    ItemTemplate = static () => new ContactRow(),
    
    Layout = CollectionLayout.List(),
    
    EmptyView = new Label
    {
        HorizontalAlignment = HorizontalAlignment.Center,
        VerticalAlignment = VerticalAlignment.Center,
        Text = "No contacts"
    }
};

ItemsSource accepts an array, list, ObservableCollection<T>, collection expression, or list binding. An observable collection animates adds, removals, moves, and replacements into the native snapshot.

Item templates

ItemTemplate creates the element tree for a reusable cell. It is called for the cell, not for every item that passes through that cell. Bind its contents through ItemView<T>:

sealed class ContactRow : ItemView<Contact>
{
    readonly Border container;

    public ContactRow()
    {
        Content = container = new Border
        {
            Height = 56,
            Padding = new Thickness(16, 0),
            Child = new Label
            {
                VerticalAlignment = VerticalAlignment.Center,
                Text = Bind(contact => contact.Name)
            }
        };
    }

    protected override void OnItemChanged(Contact contact)
    {
        container.Background = contact.IsFavorite
            ? Colors.Yellow.WithAlpha(0.12)
            : null;
    }
}

Use bindings for item properties and OnItemChanged when the whole item affects structure or a non-bindable configuration. A recycled cell may receive many items, so the override must set both sides of every state instead of assuming the old item looked the same.

CollectionView owns the cell's Item. It is null only before the template receives its first item; OnItemChanged is called only with an assigned item.

Multiple item templates

Use ItemTemplateSelector when one collection contains different runtime item types that need different element trees. Register every concrete item type with its strongly typed ItemView<T>:

abstract class Setting(string title) : ObservableObject
{
    public string Title { get; } = title;
}

sealed class NavigationSetting(
    string title,
    string detail) : Setting(title)
{
    public string Detail { get; } = detail;
}

sealed partial class ToggleSetting : Setting
{
    public ToggleSetting(string title, bool value) : base(title) =>
        Value = value;

    [ObservableProperty]
    public partial bool Value { get; set; }
}

Content = new CollectionView<Setting>
{
    ItemsSource = viewModel.Settings,
    ItemTemplateSelector = new ItemTemplateSelector<Setting>()
        .Add<NavigationSetting>(
            static () => new NavigationSettingCell())
        .Add<ToggleSetting>(
            static () => new ToggleSettingCell()),
    Layout = CollectionLayout.List(grouped: true)
};

The derived cell receives its derived item type, so its normal compiled bindings remain available:

sealed class ToggleSettingCell : ItemView<ToggleSetting>
{
    public ToggleSettingCell()
    {
        HighlightBackground = null;

        Content = new Switch
        {
            IsOn = Bind(item => item.Value).TwoWay(
                static (item, value) => item.Value = value)
        };
    }
}

The selector creates a separate native reuse pool for every registered template. Each element tree is still built once per reusable cell and rebound as compatible items scroll through it.

Selection is based on the item's exact runtime type. Register every concrete type that can appear in the source; a missing or duplicate registration throws with the relevant type name. Configure the selector before the collection is realized.

ItemTemplateSelector takes precedence if both it and ItemTemplate are assigned. Prefer using only one so the collection's template contract stays obvious.

Item identity

Collection snapshots identify objects by reference. Keep the same model instances when moving or selecting them. Two records with equal values are still separate items; replacing one instance with another is a replacement, not an in-place update.

For property changes within a model, implement INotifyPropertyChanged and bind the cell. For membership and order changes, use ObservableCollection<T>.

Grouped data

CollectionView<TItem, TSection> accepts GroupedItemsSource, where TSection implements ISection<TItem>:

sealed record ContactSection(
    string Title,
    IReadOnlyList<Contact> Items) : ISection<Contact>;

Header and footer templates are ItemView<TSection> instances. GroupedItemsSource takes precedence over a flat ItemsSource when both are assigned.

Continue with layouts and sections for list, grid, carousel, and expandable-section behavior.