在Windows应用程序中的DataGrid上单击标题时,将Sum列保留在底部

时间:2015-04-01 15:27:03

标签: c# winforms visual-studio-2010 sorting datagridview

我有一个DataGridView,它从Datatable获取数据源。我在数据表中添加了一个新行,它添加了Columns的总值,这显示正常,直到单击Header,然后数据被排序,我的Totals Row出现在我的网格的一半!

我一直在为这个问题寻找解决方案好几个小时,似乎找不到具体的东西。我见过人们重写了SortCompare方法,但是当有数据源时,这不会被使用。我见过提到一个页脚,但这似乎主要是ASP,但我已经看到了windows应用程序破碎代码的碎片。

基本上我想做的就是让我的计数列位于网格的底部,当我点击标题时它就会停留在那里!我认为这将是一个相当直接的工作,但它证明是棘手的。

我的网格由4或5个主要数据列组成,名称,位置等,其余列是基于过滤器的日期,因此这些是相当动态的。

非常感谢您的帮助。

2 个答案:

答案 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)

经过一些实验(并且需要完成相同的任务),我理解你的意思。如果DataTableDataSource,则数据似乎排序较晚。挖掘我注意到源本身永远不会改变 - 尽管显示的数据已经排序,但顺序保持不变:因此即使将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;
        }
    }
}

希望这有帮助。