In this article, we’ll see the process of grouping items in a ListView with Xamarin.Forms.
I’m creating an Address Book where we are grouping the contact’s name in the Listview by the initial character's name.
Creating the Project in VS 2017 Community
Start a new project on Visual Studio 2017, and choose File/New/Project.
We will get another window to select in Visual C# and Cross-Platform App(Xamarin.Forms).
Set the name XF_Agenda and click on OK button.
Select Blank App and check the platforms to which you want to generate the projects.
Check the Xamarin.Forms option, and, in Code Sharing strategy, check .NET Standard, which replaces the PCL projects option from VS 2017 update 15.5.
Note
In Visual Studio 2017 15.5.3, we don’t have in "Code Sharing Strategy" the PCL (Portable Class Library), it’s replaced by .NET Standard.
Creating the domain model and data repository
Create a Models folder in the portable Project.
In the Models folder, create a Contact class that represents our domain model.
- public class Contact
- {
- public string Name { get; set; }
- public string Email { get; set; }
- public string Fone { get; set; }
- }
Create the class ContactRepository in Models folder. This class represents our data repository where we will define the data used in the application.
- public class ContactRepository
- {
- public IList<Contact> GetContacts { get; private set; }
-
- public ContactRepository()
- {
- GetContacts = new List<Contact> {
- new Contact { Name = "Alex Silveira", Email = "[email protected]", Fone="9985-5623" },
- new Contact { Name = "Wilson Martin", Email = "[email protected]", Fone ="9985-5623" },
- new Contact { Name = "Osmar Moss", Email = "[email protected]", Fone ="9985-5623" },
- new Contact { Name = "Yasmim Dudley", Email = "[email protected]", Fone ="9985-5623" },
- new Contact { Name = "Yoshio Anthony", Email = "[email protected]" , Fone ="9985-5623"},
- new Contact { Name = "Valentina Poole", Email = "[email protected]" , Fone ="9985-5623"},
- new Contact { Name = "Armando Tillman", Email = "[email protected]" , Fone ="9985-5623"},
- new Contact { Name = "Klaus Hickman", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Levi Marshall", Email = "[email protected]" , Telefone="9985-5623" },
- new Contact { Name = "Norberto Boone", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Emerlindo Mendez", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Marcos Compton", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Braulio Chapman", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Heleno Roberson", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Yuri Herrera", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Lucas Brown", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Gilson Reilly", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Arsenio Suarez", Email = "[email protected]" , Fone ="9985-5623" },
- new Contact { Name = "Igor Mclaughlin", Email = "[email protected]" , Fone ="9985-3023" },
- new Contact { Name = "Carla Craft", Email = "[email protected]" , Fone ="9985-3023" },
- new Contact { Name = "Benedito Carson", Email = "[email protected]" , Fone ="9985-3023" },
- new Contact { Name = "Roberto Reynolds", Email = "[email protected]" , Fone ="9985-3023" },
- new Contact { Name = "Denis Webb", Email = "[email protected]" , Fone ="9985-2123" },
- new Contact { Name = "Jacob Singleton", Email = "[email protected]" , Telefone="9985-2123" },
- new Contact { Name = "Carina Tucker", Email = "[email protected]" , Fone ="9985-2123" },
- new Contact { Name = "Felix Holder", Email = "[email protected]" , Fone ="9985-1123" },
- new Contact { Name = "Mateus Reid", Email = "[email protected]" , Fone ="9985-1123" },
- new Contact { Name = "Anabel Noel", Email = "[email protected]" , Fone ="9985-4123" },
- new Contact { Name = "Karina Dunlap", Email = "[email protected]" , Fone ="9985-3123" },
- new Contact { Name = "Silvio Ewing", Email = "[email protected]" , Fone ="9985-5123" },
- new Contact { Name = "Lucas Reed", Email = "[email protected]" , Fone ="9985-4423" },
- new Contact { Name = "Geraldo Huff", Email = "[email protected]" , Fone ="9985-0923" },
- new Contact { Name = "Fernando Carroll", Email = "[email protected]" , Fone ="9985-0923" },
- new Contact { Name = "Leonardo Hamilton", Email = "[email protected]" , Fone ="9985-0923" },
- new Contact { Name = "Myles Knowles", Email = "[email protected]" , Fone ="9985-0923" },
- new Contact { Name = "Cristina Schmidt", Email = "[email protected]" , Fone ="9985-0923" },
- new Contact { Name = "Thais Ball", Email = "[email protected]" , Fone ="9985-0923" },
- new Contact { Name = "Renato Mclean", Email = "[email protected]" , Fone ="9985-2323" },
- new Contact { Name = "Celio Rogers", Email = "[email protected]" , Fone ="9985-1023" },
- new Contact { Name = "Otavio Estes", Email = "[email protected]" , Fone ="9985-7723"},
- };
- }
- }
Creating the ViewModel: ContactListViewModelCreate a folder ViewModels in the PCL project and then create the ContactListViewModel class that represents the logic of our View.
- public class ContactListViewModel
- {
- public IList<Contact> Items { get; private set; }
- public int ItemsCount { get; private set; }
- public string MyNumber { get; set; } = "+55 (11) 1111-1111";
- public ContactListViewModel()
- {
- var repo = new ContactRepository();
- Items = repo.GetContacts.OrderBy(c => c.Name).ToList();
- ItemsCount = repo.GetContacts.Count;
- }
- }
This code returns the list of contacts in the Items property, the total of contacts in the ItemsCount property, and the phone number defined in MyNumber.
Setting the MainPage Page Code
Open the MainPage.xaml file and include the following code.
- <?xml version="1.0" encoding="utf-8" ?>
- <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
- xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:local="clr-namespace:XF_Agenda"
- x:Class="XF_Agenda.MainPage"
- Title="Agenda">
- <ListView x:Name="LvwContacts"
- Margin="5"
- ItemsSource="{Binding Items}"
- Header="{Binding}"
- Footer="{Binding}"
- HasUnevenRows="True">
-
- <ListView.ItemTemplate>
- <DataTemplate>
- <TextCell Text="{Binding Name}" Detail="{Binding Email, StringFormat='E-mail: {0}'}"/>
- </DataTemplate>
- </ListView.ItemTemplate>
-
- <ListView.HeaderTemplate>
- <DataTemplate>
- <ContentView BackgroundColor="Beige">
- <Label Margin="10" HorizontalOptions="CenterAndExpand" Text="{Binding MeuNumero, StringFormat='My Number : {0}'}" TextColor="Black"/>
- </ContentView>
- </DataTemplate>
- </ListView.HeaderTemplate>
-
- <ListView.FooterTemplate>
- <DataTemplate>
- <ContentView BackgroundColor="Aquamarine">
- <Label Margin="10" HorizontalOptions="CenterAndExpand" Text="{Binding ItemsCount, StringFormat='Contacts : {0}'}" TextColor="Black"/>
- </ContentView>
- </DataTemplate>
- </ListView.FooterTemplate>
-
- </ListView>
- </ContentPage>
In the XAML code, we define a ListView where its ItemsSource property is doing the databinding with the Items property of our ViewModel ContactListViewModel.
To display the contacts we are using an ItemTemplate and the TextCell cell,
- TextCell - Used to display text with the second line as text detail. To do this, simply set the properties:
- Text - to display the first line of text in larger font;
- Detail - to display the second line of text in smaller font;
We also define the Header and Footer using the {Binding} notation without defining a Source or Path. XAML understands that the object that will be used for the Binding will be the same as the current BindingContext. Because ListView propagates this object to the Header and Footer templates, this is not necessary.
Next, we set the HeaderTemplate and FooterTemplate settings to display the MyNumber and ItemsCount properties.
Let's now implement the code in the MainPage.xaml.cs file.
- using Xamarin.Forms;
- using XF_Agenda.ViewModel;
-
- namespace XF_Agenda
- {
- public partial class MainPage : ContentPage
- {
- public MainPage()
- {
- InitializeComponent();
- BindingContext = new ContactListViewModel();
- }
- }
- }
In this code, we are creating an instance of our ViewModel and assigning it to the BindingContext.
Running the project we will get the following result: (Our default project is Android project)
Now, let's group the information in our Address Book to organize the names by the name’s first letter.
Grouping ListView’s Items
In order to group the information in our Address Book, first, we have to enable our ListView to accept the grouping by setting the following properties.
- IsGroupingEnable = True
- GroupDisplayBinding = "{Binding Key}"
In the MainPage.xaml file,
- ...
- <ListView x:Name="LvwContacts"
- Margin="5"
- IsGroupingEnabled="True"
- GroupDisplayBinding="{Binding Key}"
- ItemsSource="{Binding Items}"
- Header="{Binding}"
- Footer="{Binding}"
- HasUnevenRows="True">
- ...
Now, let´s change the code of our ViewModel ContactListViewModel defining the code to group the information based on the name’s first letter.
- public class ContactListViewModel
- {
- public IList<Contact> Items { get; private set; }
- public List<ObservableGroupCollection<string,Contact>> GroupedData { get; set; }
-
- public int ItemsCount { get; private set; }
- public string MyNumber { get; set; } = "+55 (11) 1111-1111";
-
- public ContactListViewModel()
- {
- var repo = new ContactRepository();
- Items = repo.GetContacts.OrderBy(c => c.Name).ToList();
-
- GroupedData = Items.OrderBy(p => p.Name)
- .GroupBy(p => p.Name[0].ToString())
- .Select(p => new ObservableGroupCollection<string, Contact>(p)).ToList();
-
- ItemsCount = repo.GetContacts.Count;
- }
- }
We define a new property called GroupedData() which is of type List <ObservableGroupCollection<string,Contact>, where we are sorting the names by the first letter of the name.
To support property implementation, we need to create the ObservableGroupCollection class in the ViewModels folder.
- public class ObservableGroupCollection<S, T> : ObservableCollection<T>
- {
- private readonly S _key;
- public ObservableGroupCollection(IGrouping<S, T> group)
- : base(group)
- {
- _key = group.Key;
- }
- public S Key
- {
- get { return _key; }
- }
- }
This class inherits from ObservableCollection allowing you to add or remove items from an existing group, it also sets the first letter to perform grouping.
The Key will be of type string and the collection will be of type IEnumerable <Contact>.
Now, just change the ItemsSource property of the ListView to the new GroupedData property,
- <?xml version="1.0" encoding="utf-8" ?>
- <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
- xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:local="clr-namespace:XF_Agenda"
- x:Class="XF_Agenda.MainPage"
- Title="Agenda">
-
- <ListView x:Name="LvwContacts"
- Margin="5"
- IsGroupingEnabled="True"
- GroupDisplayBinding="{Binding Key}"
- ItemsSource="{Binding GroupedData}"
- Header="{Binding}"
- Footer="{Binding}"
- HasUnevenRows="True">
- <ListView.ItemTemplate>
- <DataTemplate>
- <ViewCell>
- <StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
- BackgroundColor="Navy">
- <Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
- <Label Text="{Binding Name}" FontSize="Small" TextColor="Lime" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand"/>
- </Grid>
- </StackLayout>
- </ViewCell>
- </DataTemplate>
- </ListView.ItemTemplate>
- <ListView.HeaderTemplate>
- <DataTemplate>
- <ContentView BackgroundColor="Beige">
- <Label Margin="10" HorizontalOptions="CenterAndExpand" Text="{Binding MyNumber, StringFormat='My number : {0}'}" TextColor="Black"/>
- </ContentView>
- </DataTemplate>
- </ListView.HeaderTemplate>
- <ListView.FooterTemplate>
- <DataTemplate>
- <ContentView BackgroundColor="Aquamarine">
- <Label Margin="10" HorizontalOptions="CenterAndExpand" Text="{Binding ItemsCount, StringFormat='Contacts : {0}'}" TextColor="Black"/>
- </ContentView>
- </DataTemplate>
- </ListView.FooterTemplate>
- </ListView>
- </ContentPage>
And change the TextCell to a ViewCell with a StackLayout and Label view to show the Name property.
Press F5 to run and build the application.
We now see the Address Book information grouped alphabetically by the first letter of the name.