如何正确合并DataGridView行表头单元格?解决鼠标悬停失效问题
解决DataGridView行标题合并后鼠标悬停失效的问题
嘿,我来帮你搞定这个鼠标移过就失效的行标题合并问题~
问题根源
你之前用RowPostPaint事件来绘制合并的行标题,但这个事件只是在行绘制完成后额外叠加绘制。当鼠标悬停、单元格选中或者DataGridView触发重绘时,系统会自动调用CellPainting事件重新绘制单元格的默认样式,直接把你手动画的合并效果覆盖掉,这就是为什么鼠标一移过去效果就没了。
优化方案
我们需要直接接管行标题单元格的绘制逻辑,在CellPainting事件里完成合并绘制,并且阻止系统的默认绘制。同时修正原来的合并索引逻辑,确保每两行的行标题能正确合并。
修改后的完整代码
using System; using System.Drawing; using System.Windows.Forms; namespace DGVRowHeaderCellMerge { public partial class Form1 : Form { public Form1() { InitializeComponent(); this.dataGridView1.Scroll += (s, e) => this.dataGridView1.Invalidate(); // 绑定CellPainting事件处理行标题绘制 this.dataGridView1.CellPainting += DataGridView1_CellPainting; } private void Form1_Load(object sender, EventArgs e) { dataGridView1.GetType().InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, dataGridView1, new object[] { true }); dataGridView1.EnableHeadersVisualStyles = false; dataGridView1.RowHeadersWidth = 160; dataGridView1.ColumnHeadersHeight = 40; dataGridView1.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single; dataGridView1.ColumnHeadersDefaultCellStyle.BackColor = Color.White; dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor = Color.Black; dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; for (int i = 0; i < 50; i++) { dataGridView1.Columns.Add("ch" + i.ToString(), "C" + i.ToString()); dataGridView1.Columns[i].Width = 30; dataGridView1.Columns[i].SortMode = DataGridViewColumnSortMode.NotSortable; dataGridView1.Columns[i].HeaderCell.Style.Alignment = DataGridViewContentAlignment.BottomCenter; this.dataGridView1.Columns[i].Resizable = DataGridViewTriState.False; Application.DoEvents(); } dataGridView1.SelectionMode = DataGridViewSelectionMode.CellSelect; for (int j = 0; j < 100; j++) { int idx = dataGridView1.Rows.Add(); dataGridView1.Rows[idx].HeaderCell.Value = $"组{j/2 +1}"; // 改成组标题更直观 dataGridView1.Rows[idx].HeaderCell.Style.WrapMode = DataGridViewTriState.True; dataGridView1.Rows[idx].HeaderCell.Style.SelectionBackColor = Color.White; dataGridView1.Rows[idx].HeaderCell.Style.BackColor = Color.White; dataGridView1.Rows[idx].Resizable = DataGridViewTriState.False; Application.DoEvents(); } } private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { // 只处理行标题单元格(ColumnIndex为-1) if (e.ColumnIndex != -1) return; // 计算当前行所属的合并组:每2行一组 int groupIndex = e.RowIndex / 2; int startRow = groupIndex * 2; int endRow = startRow + 1; // 防止越界(比如总行数为奇数时的最后一行) if (endRow >= dataGridView1.Rows.Count) endRow = dataGridView1.Rows.Count -1; // 获取合并后的单元格区域:以组内第一行为基准,高度扩展为两行总和 Rectangle mergeRect = dataGridView1.GetCellDisplayRectangle(-1, startRow, true); mergeRect.Height = dataGridView1.Rows[startRow].Height + dataGridView1.Rows[endRow].Height; // 绘制背景:根据是否选中/悬停切换样式 Brush backBrush; bool isInteractive = dataGridView1.Rows[startRow].Selected || dataGridView1.Rows[endRow].Selected || e.RowIndex == dataGridView1.CurrentCellAddress.Y; if (isInteractive) { backBrush = new SolidBrush(Color.LightBlue); // 选中/悬停时的背景色 } else { backBrush = new SolidBrush(Color.LightYellow); // 默认背景色 } e.Graphics.FillRectangle(backBrush, mergeRect); backBrush.Dispose(); // 绘制合并后的文本(使用组内第一行的标题) string cellText = dataGridView1.Rows[startRow].HeaderCell.Value?.ToString() ?? string.Empty; StringFormat textFormat = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }; using (Brush textBrush = new SolidBrush(dataGridView1.RowHeadersDefaultCellStyle.ForeColor)) { e.Graphics.DrawString(cellText, dataGridView1.RowHeadersDefaultCellStyle.Font, textBrush, mergeRect, textFormat); } // 绘制边框 using (Pen borderPen = new Pen(Color.Red)) { e.Graphics.DrawRectangle(borderPen, mergeRect); } // 如果当前行是组内第二行,直接跳过默认绘制 if (e.RowIndex == endRow) { e.Handled = true; return; } // 阻止系统默认绘制,避免覆盖我们的合并效果 e.Handled = true; } } }
关键优化点说明
- 改用CellPainting事件:这个事件会在所有需要绘制单元格的场景触发(包括鼠标悬停、选中、滚动),确保合并效果始终显示
- 阻止默认绘制:通过
e.Handled = true告诉系统我们已经完成了单元格的绘制,不需要再用默认样式覆盖 - 修正合并逻辑:按每两行一组计算,确保合并区域正确,还处理了总行数为奇数的边界情况
- 添加交互样式:鼠标悬停或选中时,合并单元格会切换背景色,保持交互一致性
内容的提问来源于stack exchange,提问作者Jerry
相关产品推荐
相关产品推荐

