WPF/C#:如何将数据分组显示

WPF Samples中的示例

在WPF Samples中有一个关于Grouping的Demo。

该Demo结构如下:

MainWindow.xaml如下:

xaml 复制代码
<Window x:Class="Grouping.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Grouping"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" SizeToContent="Height">
    <StackPanel>

        <StackPanel.Resources>
            <XmlDataProvider x:Key="MyTasks" XPath="Tasks/Task">
                <x:XData>
                    <Tasks xmlns="">
                        <Task Name="Groceries" Priority="2" Type="Home">
                            <Description>Pick up Groceries and Detergent</Description>
                        </Task>
                        <Task Name="Laundry" Priority="2" Type="Home">
                            <Description>Do Laundry</Description>
                        </Task>
                        <Task Name="Email" Priority="1" Type="Work">
                            <Description>Email Clients</Description>
                        </Task>
                        <Task Name="Clean" Priority="3" Type="Work">
                            <Description>Clean my office</Description>
                        </Task>
                        <Task Name="Dinner" Priority="1" Type="Home">
                            <Description>Get ready for family reunion</Description>
                        </Task>
                        <Task Name="Proposals" Priority="2" Type="Work">
                            <Description>Review new budget proposals</Description>
                        </Task>
                    </Tasks>
                </x:XData>
            </XmlDataProvider>
        </StackPanel.Resources>

        <TextBlock Margin="12,5,5,0" FontSize="20" Text="My Task List"/>
        <CheckBox Margin="10,5,5,10" Checked="AddGrouping"
              Unchecked="RemoveGrouping">Group by task type</CheckBox>
        <ItemsControl Margin="10" Name="myItemsControl"
                  ItemsSource="{Binding Source={StaticResource MyTasks}}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <DataTemplate.Resources>
                        <Style TargetType="TextBlock">
                            <Setter Property="FontSize" Value="18"/>
                            <Setter Property="HorizontalAlignment" Value="Center"/>
                        </Style>
                    </DataTemplate.Resources>
                    <Grid>
                        <Ellipse Fill="Silver"/>
                        <StackPanel>
                            <TextBlock Margin="3,3,3,0"
                         Text="{Binding XPath=@Name}"/>
                            <TextBlock Margin="3,0,3,7"
                         Text="{Binding XPath=@Priority}"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemContainerStyle>
                <Style>
                    <Setter Property="Control.Width" Value="100"/>
                    <Setter Property="Control.Margin" Value="5"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <TextBlock FontWeight="Bold" FontSize="15"
                         Text="{Binding Path=Name}"/>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ItemsControl.GroupStyle>
        </ItemsControl>
    </StackPanel>
</Window>

其中:

xaml 复制代码
 <StackPanel.Resources>
            <XmlDataProvider x:Key="MyTasks" XPath="Tasks/Task">
                <x:XData>
                    <Tasks xmlns="">
                        <Task Name="Groceries" Priority="2" Type="Home">
                            <Description>Pick up Groceries and Detergent</Description>
                        </Task>
                        <Task Name="Laundry" Priority="2" Type="Home">
                            <Description>Do Laundry</Description>
                        </Task>
                        <Task Name="Email" Priority="1" Type="Work">
                            <Description>Email Clients</Description>
                        </Task>
                        <Task Name="Clean" Priority="3" Type="Work">
                            <Description>Clean my office</Description>
                        </Task>
                        <Task Name="Dinner" Priority="1" Type="Home">
                            <Description>Get ready for family reunion</Description>
                        </Task>
                        <Task Name="Proposals" Priority="2" Type="Work">
                            <Description>Review new budget proposals</Description>
                        </Task>
                    </Tasks>
                </x:XData>
            </XmlDataProvider>
        </StackPanel.Resources>

使用XmlDataProvider来加载和绑定XML数据。

xaml 复制代码
 <ItemsControl Margin="10" Name="myItemsControl"
                  ItemsSource="{Binding Source={StaticResource MyTasks}}">

将MyTasks绑定到ItemsControl。

xaml 复制代码
 <DataTemplate>
                    <DataTemplate.Resources>
                        <Style TargetType="TextBlock">
                            <Setter Property="FontSize" Value="18"/>
                            <Setter Property="HorizontalAlignment" Value="Center"/>
                        </Style>
                    </DataTemplate.Resources>
                    <Grid>
                        <Ellipse Fill="Silver"/>
                        <StackPanel>
                            <TextBlock Margin="3,3,3,0"
                         Text="{Binding XPath=@Name}"/>
                            <TextBlock Margin="3,0,3,7"
                         Text="{Binding XPath=@Priority}"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>

