因为一个Winform的项目中需要用到带有合计行的表格,并且需要满足以下需求:

  1. 合计行可自动对需要求和的列进行求和计算;
  2. 合计行必须固定(冻结)在表格的最底部,且其位置不受滚动条的滚动而移动;
  3. 可以设置合计行单元格的数据展示格式.

本以为winform程序出来已经这么多年了,这个本也是个比较基础的功能,网上应该有很多现成的例子,便想着直接从网上找个例子用到项目中即可,无奈找了很久也没有见一个合适的实现,迫于无奈,只能动手自己写了一个DataGridView的扩展.并在这里整理出来,以分享给后续有类似需求的朋友参考,助其少走一些弯路.

  1. 在开始之前,先来看一下在项目中展示效果及在程序中调用方式(当然展示的数据是随机生成的临时测试数据),各位看得顺眼再继续往后.
  2. 实现思路.
             本文中的合计行其实是一个仅有一行数据的DataGridView,即整个控件会包含原数据表格DataGridView及合计行DataGridView组成,一开始本想通过一个UserControl来组织这两个控件,但会遇到DataGridView本身一大堆属性和事件需要在UserControl上重新定义,才能方便的在设计器中直接使用,尝试了几个属性后,发现这个工作量变得非常大,这个模式便被否定了,所以不得不重新寻找一个可行的方案.
            后经转变思维,我们可以直接将控件继承于原生态的DataGridView, 在当前控件展示的的时候,去动态创建一个合计行DataGridView,并将其添加到数据源控件下面即可.几经周折尝试后,发现确实可行,确定这个方向后,我们便一步步来实现这个功能. 完成后总结这个功能点,大致需要处理以下几个问题:
    1)  如何处理DataGridView的滚动条:滚动条需要联动,且滚动条需要位于两个DataGridView的外围;
    2)  如何确定合计行的位置,并将其添加到控件中,使其看起来和原数据表格是一体的;
    3)  当通过属性设置当前控件大小后,如何对应调整合计行的位置及大小;
    4)  拖动列宽后,对应调整合计行的列宽;
    5)  自动合计.
    在接下来的文章中,跟着分解步骤,来一起看看这个的具体实现.
  3. 初始化变量并注册DataGridView相关事件,增加效果处理
    private bool _isShowSumRow = false;             //是否显示合计行
    private string _sumCellFormat = "N2"; //合计单元格格式化字符串
    private int _sumRowHeight = 30; //合计行高
    private DataGridView _dgvSumRow = null; //合计行
    private VScrollBar _vScrollBar = null; //垂直滚动条
    private HScrollBar _hScrollBar = null; //水平滚动条
    private bool _initSourceGriding = false; //指示是否正在进行初始grid
    private DockStyle _dock; //Dock private int _dgvSourceMaxHeight = 0; //dgvSource最大高度
    private int _dgvSourceMaxWidth = 0; //dgvSource最大宽度 /// <summary>
    /// 初始化
    /// </summary>
    public PDSumDataGridView()
    {
    base.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
    this.ColumnWidthChanged += new DataGridViewColumnEventHandler(this_ColumnWidthChanged);
    this.DataSourceChanged += new EventHandler(this_DataSourceChanged);
    this.RowHeadersWidthChanged += new EventHandler(this_RowHeadersWidthChanged);
    this.MouseWheel += new MouseEventHandler(dgvSource_MouseWheel);
    }
  4. 禁用DataGridView 默认的滚动条,改为手动添加横向滚动条及纵向滚动条.并将其添加到和DateGridView 相同的父控件上,示例代码如下.
           /// <summary>
    /// 初始化合计行
    /// </summary>
    private void InitSumRowDgv()
    {
    _dgvSumRow = new DataGridView();
    _dgvSumRow.BackgroundColor = this.BackgroundColor;
    _dgvSumRow.ColumnHeadersVisible = false;
    _dgvSumRow.AllowUserToResizeColumns = false;
    _dgvSumRow.AllowUserToResizeRows = false;
    _dgvSumRow.ScrollBars = System.Windows.Forms.ScrollBars.None;
    _dgvSumRow.Visible = false;
    _dgvSumRow.Height = _sumRowHeight;
    _dgvSumRow.RowTemplate.Height = _sumRowHeight;
    _dgvSumRow.AllowUserToAddRows = false;
    _dgvSumRow.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing;
    _dgvSumRow.ReadOnly = true;
    _dgvSumRow.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
    _dgvSumRow.DefaultCellStyle.SelectionBackColor = _dgvSumRow.DefaultCellStyle.BackColor;
    _dgvSumRow.DefaultCellStyle.SelectionForeColor = _dgvSumRow.DefaultCellStyle.ForeColor;
    _dgvSumRow.Font = new Font("宋体", 10, FontStyle.Bold);
    _dgvSumRow.RowPostPaint += new DataGridViewRowPostPaintEventHandler(dgvSumRow_RowPostPaint);
    } /// <summary>
    /// 初始化合计dgv及滚动条
    /// </summary>
    private void InitSumDgvAndScrolBar()
    {
    if (this.Parent == null)
    {
    return;
    } //滚动条
    _vScrollBar = new VScrollBar();
    _hScrollBar = new HScrollBar();
    if (DesignMode)
    {
    base.ScrollBars = System.Windows.Forms.ScrollBars.Both;
    }
    else
    {
    this.ScrollBars = ScrollBars.None; //禁用dgv默认滚动条
    }
    this.Parent.Controls.Add(_vScrollBar);
    this.Parent.Controls.Add(_hScrollBar); _vScrollBar.Visible = false;
    _hScrollBar.Visible = false; //注册滚动条事件已代替dgv默认的滚动条
    _vScrollBar.Scroll += new ScrollEventHandler(vScrollBar_Scroll);
    _hScrollBar.Scroll += new ScrollEventHandler(hScrollBar_Scroll);
    InitSumRowDgv();
    this.Parent.Controls.Add(_dgvSumRow); this.SizeChanged += (s, e) =>
    {
    if (!_initSourceGriding)
    {
    InitScrollWithSourceGrid();
    this.Update();
    }
    };
    }
  5. 根据数据量确定是否需要展示横向及纵向滚动条,同时确定合计行
            /// <summary>
    /// 根据源Grid设置是否需展示滚动条
    /// </summary>
    private void InitScrollWithSourceGrid()
    {
    if (_initSourceGriding || this.Parent == null)
    {
    return;
    } //初始化合计行
    if (_dgvSumRow == null)
    {
    InitSumDgvAndScrolBar();
    }
    _initSourceGriding = true; if (_dock == DockStyle.Fill)
    {
    this.Height = Parent.Height;
    this.Width = Parent.Width;
    this.Location = new Point(0, 0);
    } _dgvSourceMaxHeight = this.Height; //dgvSource最大高度
    _dgvSourceMaxWidth = this.Width; //dgvSource最大宽度 if (_isShowSumRow)
    {
    _dgvSourceMaxHeight -= _sumRowHeight;
    }
    if (_dgvSourceMaxHeight < RowHeight * 2)
    {
    _initSourceGriding = false;
    return;
    } this.Height = _dgvSourceMaxHeight;
    var displayDgvSumRowHeight = (_isShowSumRow && !DesignMode) ? _dgvSumRow.Height : 0; // this.MouseWheel -= new MouseEventHandler(dgvSource_MouseWheel);
    #region 验证是否需要显示水平滚动条 //需要展示水平滚动条
    if (this.DisplayedColumnCount(true) < this.Columns.Count)
    {
    _dgvSourceMaxHeight -= _hScrollBar.Height;
    this.Height = _dgvSourceMaxHeight; _hScrollBar.Location = new Point(this.Location.X, this.Location.Y + this.Height + displayDgvSumRowHeight);
    _hScrollBar.Width = _dgvSourceMaxWidth;
    _hScrollBar.Visible = true;
    _hScrollBar.BringToFront();
    _hScrollBar.Minimum = 0;
    _hScrollBar.SmallChange = AvgColWidth;
    _hScrollBar.LargeChange = AvgColWidth * 2;
    _hScrollBar.Maximum = ColsWidth;
    }
    else
    {
    _hScrollBar.Visible = false;
    }
    #endregion //根据源dgv设置合计行
    _dgvSumRow.RowHeadersWidth = this.RowHeadersWidth - 1; #region 验证是否需要显示纵向滚动条 var dgvSourceDisplayedRowCount = this.DisplayedRowCount(false); //最多显示行数 //不需要展示垂直滚动条
    if (dgvSourceDisplayedRowCount >= this.Rows.Count)
    {
    _vScrollBar.Visible = false;
    this.Width = _dgvSourceMaxWidth;
    _dgvSumRow.Width = _dgvSourceMaxWidth;
    }
    else
    {
    //需要展示垂直滚动条
    _dgvSourceMaxWidth = this.Width - _vScrollBar.Width; this.Width = _dgvSourceMaxWidth;
    _vScrollBar.Height = this.Height + (_isShowSumRow ? _dgvSumRow.Height : 0);
    _vScrollBar.Location = new Point(this.Location.X + this.Width, this.Location.Y);
    _vScrollBar.Visible = true;
    _vScrollBar.Maximum = (this.Rows.Count - dgvSourceDisplayedRowCount + 2) * RowHeight;
    _vScrollBar.Minimum = 0;
    _vScrollBar.SmallChange = RowHeight;
    _vScrollBar.LargeChange = RowHeight * 2;
    _vScrollBar.BringToFront();
    }
    #endregion if (_isShowSumRow && !DesignMode)
    {
    _dgvSumRow.Location = new Point(this.Location.X, this.Location.Y + _dgvSourceMaxHeight - 1);
    _dgvSumRow.Width = this.Width;
    _dgvSumRow.Visible = true;
    _dgvSumRow.BringToFront();
    }
    else
    {
    _dgvSumRow.Visible = false;
    }
    _initSourceGriding = false;
    }
  6. 处理滚动条事件,同步作用于两个DataGridView
    /// <summary>
    /// DataGridView 列总宽.用于确定横向滚动条滚动值
    /// </summary>
    private int ColsWidth
    {
    get
    {
    int width = 0;
    foreach (DataGridViewColumn col in this.Columns)
    {
    if (!col.Visible)
    {
    continue;
    }
    width += col.Width;
    }
    return width;
    }
    } /// <summary>
    /// DataGridView 列平均总宽,用于确定横向滚动条滚动值
    /// </summary>
    private int AvgColWidth
    {
    get
    {
    int width = 80;
    width = ColsWidth / this.Columns.Count;
    return width;
    }
    } /// <summary>
    /// 每行高度.用于确定纵向滚动条滚动值
    /// </summary>
    private int RowHeight
    {
    get
    {
    int height = 20;
    if (this.Rows.Count > 0)
    {
    height = (this.Rows[0].Height - 3);
    }
    return height;
    }
    } /// <summary>
    /// 处理纵向滚动条事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void vScrollBar_Scroll(object sender, ScrollEventArgs e)
    {
    this.FirstDisplayedScrollingRowIndex = e.NewValue / RowHeight;
    } /// <summary>
    /// 处理横向滚动条事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void hScrollBar_Scroll(object sender, ScrollEventArgs e)
    {
    int value = e.NewValue;
    this.HorizontalScrollingOffset = value; if (_isShowSumRow && _dgvSumRow != null)
    {
    _dgvSumRow.HorizontalScrollingOffset = value;
    }
    } /// <summary>
    /// 处理源dgv鼠标滚轮滚动事件,同步带动横向滚动条及纵向滚动条
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void dgvSource_MouseWheel(object sender, MouseEventArgs e)
    {
    if (!_vScrollBar.Visible) return; if ((_vScrollBar.Value - RowHeight) < 0 && e.Delta > 0)
    {
    _vScrollBar.Value = _vScrollBar.Minimum;
    }
    else if ((_vScrollBar.Value + RowHeight * 2) > _vScrollBar.Maximum && e.Delta < 0)
    {
    _vScrollBar.Value = _vScrollBar.Maximum;
    }
    else
    {
    _vScrollBar.Value -= Convert.ToInt32((e.Delta / Math.Abs(e.Delta))) * RowHeight;
    }
    this.FirstDisplayedScrollingRowIndex = _vScrollBar.Value / RowHeight;
    }
  7. 拖动列宽改变的时候,同步更新合计行的列宽
    private void this_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
    {
    if (_dgvSumRow != null)
    {
    _dgvSumRow.Columns[e.Column.Index].Width = e.Column.Width;
    }
    } private void this_RowHeadersWidthChanged(object sender, EventArgs e)
    {
    if (_dgvSumRow != null)
    {
    _dgvSumRow.RowHeadersWidth = this.RowHeadersWidth - 1;
    }
    }
  8. 根据设置的需要合计列,自动合计数据
    /// <summary>
    /// 需要添加合计的datagridviewrow 列名称
    /// </summary>
    [Description("获取或设置需要用于求和的列名")]
    public string[] SumColumns
    {
    get;
    set;
    } /// <summary>
    /// 合计数据
    /// </summary>
    private void SumData()
    {
    if (this.Columns.Count <= 0)
    {
    return;
    } if (_dgvSumRow.Columns.Count != this.Columns.Count)
    {
    AddDgvSumRowColumns();
    } if (_dgvSumRow.Rows.Count != 1)
    {
    _dgvSumRow.Rows.Clear();
    _dgvSumRow.Rows.Add(1);
    } if (this.Rows.Count <= 0 || SumColumns == null || SumColumns.Length == 0)
    {
    return;
    } var sumRowDataDic = new Dictionary<int, decimal>(); #region 按设置的需要合计的列求和
    Array.ForEach(SumColumns, col =>
    {
    if (!_dgvSumRow.Columns.Contains(col))
    {
    return;
    }
    var tempSumVal = 0m;
    var colIndex = _dgvSumRow.Columns[col].Index;
    for (int i = 0; i < this.Rows.Count; i++)
    {
    if (this[colIndex, i].Value == null)
    {
    continue;
    } var tempVal = 0m;
    try
    {
    tempVal = (decimal)Convert.ChangeType(this[colIndex, i].Value, typeof(decimal));
    }
    catch { }
    tempSumVal += tempVal;
    }
    sumRowDataDic[colIndex] = tempSumVal;
    });
    #endregion if (sumRowDataDic.Count > 0)
    {
    sumRowDataDic.Keys.ToList().ForEach(colIndex =>
    {
    _dgvSumRow[colIndex, 0].Value = sumRowDataDic[colIndex];
    });
    }
    } /// <summary>
    /// 获取合计行
    /// </summary>
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public DataGridViewRow SumRow
    {
    get
    {
    return (_isShowSumRow && _dgvSumRow.Rows.Count > 0) ? _dgvSumRow.Rows[0] : null;
    }
    }
  9. 当数据源改变,重新计算合计,与合计行列头重绘
    private void this_DataSourceChanged(object sender, EventArgs e)
    {
    SumData();
    } /// <summary>
    /// 绘制合计行行头
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void dgvSumRow_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
    {
    var rectangle = new Rectangle(e.RowBounds.Location.X + 1, e.RowBounds.Location.Y + 1,
    _dgvSumRow.RowHeadersWidth - 3, e.RowBounds.Height - 3); e.Graphics.FillRectangle(new SolidBrush(_dgvSumRow.RowHeadersDefaultCellStyle.BackColor), rectangle); TextRenderer.DrawText(e.Graphics, "合计",
    _dgvSumRow.RowHeadersDefaultCellStyle.Font,
    rectangle,
    _dgvSumRow.RowHeadersDefaultCellStyle.ForeColor,
    TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter);
    }
  10. 为了便于在设计器中使用,重写部分属性,并禁用可能影响展示效果的属性,同时增加上新的属性设置.
    /// <summary>
    /// 获取或设置Dock,该属性已被重新
    /// </summary>
    [Description("获取或设置Dock,该属性已被重写")]
    public new DockStyle Dock
    {
    get { return _dock; }
    set
    {
    _dock = value;
    if (value == DockStyle.Fill)
    {
    if (Parent != null)
    {
    this.Size = new Size(Parent.Width, Parent.Height);
    this.Location = new Point(0, 0);
    }
    this.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
    }
    else
    {
    this.Height = Parent.Height - 20;
    this.Width = Parent.Width - 20;
    }
    }
    } /// <summary>
    /// BorderStyle属性已被重写,该值固定为None,设置无效
    /// </summary>
    [Description("BorderStyle属性已被重写,该值固定为None,设置无效")]
    public new BorderStyle BorderStyle
    {
    get { return System.Windows.Forms.BorderStyle.None; }
    set { }
    } /// <summary>
    /// 获取或设置合计行单元格格式化字符串
    /// </summary>
    [Description("获取或设置合计行单元格格式化字符串")]
    public string SumRowCellFormat
    {
    get { return _sumCellFormat; }
    set { _sumCellFormat = value; }
    } /// <summary>
    /// 获取或设置是否显示合计行
    /// </summary>
    [Description("获取或设置是否显示合计行")]
    public bool IsShowSumRow
    {
    get { return _isShowSumRow; }
    set
    {
    _isShowSumRow = value;
    InitScrollWithSourceGrid();
    }
    }

