本篇文章讲解ListView控件的分组和索引的实现
两点说明:
1、Demo中所使用的数据源是SymbolIcon符号,MSDN链接:https://msdn.microsoft.com/zh-cn/library/windows/apps/windows.ui.xaml.controls.symbol.aspx
2、Demo采用了MVVM框架MVVMLight。由于本人初学MVVM,因此有些代码或者文件的分类不一定是合适的做法,还请注意。
一、列表展示
在实现分组与索引之前,先做一个简单的列表展示。这里做简要讲解,更详细的请参考文章:Win10开发:构建基于MVVMLight框架的Win10项目
1、Model文件夹下新增SymbolItem类
using Windows.UI.Xaml.Controls; namespace ListViewDemo.Models { public class SymbolItem { public Symbol Item { get; set; } public int IntVal { get; set; } public string StringVal { get; set; } } }
2、ViewModel文件夹下新增ViewModelLocator类和MainViewModel类
public class ViewModelLocator { public ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SimpleIoc.Default.Register<MainViewModel>(); } public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>(); public static void Cleanup() { } }
public class MainViewModel: ViewModelBase { public MainViewModel() { SetSymbolList(); } private ObservableCollection<SymbolItem> _symbolItems; public ObservableCollection<SymbolItem> SymbolItems { get { return _symbolItems; } set { _symbolItems = value; RaisePropertyChanged(); } } private void SetSymbolList() { this.SymbolItems = new ObservableCollection<SymbolItem>(); foreach (Symbol item in Enum.GetValues(typeof(Symbol))) { //SymbolIcon si = new SymbolIcon((Symbol)Enum.Parse(typeof(Symbol), i.ToString(), true)); SymbolItem symbolItem = new SymbolItem() { Item = item, IntVal=(int)item, StringVal=Enum.GetName(typeof(Symbol),item) }; SymbolItems.Add(symbolItem); } } }
因为我们要展示的是Symbol,因此我们需要获得系统的所有Symbol枚举Enum.GetValues(typeof(Symbol))
3、在App.xaml新增locator的引用
<Application.Resources> <loc:ViewModelLocator x:Key="Locator"/> </Application.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <ListView ItemsSource="{Binding SymbolItems}" > <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Rectangle Margin="0,3,20,3" Width="3" Height="35" Fill="{ThemeResource SystemControlBackgroundAccentBrush}"/> <TextBlock Text="{Binding IntVal}" VerticalAlignment="Center"/> <SymbolIcon Symbol="{Binding Item}" Margin="20,0,20,0"/> <TextBlock Text="{Binding StringVal}" VerticalAlignment="Center"/> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid>
5、运行程序就可以看到效果
二、分组实现
1、Model文件夹下新增SymbolGroup类(并不知道这个类是属于Model还是ViewModel合适)
public class SymbolGroup { private string _name = string.Empty; public string Name { get { return _name; } set { _name = value; } } public ObservableCollection<SymbolItem> SymbolItems { get; private set; } public SymbolGroup() { this.SymbolItems = new ObservableCollection<SymbolItem>(); } }
public class GroupMainViewModel : ViewModelBase { public GroupMainViewModel() { SetSymbolList(); Grouping();//实现分组 } private ObservableCollection<SymbolItem> symbolItems; private ObservableCollection<SymbolGroup> _symbolGroups; public ObservableCollection<SymbolGroup> SymbolGroups { get { return _symbolGroups; } set { _symbolGroups = value; } } private void SetSymbolList() { this.symbolItems = new ObservableCollection<SymbolItem>(); foreach (Symbol item in Enum.GetValues(typeof(Symbol))) { //SymbolIcon si = new SymbolIcon((Symbol)Enum.Parse(typeof(Symbol), i.ToString(), true)); SymbolItem symbolItem = new SymbolItem() { Item = item, IntVal = (int)item, StringVal = Enum.GetName(typeof(Symbol), item) }; symbolItems.Add(symbolItem); } } private void Grouping() { SymbolGroups = new ObservableCollection<SymbolGroup>(); List<string> groupList = new List<string>(); foreach (SymbolItem item in symbolItems) { string val = item.StringVal[0].ToString(); if (groupList.Contains(val)) { var group = SymbolGroups.First(g => g.Name == val); group.SymbolItems.Add(item); } else { groupList.Add(val); var group = new SymbolGroup() { Name=val }; group.SymbolItems.Add(item); SymbolGroups.Add(group); } } } }
3、因为新增了一个ViewModel,所以要更新ViewModelLocator类,新增GroupMainViewModel的注册和声明
public class ViewModelLocator { public ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); SimpleIoc.Default.Register<MainViewModel>(); SimpleIoc.Default.Register<GroupMainViewModel>(); } public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>(); public GroupMainViewModel GroupMain => ServiceLocator.Current.GetInstance<GroupMainViewModel>(); public static void Cleanup() { } }
4、MainPage.xaml中定义一个数据集视图CollectionView
<Page.Resources> <CollectionViewSource x:Name="symbolItems" IsSourceGrouped="True" ItemsPath="SymbolItems" Source="{Binding SymbolGroups}"/> </Page.Resources>别忘了把原来的数据上下文修改为GroupMain
DataContext="{Binding Source={StaticResource Locator}, Path=GroupMain}"
<ListView.GroupStyle> <GroupStyle > <GroupStyle.HeaderTemplate> <DataTemplate> <Button Background="{ThemeResource SystemControlBackgroundAccentBrush}" Content="{Binding Name}"/> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListView.GroupStyle>
效果图可以看出存在两个问题
a.第一个Item为选中状态
b.分组显示不是从a-z的顺序
下面我们来解决这两个问题
7、对于第一个Item为选中状态的原因暂时未知,解决方案可以在构造函数中取消选中即可
listView.SelectedItem = null;
8、对于分组不是a-z的问题,修改GroupMainViewModel类的SymbolGroup成员中的Group顺序可以解决
新增排序函数SortSymbolGroup,在分组完成后调用
private void SortSymbolGroup() { var copyArray = new SymbolGroup[SymbolGroups.Count]; SymbolGroups.CopyTo(copyArray, 0); SymbolGroups.Clear(); for (int i = 65; i < 91; i++)//65-90 ASCII A-Z { try { var group = copyArray.First(g => g.Name == ((char)i).ToString()); if (group != null) { SymbolGroups.Add(group); } } catch (Exception) { } } }这里try catch语句的作用是为了捕获泛型类First方法返回值group为空的异常
其实上面的方法用泛型自带的排序方法可以替代:
SymbolGroups = new ObservableCollection<SymbolGroup>(SymbolGroups.OrderBy(g => g.Name).ToList());
三、索引实现
先看效果图.(嗯,长的不是一点点丑...不要在意这些细节2333)
实现分组索引需要用到SemanticZoom控件,MSDN中关于SemanticZoom控件的介绍 https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=ZH-CN&k=k(Windows.UI.Xaml.Controls.SemanticZoom);k(VS.XamlEditor);k(TargetFrameworkMoniker-.NETCore,Version%3Dv5.0)&rd=true
SemanticZoom控件一个显示缩小的索引视图(ZoomOutView)和一个显示具体的分组列表(ZoomInView)
第二部分已经讲解了分组列表(ZoomInView)的实现,我们还需要一个GridView实现个缩小的视图,绑定的集合是SymbolGroups
<GridView ItemsSource="{Binding SymbolGroups}"> <GridView.ItemTemplate> <DataTemplate> <Border BorderBrush="{ThemeResource SystemControlBackgroundAccentBrush}" BorderThickness="2" Margin="10"> <Grid Height="150" Width="150"> <TextBlock Text="{Binding Name}"/> </Grid> </Border> </DataTemplate> </GridView.ItemTemplate> </GridView>
<SemanticZoom ViewChangeStarted="SemanticZoom_ViewChangeStarted" > <SemanticZoom.ZoomedOutView> <GridView ItemsSource="{Binding SymbolGroups}"> <GridView.ItemTemplate> <DataTemplate> <Border BorderBrush="{ThemeResource SystemControlBackgroundAccentBrush}" BorderThickness="2" Margin="10"> <Grid Height="150" Width="150"> <TextBlock Text="{Binding Name}"/> </Grid> </Border> </DataTemplate> </GridView.ItemTemplate> </GridView> </SemanticZoom.ZoomedOutView> <SemanticZoom.ZoomedInView> <ListView x:Name="listView" ItemsSource="{Binding Source={StaticResource symbolItems}}"> <ListView.GroupStyle> <GroupStyle > <GroupStyle.HeaderTemplate> <DataTemplate> <Button Background="{ThemeResource SystemControlBackgroundAccentBrush}" Content="{Binding Name}"/> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </ListView.GroupStyle> <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Rectangle Margin="0,3,20,3" Width="3" Height="35" Fill="{ThemeResource SystemControlBackgroundAccentBrush}"/> <TextBlock Text="{Binding IntVal}" VerticalAlignment="Center"/> <SymbolIcon Symbol="{Binding Item}" Margin="20,0,20,0"/> <TextBlock Text="{Binding StringVal}" VerticalAlignment="Center"/> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </SemanticZoom.ZoomedInView> </SemanticZoom>
然而,当点击具体某一项时,并不能实现索引,查看MSDN帮助文档找到解决方案
给SemanticZoom控件添加ViewChangeStarted事件
private void SemanticZoom_ViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e) { if (e.IsSourceZoomedInView == false) { e.DestinationItem.Item = e.SourceItem.Item; } }
源代码下载:https://github.com/hebecherish/ListViewDemo