I worked through the examples above with XAML and had a couple more notes to add.
First, it's important that the ListView.ItemsSource is a collection of collections, as in the following hierarchy (I'm using ObservableCollection assuming that most people will want to data-bind to these):
public class Item
{
public String Title { get; private set; }
public String Description {get; private set; }
public Item(String title, String description)
{
Title = title;
Description = description;
}
// Whatever other properties
}
// It's essential that each group directly derives from a collection of the individual items.
// It does *not* work to have a group that contains an item collection property, as the ListView
// won't find the items.
public class Group : ObservableCollection<Item>
{
public String Name { get; private set; }
public String ShortName { get; private set; }
public Group(String Name, String ShortName)
{
this.Name = Name;
this.ShortName = ShortName;
}
// Whatever other properties
}
// Populating the groups
ObservableCollection<Group> groupedItems = new ObservableCollection<Group>;
//Repeat for each group. This builds the top-level collection
Group group = new Group("First Group", "1");
groupedItems.Add(group);
// Repeat for each item in a group. This builds the second-level collections
Item item = new Item("First Item", "First Item Description");
group.Add(item);
Assume now you have a viewmodel class called PageViewModel that PageViewModel.GroupedItems is the groupedItems collection created above. The XAML for a Xamarin.Forms.ContentPage with the ListView is then as follows:
The context for the page is PageViewModel, thus ListView.ItemsSource gets bound to PageViewModel.GroupedItems.
That ItemsSource then automatically becomes the binding context for GroupDisplayBinding and GroupShortNameBinding.
Similarly, within the ItemTemplate, the binding context is automatically set to the current item being rendered.
And for completeness the OnItemTapped method in the code-behind would be:
void OnItemTapped(Object sender, ItemTappedEventArgs e)
{
//Cast to the data type of the actual item
var dataItem = (Item)(e.Item);
//Do what you need
}
For anyone who's interested, I've done this for a list of contacts that I want to sort by the first letter of the lastname and a custom GroupHeaderTemplate. Since I wanted to use this on more than one ListView I created (with some help from the internet) some nice code, that is reusable on every ListView I want.
The GroupHeaderCell:
public class GroupHeaderCell : ViewCell
{
public GroupHeaderCell()
{
Height = 40;
var groupKey = new Label
{
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
TextColor = Color.White,
VerticalOptions = LayoutOptions.Center
};
groupKey.SetBinding(Label.TextProperty, new Binding("Key"));
View = new StackLayout
{
HorizontalOptions = LayoutOptions.FillAndExpand,
HeightRequest = 40,
BackgroundColor = Color.FromRgb(255, 153, 0),
Padding = 5,
Orientation = StackOrientation.Horizontal,
Children = { groupKey }
};
}
}
This will add the items to their key:
public class ListViewGroupingClass<K, T> : ObservableCollection<T>
{
public K Key { get; private set; }
public ListViewGroupingClass(K key, IEnumerable<T> items)
{
Key = key;
foreach (var item in items)
{
Items.Add(item);
}
}
}
This is how I create the ObservableCollection:
private void GroupContacts(IEnumerable<WsContactModel> contacts)
{
var sorted = from contact in contacts
orderby contact.Lastname
group contact by contact.NameSort
into contactGroup
where contactGroup.Key != string.Empty
select new ListViewGroupingClass<string, WsContactModel>(contactGroup.Key, contactGroup);
_contactModel.ContactListView.ItemsSource = new ObservableCollection<ListViewGroupingClass<string, WsContactModel>>(sorted);
}
This is how the ListView declaration looks:
_contactModel.ContactListView.HasUnevenRows = true;
_contactModel.ContactListView.IsGroupingEnabled = true;
_contactModel.ContactListView.GroupDisplayBinding = new Binding("Key");
_contactModel.ContactListView.GroupShortNameBinding = new Binding("Key");
_contactModel.ContactListView.ItemTemplate = new DataTemplate(typeof(TextCell));
if (Device.OS != TargetPlatform.WinPhone)
{
_contactModel.ContactListView.GroupHeaderTemplate = new DataTemplate(typeof(GroupHeaderCell));
}
_contactModel.ContactListView.ItemTemplate.SetBinding(TextCell.TextProperty, "Lastname");
_contactModel.ContactListView.ItemTemplate.SetBinding(TextCell.DetailProperty, "Firstname");
_contactModel.ContactListView.ItemTapped += ContactListView_ItemTappedAsync;
_contactModel.ContactListView.IsPullToRefreshEnabled = true;
_contactModel.ContactListView.Refreshing += ContactListViewOnRefreshing;
For the contacts I added this to the data model.
public string NameSort
{
get
{
if (string.IsNullOrWhiteSpace(Lastname) || Lastname.Length == 0)
{
return string.Empty;
}
return Lastname[0].ToString().ToUpper();
}
}
So what about other ListViews? Well it's pretty easy. All you have to do is add another Sort to the data model or wherever you get your data to display. Like @KraigBrockschmidt-MSFT has done it in his ViewModel.
So for example I wanted to sort appointments for a calendar. All I had to add was this:
public string TimeSort
{
get
{
if (!BeginTime.HasValue)
{
return "?";
}
else if (IsCompleteDay)
{
return "Ganztägig";
}
return BeginTime.Value.Hour + " Uhr";
}
}
And the ObservableCollection:
_selectedDateTime = e;
var sorted = from appointment in _appointmentItems.Where(t => t.BeginDate == e)
orderby appointment.BeginTime.Value.Hour
orderby appointment.BeginDate.Value.Minute
group appointment by appointment.TimeSort
into appointmentGroup
select new ListViewGroupingClass<string, WsAppointmentModel>(appointmentGroup.Key, appointmentGroup);
_calendarModel.AppointmentListView.ItemsSource =
new ObservableCollection<ListViewGroupingClass<string, WsAppointmentModel>>(sorted);
If someone is interested I could hook up a quick and dirty sample project.
Hai everyone, here as per my requirement i want to add the selected contacts into groups like family group, college friends group, Office friends group like this. Can anyone share the Source code in xamarin PCL project only. Thanks in advance
Thank you Kraig for that most important distinction about binding to a collection of collections. I have read numerous posts on this subject and for whatever reasons that distinction escaped me until reading your post.
@KraigBrockschmidt-MSFT said:
I worked through the examples above with XAML and had a couple more notes to add.
First, it's important that the ListView.ItemsSource is a collection of collections, as in the following hierarchy (I'm using ObservableCollection assuming that most people will want to data-bind to these):
public class Item
{
public String Title { get; private set; }
public String Description {get; private set; }
public Item(String title, String description)
{
Title = title;
Description = description;
}
// Whatever other properties
}
// It's essential that each group directly derives from a collection of the individual items.
// It does *not* work to have a group that contains an item collection property, as the ListView
// won't find the items.
public class Group : ObservableCollection<Item>
{
public String Name { get; private set; }
public String ShortName { get; private set; }
public Group(String Name, String ShortName)
{
this.Name = Name;
this.ShortName = ShortName;
}
// Whatever other properties
}
// Populating the groups
ObservableCollection<Group> groupedItems = new ObservableCollection<Group>;
//Repeat for each group. This builds the top-level collection
Group group = new Group("First Group", "1");
groupedItems.Add(group);
// Repeat for each item in a group. This builds the second-level collections
Item item = new Item("First Item", "First Item Description");
group.Add(item);
Assume now you have a viewmodel class called PageViewModel that PageViewModel.GroupedItems is the groupedItems collection created above. The XAML for a Xamarin.Forms.ContentPage with the ListView is then as follows:
The context for the page is PageViewModel, thus ListView.ItemsSource gets bound to PageViewModel.GroupedItems.
That ItemsSource then automatically becomes the binding context for GroupDisplayBinding and GroupShortNameBinding.
Similarly, within the ItemTemplate, the binding context is automatically set to the current item being rendered.
And for completeness the OnItemTapped method in the code-behind would be:
void OnItemTapped(Object sender, ItemTappedEventArgs e)
{
//Cast to the data type of the actual item
var dataItem = (Item)(e.Item);
//Do what you need
}
// It's essential that each group directly derives from a collection of the individual items.
// It does not work to have a group that contains an item collection property, as the ListView
// won't find the items.
Is this still a problem in XF?
I am using SQLite and can't have my models derive from an ObservableCollection<> or a List<> since this is not supported in SQLite. That's why I tried using a Collection property which is not recognised by the ListView anymore at all it seems.
Does anybody have a suggestion or better an example how to use ListView Grouping using SQLite.
Solved it with a little workaround...
Just created a second model (a duplicate of the first group class) only for grouping my items. This class can derive now (just as described by Kraig) from ObservableCollection because it does not map any SQLite tables.
The first (original) class now holds explicitly the persistent group data from my DB and I deleted the OL property (mentioned earlier) because it's not needed anymore.
For anyone using Realm, or possibly interested in how they do it, they released a little add-on for supporting this with a couple of lines of code. Realm.GroupedCollection
Posts
DealerLoads is a
List<Dealer>
, whereDealer
isIEnumerable
There's also the Xamarin LabelledSectionsList sample that might help.
I worked through the examples above with XAML and had a couple more notes to add.
First, it's important that the ListView.ItemsSource is a collection of collections, as in the following hierarchy (I'm using ObservableCollection assuming that most people will want to data-bind to these):
Assume now you have a viewmodel class called PageViewModel that PageViewModel.GroupedItems is the groupedItems collection created above. The XAML for a Xamarin.Forms.ContentPage with the ListView is then as follows:
A note about binding contexts:
The context for the page is PageViewModel, thus ListView.ItemsSource gets bound to PageViewModel.GroupedItems.
That ItemsSource then automatically becomes the binding context for GroupDisplayBinding and GroupShortNameBinding.
Similarly, within the ItemTemplate, the binding context is automatically set to the current item being rendered.
And for completeness the OnItemTapped method in the code-behind would be:
the notes it was usefull
Thanks
For anyone who's interested, I've done this for a list of contacts that I want to sort by the first letter of the lastname and a custom GroupHeaderTemplate. Since I wanted to use this on more than one ListView I created (with some help from the internet) some nice code, that is reusable on every ListView I want.
The GroupHeaderCell:
This will add the items to their key:
This is how I create the ObservableCollection:
This is how the ListView declaration looks:
For the contacts I added this to the data model.
So what about other ListViews? Well it's pretty easy. All you have to do is add another Sort to the data model or wherever you get your data to display. Like @KraigBrockschmidt-MSFT has done it in his ViewModel.
So for example I wanted to sort appointments for a calendar. All I had to add was this:
And the ObservableCollection:
If someone is interested I could hook up a quick and dirty sample project.
@JamesMontemagno has prepared a really nice solution for this here:
http://motzcod.es/post/94643411707/enhancing-xamarin-forms-listview-with-grouping
have a look and get this working!
@tkowalczyk Ah that's where most of my code comes from.
@RaphaelSchindler yeah I noticed that and would like to add the source
Hai everyone, here as per my requirement i want to add the selected contacts into groups like family group, college friends group, Office friends group like this. Can anyone share the Source code in xamarin PCL project only. Thanks in advance
Thank you Kraig for that most important distinction about binding to a collection of collections. I have read numerous posts on this subject and for whatever reasons that distinction escaped me until reading your post.
Hi @RaphaelSchindler ,
Thanks for the post.
Could you let me know how to place custom Group header(multiple) with your approach.
Your example help me a lot sir. Thank you!
Is it possible to group listview two times?
My goal is to show list of cars (for example):
BMW
-- 316i
-- 318i
-- 320d
-- 520i
-- 530d
VW
-- 1.5
-- 2.0
I was trying to achieve it like this:
My idea was to draw ListView inside ListView's DataTemplate but
@Kraig Brockschmidt
much thanks!
@KraigBrockschmidt-MSFT
Thanks for the example.. I have a question regarding your NOTE...
// It's essential that each group directly derives from a collection of the individual items.
// It does not work to have a group that contains an item collection property, as the ListView
// won't find the items.
Is this still a problem in XF?
I am using SQLite and can't have my models derive from an ObservableCollection<> or a List<> since this is not supported in SQLite. That's why I tried using a Collection property which is not recognised by the ListView anymore at all it seems.
Does anybody have a suggestion or better an example how to use ListView Grouping using SQLite.
Thanks a lot!
Cheers
@SaschaMartinetz I don't know, unfortunately, as I haven't been working with Xamarin for some time now.
@KraigBrockschmidt-MSFT
Thanks anyway.. just had an idea but have to try first if that works.
Solved it with a little workaround...
Just created a second model (a duplicate of the first group class) only for grouping my items. This class can derive now (just as described by Kraig) from ObservableCollection because it does not map any SQLite tables.
The first (original) class now holds explicitly the persistent group data from my DB and I deleted the OL property (mentioned earlier) because it's not needed anymore.
Works like a charme...
Thanks again.. cheers
For anyone using Realm, or possibly interested in how they do it, they released a little add-on for supporting this with a couple of lines of code. Realm.GroupedCollection
How can I achieve the same thing in Android like in iOS? Because in Android, this header is not sticky header like in iOS.. \
@AdrianGhi have you solved the sticky headers on Android somehow?
@Martinedo no, I didn't find any solution and I didn't manage to do this sticky header... We just renounced at the idea..