后记,以上就是为DataGridView增加合计行功能扩展的关键代码,还望能帮助到有类似需求的朋友,或许本文于你的帮助仅为一个思路,并不完全代表你必须像我这么处理,各位看官还望斟酌.

另外本文不提供直接源码包下载,因为感觉如果你真的想学习还是自己这部分代码可能比较直接,直接复制文件的这种形式,估计能学到的可能性也比较小.希望直接获取源码包的朋友请绕道,还望见谅.

分享一个带有合计行功能的DataGridView扩展的更多相关文章

  1. 实现一个带有指纹加密功能的笔记本(Android)第一部分

    自己经常会忘记一些密码什么的,想把这些密码保存下来,但是别人做的软件总有一点不安全的感觉,所以自己动手做了一个带有指纹加密的笔记本. 以下是本工程用到的一些第三方包 compile 'org.gree ...

  2. 实现一个带有指纹加密功能的笔记本(Android)第二部分

    上文基本完成了整个笔记本的笔记功能的实现,接下来记录实现指纹识别加密以及一些小注意事项. 首先判断该手机是否具备指纹识别的硬件功能和用户是否开启指纹识别. public boolean isFinge ...

  3. 分享一个好用的功能强大的节点树jQuery插件

    http://www.treejs.cn/

  4. C# DataGridView合计行

    在网上搜了很多关于DataGridView合计行的设计及源码,都不是很合我心意.于是自己写了一个关于合计行的DLL.以后每次要用到合计行的时候只要引用这个DLL就可以了. 效果图如下: 引用Dll: ...

  5. 分享一个命令行计算器-bc

    分享一个命令行计算器-bc 假如你在一个图形桌面环境中需要一个计算器时,你可能只需要一路进行点击便可以找到一个计算器.例如,Fedora 工作站中就已经包含了一个名为 Calculator 的工具.它 ...

  6. 分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

  7. (转)分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

  8. 在存放源程序的文件夹中建立一个子文件夹 myPackage。例如,在“D:\java”文件夹之中创建一个与包同名的子文件夹 myPackage(D:\java\myPackage)。在 myPackage 包中创建一个YMD类,该类具有计算今年的年份、可以输出一个带有年月日的字符串的功能。设计程序SY31.java,给定某人姓名和出生日期,计算该人年龄,并输出该人姓名、年龄、出生日期。程序使用YM

    题目补充: 在存放源程序的文件夹中建立一个子文件夹 myPackage.例如,在“D:\java”文件夹之中创建一个与包同名的子文件夹 myPackage(D:\java\myPackage).在 m ...

  9. winform DataGridView添加合计行

    使用方法 /* DataTable dt= DBUtility.DB.FromSql(sql).ToDataTable(); DataGridViewAddSumRow sumRow = new Da ...

随机推荐

  1. 关于EF更新数据库,更新指定字段的设置

    1.关于EF跟新数据库更新指定字段的设置 在EF提交到数据库的时候或许某些字段不想更新.或者自己更新一个模型到数据库去! 1.更新数据不更新一些字段 /// <summary> /// 数 ...

  2. 05.while循环的练习

    练习1: namespace _05.while循环练习01 { class Program { static void Main(string[] args) { //打印100次"努力学 ...

  3. Redis 的Hashs(哈希表)数据类型

    在Memcached中,我们经常将一些结构化的信息打包成hashmap,在客户端序列化后存储为一个字符串的值,比如用户的昵称.年龄.性别.积分等,这时候在需要修改其中某一项时,通常需要将所有值取出反序 ...

  4. LI居中

    在用UL-LI时,有适合需要将Li里面的内容居中显示:方法有两种:(推荐)1.设置LI的display为inline(规定应该从父元素继承 display 属性的值),为LI设置长度,设置text-a ...

  5. nodejs进阶(7)—async异步流程控制

    Async介绍 Async是一个流程控制工具包,提供了直接而强大的异步功能.基于Javascript为Node.js设计,同时也可以直接在浏览器中使用. Async提供了大约20个函数,包括常用的 m ...

  6. drupal 基础理论

    第3章 Drupal 的基本概念 添加新评论 浏览 6795 次 Drupal的基本概念主要包括节点.内容类型.模块.主题和分类等.只有对这些概念有了足够的了解,方能灵活的构建网站.本章将对这些基本概 ...

  7. 16_Queue_利用wait()和notify()编写一个阻塞队列

    [线程间通信概念] 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就成为整体必用方式之一.当线程存在通信指挥,线程间的交互性会更强大,在提高CPU利用率的同 ...

  8. redis的使用方式

    常用的语法以及使用方式:          key中不能包含回车空格等,key不要太长,占用内存.     概念介绍:         差集: a:{1,2,3} b:{2,3,4},以a为锚点,差集 ...

  9. 新发布 | 微软开源之路最新进展:FreeBSD落地由世纪互联营运的Microsoft Azure

    微软和开源,是近几年业界孜孜不倦的讨论话题,微软也在开源之路越走越宽.最近与 FreeBSD 基金更紧密的合作踏出了微软在开源之路上的又一大步. 自2012年开始,微软在 FreeBSD 与其虚拟化平 ...

  10. jmeter中CSV Data Set Config各项说明

    Config the CSV Data Source: 1)Filename:csv文件的名称(包括绝对路径,当csv文件在bin目录下时,只需给出文件名即可) 2)File encoding:csv ...