LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

c#自带表格控件DataGridView实现一二级表头效果(单元格合并)

admin
2026年8月31日 15:42 本文热度 116
先看效果:

其实有很多第三方控件已经有实现类似的功能,但是基本收费,那我们就可以通过对DataGridView控件加一部分自绘的方式,去实现图中的效果。
其实主要就是表头需要自绘。
主要代码如下:
        /// <summary>        /// 一级表头(跨列合并标题):在上半部分绘制。        /// 只画顶部横线和每组右侧竖线(每条边界只画一次,避免线条变粗)。        /// </summary>        private void DataGridView1_Paint(object sender, PaintEventArgs e)        {            using (StringFormat sf = new StringFormat            {                Alignment = StringAlignment.Center,                LineAlignment = StringAlignment.Center            })            using (Pen pen = new Pen(dataGridView1.GridColor, 1))            using (Brush bgBrush = new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.BackColor))            using (Brush fontBrush = new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor))            {                // 仪器编号列(列1)右侧竖线:上半部分                Rectangle col1 = dataGridView1.GetCellDisplayRectangle(1-1false);                if (!col1.IsEmpty)                {                    int midY = col1.Top + col1.Height / 2;                    e.Graphics.DrawLine(pen, col1.Right - 1, col1.Top, col1.Right - 1, midY);                }
                // 每组:背景 + 一级标题文字 + 组右侧竖线(上半部分)                foreach (var group in _headerGroups)                {                    Rectangle first = dataGridView1.GetCellDisplayRectangle(group.StartColumn, -1false);                    Rectangle last = dataGridView1.GetCellDisplayRectangle(group.EndColumn, -1false);                    if (first.IsEmpty || last.IsEmpty)                        continue;
                    Rectangle topRect = Rectangle.FromLTRB(first.Left, first.Top, last.Right, first.Top + first.Height / 2);
                    e.Graphics.FillRectangle(bgBrush, topRect);                    e.Graphics.DrawString(group.HeaderText,                        dataGridView1.ColumnHeadersDefaultCellStyle.Font, fontBrush, topRect, sf);                    e.Graphics.DrawLine(pen, topRect.Right - 1, topRect.Top,                        topRect.Right - 1, topRect.Top + topRect.Height - 1);                }
                // 顶部横线:贯穿整个表头                Rectangle hFirst = dataGridView1.GetCellDisplayRectangle(1-1false);                Rectangle hLast = dataGridView1.GetCellDisplayRectangle(13-1false);                if (!hFirst.IsEmpty && !hLast.IsEmpty)                {                    e.Graphics.DrawLine(pen, hFirst.Left, hFirst.Top, hLast.Right, hLast.Top);                }            }        }
        /// <summary>        /// 二级表头:每列只画右侧竖线(下半部分)、分隔横线和底部横线。        /// 仪器编号列(列1)文字跨两行居中,不画分隔横线。        /// </summary>        private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)        {            if (e.RowIndex == -1 && e.ColumnIndex >= 0)            {                // 仪器编号列:文字跨两行居中显示                if (e.ColumnIndex == 1)                {                    e.PaintBackground(e.CellBounds, true);                    using (StringFormat sf = new StringFormat                    {                        Alignment = StringAlignment.Center,                        LineAlignment = StringAlignment.Center                    })                    using (Pen pen = new Pen(dataGridView1.GridColor, 1))                    using (Brush fontBrush = new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor))                    {                        e.Graphics.DrawString("仪器编号",                            dataGridView1.ColumnHeadersDefaultCellStyle.Font, fontBrush, e.CellBounds, sf);
                        int midY1 = e.CellBounds.Top + e.CellBounds.Height / 2;                        // 右侧竖线(下半部分,上半部分在 Paint 里画)                        e.Graphics.DrawLine(pen, e.CellBounds.Right - 1, midY1,                            e.CellBounds.Right - 1, e.CellBounds.Bottom);                        // 底部横线                        e.Graphics.DrawLine(pen, e.CellBounds.Left, e.CellBounds.Bottom - 1,                            e.CellBounds.Right, e.CellBounds.Bottom - 1);                    }                    e.Handled = true;                    return;                }
                HeaderGroup group = FindGroup(e.ColumnIndex);                if (group == null)                    return// 隐藏辅助列交给系统默认绘制
                e.PaintBackground(e.CellBounds, true);
                int midY = e.CellBounds.Top + e.CellBounds.Height / 2;                Rectangle subRect = new Rectangle(e.CellBounds.Left, midY, e.CellBounds.Width, e.CellBounds.Height - e.CellBounds.Height / 2);
                using (StringFormat sf = new StringFormat                {                    Alignment = StringAlignment.Center,                    LineAlignment = StringAlignment.Center                })                using (Pen pen = new Pen(dataGridView1.GridColor, 1))                using (Brush fontBrush = new SolidBrush(dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor))                {                    // 二级标题文字                    string subText = e.ColumnIndex < _subHeaders.Length ? _subHeaders[e.ColumnIndex] : "";                    e.Graphics.DrawString(subText,                        dataGridView1.ColumnHeadersDefaultCellStyle.Font, fontBrush, subRect, sf);
                    // 一级/二级表头分隔横线                    e.Graphics.DrawLine(pen, e.CellBounds.Left, midY, e.CellBounds.Right, midY);
                    // 右侧竖线(下半部分,上半部分在 Paint 里画)                    e.Graphics.DrawLine(pen, e.CellBounds.Right - 1, midY,                        e.CellBounds.Right - 1, e.CellBounds.Bottom);
                    // 底部横线                    e.Graphics.DrawLine(pen, e.CellBounds.Left, e.CellBounds.Bottom - 1,                        e.CellBounds.Right, e.CellBounds.Bottom - 1);                }
                e.Handled = true;                return;            }
            // ---------- 数据区:不额外处理,完全使用系统默认绘制 ----------        }        private HeaderGroup FindGroup(int columnIndex)        {            foreach (var group in _headerGroups)            {                if (group.Contains(columnIndex))                    return group;            }            return null;        }
