在Excel中最后一行下方动态显示Datagridview列总和的最佳方法?

时间:2015-10-28 13:26:43

标签: c# winforms data-binding datagridview

我知道很多次都会问这个问题,但我找不到合适的答案:(

我想在价格列的末尾显示egTotal Price的总和。

当值动态变化(在程序内)时,总和也应该动态改变。我无法添加自定义额外行,因为DatagridViews是数据绑定。

由于在表格布局中放置了动态行和许多Datagridviews。 (有时不会出现自定义绘制事件,因为滚动条也会出现)

我完全迷失了。有谁能建议更好的方法?

1 个答案:

答案 0 :(得分:2)

如果您从true采购DataGridView,我在遇到此问题时已经创建了以下解决方案。*

想法是指出哪些列需要求和,哪个列和文本将指示“总计”标签,然后获取基础DataTable并手动求和指示的列。这些总和和标签用于创建一个新的DataRow,它被添加到表的末尾。当进行任何更改时 - 删除新行,删除行,排序 - 旧总和行的程序性添加将被删除,然后计算新的行。

DataGridView类

DataTable

<强>用法

只需使用此类替换using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Data; using System.Windows.Forms; namespace TestSortWithSum { public class DataTableSumSortableDGV : DataGridView { /// <summary> /// Column index for the sum label. /// </summary> private int labelColumnIndex = -1; /// <summary> /// Text for the sum label. /// </summary> private string labelColumnText = string.Empty; /// <summary> /// Constructor. Initialize sort direction and subscribe event. /// </summary> public DataTableSumSortableDGV() : base() { this.SumColumnIndices = new ObservableCollection<int>(); this.Direction = string.Empty; this.AllowUserToAddRows = false; this.AllowUserToAddRowsChanged += DataTableSumSortableDGV_AllowUserToAddRowsChanged; this.DataBindingComplete += DataTableSumSortableDGV_DataBindingComplete; this.DataSourceChanged += DataTableSumSortableDGV_DataSourceChanged; this.SumColumnIndices.CollectionChanged += SumColumnIndices_CollectionChanged; } /// <summary> /// Text for the sum label. /// </summary> public string LabelColumnText { get { return this.labelColumnText; } set { Action action = () => { if (this.HasSumColumns()) { this.RemoveSumRow(); } this.labelColumnText = value; if (this.HasSumColumns()) { this.AddSumRow(); } }; this.MakeInternalChanges(action); } } /// <summary> /// Column index for the sum label. /// </summary> public int LabelColumnIndex { get { return this.labelColumnIndex; } set { Action action = () => { if (this.HasSumColumns()) { this.RemoveSumRow(); } this.labelColumnIndex = value; if (this.HasSumColumns()) { this.AddSumRow(); } }; this.MakeInternalChanges(action); } } /// <summary> /// Column indices for the sum(s). /// </summary> public ObservableCollection<int> SumColumnIndices { get; set; } /// <summary> /// The DataTable sort direction. /// </summary> private string Direction { get; set; } /// <summary> /// The DataTable source. /// </summary> private DataTable DataTable { get; set; } /// <summary> /// The DataTable sum row. /// </summary> private DataRow SumRow { get; set; } /// <summary> /// DataGridView Sort method. /// If DataSource is DataTable, special sort the source. /// Else normal sort. /// </summary> /// <param name="dataGridViewColumn">The DataGridViewColumn to sort by header click.</param> /// <param name="direction">The desired sort direction.</param> public override void Sort(DataGridViewColumn dataGridViewColumn, System.ComponentModel.ListSortDirection direction) { if (this.HasSumColumns()) { Action action = () => { this.RemoveSumRow(); string col = this.DataTable.Columns[dataGridViewColumn.Index].ColumnName; if (!this.Direction.Contains(col)) { this.ClearOldSort(); } string sort = this.Direction.Contains("ASC") ? "DESC" : "ASC"; this.Direction = string.Format("{0} {1}", col, sort); this.SortRows(this.Direction); this.AddSumRow(); }; this.MakeInternalChanges(action); dataGridViewColumn.HeaderCell.SortGlyphDirection = this.Direction.Contains("ASC") ? SortOrder.Ascending : SortOrder.Descending; } else { this.DataTable.DefaultView.Sort = string.Empty; base.Sort(dataGridViewColumn, direction); } } /// <summary> /// DataBindingComplete event handler. /// Add the sum row when DataSource = a new DataTable. /// </summary> /// <param name="sender">This DataGridView object.</param> /// <param name="e">The DataGridViewBindingCompleteEventArgs.</param> private void DataTableSumSortableDGV_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) { this.DataTable = (DataTable)this.DataSource; this.AddInitialSumRow(); } /// <summary> /// For a new DataSource, start with a fresh SumRow. /// </summary> /// <param name="sender">The DataGridView object.</param> /// <param name="e">The EventArgs.</param> void DataTableSumSortableDGV_DataSourceChanged(object sender, EventArgs e) { this.SumRow = null; } /// <summary> /// Prevent users from adding a row as this is DataSourced and rows should be added to the DataTable instead. /// </summary> /// <param name="sender">The DataGridView object.</param> /// <param name="e">The EventArgs.</param> private void DataTableSumSortableDGV_AllowUserToAddRowsChanged(object sender, EventArgs e) { if (this.AllowUserToAddRows) { this.AllowUserToAddRows = false; } } /// <summary> /// The sum columns have been altered. Reflect the change immediately. /// </summary> /// <param name="sender">The SumColumnIndices object.</param> /// <param name="e">The NotifyCollectionChangedEventArgs.</param> private void SumColumnIndices_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { this.AddInitialSumRow(); } /// <summary> /// Add the sum row for the first time: once the DataTable is sourced and /// the label column index, label text, and sum column index are set. /// </summary> private void AddInitialSumRow() { if (this.HasSumColumns()) { Action action = () => { this.RemoveSumRow(); this.AddSumRow(); }; this.MakeInternalChanges(action); } } /// <summary> /// Add the sum row to the DataTable as a ReadOnly row. /// </summary> private void AddSumRow() { List<decimal> sum = this.CreateListOfSums(); List<int> exclude = new List<int>(); for (int row = 0; row < this.DataTable.Rows.Count; row++) { for (int index = 0; index < this.SumColumnIndices.Count; index++) { try { int col = this.SumColumnIndices[index]; decimal value = 0; if (Decimal.TryParse(this.DataTable.Rows[row].ItemArray[col].ToString(), out value)) { sum[index] += value; } else if (!exclude.Contains(col)) { exclude.Add(col); } } catch (RowNotInTableException) { continue; } } } object[] items = this.CreateItemsArray(this.DataTable.Columns.Count, sum, exclude); if (Array.TrueForAll<object>(items, item => { return item == null; })) { this.SumRow = null; } else { this.SumRow = this.DataTable.NewRow(); this.SumRow.ItemArray = items; this.DataTable.Rows.Add(this.SumRow); if (this.Rows.Count > 0) { this.Rows[this.Rows.Count - 1].ReadOnly = true; } } } /// <summary> /// Clear the old sort string and any set glyph directions in header cells. /// </summary> private void ClearOldSort() { if (!string.IsNullOrEmpty(this.Direction)) { string[] sortVals = this.Direction.Split(new char[] { ' ' }); // [ "ColName", "ASC/DESC" ] this.Columns[sortVals[0]].HeaderCell.SortGlyphDirection = SortOrder.None; } this.Direction = string.Empty; } /// <summary> /// Create the items array for the new sum row. /// </summary> /// <param name="length">The array length for the items.</param> /// <param name="sum">The list of sums.</param> /// <param name="exclude">The list of sum columns that aren't actually sum columns.</param> /// <returns>Object array for the sum row.</returns> private object[] CreateItemsArray(int length, List<decimal> sum, List<int> exclude) { object[] items = new object[length]; if (this.IsValidIndex()) { items[this.LabelColumnIndex] = this.LabelColumnText; } for (int index = 0; index < this.SumColumnIndices.Count; index++) { int col = this.SumColumnIndices[index]; if (!exclude.Contains(col)) { items[col] = sum[index]; } } return items; } /// <summary> /// Create a list of sums for each sum column index. /// </summary> /// <returns>A new list of sums.</returns> private List<decimal> CreateListOfSums() { List<decimal> sum = new List<decimal>(); foreach (int index in this.SumColumnIndices) { sum.Add(0m); } return sum; } /// <summary> /// Determine if the index is a valid column for the label. /// </summary> /// <returns>True if the index is valid.</returns> private bool IsValidIndex() { return this.LabelColumnIndex >= 0 && this.LabelColumnIndex < this.DataTable.Columns.Count && this.DataTable.Columns[this.LabelColumnIndex].DataType == typeof(string); } /// <summary> /// Unsubscribe the DataBindingComplete event handler, call internal sorting changes, /// then re-subscribe to the DataBindingComplete event handler. This must be done /// with any item removal/addition to the DataSource DataTable to prevent recursion /// resulting in a Stack Overflow. /// </summary> /// <param name="operation">The internal changes to be made to the DataSource.</param> private void MakeInternalChanges(Action operation) { this.DataBindingComplete -= DataTableSumSortableDGV_DataBindingComplete; operation(); this.DataBindingComplete += DataTableSumSortableDGV_DataBindingComplete; } /// <summary> /// Remove any existing sum row. /// </summary> private void RemoveSumRow() { if (this.SumRow != null) { this.DataTable.Rows.Remove(this.SumRow); } } /// <summary> /// Determine if the grid has sum sortable columns. /// </summary> /// <returns> /// True if the source and sum column(s) exist. /// False if any one condition fails = sort as normal DataGridView. /// </returns> private bool HasSumColumns() { return this.DataTable != null && this.SumColumnIndices.Count > 0; } /// <summary> /// Sort the DataTable by re-ordering the actual items. /// Get the sorted row order. For each sorted row, /// remove it from the original list, then re-add it to the end. /// </summary> /// <param name="sort">The "ColumnName ASC/DESC" sort string.</param> private void SortRows(string sort) { DataRow[] sortedRows = this.DataTable.Select(string.Empty, sort); foreach (DataRow row in sortedRows) { object[] items = (object[])row.ItemArray.Clone(); this.DataTable.Rows.Remove(row); this.DataTable.Rows.Add(items); } } } } 的实例:

DataGridView

然后,指示您的总和列和标签列,如下所示:

DataTableSumSortableDGV dataGridView1 = new DataTableSumSortableDGV();

以上几行的顺序无关紧要,应该可行。下面我抓住了一个展示行动中的行为的例子:

Example in action

*或者您当然可以添加this.dataGridView1.DataSource = GoGetTheDataTable(); this.dataGridView1.SumColumnIndices.Add(3); this.dataGridView1.SumColumnIndices.Add(4); this.dataGridView1.LabelColumnIndex = 2; this.dataGridView1.LabelColumnText = "Total"; ,但我个人不喜欢这种方法的外观。