在Windows Phone 7上使用LongListSelector控件
昨天我研究了 Silverlight Toolkit for Windows Phone,并介绍了当前版本中的一些控件。其中一个控件的配置和工作可能有点不寻常,那就是LongListSelector,它允许你在一个列表中分组项目。使用普通的ListBox,你可以显示数据,但没有办法直接跳到一个项目集,也没有办法对项目进行分组。
将项目集中在一起
任何手机的最佳功能之一是能够将大的项目列表集中在一起,这样它们就更容易被分类。但并不是每部手机都有这种能力,对于一些只是想以最简单的方式浏览清单的手机用户来说,这可能是非常令人沮丧的。
关于 Windows 7手机,需要记住的主要一点是,你可以通过编程来更容易地对长列表进行排序。你不必再担心对这些列表进行逐行排序。你可以通过你的编程来完成这一切,然后你就可以继续做下一件事了。如果这对你来说听起来很理想,那么至少考虑现在就把你的Windows 7手机的编程完成。这是你欠自己的一个人情。
首先,添加对Microsoft.Phone.Controls.Toolkit库的引用,该库是与Silverlight工具包捆绑在一起的。如果你不确定该库的位置,请查看我之前的文章,以获得有关分布的基本信息。同时,通过在页面标签中添加一个额外的XML命名空间引用来添加对该库的引用。
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
一旦完成,你就可以在页面本身创建一个控件实例,或者在任何其他支持控件被设置为内容的容器中创建。
<toolkit:LongListSelector x:Name="LongList">
</toolkit:LongListSelector>
但是现在,列表本身只是一个 空白的控件。要解决这个问题,首先你需要定义一些模板,通过使用列表头、组头和组项头(用于在组之间切换),以及项目模板,来显示数据在列表中到底是如何被分开的。
在基于页面的资源字典中,添加以下数据模板。
<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>
这些模板与Silverlight Toolkit网站上提供的样本中使用的模板非常相似。我决定在这里使用它们,因为它们代表了与现有Windows Phone元素的UI一致性的最好例子。但这里重要的不是外观。
第一个模板是 GroupHeader- 这是用来分隔内容的,用来表示一组由共同属性组成的项目。如果你看一下绑定,它被绑定到Key属性上,这取决于ItemsSource设置为主列表。
GroupItem用于表示相同的组头,但在跳转列表的上下文中,当用户触摸到实际的组头时就会出现(这就是为什么有可能跳转到不同的项目类别)。它被绑定到与每个组头相同的 Key属性上。
ListHeader在加载时显示在列表的顶部。它不会随着列表的滚动而移动,它也不与任何组相关联,因此它不会显示在除第一个组头之外的其他组头之上。我没有绑定这个,但你当然可以把它绑定到一个超级类别的名称上(在列表中的某个地方传递,或者一个单独的属性)。
ItemTmpl表示属于特定类别的每个项目的外观。在我的例子中,我只使用了一个TextBlock控件,然而,你可以添加一个更丰富的集合(例如,包括图像和/或多个控件)。目前,它被绑定到Title属性上,这是一个我正在传递的自定义对象的一部分,但我将在后面得到它。
现在我需要将列表实际绑定到一个以类别区分的项目集合上。在这里,涉及到一个有趣的过程。首先,我将创建一个自定义类,代表一个项目对象。
public class Item
{
public string Title { get; set; }
public string Content { get; set; }
}
它非常简单--两个可以被设置和检索的字符串属性。在我的例子中,内容属性将是分组的关键。但目前还没有可以绑定的实际列表。
所以,当我的主页面初始化时,我也在创建一个项目列表,并用样本内容填充它。
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() });
}
但这里有另一个问题--这些项目并没有分组。的确,这些项目被划分为具有不同内容属性值的元素(有20个项目的这个属性被设置为相同的值),但就这样--没有分组。
现在是使用IGrouping接口的情况,我正在通过LINQ为每个对象集获得它的一个实例。
var selected = from c in mainItem group c by c.Content into n select new GroupingLayer<string,Item>(n);
但看看这里是什么--我没有使用具有相同通用参数的IGrouping,而是使用GroupingLayer。它是IGrouping接口的一个实现,暴露了Key属性,用于设置组名。如果我传递一个IGrouping实例,我将无法绑定到Key属性,因为它是内部的。GroupingLayer是其上面的一个抽象层。
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();
}
}
所以一旦LINQ查询执行,你会得到一个IEnumerable<GroupingLayer<string,Item>>的实例。组头将自动链接到字符串值,每个被分组的项目将链接到存储在该对象中的Item实例。
你现在可以将列表绑定到控件上。
LongList.ItemsSource = selected;
当然,这些项目将显示在列表中。