背景
今天在做系统报表的过程中,我想实现批量操作DataGridView中的数据,在列中加复选框,通过一个事件触发进行全选或取消,可是在外面添加按钮,这种模式虽然能够实现,但是从系统界面设计的角度,美观和灵活性就差很多了,能否在DataGridView头标题栏上呈现复选框,通过这个头标题复选框来对这一列的复选框,这样是不是更灵活,也美观一点?
问题
可是,找了半天,发现微软原始的DataGridView头标题栏没有CheckBox的功能,郁闷了~~。
我在伍兄的博客找到有关于扩展DataGridVew在头标题栏添加全选功能按钮的功能(不是源码开源),而且他的这个程序集也不能满足我的需求,怎么办?
只有靠我自已去探索了,我在传统的DataGridView中实现了这个功能,但是整合进我的换肤组件中不能实现,Why? 我找了好友Strong一起研究,自已摸不清方向,最后还是他找到了问题点,可是却无法入手?(no any solution)
最后,经过自已的努力一步一步debug, 终于解决了问题,并整合进自已的换肤组件中,个人觉得有必要总结一下。
在总结技术点前,先展示一下我的成果,然后再做扩展说明,如下:
(图一)Office2007Blue效果
(图二) Office2007Silver效果
传统的解决方案
在传统的DataGridView上,实现基本上没有什么难处,只要按如下的步骤去操作就可以了。
添加一个DataGridViewColumnHeaderCellW.cs,继承DataGridViewColumnHeaderCell,
源码如下:
public class DataGridViewColumnHeaderCellW : DataGridViewColumnHeaderCell { public object HeaderTextDataSource { get; set; } private Type _dataSourceType = null; public Type DataSourceType { get { if (HeaderTextDataSource != null && _dataSourceType == null) _dataSourceType = HeaderTextDataSource.GetType(); return _dataSourceType; } set { _dataSourceType = value; } } public string FieldName { get; set; } public string Prefix { get; set; } public string Suffix { get; set; } Point checkBoxLocation; Size checkBoxSize; bool _checked = false; Point _cellLocation = new Point(); System.Windows.Forms.VisualStyles.CheckBoxState _cbState = System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal; public event datagridviewcheckboxHeaderEventHander OnCheckBoxClicked; //绘制列头checkbox protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle cellBounds, int rowIndex, DataGridViewElementStates dataGridViewElementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) { base.Paint(graphics, clipBounds, cellBounds, rowIndex, dataGridViewElementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts); Point p = new Point(); Size s = CheckBoxRenderer.GetGlyphSize(graphics, System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal); p.X = cellBounds.Location.X + (cellBounds.Width / 2) - (s.Width / 2) - 1; //列头checkbox的X坐标 p.Y = cellBounds.Location.Y + (cellBounds.Height / 2) - (s.Height / 2); //列头checkbox的Y坐标 _cellLocation = cellBounds.Location; checkBoxLocation = p; checkBoxSize = s; if (_checked) _cbState = System.Windows.Forms.VisualStyles. CheckBoxState.CheckedNormal; else _cbState = System.Windows.Forms.VisualStyles. CheckBoxState.UncheckedNormal; CheckBoxRenderer.DrawCheckBox (graphics, checkBoxLocation, _cbState); } /// <summary> /// 点击列头checkbox单击事件 /// </summary> protected override void OnMouseClick(DataGridViewCellMouseEventArgs e) { Point p = new Point(e.X + _cellLocation.X, e.Y + _cellLocation.Y); if (p.X >= checkBoxLocation.X && p.X <= checkBoxLocation.X + checkBoxSize.Width && p.Y >= checkBoxLocation.Y && p.Y <= checkBoxLocation.Y + checkBoxSize.Height) { _checked = !_checked; //获取列头checkbox的选择状态 datagridviewCheckboxHeaderEventArgs ex = new datagridviewCheckboxHeaderEventArgs(); ex.CheckedState = _checked; object sender = new object();//此处不代表选择的列头checkbox,只是作为参数传递。应该列头checkbox是绘制出来的,无法获得它的实例 if (OnCheckBoxClicked != null) { OnCheckBoxClicked(sender, ex);//触发单击事件 this.DataGridView.InvalidateCell(this); } } base.OnMouseClick(e); }
在DataGridViewColumnHeaderCellW.cs内部,添加一个委托和继承EventArgs事件数据的基类:
public delegate void datagridviewcheckboxHeaderEventHander(object sender, datagridviewCheckboxHeaderEventArgs e); //定义包含列头checkbox选择状态的参数类 public class datagridviewCheckboxHeaderEventArgs : EventArgs { private bool checkedState = false; public bool CheckedState { get { return checkedState; } set { checkedState = value; } } }
如何调用?
先在界面上添加一个DataGridView,并添加一列,选类型为DataGridViewCheckBoxColumn,在Form_Load事件中添加如下代码:
private void Form1_Load(object sender, EventArgs e) { DataGridViewColumnHeaderCellW ch = new DataGridViewColumnHeaderCellW(); ch.OnCheckBoxClicked += new datagridviewcheckboxHeaderEventHander(OnCheckBoxClicked); //第三列为DataGridViewCheckBoxColumn DataGridViewCheckBoxColumn checkboxCol = this.dataGridView1.Columns[0] as DataGridViewCheckBoxColumn; checkboxCol.HeaderCell = ch; checkboxCol.HeaderCell.Value = string.Empty;//消除列头checkbox旁出现的文字 }
没题解决了!
展示一下,这个复选框暂放在最后一列,如下图:
这个真是灰头土脸,像是灰姑娘那么丑。
于是,我整合进我的换肤中,可是怎么也不能实现,标题栏就是不出来,结果成了如下图这样子:
最后经过一系列努力,定位问题在CellPainting事件中,重绘的过程把标题栏的checkbox效果覆盖了。
我在此事件中添加如下代码:
if (e.RowIndex == -1) { if (!(_columnHeaderUpColor == Color.Transparent) && !(_columnHeaderDownColor == Color.Transparent) && !_columnHeaderUpColor.IsEmpty && !_columnHeaderDownColor.IsEmpty) { DrawLinearGradient(e.CellBounds, e.Graphics, _columnHeaderUpColor, _columnHeaderDownColor); if (ShowColumnHeaderCheckBox) { e.Paint(e.ClipBounds, (DataGridViewPaintParts.All & ~DataGridViewPaintParts.Background)); } else { DrawText(e); } e.Handled = true; } }
问题终于解决,但是在代码中为什么用ShowColumnHeaderCheckBox?
并不是所有的数据呈现功能都要有这个头标题栏复选框的功能,为了更好的兼容性,我在添加了这个属性,开发人员可以通过此属性灵活选择,默认是false。
public bool showColumnHeaderCheckBox; public bool ShowColumnHeaderCheckBox { get { return showColumnHeaderCheckBox; } set { showColumnHeaderCheckBox = value; } }
在客户端这样去设就可以了。
private void Form1_Load(object sender, EventArgs e) { InitParameterList(); dataGridView1.ShowColumnHeaderCheckBox = true;//此处设为true DataGridViewColumnHeaderCellW ch = new DataGridViewColumnHeaderCellW(); ch.OnCheckBoxClicked += new datagridviewcheckboxHeaderEventHander(OnCheckBoxClicked); //第三列为DataGridViewCheckBoxColumn DataGridViewCheckBoxColumn checkboxCol = this.dataGridView1.Columns[0] as DataGridViewCheckBoxColumn; checkboxCol.HeaderCell = ch; checkboxCol.HeaderCell.Value = string.Empty;//消除列头checkbox旁出现的文字 }
总结
在研究一个新的东西时,我一般是先实现粗糙的功能,由浅入深渐渐细化这么一个演变的过程,毕竟灰姑娘一下要变成白雪公主也是要有个过程的。
元芳,你怎么看?