因为一个Winform的项目中需要用到带有合计行的表格,并且需要满足以下需求:
- 合计行可自动对需要求和的列进行求和计算;
- 合计行必须固定(冻结)在表格的最底部,且其位置不受滚动条的滚动而移动;
- 可以设置合计行单元格的数据展示格式.
本以为winform程序出来已经这么多年了,这个本也是个比较基础的功能,网上应该有很多现成的例子,便想着直接从网上找个例子用到项目中即可,无奈找了很久也没有见一个合适的实现,迫于无奈,只能动手自己写了一个DataGridView的扩展.并在这里整理出来,以分享给后续有类似需求的朋友参考,助其少走一些弯路.
- 在开始之前,先来看一下在项目中展示效果及在程序中调用方式(当然展示的数据是随机生成的临时测试数据),各位看得顺眼再继续往后.
- 实现思路.
本文中的合计行其实是一个仅有一行数据的DataGridView,即整个控件会包含原数据表格DataGridView及合计行DataGridView组成,一开始本想通过一个UserControl来组织这两个控件,但会遇到DataGridView本身一大堆属性和事件需要在UserControl上重新定义,才能方便的在设计器中直接使用,尝试了几个属性后,发现这个工作量变得非常大,这个模式便被否定了,所以不得不重新寻找一个可行的方案.
后经转变思维,我们可以直接将控件继承于原生态的DataGridView, 在当前控件展示的的时候,去动态创建一个合计行DataGridView,并将其添加到数据源控件下面即可.几经周折尝试后,发现确实可行,确定这个方向后,我们便一步步来实现这个功能. 完成后总结这个功能点,大致需要处理以下几个问题:
1) 如何处理DataGridView的滚动条:滚动条需要联动,且滚动条需要位于两个DataGridView的外围;
2) 如何确定合计行的位置,并将其添加到控件中,使其看起来和原数据表格是一体的;
3) 当通过属性设置当前控件大小后,如何对应调整合计行的位置及大小;
4) 拖动列宽后,对应调整合计行的列宽;
5) 自动合计.
在接下来的文章中,跟着分解步骤,来一起看看这个的具体实现. - 初始化变量并注册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); }
- 禁用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(); } }; }
- 根据数据量确定是否需要展示横向及纵向滚动条,同时确定合计行
/// <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; }
- 处理滚动条事件,同步作用于两个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; }
- 拖动列宽改变的时候,同步更新合计行的列宽
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; } }
-
根据设置的需要合计列,自动合计数据
/// <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; } }
-
当数据源改变,重新计算合计,与合计行列头重绘
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); }
-
为了便于在设计器中使用,重写部分属性,并禁用可能影响展示效果的属性,同时增加上新的属性设置.
/// <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增加合计行功能扩展的关键代码,还望能帮助到有类似需求的朋友,或许本文于你的帮助仅为一个思路,并不完全代表你必须像我这么处理,各位看官还望斟酌.
另外本文不提供直接源码包下载,因为感觉如果你真的想学习还是自己这部分代码可能比较直接,直接复制文件的这种形式,估计能学到的可能性也比较小.希望直接获取源码包的朋友请绕道,还望见谅.