因為一個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增加合計行功能擴展的關鍵代碼,還望能幫助到有類似需求的朋友,或許本文於你的幫助僅為一個思路,並不完全代表你必須像我這么處理,各位看官還望斟酌.
另外本文不提供直接源碼包下載,因為感覺如果你真的想學習還是自己這部分代碼可能比較直接,直接復制文件的這種形式,估計能學到的可能性也比較小.希望直接獲取源碼包的朋友請繞道,還望見諒.