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

如何在C#中创建带可展开/折叠行的表格

实现可展开行的表格(WinForms原生方案)

一、DataGridView 实现方案

这是原生环境下最易实现的方案,通过控制行高+自定义单元格绘制来完成展开效果:

步骤1:定义数据结构

先封装书籍数据,包含显示列和隐藏的描述字段:

public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public string Description { get; set; }
}

步骤2:初始化表格与数据

在窗体加载时绑定数据源,隐藏描述列,设置默认行高:

private void Form1_Load(object sender, EventArgs e)
{
    var books = new List<Book>
    {
        new Book { Title = "百年孤独", Author = "加西亚·马尔克斯", Description = "魔幻现实主义文学的代表作,描述了布恩迪亚家族七代人的传奇故事,融合了神话传说、民间故事与当代现实,构建出一个充满奇幻色彩的小镇马孔多。" },
        new Book { Title = "活着", Author = "余华", Description = "讲述了福贵悲惨而又充满韧性的一生,从富家子弟到贫农,亲眼见证亲人接连离世,却依然在苦难中守住生命的尊严,展现了生命的顽强与苦难的重量。" }
    };

    dataGridView1.DataSource = books;
    dataGridView1.Columns["Description"].Visible = false; // 隐藏描述列,仅展开时显示
    dataGridView1.RowTemplate.Height = 40; // 默认行高
    dataGridView1.MultiSelect = false; // 禁止多选,避免展开冲突
}

步骤3:实现展开/收起逻辑

通过SelectionChanged切换行高,CellPainting绘制跨列的描述内容:

private int _lastExpandedRowIndex = -1;

private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
    if (dataGridView1.SelectedRows.Count == 0) return;

    int currentRowIndex = dataGridView1.SelectedRows[0].Index;

    // 收起之前展开的行
    if (_lastExpandedRowIndex != -1 && _lastExpandedRowIndex != currentRowIndex)
    {
        dataGridView1.Rows[_lastExpandedRowIndex].Height = 40;
    }

    // 切换当前行的展开状态
    if (dataGridView1.Rows[currentRowIndex].Height == 40)
    {
        dataGridView1.Rows[currentRowIndex].Height = 150; // 展开后的高度,可根据内容调整
        _lastExpandedRowIndex = currentRowIndex;
    }
    else
    {
        dataGridView1.Rows[currentRowIndex].Height = 40;
        _lastExpandedRowIndex = -1;
    }
}

private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    // 仅处理展开行的第一列,绘制跨列描述
    if (e.RowIndex == _lastExpandedRowIndex && e.ColumnIndex == 0)
    {
        e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.ContentForeground);
        
        string description = dataGridView1.Rows[e.RowIndex].Cells["Description"].Value.ToString();
        
        // 计算跨列绘制区域
        Rectangle drawRect = new Rectangle(
            e.CellBounds.Left, 
            e.CellBounds.Top + 40, // 从默认行高下方开始绘制
            dataGridView1.Width - dataGridView1.RowHeadersWidth, // 覆盖所有列宽度
            e.CellBounds.Height - 40
        );
        
        // 自动换行绘制文本
        TextRenderer.DrawText(e.Graphics, description, dataGridView1.Font, drawRect, 
            dataGridView1.ForeColor, TextFormatFlags.WordBreak);
        
        e.Handled = true;
    }
}

注意要给DataGridView绑定上述两个事件。

二、ListView(Details模式)实现方案

ListView需开启自定义绘制来实现行展开,步骤如下:

步骤1:设置ListView基础属性

listView1.View = View.Details;
listView1.FullRowSelect = true;
listView1.OwnerDraw = true; // 开启自定义绘制
listView1.Columns.Add("标题", 200);
listView1.Columns.Add("作者", 150);

步骤2:存储数据与展开状态

用字典记录行的展开状态,将描述存入Item的Tag属性:

private Dictionary<ListViewItem, bool> _expandedItems = new Dictionary<ListViewItem, bool>();

private void Form1_Load(object sender, EventArgs e)
{
    var books = new List<Book>
    {
        new Book { Title = "百年孤独", Author = "加西亚·马尔克斯", Description = "魔幻现实主义文学的代表作,描述了布恩迪亚家族七代人的传奇故事,融合了神话传说、民间故事与当代现实,构建出一个充满奇幻色彩的小镇马孔多。" },
        new Book { Title = "活着", Author = "余华", Description = "讲述了福贵悲惨而又充满韧性的一生,从富家子弟到贫农,亲眼见证亲人接连离世,却依然在苦难中守住生命的尊严,展现了生命的顽强与苦难的重量。" }
    };

    foreach (var book in books)
    {
        var item = new ListViewItem(new[] { book.Title, book.Author });
        item.Tag = book.Description;
        _expandedItems[item] = false;
        listView1.Items.Add(item);
    }
}

步骤3:处理点击与自定义绘制

private void listView1_MouseClick(object sender, MouseEventArgs e)
{
    var hitTest = listView1.HitTest(e.X, e.Y);
    if (hitTest.Item == null) return;

    // 切换展开状态并触发重绘
    _expandedItems[hitTest.Item] = !_expandedItems[hitTest.Item];
    listView1.Invalidate();
}

private void listView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    // 绘制默认行内容
    e.DrawDefault = true;

    // 绘制展开后的描述区域
    if (_expandedItems[e.Item])
    {
        Rectangle descRect = new Rectangle(
            e.Bounds.Left, 
            e.Bounds.Bottom + 2, 
            listView1.Width - 20, // 预留滚动条宽度
            100 // 描述区域高度
        );

        // 绘制背景与文本
        e.Graphics.FillRectangle(Brushes.LightGray, descRect);
        TextRenderer.DrawText(e.Graphics, e.Item.Tag.ToString(), listView1.Font, descRect, 
            listView1.ForeColor, TextFormatFlags.WordBreak);
    }
}

private void listView1_MeasureItem(object sender, MeasureListViewItemEventArgs e)
{
    // 调整展开行的总高度
    e.ItemHeight = _expandedItems[e.Item] ? 142 : 40; // 默认行高40 + 描述区100 + 间距2
}

替代简化方案

如果自定义绘制成本过高,可采用以下轻量化方案:

  • 选中行时,在表格下方的独立TextBox/Label控件中显示详细描述,无需展开行
  • 给行添加双击事件,弹出小窗口展示完整描述,实现成本极低

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 15:22:46