我有一个DataGridView,它从Datatable获取数据源。我在数据表中添加了一个新行,它添加了Columns的总值,这显示正常,直到单击Header,然后数据被排序,我的Totals Row出现在我的网格的一半!
我一直在为这个问题寻找解决方案好几个小时,似乎找不到具体的东西。我见过人们重写了SortCompare方法,但是当有数据源时,这不会被使用。我见过提到一个页脚,但这似乎主要是ASP,但我已经看到了windows应用程序破碎代码的碎片。
基本上我想做的就是让我的计数列位于网格的底部,当我点击标题时它就会停留在那里!我认为这将是一个相当直接的工作,但它证明是棘手的。
我的网格由4或5个主要数据列组成,名称,位置等,其余列是基于过滤器的日期,因此这些是相当动态的。
非常感谢您的帮助。
答案 0 :(得分:0)
您要做的是处理Sorted
事件,实质上是在排序时将所需的行固定到底部。请注意,新的空行必须始终是最后一行,但这很简单。
首先,您需要在创建时将标记添加到所需的行。
DataGridViewRow row = new DataGridViewRow();
row.CreateCells(this.dataGridView1);
/* Your code to sum columns... */
row.Tag = "Totals";
row.ReadOnly = true;
this.dataGridView1.Rows.Add(row);
然后处理Sorted
事件。我已经将排序事件的代码分解为另一种方法,你会明白为什么。
private void dataGridView1_Sorted(object sender, EventArgs e)
{
this.LockBottomRow();
}
private void LockBottomRow()
{
foreach (DataGridViewRow row in this.dataGridView1.Rows)
{
if (row.Tag != null && row.Tag.ToString() == "Totals")
{
int index = this.dataGridView1.Rows.Count - 2;
this.dataGridView1.Rows.Remove(row);
this.dataGridView1.Rows.Insert(index, row);
}
}
}
这会将您的Totals行锁定在所有其他已填充的行下方。作为奖励,我认为在输入新行时会很好,如果它不是在你的Totals行之后添加并且需要使用,我们只是在RowsAdded
事件中处理它。
private void dataGridView1_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
DataGridViewRow row = this.dataGridView1.Rows[e.RowIndex];
if (row.Tag == null || row.Tag.ToString() != "Totals")
{
if (this.dataGridView1.CurrentCell != null)
{
this.LockBottomRow();
this.dataGridView1.BeginEdit(false);
}
}
}
希望这有帮助!我喜欢尝试让它发挥作用。
答案 1 :(得分:0)
经过一些实验(并且需要完成相同的任务),我理解你的意思。如果DataTable
为DataSource
,则数据似乎排序较晚。挖掘我注意到源本身永远不会改变 - 尽管显示的数据已经排序,但顺序保持不变:因此即使将sum行添加到源的末尾,它仍然会被排序。
我的解决方案:从DataGridView
类继承,覆盖Sort
方法,并根据排序顺序强制DataTable
项重新排序通过按顺序删除和重新添加项目。这样做,必须防止默认Sort
。另外,我还处理了HeaderCell
,表示它已经排序,并允许多个和列。
新的DataGridView:
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);
}
}
}
}
使用示例:
namespace TestSortWithSum
{
public partial class Form1: Form
{
public Form1()
{
this.InitializeComponent();
DataTableSumSortableDGV dataGridView1 = new DataTableSumSortableDGV();
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.AllowUserToAddRows = false;
dataGridView1.DataSource = GetSumTable();
dataGridView1.SumColumnIndices.Add(3);
dataGridView1.SumColumnIndices.Add(4);
dataGridView1.LabelColumnIndex = 2;
dataGridView1.LabelColumnText = "Total";
this.Controls.Add(dataGridView1);
}
private DataTable GetSumTable()
{
DataTable table = new DataTable();
table.Columns.Add("Foo", typeof(string));
table.Columns.Add("Bar", typeof(string));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Amount", typeof(decimal));
table.Columns.Add("Quantity", typeof(int));
table.Rows.Add("Foo 1", "Bar 7", "Abcd", 1.11, 1);
table.Rows.Add("Foo 2", "Bar 8", "Hijk", 2.22, 2);
table.Rows.Add("Foo 3", "Bar 9", "Qrs", 3.33, 3);
table.Rows.Add("Foo 4", "Bar 10", "W", 4.44, 4);
table.Rows.Add("Foo 5", "Bar 1", "Y", 5.55, 5);
table.Rows.Add("Foo 6", "Bar 2", "Z", 6.66, 1);
table.Rows.Add("Foo 7", "Bar 3", "X", 7.77, 2);
table.Rows.Add("Foo 8", "Bar 4", "Tuv", 8.88, 3);
table.Rows.Add("Foo 9", "Bar 5", "Lmnop", 9.99, 4);
table.Rows.Add("Foo 10", "Bar 6", "Efg", 1.11, 5);
return table;
}
}
}
希望这有帮助。