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

UWP实现可整列调整宽度的表格视图及动态加列需求

UWP模拟DataGrid:实现整列宽度调整+动态添加列

我之前也踩过这个坑——UWP原生确实没有自带的DataGrid/DataGridView控件,用ListView+GridSplitter模拟时很容易出现只能调整当前行对应列宽的问题。下面是我亲测有效的解决方案,既能实现整列同步调整宽度,又支持动态添加列:

1. 先搞定核心数据结构

首先我们需要一个类来存储每一列的配置信息(表头、绑定路径、宽度),还要实现属性变更通知,这样宽度变化时所有行的对应列才能同步更新:

using CommunityToolkit.Mvvm.ComponentModel;

public class GridColumnConfig : ObservableObject
{
    // 默认列宽设为150,可按需调整
    private double _width = 150;
    
    // 表头文本
    public string Header { get; set; }
    
    // 对应行数据的绑定路径
    public string BindingPath { get; set; }
    
    // 列宽,用ObservableObject的SetProperty实现自动通知
    public double Width
    {
        get => _width;
        set => SetProperty(ref _width, value);
    }
}

这里用到了CommunityToolkit.Mvvm包里的ObservableObject,能省掉手动写INotifyPropertyChanged的冗余代码,非常实用。

接下来是ViewModel,需要两个核心集合:列配置集合和数据行集合,还要写动态添加列的方法:

using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;

public class MainViewModel : ObservableObject
{
    // 列配置集合,所有行的列定义都绑定这个集合
    public ObservableCollection<GridColumnConfig> ColumnConfigs { get; } = new();
    
    // 用Dictionary存储行数据,方便动态列的绑定(如果是强类型实体,动态列会很麻烦)
    public ObservableCollection<Dictionary<string, object>> Rows { get; } = new();

    // 动态添加列的方法
    public void AddColumn(string header, string bindingPath)
    {
        // 添加新列的配置
        ColumnConfigs.Add(new GridColumnConfig { Header = header, BindingPath = bindingPath });
        
        // 给现有所有行添加新列的默认值,避免绑定报错
        foreach (var row in Rows)
        {
            if (!row.ContainsKey(bindingPath))
                row[bindingPath] = string.Empty;
        }
    }
}

2. XAML布局:表头+内容列表

表头部分(带可拖拽的GridSplitter)

表头用Grid,列定义直接绑定ColumnConfigs,每个列里放表头文本和GridSplitter,关键是让GridSplitter和列配置关联起来:

<Grid DataContext="{Binding}">
    <!-- 表头的列定义,绑定ColumnConfigs的Width -->
    <Grid.ColumnDefinitions>
        <ItemsControl ItemsSource="{Binding ColumnConfigs}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ColumnDefinition Width="{Binding Width, Mode=TwoWay}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid.ColumnDefinitions>

    <!-- 表头内容:每个列的文本+GridSplitter -->
    <ItemsControl ItemsSource="{Binding ColumnConfigs}" Grid.Column="0" Grid.ColumnSpan="{Binding ColumnConfigs.Count}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid IsItemsHost="True" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="5" /> <!-- GridSplitter的宽度 -->
                    </Grid.ColumnDefinitions>
                    <!-- 表头文本 -->
                    <TextBlock Text="{Binding Header}" VerticalAlignment="Center" Padding="5" FontWeight="SemiBold"/>
                    <!-- GridSplitter,把当前列配置绑定到Tag,方便后台处理 -->
                    <controls:GridSplitter Grid.Column="1" Width="5" ResizeDirection="Columns"
                                           ResizeBehavior="PreviousAndCurrent"
                                           Tag="{Binding}"
                                           DragCompleted="GridSplitter_DragCompleted"/>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

这里的GridSplitter来自Microsoft.Toolkit.Uwp.UI.Controls,记得先在NuGet安装这个包。

内容ListView部分

ListView的每个Item用Grid,列定义同样绑定ColumnConfigs,这样所有行的列宽和表头完全同步:

<ListView ItemsSource="{Binding Rows}" Margin="0,10,0,0" BorderBrush="#EEE" BorderThickness="1">
    <ListView.ItemTemplate>
        <DataTemplate>
            <Grid>
                <!-- 行的列定义,绑定ViewModel的ColumnConfigs -->
                <Grid.ColumnDefinitions>
                    <ItemsControl ItemsSource="{Binding DataContext.ColumnConfigs, RelativeSource={RelativeSource AncestorType=ListView}}">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <ColumnDefinition Width="{Binding Width, Mode=TwoWay}" />
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                </Grid.ColumnDefinitions>

                <!-- 行内容:每个列绑定对应的数据 -->
                <ItemsControl ItemsSource="{Binding DataContext.ColumnConfigs, RelativeSource={RelativeSource AncestorType=ListView}}"
                              Grid.Column="0" Grid.ColumnSpan="{Binding DataContext.ColumnConfigs.Count, RelativeSource={RelativeSource AncestorType=ListView}}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <Grid IsItemsHost="True" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <ContentControl Content="{Binding DataContext[BindingPath], RelativeSource={RelativeSource AncestorType=ListViewItem}}"
                                            Padding="5" VerticalAlignment="Center" BorderBrush="#EEE" BorderThickness="0,0,1,1"/>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

通过RelativeSource获取ViewModel的ColumnConfigs,确保所有行的列定义和表头保持一致,并且绑定同一个Width值。

3. 后台处理GridSplitter的拖拽事件

当拖拽GridSplitter调整宽度后,需要把当前列的实际宽度同步到GridColumnConfig的Width属性,这样所有行的对应列才会跟着更新:

private void GridSplitter_DragCompleted(object sender, DragCompletedEventArgs e)
{
    var splitter = sender as GridSplitter;
    var columnConfig = splitter?.Tag as GridColumnConfig;
    if (columnConfig == null) return;

    // 获取表头Grid对应的列,拿到实际宽度
    var parentGrid = splitter.Parent as Grid;
    var columnIndex = Grid.GetColumn(parentGrid);
    var headerGrid = parentGrid.Parent as Grid;
    var column = headerGrid.ColumnDefinitions[columnIndex];
    
    // 更新列配置的Width,触发所有绑定的列定义更新
    columnConfig.Width = column.ActualWidth;
}

4. 动态添加列的调用

比如在页面加个按钮,点击时调用ViewModel的AddColumn方法:

<Button Content="添加新列" Click="AddColumnButton_Click" Margin="10"/>

后台代码:

private void AddColumnButton_Click(object sender, RoutedEventArgs e)
{
    var vm = DataContext as MainViewModel;
    if (vm == null) return;
    
    // 生成新列的表头和绑定路径
    var columnNum = vm.ColumnConfigs.Count + 1;
    vm.AddColumn($"新列{columnNum}", $"Column{columnNum}");
}

关键注意点

  • 一定要用ObservableCollection和ObservableObject,确保集合/属性变化时UI自动更新。
  • GridSplitter的ResizeBehavior设为PreviousAndCurrent,避免调整时影响其他不相关的列。
  • 行数据用Dictionary是动态列场景下最灵活的选择,如果用强类型实体,动态列的绑定会非常繁琐。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:30:02