Just a few hours ago, the .NET MAUI team announced a significant change coming in .NET 10: the ListView
control and all its related cell types (TextCell
, ImageCell
, ViewCell
, etc.) will be marked as obsolete. This decision is part of Microsoft's strategy to streamline the developer experience by focusing on a single, optimized control for displaying collections of data. Which I'm personally happy with, the less duplicated controls to maintain the better.
If you've been using MAUI (or Xamarin.Forms before it), you're likely familiar with ListView
- it's been a staple for displaying lists of data since the early days. However, CollectionView
in the past had several performance and rendering issues, but these days thanks to Microsoft and the community, the Collection View offers numerous advantages, including better performance, more flexible layouts, and improved customization options. With .NET 10 on the horizon, now is the perfect time to migrate your existing ListView
implementations to CollectionView
.
In this article, I'll walk you through a straightforward migration process, highlighting the key differences between these controls and providing practical examples to ensure a smooth transition for your MAUI applications.
Understanding the Key Differences
Before diving into the migration process, it's helpful to understand some fundamental differences between ListView
and CollectionView
:
Feature | ListView | CollectionView |
---|---|---|
Cell Types | Uses predefined cells (TextCell , ImageCell , etc.) |
Uses DataTemplates directly |
Selection | Single or multiple selection with built-in visual feedback | Single or multiple but even more flexible selection with customizable visual feedback |
Item Appearance | Uses Cell hierarchy |
Uses direct DataTemplate
|
Layouts | Vertical list only | Vertical, horizontal, and grid layouts |
Performance | Less optimized | Better virtualization and performance |
Grouping | Through GroupDisplayBinding
|
More flexible grouping options |
Headers/Footers | Basic header/footer templates | Enhanced header/footer templates |
Step 1: Replace the Control Declaration
The first step is to replace the ListView
declaration with CollectionView
in your XAML:
Before (ListView):
<ListView x:Name="MyListView"
ItemsSource="{Binding Items}">
<!-- Cell templates here -->
</ListView>
After (CollectionView):
<CollectionView x:Name="MyCollectionView"
ItemsSource="{Binding Items}">
<!-- Item templates here -->
</CollectionView>
Step 2: Convert Cell Templates to DataTemplates
One of the biggest differences is how items are templated. ListView
uses various cell types, while CollectionView
uses DataTemplate
directly.
Example: Converting TextCell
Before (ListView with TextCell):
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Title}"
Detail="{Binding Description}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
After (CollectionView):
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{Binding Title}" FontAttributes="Bold" />
<Label Grid.Row="1" Text="{Binding Description}" FontSize="Small" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Example: Converting ImageCell
Before (ListView with ImageCell):
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell Text="{Binding Title}"
Detail="{Binding Description}"
ImageSource="{Binding ImageUrl}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
After (CollectionView):
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.Row="0" Grid.RowSpan="2" Grid.Column="0"
Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="50" WidthRequest="50" />
<Label Grid.Row="0" Grid.Column="1"
Text="{Binding Title}"
FontAttributes="Bold" />
<Label Grid.Row="1" Grid.Column="1"
Text="{Binding Description}"
FontSize="Small" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Step 3: Update Selection Handling
The selection mechanism differs between the two controls:
Before (ListView):
void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem == null)
return;
// Handle the selected item
var selectedItem = e.SelectedItem as MyItemType;
// Important: Deselect the item
((ListView)sender).SelectedItem = null;
}
After (CollectionView):
<CollectionView
x:Name="collectionView"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged">
</CollectionView>
async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previous = e.PreviousSelection.FirstOrDefault();
var current = e.CurrentSelection.FirstOrDefault();
}
Step 4: Convert Grouping
If you're using grouping in your ListView
, you'll need to adapt it for CollectionView
:
Before (ListView with grouping):
<ListView ItemsSource="{Binding GroupedItems}"
IsGroupingEnabled="True"
GroupDisplayBinding="{Binding Key}">
<ListView.ItemTemplate>
<!-- Item template -->
</ListView.ItemTemplate>
</ListView>
After (CollectionView with grouping):
<CollectionView ItemsSource="{Binding GroupedItems}"
IsGrouped="True">
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding Key}"
FontAttributes="Bold"
BackgroundColor="LightGray"
Padding="10" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<!-- Item template -->
</CollectionView.ItemTemplate>
</CollectionView>
Step 5: Headers and Footers
Converting headers and footers is straightforward:
Before (ListView):
<ListView ItemsSource="{Binding Items}">
<ListView.Header>
<Label Text="Items List" FontSize="Large" />
</ListView.Header>
<ListView.Footer>
<Label Text="End of list" />
</ListView.Footer>
<!-- Item template -->
</ListView>
After (CollectionView):
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.Header>
<Label Text="Items List" FontSize="Large" />
</CollectionView.Header>
<CollectionView.Footer>
<Label Text="End of list" />
</CollectionView.Footer>
<!-- Item template -->
</CollectionView>
Step 6: Take Advantage of New Layout Options
One significant advantage of CollectionView
is its flexible layout options:
Vertical List (default):
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="5" />
</CollectionView.ItemsLayout>
<!-- Item template -->
</CollectionView>
Horizontal List:
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" ItemSpacing="5" />
</CollectionView.ItemsLayout>
<!-- Item template -->
</CollectionView>
Grid Layout:
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2"
HorizontalItemSpacing="5"
VerticalItemSpacing="5" />
</CollectionView.ItemsLayout>
<!-- Item template -->
</CollectionView>
Step 7: Handle Empty State
CollectionView
has better support for empty state handling:
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.EmptyView>
<StackLayout Padding="20">
<Label Text="No items available"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Button Text="Refresh" Command="{Binding RefreshCommand}" />
</StackLayout>
</CollectionView.EmptyView>
<!-- Item template -->
</CollectionView>
Common Challenges and Solutions
Challenge 1: Context Actions
ListView's ContextActions
don't have a direct equivalent in CollectionView
. Instead, you can use SwipeView
:
Before (ListView with ContextActions):
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Edit" Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=EditCommand}" CommandParameter="{Binding .}" />
<MenuItem Text="Delete" Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=DeleteCommand}" CommandParameter="{Binding .}" />
</ViewCell.ContextActions>
<Label Text="{Binding Title}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
After (CollectionView with SwipeView):
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Edit"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=EditCommand}"
CommandParameter="{Binding .}"
BackgroundColor="Blue" />
<SwipeItem Text="Delete"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding .}"
BackgroundColor="Red" />
</SwipeItems>
</SwipeView.RightItems>
<Grid Padding="10">
<Label Text="{Binding Title}" />
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Challenge 2: HasUnevenRows
ListView
had HasUnevenRows
for variable height rows. With CollectionView
, rows are automatically sized based on content:
Before (ListView):
<ListView ItemsSource="{Binding Items}" HasUnevenRows="True">
<!-- Item template -->
</ListView>
After (CollectionView):
<CollectionView ItemsSource="{Binding Items}">
<!-- No equivalent property needed - it's the default behavior -->
<!-- Item template -->
</CollectionView>
Conclusion
With .NET 10 marking ListView
as obsolete, now is the ideal time to migrate your MAUI applications to use CollectionView
. The transition may require some initial effort, especially if you have complex templates or custom behaviors, but the benefits are substantial. CollectionView
offers better performance, more flexible layouts, and an overall improved developer experience.
The migration process outlined in this article provides a straightforward path to update your applications, covering the most common scenarios you'll encounter. By embracing CollectionView
now, you'll future-proof your applications and take advantage of the enhancements that Microsoft continues to make to this control.
Remember that while the obsolescence marking begins with .NET 10 Preview 3, ListView
will continue to function in .NET 10, giving you some time to complete your migration. However, bug fixes for ListView
will generally not be prioritized, so it's best to start the transition sooner rather than later.
Have you encountered any specific challenges in migrating from ListView
to CollectionView
? Let me know in the comments below, and let's work through them together!
Top comments (0)