- 在开始之前,先来看一下在项目中展示效果及在程序中调用方式(当然展示的数据是随机生成的临时测试数据),各位看得顺眼再继续往后.
- 实现思路.
本文中的合计行其实是一个仅有一行数据的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();
}
}