The ListView (as probably expected) allows us to display a list of data items, just like WPF/XAML we can alter the list item (or ViewCell) to display a custom view of our data, including images, buttons etc.
Bind to the ItemsSource property
For some unknown reason, I continually forget that the property used to bind our list of items to on a ListView is ItemsSource, not BindingContext, so just putting this note to myself Bind to the ItemsSource.
Okay back to the post…
Basic use of the ListView
In the case where we have a list of items, such as strings, then we can simply use
<ListView ItemsSource="{Binding Items}" />
assuming we have a view model which exposes an Items property which is a collection (i.e. ObservableCollection or the likes).
Displaying more complex items
As you’d probably expect, if you’re trying to display a non-primitive type, you can override the class ToString method to return a string representation of the data, for example if we’re trying to display for the type SlightlyLessBasicItemViewModel
public class SlightlyLessBasicItemViewModel { public string Key { get; set; } public string Value { get; set; } public override string ToString() { return $"{Key} : {Value}"; } }
However, it’s also likely that we’ll want to formatting, images. buttons etc. into a list view item at which point we need to create our own item template.
If we assume we have a view model with an Items collection of SlightlyLessBasicItemViewModel we might like to display our row/cell with the key in bold and the value italic, for example we would use something like
<ListView ItemsSource="{Binding Items}"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal" VerticalOptions="Center"> <Label Text="{Binding Key}" FontAttributes="Bold" /> <Label Text="{Binding Value}" FontAttributes="Italic" /> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView>
Grouping
The ListView supports grouping, i.e. displaying something slightly different to indicate that the items belong to a group. Let’s create the following
public class GroupViewModel : ObservableCollection<SlightlyLessBasicItemViewModel> { public string GroupName { get; set; } }
Now our main view model will return an Items collection of GroupViewModel types, each GroupViewModel will act as a separate header (and bind to the GroupName for the group heading) and the items will then be displayed beneath each grouping name in the ListView.
All we need to do to our XAML is add IsGroupingEnabled=”True” and then tell the List view to display the GroupName using GroupDisplayBinding=”{Binding GroupName}”. For completeness, here’s the full XAML for this
<ListView ItemsSource="{Binding Items}" IsGroupingEnabled="True" GroupDisplayBinding="{Binding GroupName}"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal" VerticalOptions="Center"> <Label Text="{Binding Key}" FontAttributes="Bold" /> <Label Text="{Binding Value}" FontAttributes="Italic" /> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView>
We can also add the GroupShortNameBinding binding to add to the jump list for jumping to specific parts of our list.
Ofcourse it’s also likely we’re want to override the cell that displays the group. To do this we override the GroupHeaderTemplate (we no longer need the GroupDisplayBinding if we’re taking over the displaying of it). So for example here’s out new group header template displayed in bright green
<ListView ItemsSource="{Binding Items}" IsGroupingEnabled="True"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal" VerticalOptions="Center"> <Label Text="{Binding Key}" FontAttributes="Bold" /> <Label Text="{Binding Value}" FontAttributes="Italic" /> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> <ListView.GroupHeaderTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal" VerticalOptions="Center"> <Label Text="{Binding GroupName}" TextColor="Chartreuse"/> </StackLayout> </ViewCell> </DataTemplate> </ListView.GroupHeaderTemplate> </ListView>
Headers and Footers
Along with groupings we can define headers (displayed as you’d expect at the top of the list view) and footers (yes, these are displayed at the end of the list view). Again, like groupings, we can create/use the default look and feel or override it, so here’s an example with the default look and feel (as our ListView XAML’s getting rather large I’ll show an abridge version to just show the changes here, source for this post will be available on my GitHub repos).
Note: I’m not going to change the view model (the GroupingViewModel) used in this example but instead hard code the header and footer, ofcourse you can data bind to these from your view model just as we’ve done for groups etc.
<ListView ItemsSource="{Binding Items}" IsGroupingEnabled="True" Header="---Header---" Footer="---Footer---"> </ListView>
Now let’s override our templates to customise the display of our header and footer. Remove the text only from the Header and Footer attributes. We’ll need the attributes still to tell the ListView to display a header and footer. Then we added our HeaderTemplate and FooterTemplate, for example
<ListView ItemsSource="{Binding Items}" IsGroupingEnabled="True" Header="" Footer=""> <!-- other content --> <ListView.GroupHeaderTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal" VerticalOptions="Center"> <Label Text="{Binding GroupName}" TextColor="Chartreuse"/> </StackLayout> </ViewCell> </DataTemplate> </ListView.GroupHeaderTemplate> <ListView.HeaderTemplate> <DataTemplate> <StackLayout Orientation="Horizontal"> <Label Text="---" TextColor="Crimson" /> <Label Text="Header" TextColor="DarkOrange"/> <Label Text="---" TextColor="Crimson" /> </StackLayout> </DataTemplate> </ListView.HeaderTemplate> <ListView.FooterTemplate> <DataTemplate> <StackLayout Orientation="Horizontal"> <Label Text="---" TextColor="Orange" /> <Label Text="Footer" TextColor="Crimson"/> <Label Text="---" TextColor="Orange" /> </StackLayout> </DataTemplate> </ListView.FooterTemplate> </ListView>
Different (or uneven) row heights
In some situations we might want the ListView to display rows with different heights. By default all rows will have the same height, we simply need to set HasUnevenRows=”True” in the ListView and then, assuming our ItemTemplate expands to fit the data it’s given, we’ll get rows of differing sizes.
This example is slightly contrived to save creating another view model, so we’ll make the changes to the ItemTemplate to support larger labels and also to expand so when the label wraps the row expands. Here’s the changes to the ListView
<ListView ItemsSource="{Binding Items}" IsGroupingEnabled="True" Header="" Footer="" HasUnevenRows="True"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <StackLayout Orientation="Horizontal" VerticalOptions="StartAndExpand"> <Label Text="{Binding Key}" FontAttributes="Bold" FontSize="24"/> <Label Text="{Binding Value}" FontAttributes="Italic" FontSize="24"/> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> <!-- other content --> </ListView>
Pull to refresh
A paradigm used in mobile list views is the idea of pulling the list view to refresh it, for example maybe the list view displays a list of email messages, instead of automatically updating the list the user might see an indicator which says more messages are pending, then the user pulls the list to refresh/update the view.
To add such functionality we need to add the IsPullToRefreshEnabled=”True” to the ListView itself and then we’ll need our view model to supply a RefreshCommand and also a boolean property to tell the UI when the refresh has completed.
So let’s go back to a very basic list view and view model. Here’s my view model
public class PullToRefereshViewModel : ViewModel { public PullToRefereshViewModel() { RefreshCommand = new ActionCommand(() => { Items.Add(DateTime.Now.ToString()); IsRefreshing = false; }); } [CreateInstance] public ExtendedObservableCollection<string> Items => GetProperty<ExtendedObservableCollection<string>>(); public ICommand RefreshCommand { get; } public bool IsRefreshing { get => GetProperty<bool>(); set => SetProperty(value); } }
Note: I’m using my Presentations.Core view model code which hopefully is fairly obvious.
Here’s the list view
<ListView ItemsSource="{Binding Items}" IsPullToRefreshEnabled="True" RefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}"/>
When the user pulls the ListView down, it (assuming IsPullToRefreshEnabled is true and the supplied RefreshCommand CanExecute is true) will set IsRefreshing to true and display a activity/busy indicator. It will then call the ICommand assigned to RefreshCommand. However after the command completes the IsRefreshing will need to be set to false to hide the activity/busy indicator. It sort of feels like this should happen automatically when the command finishes, but seems it’s down to the view model to reset this flag.
Note: It’s important that the IsRefreshing is set to false or you’ll be stuck the busy indicator being displayed. Therefore, it’s probably best to put the code to reset IsRefreshing into a finally block if there’s chances of exceptions in the refresh command code.
Context Menu/Actions
We can add a context menu/action to the cell of a listview, for example a Delete option when the cell is tapped the option to delete the selected cell should appear.
We’ll extend the “Pull to Refresh” example by adding an ItemTemplate and changing the view model a little. Here’s the ItemTemplate
<ListView.ItemTemplate> <DataTemplate> <ViewCell> <ViewCell.ContextActions> <MenuItem Text="Delete" Command="{Binding DeleteCommand}" IsDestructive="True" /> </ViewCell.ContextActions> <StackLayout> <Label Text="{Binding Value}" VerticalOptions="Center" /> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate>
Note: the use of IsDestructive=”True” has significance on iOS devices, only a single action can have this set to true.
In this example we need to move away from using a collection of strings for the ItemSource and have a class with a DeleteCommand on it, hence in the example above the cell label binds to a Value property on this new class. Here’s one possible way of implementing the view model for this along with the replacement for the PullToRefreshViewModel
public class ItemViewModel : ViewModel { private readonly ContextMenuViewModel _parent; public ItemViewModel(ContextMenuViewModel parent, string value) { _parent = parent; Value = value; DeleteCommand = new ActionCommand<ItemViewModel>(s => { _parent.Items.Remove(this); }); } public ICommand DeleteCommand { get; } public string Value { get => GetProperty<string>(); set => SetProperty(value); } public class ContextMenuViewModel : ViewModel { public ContextMenuViewModel() { RefreshCommand = new ActionCommand(() => { Items.Add(new ItemViewModel(this, DateTime.Now.ToString())); IsRefreshing = false; }); } [CreateInstance] public ExtendedObservableCollection<ItemViewModel> Items => GetProperty<ExtendedObservableCollection<ItemViewModel>>(); public ICommand RefreshCommand { get; } public bool IsRefreshing { get => GetProperty<bool>(); set => SetProperty(value); } }
Note: there are several ways we might want this sort of code to work, this is just one example.
Code for the samples
Code for the samples listed, can be found at GitHub https://github.com/putridparrot/blog-projects/tree/master/ListViewExamples