From dotnet-maui
Implements CollectionView in .NET MAUI for scrollable lists/grids with data binding, selection, grouping, templates, incremental loading, swipe actions, and pull-to-refresh.
npx claudepluginhub dotnet/skills --plugin dotnet-mauiThis skill uses the workspace's default tool permissions.
`CollectionView` is the primary control for displaying scrollable lists and grids of data in .NET MAUI. It replaces `ListView` with better performance, flexible layouts, and no `ViewCell` requirement.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
CollectionView is the primary control for displaying scrollable lists and grids of data in .NET MAUI. It replaces ListView with better performance, flexible layouts, and no ViewCell requirement.
Grid or StackLayout directlyMicrosoft.Maui.Controls.Maps NuGet packageBindableLayout on a StackLayoutObservableCollection<T>) bound to ItemsSourceDataTemplate defining how each item renders<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<HorizontalStackLayout Padding="8" Spacing="8">
<Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" />
<Label Text="{Binding Name}" VerticalOptions="Center" />
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Key rules:
ItemsSource to an ObservableCollection<T> so the UI updates on add/remove.Layout or View — never use ViewCell.x:DataType on DataTemplate for compiled bindings.Set ItemsLayout to control arrangement. Default is VerticalList.
| Layout | XAML value |
|---|---|
| Vertical list | VerticalList (default) |
| Horizontal list | HorizontalList |
| Vertical grid | GridItemsLayout with Orientation="Vertical" |
| Horizontal grid | GridItemsLayout with Orientation="Horizontal" |
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2"
VerticalItemSpacing="8"
HorizontalItemSpacing="8" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<Border Padding="8" StrokeThickness="0">
<VerticalStackLayout>
<Image Source="{Binding Image}" HeightRequest="120" Aspect="AspectFill" />
<Label Text="{Binding Name}" FontAttributes="Bold" />
</VerticalStackLayout>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<CollectionView ItemsSource="{Binding Items}"
ItemsLayout="HorizontalList" />
| Mode | Property to bind | Binding mode |
|---|---|---|
None | — | — |
Single | SelectedItem | TwoWay |
Multiple | SelectedItems | OneWay |
<CollectionView ItemsSource="{Binding Items}"
SelectionMode="Single"
SelectedItem="{Binding CurrentItem, Mode=TwoWay}"
SelectionChangedCommand="{Binding ItemSelectedCommand}" />
For Multiple selection, bind SelectedItems (type IList<object>):
<CollectionView SelectionMode="Multiple"
SelectedItems="{Binding ChosenItems, Mode=OneWay}" />
Highlight selected items using VisualStateManager:
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<Grid Padding="8">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Transparent" />
</VisualState.Setters>
</VisualState>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Label Text="{Binding Name}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
List<T>:public class AnimalGroup : List<Animal>
{
public string Name { get; }
public AnimalGroup(string name, List<Animal> animals) : base(animals)
{
Name = name;
}
}
ObservableCollection<AnimalGroup> and set IsGrouped="True":<CollectionView ItemsSource="{Binding AnimalGroups}"
IsGrouped="True">
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="models:AnimalGroup">
<Label Text="{Binding Name}"
FontAttributes="Bold"
BackgroundColor="{StaticResource Gray100}"
Padding="8" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Animal">
<Label Text="{Binding Name}" Padding="16,4" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Wrap CollectionView in a RefreshView. Set IsRefreshing back to false when done:
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CollectionView ItemsSource="{Binding Items}" />
</RefreshView>
<CollectionView ItemsSource="{Binding Items}"
RemainingItemsThreshold="5"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}" />
⚠️ Do NOT use with non-virtualizing layouts.
LinearItemsLayoutandGridItemsLayoutsupport virtualization. UsingBindableLayouton aStackLayoutas an alternative toCollectionViewhas no virtualization, which triggers infinite threshold-reached events.
Commands inside a DataTemplate can't directly reach your ViewModel. Use RelativeSource AncestorType:
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete"
BackgroundColor="Red"
Command="{Binding BindingContext.DeleteCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}"
CommandParameter="{Binding}" />
</SwipeItems>
</SwipeView.RightItems>
<Grid Padding="8">
<Label Text="{Binding Name}" />
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
Shown when ItemsSource is empty or null.
<CollectionView ItemsSource="{Binding SearchResults}"
EmptyView="No items found." />
For a custom empty view, wrap in ContentView:
<CollectionView ItemsSource="{Binding SearchResults}">
<CollectionView.EmptyView>
<ContentView>
<VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Image Source="empty_state.png" WidthRequest="120" />
<Label Text="Nothing here yet" HorizontalTextAlignment="Center" />
</VerticalStackLayout>
</ContentView>
</CollectionView.EmptyView>
</CollectionView>
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.Header>
<Label Text="Header" FontAttributes="Bold" Padding="8" />
</CollectionView.Header>
<CollectionView.Footer>
<Label Text="Footer" FontAttributes="Italic" Padding="8" />
</CollectionView.Footer>
</CollectionView>
Use HeaderTemplate / FooterTemplate when headers or footers are data-bound.
Programmatically scroll by index or item:
// Scroll to index
collectionView.ScrollTo(index: 10, position: ScrollToPosition.Center, animate: true);
// Scroll to item
collectionView.ScrollTo(item: myItem, position: ScrollToPosition.MakeVisible, animate: true);
| ScrollToPosition | Behavior |
|---|---|
MakeVisible | Scrolls just enough to make the item visible |
Start | Scrolls item to the start of the viewport |
Center | Scrolls item to the center of the viewport |
End | Scrolls item to the end of the viewport |
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Center" />
</CollectionView.ItemsLayout>
SnapPointsType: None, Mandatory, MandatorySingleSnapPointsAlignment: Start, Center, EndMeasureFirstItem for uniform item sizes — significantly faster than MeasureAllItems:
<LinearItemsLayout Orientation="Vertical" ItemSizingStrategy="MeasureFirstItem" />
ObservableCollection<T>, not List<T>. Swapping a List forces a full re-render.MainThread.BeginInvokeOnMainThread(() => Items.Add(item)).| Issue | Fix |
|---|---|
| UI doesn't update when items change | Use ObservableCollection<T>, not List<T>. |
| App crashes or blank items | Never use ViewCell — use Grid, StackLayout, or any View as template root. |
| Items disappear or layout breaks | Always update ItemsSource and the collection on the UI thread (MainThread.BeginInvokeOnMainThread). |
| Incremental loading fires endlessly | Don't use StackLayout as layout; use LinearItemsLayout or GridItemsLayout. |
| EmptyView doesn't render correctly | Wrap custom empty views in ContentView. |
| Poor scroll performance | Use MeasureFirstItem sizing strategy for uniform item sizes. |
| Selected state not visible | Add VisualState Name="Selected" to the item template root element. |
| Binding errors in SwipeView commands | Use RelativeSource AncestorType to reach the ViewModel from inside the item template. |
| Using ListView instead of CollectionView | CollectionView replaces ListView — it has better performance, no ViewCell, and flexible layouts. |