飞刀博客

WinForm DataGridView 组件合并单元格

 信息技术  2020-08-24  154  0

我最近在用.NET Windows Forms ( WinForms ) 开发的过程中遇到了对 DataGridView 表头进行多样化合并的需求。

最早期的思路是去重绘DataGridView组件的表头部分,后来发现还是无法满足表格内部单元格合并的需求,于是决定隐藏表头,完全用其单元格来显示表格,上面的几行数据行作为表头。

对表格的进行单元格合并是在程序开发中经常遇到的需求,可惜截止目前,微软公司还没有在 .NET Framework 中自带这样的功能,开发者只能自己设法解决。

参考了网上一些内容后,思路清晰了:

用 Point 对象的XY值记录单元格的横纵序号,定义一个结构,记录哪些区域的单元格要合并。

定义一个对象,记录合并的区域的文本、起始坐标,宽度和高度信息。

首先正常输出表格,要合并的单元格里面填写相同的内容。

遍历要合并的区域信息,重新绘制表格边线,再将文本居中绘制。

在绘制边线的时候有注意到,DataGridView 自己会处理左侧和上边缘的线条,因此只需绘制下边线和和右边线。

演示合并效果如下:

具体代码如下:

/// <summary>
/// 表明演示中要合并的区域开始单元格的序号坐标,X为横向,y为纵向
/// </summary>
List<MergeCellInfo> lstMergeCellInfo = new List<MergeCellInfo>() {
    new MergeCellInfo{ PtStart = new Point(0, 0), PtEnd= new Point(1, 0) },
    new MergeCellInfo{ PtStart = new Point(0, 1), PtEnd= new Point(0, 2) },
    new MergeCellInfo{ PtStart = new Point(1, 3), PtEnd= new Point(2, 4) },
    new MergeCellInfo{ PtStart = new Point(3, 8), PtEnd= new Point(7, 8) },
};
/// <summary>
/// 用来存放要合并的内容、左上角点、宽度高度
/// </summary>
public partial class SpanEntity
{
    /// <summary>
    /// 显示文本
    /// </summary>
    public virtual string Item { get; set; }
    /// <summary>
    /// 合并区域的左上角坐标
    /// </summary>
    public virtual Point XY { get; set; }
    /// <summary>
    /// 宽度和高度
    /// </summary>
    public virtual Size WH { get; set; }
}
/// <summary>
/// 要合并的区域开始单元格的起止信息,区域左角的单元格为开始,右下为结束。
/// 如要合并左上角的4个单元格合并,则new MergeCellInfo{ PtStart = new Point(0, 0), PtEnd= new Point(1, 1) }
/// </summary>
public class MergeCellInfo
{
    /// <summary>
    /// 要合并的区域开始单元格的序号坐标,X为横向,y为纵向
    /// </summary>
    public Point PtStart { get; set; }
    /// <summary>
    /// 要合并的区域结束单元格的序号坐标,X为横向,y为纵向
    /// </summary>
    public Point PtEnd { get; set; }
}
/// <summary>
/// 绘制表格事件中对指明要合并的区域进行处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void dgvData_Paint(object sender, PaintEventArgs e)
{
    List<SpanEntity> PointList = new List<SpanEntity>();
    for (int i = 0; i < lstMergeCellInfo.Count; i++)
    {
        if (dgvData.Rows.Count > lstMergeCellInfo[i].PtEnd.X && dgvData.Columns.Count > lstMergeCellInfo[i].PtEnd.Y)
        {
            var rectStart = dgvData.GetCellDisplayRectangle(lstMergeCellInfo[i].PtStart.Y, lstMergeCellInfo[i].PtStart.X, true);
            PointList.Add(new SpanEntity()
            {
                Item = dgvData.Rows[lstMergeCellInfo[i].PtStart.X].Cells[lstMergeCellInfo[i].PtStart.Y].Value.ToString(),
                XY = new Point(rectStart.X, rectStart.Y),
                WH = new Size(rectStart.Width * (lstMergeCellInfo[i].PtEnd.Y - lstMergeCellInfo[i].PtStart.Y + 1), rectStart.Height * (lstMergeCellInfo[i].PtEnd.X - lstMergeCellInfo[i].PtStart.X + 1))
            });
        }
    }
    //重绘合并的cell
    Brush backColorBrush = new SolidBrush(Color.White);
    Pen gridLinePen = new Pen(this.dgvData.GridColor);//线色
    foreach (var obj in PointList)
    {
        e.Graphics.FillRectangle(backColorBrush, new Rectangle(obj.XY, obj.WH));
        //画右边线 //右边线X点
        e.Graphics.DrawLine(gridLinePen, obj.XY.X + obj.WH.Width - 1, obj.XY.Y, obj.XY.X + obj.WH.Width - 1, obj.XY.Y + obj.WH.Height - 1);
        //画下边线 //右边线X点
        e.Graphics.DrawLine(gridLinePen, obj.XY.X, obj.XY.Y + obj.WH.Height - 1, obj.XY.X + obj.WH.Width - 1, obj.XY.Y + obj.WH.Height - 1);
        var sizeFont = e.Graphics.MeasureString(obj.Item, dgvData.Font); //根据第1列的字体算出value所占大小
        StringFormat format = new StringFormat();  //样式
        format.LineAlignment = StringAlignment.Center;  // 垂直居中
        format.Alignment = StringAlignment.Center;      // 水平居中
        RectangleF rect = new Rectangle(obj.XY, obj.WH);
        e.Graphics.DrawString(obj.Item, dgvData.Font, Brushes.Black, rect, format);
    }
}

这个重新绘制的方法还不是很完美,在点击单元格或者拖动边框的时候,可能会影响效果,有待进一步完善。

赞赏博主

 留言评论

天上的神明和星辰,人间的艺术与真纯,
我们所敬畏和景仰的,莫过于此。

网站分类
文章归档
最新留言
友情链接

天上的神明和星辰,人间的艺术与真纯,
我们所敬畏和景仰的,莫过于此。

网站分类