UWP ListView控件分组显示数据
本文介绍如何使用 ListView 分组显示数据。这里使用ListView来实现一个简单的联系人列表,并将联系人按首字母分组显示,效果如图:
listview_contacts_group.png一、定义数据类
(一)联系人类 Contact
Contact
类只有二个属性,名字和号码。Name
表示联系人的姓名,姓名的首字母就是后面我们将用来分组联系人数据的Key
。
class Contact
{
public string Name { get; set; }
public string Phone { get; set; }
}
(二)联系人组 ContactGroup
这个类需要实现IGrouping
接口:
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
实现这个接口时,要指定TKey
和TElement
的类型。TKey
就是对数据分组的Key
,我们要用联系人姓名的首字母来分组数据,首字母是个char
(当然,用String
也可以,这二者很容易转换),所以这里TKey
的类型设定为char
,而TElement
是数据本身的类型,我们的数据是联系人,所以TElement
的类型就是Contact
。
所以最终ContactGroup
实现的接口类型为IGrouping<char, Contact>
:
class ContactGroup : IGrouping<char, Contact>
{
private readonly IEnumerable<Contact> contacts;
public ContactGroup(char key, IEnumerable<Contact> contacts)
{
Key = key;
this.contacts = contacts;
}
public char Key { get; }
public IEnumerator<Contact> GetEnumerator() => contacts.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => contacts.GetEnumerator();
}
这里要实现接口的一个属性和二个方法:
-
Key属性:只读,数据分组依据的对象,本例按姓名首字母排序,那么这里就是指联系人的首字母,所以是个
char
类型;当然,根据不同的分组要求,可以设定为任何类型对象,比如,如果我们的Contact
类中还保存了联系人的生日(DateTime Birthday
),我们想用生日来排序联系人,那么这里的Key
就是DateTime
类型,当然,此时ContactGroup
实现的接口类型要改为IGrouping<DateTime, Contact>
。 -
GetEnumerator()方法: 这二个方法都直接返回所有联系人(
contacts
)的迭代器,contacts
是该分组中包含的所有联系人。
二、分组数据
这里定义一组联系人的示例数据:
var contacts = new Contact[]
{
new Contact(){ Name = "Mand", Phone = "16812345678"},
new Contact(){ Name = "Mike", Phone = "16887654321"},
new Contact(){ Name = "Jack", Phone = "16812121212"},
new Contact(){ Name = "Jobs", Phone = "16800008888"},
new Contact(){ Name = "Json", Phone = "16887654321"},
new Contact(){ Name = "Rose", Phone = "16888888888"},
};
然后将这些联系人分组:
var contactsGroup = contacts.GroupBy(contact => contact.Name.First(), (key, list) => new ContactGroup(key, list));
这里使用GroupBy
的下面这个重载来生成分组后的数据:
public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector);
这里keySelector
(contact => contact.Name.First()
)用来生成每个联系人的排序的键。前面说了,我们要按联系人的名字首字母排序,所以这里使用contact.Name.First()
来生成它的首字母,它的类型是个char
,所以这里生成的就是char
类型的键(同时这个键也作为后面的resultSelector
中的key
参数值),resultSelector
((key, list) => new ContactGroup(key, list)
)用来生成每个组的分组对象(ContactGroup
对象,参数key
是keySelector
中生成的排序对象,list
是这个分组中包含的联系人数据),最终得到的contactsGroup
对象的类型也就是IEnumerable<ContactGroup>
。
思考:
我们按姓名首字母来分组联系人时,默认情况下是按首字母升序排列的,那么能不能按首字母降序排列呢?这个只需要对生成的
contactsGroup
进行排序即可,如要按首字母降序排列:contactsGroup = contactsGroup.OrderByDescending(group => group.Key);
以此类推,还可以做其它要求的排序。
三、Xaml页面显示数据
现在可以在xaml页面来显示分组数据了。
(一)定义 CollectionViewSource 资源
首先在<Page.Resources>
定义一个CollectionViewSource
资源,标识符设置为CSVContacts
,并将其IsSourceGrouped
属性设置为True
,表明数据已经分组了 。ListView
的分组数据需要通过CollectionViewSource
来显示。
<Page.Resources>
<CollectionViewSource x:Name="CvsContacts" x:Key="CvsContacts" IsSourceGrouped="True"/>
</Page.Resources>
(二)定义 ListView
1、绑定数据
<ListView ItemsSource="{x:Bind CvsContacts.View, Mode=OneWay}"></ListView>
ListView
的ItemsSource
不能直接绑定到联系人数据(否则就不会分组显示),而是要绑定到CSvContacts.View
,然后在.cs
代码中,设置CsvContacts.Source
来加载数据,这里就是将前面生成的联系人分组数据设置给它:
CvsContacts.Source = contactsGroup;
2、显示分组Header信息
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="local:ContactGroup">
<TextBlock Text="{x:Bind Key}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
Header(组眉)用来显示分组对象(ContactGroup
,local:ContactGroup
)的信息,本例就用来显示分组的Key
({x:Bind Key}
),也就是各分组中姓名共同的首字母。
3、显示列表项信息
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Contact">
<StackPanel Orientation="Horizontal" Margin="12,0,0,0">
<SymbolIcon Symbol="Phone"/>
<TextBlock Text="{x:Bind Name}" FontWeight="Bold" Margin="8,0,8,0"/>
<TextBlock Text="{x:Bind Phone}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
每个列表项就是一个Contact
对象,这里用来显示这个对象的信息。这里用一个SymbolIcon
来显示一个电话图标,跟着用二个TextBlock
分别显示联系人的姓名和其电话号码。
到这里整个示例就完成了。
三、完整代码
MainPage.cs.xaml
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var contacts = new Contact[]
{
new Contact(){ Name = "Mand", Phone = "16812345678"},
new Contact(){ Name = "Mike", Phone = "16887654321"},
new Contact(){ Name = "Jack", Phone = "16812121212"},
new Contact(){ Name = "Jobs", Phone = "16800008888"},
new Contact(){ Name = "Json", Phone = "16887654321"},
new Contact(){ Name = "Rose", Phone = "16888888888"},
};
var contactsGroup = contacts.GroupBy(contact => contact.Name.First(), (key, list) => new ContactGroup(key, list));
CvsContacts.Source = contactsGroup;
}
}
MainPage.xaml
<Page
x:Class="TestUWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestUWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<CollectionViewSource x:Name="CvsContacts" x:Key="CvsContacts" IsSourceGrouped="True"/>
</Page.Resources>
<Grid>
<ListView
ItemsSource="{x:Bind CvsContacts.View, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Contact">
<StackPanel Orientation="Horizontal" Margin="12,0,0,0">
<SymbolIcon Symbol="Phone"/>
<TextBlock Text="{x:Bind Name}" FontWeight="Bold" Margin="8,0,8,0"/>
<TextBlock Text="{x:Bind Phone}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="local:ContactGroup">
<TextBlock Text="{x:Bind Key}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
</Page>
这只是一个基本的分组方法,还有其它更好的方案,有时间再扯。
by 鳗驼螺 2019.05.17