做WinForm开发离不开一些基本的控件,作为数据列表显示控件中,其中最为重要的要数 DataGridView,以前用的是一些第三方控件,提供了比较灵活和方便的功能,比如:根据某列分组统计等等,确实方便,但加载第三方控件的缺点就是加载数据太慢,打包布署后体积大,更为甚者还要收费,咱又不能用盗版,没办法自力更生才是生存之道。
DataGridView的行统计汇总功能在网络上搜索后,有一些同仁实现过(Summary DataGridView),但只是在某种特定的需求下实现了目标,要么是扩展不方便,要么是性能太差,加载一个DataGridView不知重复运行几十次代码(未做详细统计) ,为了一劳永逸的解决这个问题,特改写了代码,在此提供给各位同仁,欢迎斧正。
1、首先,看使用方法:
dgvProjectList.ShowSummary(new string[] { "TotalIncome", "TotalFee", "TotalCost", "SaleAmount", "RetainedProfits" });
如果不使用统计汇总,不会对现有 DateGridView控件产生任何负作用,也不占用内存;"TotalIncome", "TotalFee", "TotalCost", "SaleAmount", "RetainedProfits" 为要统计的各行。
效果如下:
代码如下:
1、DataGridView扩展方法定义
代码
#region 显示统计列
/// <summary>
/// 显示DataGridView的统计信息
/// </summary>
/// <param name="dgv"></param>
/// <param name="SummaryColumns">要统计的列名称或数据源绑定列名称</param>
public static void ShowSummary(this DataGridView dgv,string[] SummaryColumns)
{
SummaryControlContainer summaryControl = new SummaryControlContainer(dgv,SummaryColumns);
dgv.Controls.Add(summaryControl);
//dgv.Tag = summaryControl;
summaryControl.BringToFront();
summaryControl.Show();
}
/// <summary>
/// 显示DataGridView的统计信息
/// </summary>
/// <param name="dgv"></param>
/// <param name="DisplaySumRowHeader">是否显示合计行标题</param>
/// <param name="SumRowHeaderText">合计列标题</param>
/// <param name="SumRowHeaderTextBold">合计列标题用粗体显示</param>
/// <param name="SummaryColumns">要统计的列名称或数据源绑定列名称</param>
public static void ShowSummary(this DataGridView dgv, bool DisplaySumRowHeader, string SumRowHeaderText, bool SumRowHeaderTextBold, string[] SummaryColumns)
{
SummaryControlContainer summaryControl = new SummaryControlContainer(dgv, DisplaySumRowHeader, SumRowHeaderText, SumRowHeaderTextBold, SummaryColumns);
dgv.Controls.Add(summaryControl);
//dgv.Tag = summaryControl;
summaryControl.BringToFront();
summaryControl.Show();
}
#endregion
2、核心类定义
代码
internal class SummaryControlContainer : UserControl
{
#region 公有属性
private bool _DisplaySumRowHeader;
/// <summary>
/// 是否显示合计行标题
/// </summary>
public bool DisplaySumRowHeader
{
get { return _DisplaySumRowHeader; }
set { _DisplaySumRowHeader = value; }
}
private string _SumRowHeaderText = "合计";
/// <summary>
/// 合计列标题
/// </summary>
public string SumRowHeaderText
{
get
{
if (_DisplaySumRowHeader)
{
return _SumRowHeaderText;
}
else
{
return string.Empty;
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_SumRowHeaderText = "合计";
}
else
{
_SumRowHeaderText = value;
}
}
}
private bool _SumRowHeaderTextBold;
/// <summary>
/// 合计列标题用粗体显示
/// </summary>
public bool SumRowHeaderTextBold
{
get { return _SumRowHeaderTextBold; }
set { _SumRowHeaderTextBold = value; }
}
private string[] _SummaryColumns;
/// <summary>
/// 要统计的列名称或数据源绑定列名称
/// </summary>
public string[] SummaryColumns
{
get { return _SummaryColumns; }
set
{
_SummaryColumns = value;
}
}
private string _FormatString = "F02";
public string FormatString
{
get { return _FormatString; }
set { _FormatString = value; }
}
#endregion
#region 私有变量
private Hashtable sumBoxHash;
private DataGridView dgv;
private Label sumRowHeaderLabel;
#endregion
#region 构造函数
public SummaryControlContainer(DataGridView dgv, string[] summaryColumns)
: this(dgv, true, "合计", false, summaryColumns)
{
}
public SummaryControlContainer(DataGridView dgv, bool displaySumRowHeader, string sumRowHeaderText,
bool sumRowHeaderTextBold, string[] summaryColumns)
{
if (dgv == null)
{
throw new Exception("DataGridView 不能为空!");
}
this.dgv = dgv;
_DisplaySumRowHeader = displaySumRowHeader;
_SumRowHeaderText = sumRowHeaderText;
_SumRowHeaderTextBold = sumRowHeaderTextBold;
_SummaryColumns = summaryColumns;
this.Visible = true;
this.Height = dgv.RowTemplate.Height;
this.Top = dgv.Height - this.Height;
this.Left = dgv.Left;
this.BackColor = dgv.RowHeadersDefaultCellStyle.BackColor;
sumBoxHash = new Hashtable();
sumRowHeaderLabel = new Label();
sumRowHeaderLabel.Height = this.Height;
sumRowHeaderLabel.Width = dgv.RowHeadersWidth;
sumRowHeaderLabel.BackColor = dgv.RowHeadersDefaultCellStyle.BackColor;
this.dgv.Resize += new EventHandler(dgv_Resize);
this.dgv.Scroll += new ScrollEventHandler(dgv_Scroll);
this.dgv.ColumnWidthChanged += new DataGridViewColumnEventHandler(dgv_ColumnWidthChanged);
this.dgv.RowHeadersWidthChanged += new EventHandler(dgv_RowHeadersWidthChanged);
this.dgv.RowsAdded += new DataGridViewRowsAddedEventHandler(dgv_RowsAdded);
this.dgv.RowsRemoved += new DataGridViewRowsRemovedEventHandler(dgv_RowsRemoved);
this.dgv.CellValueChanged += new DataGridViewCellEventHandler(dgv_CellValueChanged);
this.dgv.DataSourceChanged += new EventHandler(dgv_DataSourceChanged);
this.dgv.ColumnAdded += new DataGridViewColumnEventHandler(dgv_ColumnAdded);
this.dgv.ColumnRemoved += new DataGridViewColumnEventHandler(dgv_ColumnRemoved);
this.dgv.ColumnStateChanged += new DataGridViewColumnStateChangedEventHandler(dgv_ColumnStateChanged);
this.dgv.ColumnDisplayIndexChanged += new DataGridViewColumnEventHandler(dgv_ColumnDisplayIndexChanged);
reCreateSumBoxes();
}
#endregion
#region 私有方法
/// <summary>
/// Checks if passed object is of type of integer
/// </summary>
/// <param name="o">object</param>
/// <returns>true/ false</returns>
protected bool IsInteger(object o)
{
if (o is Int64)
{
return true;
}
if (o is Int32)
{
return true;
}
if (o is Int16)
{
return true;
}
return false;
}
/// <summary>
/// Checks if passed object is of type of decimal/ double
/// </summary>
/// <param name="o">object</param>
/// <returns>true/ false</returns>
protected bool IsDecimal(object o)
{
if (o is Decimal)
{
return true;
}
if (o is Single)
{
return true;
}
if (o is Double)
{
return true;
}
return false;
}
/// <summary>
/// Calculate the Sums of the summary columns
/// </summary>
private void calcSummaries()
{
foreach (ReadOnlyTextBox roTextBox in sumBoxHash.Values)
{
if (roTextBox.IsSummary)
{
roTextBox.Tag = 0;
roTextBox.Text = "0";
roTextBox.Invalidate();
}
}
if (SummaryColumns != null && SummaryColumns.Length > 0 && sumBoxHash.Count > 0)
{
foreach (DataGridViewRow dgvRow in dgv.Rows)
{
foreach (DataGridViewCell dgvCell in dgvRow.Cells)
{
foreach (DataGridViewColumn dgvColumn in sumBoxHash.Keys)
{
if (dgvCell.OwningColumn.Equals(dgvColumn))
{
ReadOnlyTextBox sumBox = (ReadOnlyTextBox)sumBoxHash[dgvColumn];
if (sumBox != null && sumBox.IsSummary)
{
if (dgvCell.Value != null && !(dgvCell.Value is DBNull))
{
if (IsInteger(dgvCell.Value))
{
sumBox.Tag = Convert.ToInt64(sumBox.Tag) + Convert.ToInt64(dgvCell.Value);
}
else if (IsDecimal(dgvCell.Value))
{
sumBox.Tag = Convert.ToDecimal(sumBox.Tag) + Convert.ToDecimal(dgvCell.Value);
}
sumBox.Text = string.Format("{0}", sumBox.Tag);
sumBox.Invalidate();
}
}
}
}
}
}
}
}
/// <summary>
/// Create summary boxes for each Column of the DataGridView
/// </summary>
private void reCreateSumBoxes()
{
foreach (Control control in sumBoxHash.Values)
{
this.Controls.Remove(control);
}
sumBoxHash.Clear();
int iCnt = 0;
ReadOnlyTextBox sumBox;
List<DataGridViewColumn> sortedColumns = SortedColumns;
foreach (DataGridViewColumn dgvColumn in sortedColumns)
{
sumBox = new ReadOnlyTextBox();
sumBoxHash.Add(dgvColumn, sumBox);
sumBox.Top = 0;
sumBox.Height = dgv.RowTemplate.Height;
sumBox.BorderColor = dgv.GridColor;
sumBox.BackColor = dgv.DefaultCellStyle.BackColor;
sumBox.ForeColor = dgv.DefaultCellStyle.ForeColor;
sumBox.BringToFront();
if (dgv.ColumnCount - iCnt == 1)
{
sumBox.IsLastColumn = true;
}
if (SummaryColumns != null && SummaryColumns.Length > 0)
{
for (int iCntX = 0; iCntX < SummaryColumns.Length; iCntX++)
{
if (SummaryColumns[iCntX] == dgvColumn.DataPropertyName ||
SummaryColumns[iCntX] == dgvColumn.Name)
{
sumBox.TextAlign = TextHelper.TranslateGridColumnAligment(dgvColumn.DefaultCellStyle.Alignment);
sumBox.IsSummary = true;
sumBox.FormatString = dgvColumn.DefaultCellStyle.Format;
if (dgvColumn.ValueType == typeof(System.Int32) || dgvColumn.ValueType == typeof(System.Int16) ||
dgvColumn.ValueType == typeof(System.Int64) || dgvColumn.ValueType == typeof(System.Single) ||
dgvColumn.ValueType == typeof(System.Double) || dgvColumn.ValueType == typeof(System.Single) ||
dgvColumn.ValueType == typeof(System.Decimal))
{
sumBox.Tag = System.Activator.CreateInstance(dgvColumn.ValueType);
}
}
}
}
sumBox.BringToFront();
this.Controls.Add(sumBox);
iCnt++;
}
sumRowHeaderLabel.Font = new Font(dgv.DefaultCellStyle.Font, SumRowHeaderTextBold ? FontStyle.Bold : FontStyle.Regular);
sumRowHeaderLabel.Anchor = AnchorStyles.Left;
sumRowHeaderLabel.TextAlign = ContentAlignment.MiddleRight;
sumRowHeaderLabel.Height = this.Height;
sumRowHeaderLabel.Width = dgv.RowHeadersWidth;
sumRowHeaderLabel.Top = 0;
sumRowHeaderLabel.Text = DisplaySumRowHeader ? SumRowHeaderText : string.Empty;
sumRowHeaderLabel.ForeColor = dgv.DefaultCellStyle.ForeColor;
sumRowHeaderLabel.Margin = new Padding(0);
sumRowHeaderLabel.Padding = new Padding(0);
this.Controls.Add(sumRowHeaderLabel);
calcSummaries();
resizeSumBoxes();
}
/// <summary>
/// Order the columns in the way they are displayed
/// </summary>
private List<DataGridViewColumn> SortedColumns
{
get
{
List<DataGridViewColumn> result = new List<DataGridViewColumn>();
DataGridViewColumn column = dgv.Columns.GetFirstColumn(DataGridViewElementStates.None);
if (column == null)
{
return result;
}
result.Add(column);
while ((column = dgv.Columns.GetNextColumn(column, DataGridViewElementStates.None, DataGridViewElementStates.None)) != null)
{
result.Add(column);
}
return result;
}
}
/// <summary>
/// Resize the summary Boxes depending on the
/// width of the Columns of the DataGridView
/// </summary>
private void resizeSumBoxes()
{
try
{
this.SuspendLayout();
if (sumBoxHash != null && sumBoxHash.Count > 0)
try
{
int rowHeaderWidth = dgv.RowHeadersVisible ? dgv.RowHeadersWidth - 1 : 0;
int sumLabelWidth = dgv.RowHeadersVisible ? dgv.RowHeadersWidth - 1 : 0;
int curPos = rowHeaderWidth;
if (DisplaySumRowHeader && sumLabelWidth > 0)
{
sumRowHeaderLabel.Visible = true;
sumRowHeaderLabel.Width = sumLabelWidth;
if (dgv.RightToLeft == RightToLeft.Yes)
{
if (sumRowHeaderLabel.Dock != DockStyle.Right)
{
sumRowHeaderLabel.Dock = DockStyle.Right;
}
}
else
{
if (sumRowHeaderLabel.Dock != DockStyle.Left)
{
sumRowHeaderLabel.Dock = DockStyle.Left;
}
}
}
else
{
if (sumRowHeaderLabel.Visible)
{
sumRowHeaderLabel.Visible = false;
}
}
int iCnt = 0;
Rectangle oldBounds;
foreach (DataGridViewColumn dgvColumn in SortedColumns)
{
ReadOnlyTextBox sumBox = (ReadOnlyTextBox)sumBoxHash[dgvColumn];
if (sumBox != null)
{
oldBounds = sumBox.Bounds;
if (!dgvColumn.Visible)
{
sumBox.Visible = false;
continue;
}
int from = dgvColumn.Frozen ? curPos : curPos - dgv.HorizontalScrollingOffset;
int width = dgvColumn.Width + (iCnt == 0 ? 0 : 0);
if (from < rowHeaderWidth)
{
width -= rowHeaderWidth - from;
from = rowHeaderWidth;
}
if (from + width > this.Width)
{
width = this.Width - from;
}
if (width < 4)
{
if (sumBox.Visible)
{
sumBox.Visible = false;
}
}
else
{
if (this.RightToLeft == RightToLeft.Yes)
{
from = this.Width - from - width;
}
if (sumBox.Left != from || sumBox.Width != width)
{
sumBox.SetBounds(from, 0, width, 0, BoundsSpecified.X | BoundsSpecified.Width);
}
if (!sumBox.Visible)
{
sumBox.Visible = true;
}
}
curPos += dgvColumn.Width + (iCnt == 0 ? 0 : 0);
if (oldBounds != sumBox.Bounds)
{
sumBox.Invalidate();
}
}
iCnt++;
}
}
finally
{
this.ResumeLayout();
}
}
#if (DEBUG)
catch (Exception ee)
{
MessageBox.Show(ee.ToString());
System.Diagnostics.Debug.WriteLine(ee.ToString());
}
#else
catch
{ }
#endif
}
#endregion
#region 事件处理程序
void dgv_DataSourceChanged(object sender, EventArgs e)
{
calcSummaries();
}
private void dgv_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
ReadOnlyTextBox roTextBox = (ReadOnlyTextBox)sumBoxHash[dgv.Columns[e.ColumnIndex]];
if (roTextBox != null)
{
if (roTextBox.IsSummary)
{
calcSummaries();
}
}
}
private void dgv_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
calcSummaries();
}
private void dgv_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
{
calcSummaries();
}
private void dgv_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e)
{
//resizeSumBoxes();
reCreateSumBoxes();
}
private void dgv_ColumnStateChanged(object sender, DataGridViewColumnStateChangedEventArgs e)
{
resizeSumBoxes();
}
private void dgv_Scroll(object sender, ScrollEventArgs e)
{
resizeSumBoxes();
}
private void dgv_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
{
resizeSumBoxes();
}
private void dgv_RowHeadersWidthChanged(object sender, EventArgs e)
{
int columnsWidth = 0;
for (int iCnt = 0; iCnt < dgv.Columns.Count; iCnt++)
{
if (dgv.Columns[iCnt].Visible)
{
if (dgv.Columns[iCnt].AutoSizeMode == DataGridViewAutoSizeColumnMode.Fill)
{
columnsWidth += dgv.Columns[iCnt].MinimumWidth;
}
else
columnsWidth += dgv.Columns[iCnt].Width;
}
}
this.Width = columnsWidth;
resizeSumBoxes();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
resizeSumBoxes();
}
private void dgv_Resize(object sender, EventArgs e)
{
adjustSumControlToGrid();
resizeSumBoxes();
}
private void adjustSumControlToGrid()
{
ScrollBar horizontalScrollBar = (ScrollBar)typeof(DataGridView).GetProperty("HorizontalScrollBar", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(dgv, null);
ScrollBar verticalScrollBar = (ScrollBar)typeof(DataGridView).GetProperty("VerticalScrollBar", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(dgv, null);
if (horizontalScrollBar.Visible)
{
this.Top = dgv.Height - this.Height - horizontalScrollBar.Height;
}
else
{
this.Top = dgv.Height - this.Height;
}
this.Left = dgv.Left;
if (verticalScrollBar.Visible)
{
this.Width = dgv.Width - verticalScrollBar.Width;
}
else
{
this.Width = dgv.Width;
}
}
private void dgv_ColumnRemoved(object sender, DataGridViewColumnEventArgs e)
{
reCreateSumBoxes();
}
private void dgv_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
{
reCreateSumBoxes();
}
#endregion
}
internal partial class ReadOnlyTextBox : Control
{
StringFormat format;
public ReadOnlyTextBox()
{
InitializeComponent();
format = new StringFormat( StringFormatFlags.NoWrap | StringFormatFlags.FitBlackBox | StringFormatFlags.MeasureTrailingSpaces);
format.LineAlignment = StringAlignment.Center;
this.Height = 10;
this.Width = 10;
this.Padding = new Padding(2);
}
public ReadOnlyTextBox(IContainer container)
{
container.Add(this);
InitializeComponent();
this.TextChanged += new EventHandler(ReadOnlyTextBox_TextChanged);
}
private void ReadOnlyTextBox_TextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(formatString) && !string.IsNullOrEmpty(Text))
{
Text = string.Format(formatString, Text);
}
}
private Color borderColor = Color.Black;
private bool isSummary;
public bool IsSummary
{
get { return isSummary; }
set { isSummary = value; }
}
private bool isLastColumn;
public bool IsLastColumn
{
get { return isLastColumn; }
set { isLastColumn = value; }
}
private string formatString;
public string FormatString
{
get { return formatString; }
set { formatString = value; }
}
private HorizontalAlignment textAlign = HorizontalAlignment.Left;
[DefaultValue(HorizontalAlignment.Left)]
public HorizontalAlignment TextAlign
{
get { return textAlign; }
set
{
textAlign = value;
setFormatFlags();
}
}
private StringTrimming trimming = StringTrimming.None;
[DefaultValue(StringTrimming.None)]
public StringTrimming Trimming
{
get { return trimming; }
set
{
trimming = value;
setFormatFlags();
}
}
private void setFormatFlags()
{
format.Alignment = TextHelper.TranslateAligment(TextAlign);
format.Trimming = trimming;
}
public Color BorderColor
{
get { return borderColor; }
set { borderColor = value; }
}
protected override void OnPaint(PaintEventArgs e)
{
int subWidth = 0;
Rectangle textBounds;
if (!string.IsNullOrEmpty(formatString) && !string.IsNullOrEmpty(Text))
{
Text = String.Format("{0:" + formatString + "}", Convert.ToDecimal(Text));
}
textBounds = new Rectangle(this.ClientRectangle.X + 2, this.ClientRectangle.Y + 2, this.ClientRectangle.Width - 2 , this.ClientRectangle.Height - 2 );
using(Pen pen = new Pen(borderColor))
{
if (isLastColumn)
subWidth = 1;
e.Graphics.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
e.Graphics.DrawRectangle(pen, this.ClientRectangle.X, this.ClientRectangle.Y, this.ClientRectangle.Width - subWidth , this.ClientRectangle.Height - 1);
e.Graphics.DrawString(Text, Font, Brushes.Black, textBounds , format );
}
}
}
internal static class TextHelper
{
public static StringAlignment TranslateAligment(HorizontalAlignment aligment)
{
if (aligment == HorizontalAlignment.Left)
return StringAlignment.Near;
else if (aligment == HorizontalAlignment.Right)
return StringAlignment.Far;
else
return StringAlignment.Center;
}
public static HorizontalAlignment TranslateGridColumnAligment(DataGridViewContentAlignment aligment)
{
if (aligment == DataGridViewContentAlignment.BottomLeft || aligment == DataGridViewContentAlignment.MiddleLeft || aligment == DataGridViewContentAlignment.TopLeft)
return HorizontalAlignment.Left;
else if (aligment == DataGridViewContentAlignment.BottomRight || aligment == DataGridViewContentAlignment.MiddleRight || aligment == DataGridViewContentAlignment.TopRight )
return HorizontalAlignment.Right;
else
return HorizontalAlignment.Center;
}
public static TextFormatFlags TranslateAligmentToFlag(HorizontalAlignment aligment)
{
if (aligment == HorizontalAlignment.Left)
return TextFormatFlags.Left;
else if (aligment == HorizontalAlignment.Right)
return TextFormatFlags.Right;
else
return TextFormatFlags.HorizontalCenter;
}
public static TextFormatFlags TranslateTrimmingToFlag(StringTrimming trimming)
{
if (trimming == StringTrimming.EllipsisCharacter)
return TextFormatFlags.EndEllipsis;
else if (trimming == StringTrimming.EllipsisPath)
return TextFormatFlags.PathEllipsis;
if (trimming == StringTrimming.EllipsisWord)
return TextFormatFlags.WordEllipsis;
if (trimming == StringTrimming.Word)
return TextFormatFlags.WordBreak;
else
return TextFormatFlags.Default;
}
}