小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)

时间:2021-08-09 14:45:09

背景

今天在做系统报表的过程中,我想实现批量操作DataGridView中的数据,在列中加复选框,通过一个事件触发进行全选或取消,可是在外面添加按钮,这种模式虽然能够实现,但是从系统界面设计的角度,美观和灵活性就差很多了,能否在DataGridView头标题栏上呈现复选框,通过这个头标题复选框来对这一列的复选框,这样是不是更灵活,也美观一点?

问题

可是,找了半天,发现微软原始的DataGridView头标题栏没有CheckBox的功能,郁闷了~~小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)

我在伍兄的博客找到有关于扩展DataGridVew在头标题栏添加全选功能按钮的功能(不是源码开源),而且他的这个程序集也不能满足我的需求,怎么办?

只有靠我自已去探索了,我在传统的DataGridView中实现了这个功能,但是整合进我的换肤组件中不能实现,Why? 我找了好友Strong一起研究,自已摸不清方向,最后还是他找到了问题点,可是却无法入手?(no any solution)

最后,经过自已的努力一步一步debug, 终于解决了问题,并整合进自已的换肤组件中,个人觉得有必要总结一下。

在总结技术点前,先展示一下我的成果,然后再做扩展说明,如下:

(图一)Office2007Blue效果

小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)

(图二) Office2007Silver效果

小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)

 

传统的解决方案

在传统的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旁出现的文字 
        }

没题解决了!

展示一下,这个复选框暂放在最后一列,如下图:

小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)

这个真是灰头土脸,像是灰姑娘那么丑。

于是,我整合进我的换肤中,可是怎么也不能实现,标题栏就是不出来,结果成了如下图这样子:

小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)

最后经过一系列努力,定位问题在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旁出现的文字 
        }

总结

在研究一个新的东西时,我一般是先实现粗糙的功能,由浅入深渐渐细化这么一个演变的过程,毕竟灰姑娘一下要变成白雪公主也是要有个过程的。

元芳,你怎么看?小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)