在Windows Phone 7上使用LongListSelector控件的教程

295 阅读4分钟

在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;

当然,这些项目将显示在列表中。