如何在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
相关产品推荐
相关产品推荐

