PropertyGroupDescription自定义排序时CollectionViewGroup的ItemCount始终为0
问题:WPF DataGrid分组按数量排序时CollectionViewGroup.ItemCount始终为0
前置说明
已检索StackOverflow,所有关于分组DataGrid排序的主题均不适用于此问题,目前没有不借助第三方库实现按分组数量排序的公开解决方案。
问题描述
尝试通过重写PropertyGroupDescription的CustomSort属性,实现DataGrid分组按数量排序。但为GroupDescription分配CustomSort方法后,Compare函数中的CollectionViewGroup对象的ItemCount始终为0,导致排序逻辑失效。
示例XAML代码
<Window x:Class="GroupedSorting.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:GroupedSorting" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> </Grid.RowDefinitions> <DataGrid Name="dg" Grid.Row="0" ItemsSource="{Binding ItemVMs}"> <DataGrid.GroupStyle> <!-- Style for groups at top level. --> <GroupStyle> <GroupStyle.ContainerStyle> <Style TargetType="{x:Type GroupItem}"> <Setter Property="Margin" Value="0,0,0,5"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type GroupItem}"> <Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5"> <Expander.Header> <DockPanel> <TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/> <TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/> </DockPanel> </Expander.Header> <Expander.Content> <ItemsPresenter /> </Expander.Content> </Expander> </ControlTemplate> </Setter.Value> </Setter> </Style> </GroupStyle.ContainerStyle> </GroupStyle> <!-- Style for groups under the top level. --> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <DockPanel Background="LightBlue"> <TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/> <TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/> </DockPanel> </DataTemplate> </GroupStyle.HeaderTemplate> </GroupStyle> </DataGrid.GroupStyle> <DataGrid.RowStyle> <Style TargetType="DataGridRow"> <Setter Property="Foreground" Value="Black" /> <Setter Property="Background" Value="White" /> </Style> </DataGrid.RowStyle> </DataGrid> </Grid> </Window>
对应后台代码
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Data; namespace GroupedSorting { public partial class MainWindow : Window { public ObservableCollection<MyViewModel> ItemVMs { get; set; } = new ObservableCollection<MyViewModel>(); public MainWindow() { InitializeComponent(); DataContext = this; var r = new Random(); for (int i = 0; i < 10; i++) { ItemVMs.Add(new MyViewModel() { Name = "Group 1", MyIndex = r.Next() }); ; } for (int i = 0; i < 5; i++) { ItemVMs.Add(new MyViewModel() { Name = "Group 2", MyIndex = r.Next() }); } for (int i = 0; i < 1; i++) { ItemVMs.Add(new MyViewModel() { Name = "Group 3", MyIndex = r.Next() }); } List<MyViewModel> sortedItems = new List<MyViewModel>(); var groups = ItemVMs.GroupBy(x => x.Name); foreach (var group in groups.OrderByDescending(x => x.Count())) { sortedItems.AddRange(group); } ItemVMs.Clear(); ItemVMs = new ObservableCollection<MyViewModel>(sortedItems); Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { var sourceView = CollectionViewSource.GetDefaultView(dg.ItemsSource) as ListCollectionView; var gd = new PropertyGroupDescription(nameof(MyViewModel.Name)); gd.CustomSort = new GroupComparer(); sourceView.GroupDescriptions.Add(gd); sourceView.Refresh(); } } public class MyViewModel { public string Name { get; set; } public int MyIndex { get; set; } } public class GroupComparer : System.Collections.IComparer { public int Compare(object x, object y) { if (!(x is CollectionViewGroup xViewGroup)) return 0; if (!(y is CollectionViewGroup yViewGroup)) return 0; Debug.WriteLine($"{xViewGroup.Name} {xViewGroup.ItemCount}, {yViewGroup.Name} {yViewGroup.ItemCount}"); if (xViewGroup.ItemCount < yViewGroup.ItemCount) return 1; else if (xViewGroup.ItemCount > yViewGroup.ItemCount) return -1; return 0; } } }
解决方案
问题根源
PropertyGroupDescription的CustomSort执行时机早于WPF内部填充CollectionViewGroup.ItemCount的阶段,此时无法通过该属性获取有效分组数量。
实现方案
基础版:直接从数据源统计数量
修改GroupComparer,让它持有数据源引用,在比较时直接从数据源统计对应分组的条目数:
public class GroupComparer : System.Collections.IComparer { private readonly IEnumerable<MyViewModel> _sourceData; public GroupComparer(IEnumerable<MyViewModel> sourceData) { _sourceData = sourceData; } public int Compare(object x, object y) { if (!(x is CollectionViewGroup xViewGroup) || string.IsNullOrEmpty(xViewGroup.Name?.ToString())) return 0; if (!(y is CollectionViewGroup yViewGroup) || string.IsNullOrEmpty(yViewGroup.Name?.ToString())) return 0; int xCount = _sourceData.Count(item => item.Name == xViewGroup.Name.ToString()); int yCount = _sourceData.Count(item => item.Name == yViewGroup.Name.ToString()); return yCount.CompareTo(xCount); // 降序排列,如需升序则改为xCount.CompareTo(yCount) } }
然后在初始化比较器时传入数据源:
private void MainWindow_Loaded(object sender, RoutedEventArgs e) { var sourceView = CollectionViewSource.GetDefaultView(dg.ItemsSource) as ListCollectionView; var gd = new PropertyGroupDescription(nameof(MyViewModel.Name)); gd.CustomSort = new GroupComparer(ItemVMs); sourceView.GroupDescriptions.Add(gd); sourceView.Refresh(); }
优化版:预缓存分组数量
如果数据量较大,每次比较都统计会影响性能,可提前预统计分组数量并缓存:
namespace GroupedSorting { public partial class MainWindow : Window { public ObservableCollection<MyViewModel> ItemVMs { get; set; } = new ObservableCollection<MyViewModel>(); private Dictionary<string, int> _groupCountCache; public MainWindow() { InitializeComponent(); DataContext = this; // ... 原有填充数据的代码 ... // 预统计分组数量并缓存 _groupCountCache = ItemVMs.GroupBy(x => x.Name) .ToDictionary(group => group.Key, group => group.Count()); Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { var sourceView = CollectionViewSource.GetDefaultView(dg.ItemsSource) as ListCollectionView; var gd = new PropertyGroupDescription(nameof(MyViewModel.Name)); gd.CustomSort = new GroupComparer(_groupCountCache); sourceView.GroupDescriptions.Add(gd); sourceView.Refresh(); } } public class GroupComparer : System.Collections.IComparer { private readonly Dictionary<string, int> _groupCountCache; public GroupComparer(Dictionary<string, int> groupCountCache) { _groupCountCache = groupCountCache; } public int Compare(object x, object y) { if (!(x is CollectionViewGroup xViewGroup) || !_groupCountCache.TryGetValue(xViewGroup.Name.ToString(), out int xCount)) return 0; if (!(y is CollectionViewGroup yViewGroup) || !_groupCountCache.TryGetValue(yViewGroup.Name.ToString(), out int yCount)) return 0; return yCount.CompareTo(xCount); // 降序排列 } } }
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

