zoukankan      html  css  js  c++  java
  • 分享一个带有合计行功能的DataGridView扩展

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

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

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

    1. 在开始之前,先来看一下在项目中展示效果及在程序中调用方式(当然展示的数据是随机生成的临时测试数据),各位看得顺眼再继续往后.
      imageimageimage
    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增加合计行功能扩展的关键代码,还望能帮助到有类似需求的朋友,或许本文于你的帮助仅为一个思路,并不完全代表你必须像我这么处理,各位看官还望斟酌.

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

  • 相关阅读:
    20 模块之 re subprocess
    19 模块之shelve xml haslib configparser
    18 包 ,logging模块使用
    vue项目的搭建使用
    课程模块表结构
    DRF分页组件
    Django ContentType组件
    跨域
    解析器和渲染器
    DRF 权限 频率
  • 原文地址:https://www.cnblogs.com/xie-zhonglai/p/data_grid_view_sum_row.html
Copyright © 2011-2022 走看看