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

如何正确合并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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:58:26