Using the LongListSelector control on Windows Phone 7
Join the DZone community and get the full member experience.
Join For FreeYesterday I looked into the Silverlight Toolkit for Windows Phone and I covered some of the controls that are included in the current build. One of the controls that might be a bit unusual to configure and get working is LongListSelector, which allows you to group items inside a list. With a regular ListBox, you can display data but there is no way to jump directly to an item set, and there is no way to group items.
Clumping Items Together
One of the best features of any phone is the ability to clump together large lists of items so that they become easier to sort through. Not every phone has this ability though, and it can be very frustrating for some phone owners who are just trying to go through their lists in the easiest ways possible.
The main thing to remember about Windows 7 phones is that you can program in the ability to sort through long lists much more easily. You don’t have to worry about sorting through those lists line by line anymore. You can get it all done via the programming that you put in place, and then you will be all set to move on to the next thing. If this sounds ideal to you, then at least consider getting the programming for your Windows 7 phone done right now. It is a favor that you owe to yourself.
To start, add a reference to Microsoft.Phone.Controls.Toolkit library, which was bundled with the Silverlight Toolkit. If you are not sure where the library is located, check out my previous article to get the basic info about the distribution. Also, add a reference to the library by adding an additional XML namespace reference in the page tag:
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
Once done, you can create a control instance on the page itself, or in any other container that supports a control to be set as Content.
<toolkit:LongListSelector x:Name="LongList"> </toolkit:LongListSelector>
But right now the list itself is nothing but a blank control. To fix this, first of all you need to define some templates that will show how exactly the data will be separated in the list, by using a list header, group headers, and group item headers (used to switch between groups), and an item template.
In the page-based resource dictionary, add the following data templates:
<DataTemplate x:Key="GroupHeader"> <Border Background="{StaticResource PhoneAccentBrush}" Margin="{StaticResource PhoneTouchTargetOverhang}" Padding="{StaticResource PhoneTouchTargetOverhang}"> <TextBlock Text="{Binding Key}"/> </Border> </DataTemplate> <DataTemplate x:Key="GroupItem"> <Border Background="{StaticResource PhoneAccentBrush}" Margin="{StaticResource PhoneTouchTargetOverhang}" Padding="{StaticResource PhoneTouchTargetOverhang}"> <TextBlock Text="{Binding Key}" Style="{StaticResource PhoneTextLargeStyle}"/> </Border> </DataTemplate> <DataTemplate x:Key="ListHeader"> <TextBlock Text="Header" Style="{StaticResource PhoneTextTitle1Style}"/> </DataTemplate> <DataTemplate x:Key="ItemTmpl"> <Grid> <TextBlock Text="{Binding Title}"></TextBlock> </Grid> </DataTemplate>
These are templates very similar to those used in the sample provided on the Silverlight Toolkit website. I decided to use them here since they represent the best example of UI consistency with the existing Windows Phone elements. But it's not the appearance that matters here.
The first template is GroupHeader - this is used to separate content and is used to represent a set of items grouped by a common property. If you look at the binding, it is bound to the Key property, which is dependent on ItemsSource set to the main list.
GroupItem is used to represent the same group header but in the context of a jump list that appears when the user touches an actual group header (that is how it is possible to jump to different item categories). It is bound to the same Key property as each of the group headers.
ListHeader is displayed at the top of the list when it is loaded. It is not moved with the list as that is scrolled and it is also not associated with any group, therefore it won't be displayed above a group header other than the first one. I left this one un-bound, but you can certainly bind it to a super-category name (passed somewhere in a list or a separate property).
ItemTmpl represents the appearance of each item that falls into a specific category. In my case, I am only using a TextBlock control, however, you can add a much richer set (e.g. include images and/or multiple controls). Currently, it is bound to the Title property, which is a part of a custom object I am passing, but I will get there later.
Now I need to actually bind the list to a collection of items that are differentiated in categories. And here, an interesting process is involved. First of all, I am going to create a custom class that will represent an Item object:
public class Item { public string Title { get; set; } public string Content { get; set; } }
It is pretty simple - two string properties that can be set and retrieved. The Content property in my case will be the grouping key. But there is no actual list yet that can be bound.
So, when my main page is initializing, I am also creating a list of items and populating it with sample content:
List<Item> mainItem = new List<Item>(); for (int i = 0; i < 20; i++) { mainItem.Add(new Item() { Content = "Category A", Title = "Sample " + i.ToString() }); mainItem.Add(new Item() { Content = "Category B", Title = "Sample B" + i.ToString() }); mainItem.Add(new Item() { Content = "Category C", Title = "Sample C" + i.ToString() }); }
But here is another problem - the items aren't grouped. Indeed, these are divided into elements that have different Content property values (and there are 20 items that have this property set to the same value), but as such - there are no groups.
Right now is the case to use the IGrouping interface, and I am getting an instance of it for each object set via LINQ:
var selected = from c in mainItem group c by c.Content into n select new GroupingLayer<string,Item>(n);
But look what's here - instead of using IGrouping with the same generic parameters, I am using GroupingLayer. It is an implementation of the IGrouping interface that exposes the Key property, used to set group names. If I would pass an IGrouping instance, I wouldn't be able to bind to the Key property since it is internal. GroupingLayer is an abstraction layer on top of that:
public class GroupingLayer<TKey, TElement> : IGrouping<TKey, TElement> { private readonly IGrouping<TKey, TElement> grouping; public GroupingLayer(IGrouping<TKey, TElement> unit) { grouping = unit; } public TKey Key { get { return grouping.Key; } } public IEnumerator<TElement> GetEnumerator() { return grouping.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return grouping.GetEnumerator(); } }
So once the LINQ query executes, you get an instance of IEnumerable<GroupingLayer<string,Item>>. The group header will automatically link to the string value and every item grouped will be linked to the Item instance stored in the object.
You are now able to bind the list to the control:
LongList.ItemsSource = selected;
Opinions expressed by DZone contributors are their own.
Comments