设置数据模板。

跟本次介绍的主题Grouping有关的内容如下:

xaml 复制代码
 <ItemsControl.GroupStyle>
    <GroupStyle>
         <GroupStyle.HeaderTemplate>
             <DataTemplate>
                   <TextBlock FontWeight="Bold" FontSize="15"
                       Text="{Binding Path=Name}"/>
              </DataTemplate>
          </GroupStyle.HeaderTemplate>
     </GroupStyle>
 </ItemsControl.GroupStyle>

ItemsControl.GroupStyle获取定义每个级别的组的外观的 GroupStyle 对象集合。

GroupStyle如下所示:

csharp 复制代码
    public class GroupStyle : INotifyPropertyChanged
    {
       
        public static readonly ItemsPanelTemplate DefaultGroupPanel;       
        public GroupStyle();
        public static GroupStyle Default { get; }
        [DefaultValue(0)]
        public int AlternationCount { get; set; }    
        [DefaultValue(null)]
        public Style ContainerStyle { get; set; }
        [DefaultValue(null)]
        public StyleSelector ContainerStyleSelector { get; set; }
        [DefaultValue(null)]
        public string HeaderStringFormat { get; set; }
        [DefaultValue(null)]
        public DataTemplate HeaderTemplate { get; set; }     
        [DefaultValue(null)]
        public DataTemplateSelector HeaderTemplateSelector { get; set; }
        [DefaultValue(false)]
        public bool HidesIfEmpty { get; set; }
        public ItemsPanelTemplate Panel { get; set; }
        protected event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e);
    }
}

这里设置了GroupStyle.HeaderTemplate,这个元素定义了分组头的数据模板。数据模板决定了分组头的具体显示方式。

xaml 复制代码
 <TextBlock FontWeight="Bold" FontSize="15"
                       Text="{Binding Path=Name}"/>

这里的Name指的是CollectionViewGroup 类的Name属性。

CollectionViewGroup 类表示根据 GroupDescriptionsCollectionView 对象创建的组。

MainWindow.cs如下:

csharp 复制代码
 public partial class MainWindow : Window
 {
     private CollectionView _myView;

     public MainWindow()
     {
         InitializeComponent();
     }

     private void AddGrouping(object sender, RoutedEventArgs e)
     {
         _myView = (CollectionView) CollectionViewSource.GetDefaultView(myItemsControl.ItemsSource);
         if (_myView.CanGroup)
         {
             var groupDescription
                 = new PropertyGroupDescription("@Type");
             _myView.GroupDescriptions.Add(groupDescription);
         }
     }

     private void RemoveGrouping(object sender, RoutedEventArgs e)
     {
         _myView = (CollectionView) CollectionViewSource.GetDefaultView(myItemsControl.ItemsSource);
         _myView.GroupDescriptions.Clear();
     }
 }

只包含两个事件处理程序。

进行分组是这样写的:

csharp 复制代码
 private void AddGrouping(object sender, RoutedEventArgs e)
     {
         _myView = (CollectionView) CollectionViewSource.GetDefaultView(myItemsControl.ItemsSource);
         if (_myView.CanGroup)
         {
             var groupDescription
                 = new PropertyGroupDescription("@Type");
             _myView.GroupDescriptions.Add(groupDescription);
         }
     }
csharp 复制代码
_myView = (CollectionView) CollectionViewSource.GetDefaultView(myItemsControl.ItemsSource);

虽然CollectionViewSource本身不是一个静态类,但它提供了一个静态方法GetDefaultView,这个方法用于获取与特定数据源关联的默认视图。这种设计允许开发者不必实例化CollectionViewSource对象就能访问和操作数据源的视图。

csharp 复制代码
 var groupDescription
     = new PropertyGroupDescription("@Type");
 _myView.GroupDescriptions.Add(groupDescription);

PropertyGroupDescription类描述使用属性名作为条件对项进行分组。

使用的是这个构造函数:

csharp 复制代码
  = new PropertyGroupDescription("@Type");

在XML和XPath的上下文中,@符号用于引用元素的属性。

这样就实现了基于Type属性进行分组。

csharp 复制代码
  private void RemoveGrouping(object sender, RoutedEventArgs e)
  {
      _myView = (CollectionView) CollectionViewSource.GetDefaultView(myItemsControl.ItemsSource);
      _myView.GroupDescriptions.Clear();
  }

取消分组将_myView.GroupDescriptions清空即可。

该Demo的效果如下:

分组前:

分组后:

代码来源

[WPF-Samples/Data Binding/Grouping at main · microsoft/WPF-Samples (github.com)]

欢迎关注微信公众号:DotNet学习交流。