You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WPF MVVM中实现带动态列的可排序分组表格?

控件选择与实现方案

优先用WPF原生的DataGrid,它天生适配动态列、排序、分组这类表格需求,比ListView省大量自定义代码——ListView要实现表格化展示得手动搭建GridView模板,排序、分组逻辑也得从零写起,完全没必要舍近求远。

具体实现步骤

1. 动态生成列

因为每个Dictionary<string, string>的键不固定,首先要收集所有唯一属性键作为列头,再逐个生成DataGrid列:

// 从字典列表中提取所有唯一属性键
var allPropertyKeys = new HashSet<string>();
foreach (var dict in YourViewModel.DictionaryList)
{
    foreach (var key in dict.Keys)
    {
        allPropertyKeys.Add(key);
    }
}

// 为每个键生成DataGrid列
foreach (var key in allPropertyKeys)
{
    var column = new DataGridTextColumn
    {
        Header = key,
        // 通过索引器绑定字典对应键的值,无值时自动显示空
        Binding = new Binding($"[{key}]"),
        CanUserSort = true, // 开启列排序支持
        Width = DataGridLength.Auto
    };
    YourDataGrid.Columns.Add(column);
}

2. 数据绑定

ViewModel里用ObservableCollection<Dictionary<string, string>>存储数据(保证数据变更时UI自动更新),直接绑定到DataGrid的ItemsSource:

<DataGrid ItemsSource="{Binding DictionaryList}" AutoGenerateColumns="False"/>

3. 排序实现

  • 基础排序:上面生成列时已经开启CanUserSort=true,点击列头就能触发默认的字符串排序。
  • 自定义排序(比如按数字排序age列):处理DataGrid的Sorting事件,替换默认排序逻辑:
private void YourDataGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
    e.Handled = true;
    var columnKey = e.Column.Header.ToString();
    var view = CollectionViewSource.GetDefaultView(YourDataGrid.ItemsSource);
    
    // 清空原有排序规则
    view.SortDescriptions.Clear();
    
    // 判断是否为数字类型属性,做数值排序
    if (columnKey == "age")
    {
        view.SortDescriptions.Add(new SortDescription(
            $"[{columnKey}]", 
            e.Column.SortDirection == ListSortDirection.Ascending 
                ? ListSortDirection.Ascending 
                : ListSortDirection.Descending));
        // 自定义比较器,把字符串转成int再比较
        view.CustomSort = new AgeComparer(e.Column.SortDirection.Value);
    }
    else
    {
        // 其他列用默认字符串排序
        view.SortDescriptions.Add(new SortDescription(
            $"[{columnKey}]", 
            e.Column.SortDirection ?? ListSortDirection.Ascending));
    }
}

// 自定义年龄比较器
public class AgeComparer : IComparer
{
    private readonly ListSortDirection _direction;
    public AgeComparer(ListSortDirection direction) => _direction = direction;
    
    public int Compare(object x, object y)
    {
        var dictX = x as Dictionary<string, string>;
        var dictY = y as Dictionary<string, string>;
        
        int.TryParse(dictX?["age"], out int ageX);
        int.TryParse(dictY?["age"], out int ageY);
        
        return _direction == ListSortDirection.Ascending 
            ? ageX.CompareTo(ageY) 
            : ageY.CompareTo(ageX);
    }
}

4. 分组实现(类似Excel分类汇总)

借助CollectionViewSource实现分组,再通过GroupStyle自定义分组头部样式:

ViewModel代码

private ObservableCollection<Dictionary<string, string>> _dictionaryList;
public ObservableCollection<Dictionary<string, string>> DictionaryList
{
    get => _dictionaryList;
    set
    {
        _dictionaryList = value;
        OnPropertyChanged();
        UpdateGroupedView();
    }
}

public ICollectionView GroupedItems { get; private set; }

// 更新分组视图,可根据用户选择的属性键动态切换分组依据
public void UpdateGroupedView(string groupByKey = "favourite fruit")
{
    var cvs = new CollectionViewSource { Source = DictionaryList };
    cvs.GroupDescriptions.Clear();
    // 按指定属性键分组
    cvs.GroupDescriptions.Add(new PropertyGroupDescription($"[{groupByKey}]"));
    GroupedItems = cvs.View;
    OnPropertyChanged(nameof(GroupedItems));
}

XAML代码

<DataGrid ItemsSource="{Binding GroupedItems}" AutoGenerateColumns="False">
    <!-- 分组头部样式,显示分组名和条目数量 -->
    <DataGrid.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Background="#F0F0F0" Padding="5">
                        <TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="12"/>
                        <TextBlock Text=" (" Margin="5,0,0,0"/>
                        <TextBlock Text="{Binding ItemCount}"/>
                        <TextBlock Text=" 条)"/>
                    </StackPanel>
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
            <GroupStyle.ContainerStyle>
                <Style TargetType="{x:Type GroupItem}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type GroupItem}">
                                <Expander IsExpanded="True">
                                    <Expander.Header>
                                        <ContentPresenter Content="{TemplateBinding Content}"/>
                                    </Expander.Header>
                                    <ItemsPresenter/>
                                </Expander>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </GroupStyle.ContainerStyle>
        </GroupStyle>
    </DataGrid.GroupStyle>
</DataGrid>

为什么不选ListView?

ListView本质是列表控件,要实现表格展示得手动嵌套GridView,动态列生成、列头排序、分组展示都需要自定义模板和事件逻辑,开发成本远高于DataGrid,完全不符合高效实现需求。

内容的提问来源于stack exchange,提问作者Sylvain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:43:21