Win10开发:ListView实现分组和索引

本篇文章讲解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);
            }
        }
    }

这里对SetSymbolList函数做个简要说明

因为我们要展示的是Symbol,因此我们需要获得系统的所有Symbol枚举Enum.GetValues(typeof(Symbol))

3、在App.xaml新增locator的引用

<Application.Resources>
        <loc:ViewModelLocator x:Key="Locator"/>
    </Application.Resources>

4、MainPage.xaml布局如下:

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

引用的数据上下文DataContext="{Binding Source={StaticResource Locator}, Path=Main}"


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>();
        }
    }


2、ViewModel文件夹下新增GroupMainViewModel类,与MainViewModel类类似。Grouping()方法的功能实现原列表的分组

 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}"

5、在原有的ListView模板中新增GroupStyle

 <ListView.GroupStyle>
                <GroupStyle >
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <Button Background="{ThemeResource SystemControlBackgroundAccentBrush}" Content="{Binding Name}"/>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
 </ListView.GroupStyle>

6、运行程序可以看到下面效果



效果图可以看出存在两个问题

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>

将上面的GridView和第二步中的ListView分别放置在ZoomedOutView和ZoomedInView中,代码如下:

<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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值