Continuum Transition for Windows Phone Silverlight Toolkit
[ and the group items panel, in which I’ve used a wrap panel. The second two templates we will add to the resources section of the page as follows:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="LongListGroupHeader">
<Grid Margin="12,0,0,0">
<Grid Width="75" Height="75" HorizontalAlignment="Left">
<TextBlock Margin="12,0,1,7" TextWrapping="Wrap" d:LayoutOverrides="Width, Height" Style="{StaticResource PhoneTextLargeStyle}" Text="{Binding Title}" VerticalAlignment="Bottom"/>
<Border BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="{StaticResource PhoneAccentColor}"/>
</Border.BorderBrush>
</Border>
</Grid>
</Grid>
</DataTemplate>
<DataTemplate x:Key="LongListGroupItemTemplate">
<Border Background="{Binding GroupBackgroundBrush}" Width="99" Height="99" Margin="6" IsHitTestVisible="{Binding HasItems}">
<TextBlock Text="{Binding Title}"
FontFamily="{StaticResource PhoneFontFamilySemiBold}"
FontSize="36"
Margin="{StaticResource PhoneTouchTargetOverhang}"
Foreground="{StaticResource PhoneForegroundBrush}"
VerticalAlignment="Bottom"/>
</Border>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
You now have all the XAML you need to rock a kick ass selector like the below screenshots:
Excellent question, you cant just bind a list of items to the itemsource with a long list selector. What you need is the following class and linq queries.
public class Group<T> : IEnumerable<T>
{
public Group(string name, IEnumerable<T> items)
{
this.Title = name;
this.Items = new List<T>(items);
}
public override bool Equals(object obj)
{
Group<T> that = obj as Group<T>;
return (that != null) && (this.Title.Equals(that.Title));
}
public override int GetHashCode()
{
return this.Title.GetHashCode();
}
public string Title
{
get;
set;
}
public IList<T> Items
{
get;
set;
}
public bool HasItems
{
get
{
return Items.Count > 0;
}
}
public Brush GroupBackgroundBrush
{
get
{
if (HasItems)
return (SolidColorBrush)Application.Current.Resources["PhoneAccentBrush"];
else
return (SolidColorBrush)Application.Current.Resources["PhoneChromeBrush"];
}
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return this.Items.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.Items.GetEnumerator();
}
#endregion
}
The group class is fairly simple, and is mostly just a container with a title and a list of items. What makes it handy are the other properties that it provides to the long list selector, such as the background colour and hit test (whether or not a group item can be clicked).
The idea is we are going to bind a list of groups to our long list selector. Check out the following code and then meet me on the other side for an explanation.
var allTorrents = (from torrent in model
select new TorrentListItemViewModel(torrent));
var emptyGroups = new List<Group<TorrentListItemViewModel>>()
{
new Group<TorrentListItemViewModel>("#", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("a", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("b", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("c", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("d", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("e", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("f", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("g", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("h", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("i", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("j", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("k", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("l", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("m", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("n", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("o", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("p", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("q", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("r", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("s", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("t", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("u", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("v", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("w", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("x", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("y", new List<TorrentListItemViewModel>()),
new Group<TorrentListItemViewModel>("z", new List<TorrentListItemViewModel>())
};
var groupedTorrents = (from t in allTorrents
group t by t.GroupHeader into grp
orderby grp.Key
select new Group<TorrentListItemViewModel>(grp.Key, grp));
lstMain.ItemsSource = (from t in groupedTorrents.Union(emptyGroups)
orderby t.Title
select t).ToList();
We start off by fetching the data I need and creating a list of my item view models (TorrentListItemViewModel). Next I create a base list of groups that I want to appear on my screen, regardless of whether or not they have data.
Next I turn my flat list of torrents into a list of groups based on the first letter of their name. The magic coming from the GroupHeader property on my view model, which i’ve shared below:
public string GroupHeader
{
get
{
switch (Name.ToLower().Substring(0,1))
{
case "a": return "a";
case "b": return "b";
case "c": return "c";
case "d": return "d";
case "e": return "e";
case "f": return "f";
case "g": return "g";
case "h": return "h";
case "i": return "i";
case "j": return "j";
case "k": return "k";
case "l": return "l";
case "m": return "m";
case "n": return "n";
case "o": return "o";
case "p": return "p";
case "q": return "q";
case "r": return "r";
case "s": return "s";
case "t": return "t";
case "u": return "u";
case "v": return "v";
case "w": return "w";
case "x": return "x";
case "y": return "y";
case "z": return "z";
default: return "#";
}
}
}
Basically I calculate on the fly which group the torrent belongs to based on it’s Name property, defaulting to symbol if no letter is found.
Lastly I Union together the list of torrent groups and empty groups to create a fully filled list of groups that I bind to the long list selector’s ItemsSource property.
That should be all you need to create an alphabetical long list selector. A bit long winded in places, but it does the job nicely.
Please check out the silverlight toolkit’s sample project for a succinct and easy to follow piece of sample code for the long list selector and other cool controls.
Send me a message and I'll get back to you.