对了,需要在初始化时关闭关闭自动排序,避免表头绘制错位:
 foreach (DataGridViewColumn col in dataGridView1.Columns)            {                col.SortMode = DataGridViewColumnSortMode.NotSortable;                col.ReadOnly = false// 允许编辑,双击即可修改            }
我们自定义的表头内容,单独拿出来:
/// <summary>        /// 一级表头分组:仪器编号单独一列,不需要合并,不放在这里。        /// 列结构:Column0(ID,隐藏) | Column1(仪器编号) | 每组拆成 线号、点号 两列        /// </summary>        private readonly List<HeaderGroup> _headerGroups = new List<HeaderGroup>        {            new HeaderGroup("E1"23),            new HeaderGroup("E2"45),            new HeaderGroup("H1/E3"67),            new HeaderGroup("H2/E4"89),            new HeaderGroup("H3/E5"1011),            new HeaderGroup("M"1213)        };
        /// <summary>        /// 二级表头文字:按列索引取(索引 0 未使用)        /// </summary>        private readonly string[] _subHeaders =        {            "",         // 0            "仪器编号"// 1            "线号""点号"// 2,3   E1            "线号""点号"// 4,5   E2            "线号""点号"// 6,7   H1/E3            "线号""点号"// 8,9   H2/E4            "线号""点号"// 10,11 H3/E5            "线号""点号"  // 12,13 M        };
private class HeaderGroup        {            public string HeaderText { get; }            public int StartColumn { get; }            public int EndColumn { get; }
            public HeaderGroup(string headerText, params int[] columns)            {                HeaderText = headerText;                StartColumn = columns[0];                EndColumn = columns[columns.Length - 1];            }
            public bool Contains(int columnIndex)            {                return columnIndex >= StartColumn && columnIndex <= EndColumn;            }        }
那如何添加数据如下:
dataGridView1.Rows.Clear();
foreach (var kv in list)//list是数据{     Devs dev = kv.Value;     int index = dataGridView1.Rows.Add();     DataGridViewRow row = dataGridView1.Rows[index];
     row.Cells[1].Value = kv.Key;    // 仪器编号
     SetLinePoint(row, 2, dev.E1);     // E1     SetLinePoint(row, 4, dev.E2);     // E2     SetLinePoint(row, 6, dev.H1_E3);  // H1/E3     SetLinePoint(row, 8, dev.H2_E4);  // H2/E4     SetLinePoint(row, 10, dev.H3_E5); // H3/E5     SetLinePoint(row, 12, dev.M);     // M}
private void SetLinePoint(DataGridViewRow row, int startColumn, int[] values)        {            if (values == null || values.Length < 2)                return;            row.Cells[startColumn].Value = values[0];     // 线号            row.Cells[startColumn + 1].Value = values[1]; // 点号        }
那我的需求是还需要双击可以编辑单元格,这个也不涉及啥变动,就列出来方便大家复制:
private void DataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)        {            if (e.RowIndex < 0 || e.ColumnIndex < 0)                return;            dataGridView1.BeginEdit(true);        }
然后为了美观,我也做了一些颜色区分:
​ /// <summary>        /// 列交替效果:每两列一组(E1/E2/H1-E3...),奇数组列叠加淡色区分。        /// 同时保留行的奇偶交替色:偶数行 247,247,250;奇数行 White。        /// </summary>        private void DataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)        {            if (e.RowIndex < 0 || e.ColumnIndex < 2 || e.ColumnIndex > 13)                return;
            int groupIndex = (e.ColumnIndex - 2) / 2// 0=E1,1=E2,2=H1/E3...            bool isOddGroup = groupIndex % 2 == 1;            bool isEvenRow = e.RowIndex % 2 == 0;
            if (isEvenRow && isOddGroup)            {                e.CellStyle.BackColor = Color.FromArgb(236240248); // 偶数行 + 奇数组列            }            else if (!isEvenRow && isOddGroup)            {                e.CellStyle.BackColor = Color.FromArgb(246248252); // 奇数行 + 奇数组列            }            else if (isEvenRow)            {                e.CellStyle.BackColor = Color.FromArgb(247247250); // 偶数行 + 偶数组列            }            else            {                e.CellStyle.BackColor = Color.White; // 奇数行 + 偶数组列            }        }
#c#     #自绘表格  #DataGridView


阅读原文:https://mp.weixin.qq.com/s/0PQnpaFgGTHejE-ZoA919w


该文章在 2026/8/31 18:28:46 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号