视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
DataGridView表头的实现方法
2020-11-27 22:41:17 责编:小采
文档

背景

对于.NET 原本提供的DataGridView控件,制作成如下形式的表格是毫无压力的。

但是如果把表格改了一下,变成如下形式

传统的DataGridView就做不到了,如果扩展一下还是行的,有不少网友也扩展了DataGridView控件,不过有些也只能制作出二维的表头。或者使用第三方的控件,之前也用过DevExpress的BoundGridView。不过在没有可使用的第三方控件的情况下,做到下面的效果,就有点麻烦了。

那得自己扩展了,不过最后还是用了一个控件库的报表控件,Telerik的Reporting。不过我自己还是扩展了DataGridView,使之能制作出上面的报表。

准备

学习了一些网友的代码,原来制作这个表头都是利用GDI+对DataGirdView的表头进行重绘。

用到的方法包括

Graphics.FillRectangle //填充一个矩形

Graphics.DrawLine //画一条线

Graphics.DrawString  //写字符串

此外为了方便组织表头,本人还定义了一个表头的数据结构 HeaderItem 和 HeaderCollection 分别作为每个表头单元格的数据实体和整个表头的集合。

HeaderItem的定义如下

代码如下:
public class HeaderItem
     {
         private int _startX;//起始横坐标
         private int _startY;//起始纵坐标
         private int _endX; //终止横坐标
         private int _endY; //终止纵坐标
         private bool _baseHeader; //是否基础表头

         public HeaderItem(int startX, int endX, int startY, int endY, string content)
         {
             this._endX = endX;
             this._endY = endY;
             this._startX = startX;
             this._startY = startY;
             this.Content = content;
         }

         public HeaderItem(int x, int y, string content):this(x,x,y,y,content)
         {

         }

         public HeaderItem()
         {

         }

         public static HeaderItem CreateBaseHeader(int x,int y,string content)
         {
             HeaderItem header = new HeaderItem();
             header._endX= header._startX = x;
             header._endY= header._startY = y;
             header._baseHeader = true;
             header.Content = content;
             return header;
         }

         public int StartX
         {
             get { return _startX; }
             set
             {
                 if (value > _endX)
                 {
                     _startX = _endX;
                     return;
                 }
                 if (value < 0) _startX = 0;
                 else _startX = value;
             }
         }

         public int StartY
         {
             get { return _startY; }
             set
             {
                 if (_baseHeader)
                 {
                     _startY = 0;
                     return;
                 }
                 if (value > _endY)
                 {
                     _startY = _endY;
                     return;
                 }
                 if (value < 0) _startY = 0;
                 else _startY = value;
             }
         }

         public int EndX
         {
             get { return _endX; }
             set
             {
                 if (_baseHeader)
                 {
                     _endX = _startX;
                     return;
                 }
                 if (value < _startX)
                 {
                     _endX = _startX;
                     return;
                 }
                 _endX = value;
             }
         }

         public int EndY
         {
             get { return _endY; }
             set
             {
                 if (value < _startY)
                 {
                     _endY = _startY;
                     return;
                 }
                 _endY = value;
             }
         }

         public bool IsBaseHeader
         {get{ return _baseHeader;} }

         public string Content { get; set; }
     }

设计思想是利用数学的直角坐标系,给每个表头单元格定位并划定其大小。与计算机显示的坐标定位不同,这里的原点是跟数学的一样放在左下角,X轴正方向是水平向右,Y轴正方向是垂直向上。如下图所示

之所以要对GridView中原始的列头进行特别处理,是因为这里的起止坐标和终止坐标都可以设置,而原始列头的起始纵坐标(StartY)只能是0,终止横坐标(EndX)必须与起始横坐标(StartY)相等。

另外所有列头单元格的集合HeaderCollection的定义如下
代码如下:
public class HeaderCollection
     {
         private List<HeaderItem> _headerList;
         private bool _iniLock;

         public DataGridViewColumnCollection BindCollection{get;set;}

         public HeaderCollection(DataGridViewColumnCollection cols)
         {
             _headerList = new List<HeaderItem>();
             BindCollection=cols;
             _iniLock = false;
         }

         public int GetHeaderLevels()
         {
             int max = 0;
             foreach (HeaderItem item in _headerList)
                 if (item.EndY > max)
                     max = item.EndY;

             return max;
         }

         public List<HeaderItem> GetBaseHeaders()
         {
             List<HeaderItem> list = new List<HeaderItem>();
             foreach (HeaderItem item in _headerList)
                 if (item.IsBaseHeader) list.Add(item);
             return list;
         }

         public HeaderItem GetHeaderByLocation(int x, int y)
         {
             if (!_iniLock) InitHeader();
             HeaderItem result=null;
             List<HeaderItem> temp = new List<HeaderItem>();
             foreach (HeaderItem item in _headerList)
                 if (item.StartX <= x && item.EndX >= x)
                     temp.Add(item);
             foreach (HeaderItem item in temp)
                 if (item.StartY <= y && item.EndY >= y)
                     result = item;

             return result;
         }

         public IEnumerator GetHeaderEnumer()
         {
             return _headerList.GetEnumerator();
         }

         public void AddHeader(HeaderItem header)
         {
             this._headerList.Add(header);
         }

         public void AddHeader(int startX, int endX, int startY, int endY, string content)
         {
             this._headerList.Add(new HeaderItem(startX,endX,startY,endY,content));
         }

         public void AddHeader(int x, int y, string content)
         {
             this._headerList.Add(new HeaderItem(x, y, content));
         }

         public void RemoveHeader(HeaderItem header)
         {
             this._headerList.Remove(header);
         }

         public void RemoveHeader(int x, int y)
         {
            HeaderItem header= GetHeaderByLocation(x, y);
            if (header != null) RemoveHeader(header);
         }

         private void InitHeader()
         {
             _iniLock = true;
             for (int i = 0; i < this.BindCollection.Count; i++)
                 if(this.GetHeaderByLocation(i,0)==null)
                 this._headerList.Add(HeaderItem.CreateBaseHeader(i,0 , this.BindCollection[i].HeaderText));
             _iniLock = false;
         }
     }

这里仿照了.NET Frameword的Collection那样定义了Add方法和Remove方法,此外说明一下那个 GetHeaderByLocation 方法,这个方法可以通过给定的坐标获取那个坐标的HeaderItem。这个坐标是忽略了整个表头合并单元格的情况,例如

上面这幅图,如果输入0,0 返回的是灰色区域,输入2,1 或3,2 或 5,1返回的都是橙色的区域。

扩展控件

到真正扩展控件了,最核心的是重写 OnCellPainting 方法,这个其实是与表格单元格重绘时触发事件绑定的方法,通过参数 DataGridViewCellPaintingEventArgs 的 ColumnIndex 和 RowIndex 属性可以知道当前重绘的是哪个单元格,于是就通过HeaderCollection获取要绘制的表头单元格的信息进行重绘,对已经重绘的单元格会进行标记,以防重复绘制。
代码如下:
protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
         {
             if (e.ColumnIndex == -1 || e.RowIndex != -1)
             {
                 base.OnCellPainting(e);
                 return;
             }
             int lev=this.Headers.GetHeaderLevels();
             this.ColumnHeadersHeight = (lev + 1) * _baseColumnHeadHeight;
             for (int i = 0; i <= lev; i++)
             {
                 HeaderItem tempHeader= this.Headers.GetHeaderByLocation(e.ColumnIndex, i);
                 if (tempHeader==null|| i != tempHeader.EndY || e.ColumnIndex != tempHeader.StartX) continue;
                 DrawHeader(tempHeader, e);
             }
             e.Handled = true;
         }

上面的代码中,最初是先判断当前要重绘的单元格是不是表头部分,如果不是则调用原本的OnCellPainting方法。 e.Handled=true; 比较关键,有了这句代码,重绘才能生效。

绘制单元格的过程封装在方法DrawHeader里面

代码如下:
private void DrawHeader(HeaderItem item,DataGridViewCellPaintingEventArgs e)
         {
             if (this.ColumnHeadersHeightSizeMode != DataGridViewColumnHeadersHeightSizeMode.DisableResizing)
                 this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
             int lev=this.Headers.GetHeaderLevels();
             lev=(lev-item.EndY)*_baseColumnHeadHeight;

             SolidBrush backgroundBrush = new SolidBrush(e.CellStyle.BackColor);
             SolidBrush lineBrush = new SolidBrush(this.GridColor);
             Pen linePen = new Pen(lineBrush);
             StringFormat foramt = new StringFormat();
             foramt.Alignment = StringAlignment.Center;
             foramt.LineAlignment = StringAlignment.Center;

             Rectangle headRec = new Rectangle(e.CellBounds.Left, lev, ComputeWidth(item.StartX, item.EndX)-1, ComputeHeight(item.StartY, item.EndY)-1);
             e.Graphics.FillRectangle(backgroundBrush, headRec);
             e.Graphics.DrawLine(linePen, headRec.Left, headRec.Bottom, headRec.Right, headRec.Bottom);
             e.Graphics.DrawLine(linePen, headRec.Right, headRec.Top, headRec.Right, headRec.Bottom);
             e.Graphics.DrawString(item.Content, this.ColumnHeadersDefaultCellStyle.Font, Brushes.Black,headRec, foramt);
         }

填充矩形时,记得要给矩形的常和宽减去一个像素,这样才不会与相邻的矩形重叠区域导致矩形的边线显示不出来。还有这里的要设置 ColumnHeadersHeightSizeMode 属性,如果不把它设成 DisableResizing ,那么表头的高度是改变不了的,这样即使设置了二维,三维,n维,最终只是一维。

这里用到的一些辅助方法如下,分别是通过坐标计算出高度和宽度。

代码如下:
private int ComputeWidth(int startX, int endX)
         {
             int width = 0;
             for (int i = startX; i <= endX; i++)
                 width+= this.Columns[i].Width;
             return width;
         }

         private int ComputeHeight(int startY, int endY)
         {
             return _baseColumnHeadHeight * (endY - startY+1);
         }

给一段使用的实例代码,这里要预先给DataGridView每一列设好绑定的字段,否则自动添加的列是做不出效果来的。

代码如下:
HeaderItem item= this.boundGridView1.Headers.GetHeaderByLocation(0, 0);
             item.EndY = 2;
             item = this.boundGridView1.Headers.GetHeaderByLocation(9,0 );
             item.EndY = 2;
             item = this.boundGridView1.Headers.GetHeaderByLocation(10, 0);
             item.EndY = 2;
             item = this.boundGridView1.Headers.GetHeaderByLocation(11, 0);
             item.EndY = 2;

             this.boundGridView1.Headers.AddHeader(1, 2, 1, 1, "语文");
             this.boundGridView1.Headers.AddHeader(3, 4, 1, 1, "数学");
             this.boundGridView1.Headers.AddHeader(5, 6, 1, 1, "英语");
             this.boundGridView1.Headers.AddHeader(7, 8, 1, 1, "X科");
             this.boundGridView1.Headers.AddHeader(1, 8, 2, 2, "成绩");

效果图如下所示

 

总的来说自我感觉有点小题大做,但想不出有什么更好的办法,各位如果觉得以上说的有什么不好的,欢迎拍砖;如果发现以上有什么说错了,恳请批评指正;如果觉得好的,请支持一下。谢谢!最后附上整个控件的源码
控件的完整代码
代码如下:

     public class BoundGridView : DataGridView
     {
         private int _baseColumnHeadHeight;

         public BoundGridView():base()
         {
             this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
             _baseColumnHeadHeight = this.ColumnHeadersHeight;
             this.Headers = new HeaderCollection(this.Columns);
         }

         public HeaderCollection Headers{ get;private set; }

         protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
         {
             if (e.ColumnIndex == -1 || e.RowIndex != -1)
             {
                 base.OnCellPainting(e);
                 return;
             }
             int lev=this.Headers.GetHeaderLevels();
             this.ColumnHeadersHeight = (lev + 1) * _baseColumnHeadHeight;
             for (int i = 0; i <= lev; i++)
             {
                 HeaderItem tempHeader= this.Headers.GetHeaderByLocation(e.ColumnIndex, i);
                 if (tempHeader==null|| i != tempHeader.EndY || e.ColumnIndex != tempHeader.StartX) continue;
                 DrawHeader(tempHeader, e);
             }
             e.Handled = true;
         }

         private int ComputeWidth(int startX, int endX)
         {
             int width = 0;
             for (int i = startX; i <= endX; i++)
                 width+= this.Columns[i].Width;
             return width;
         }

         private int ComputeHeight(int startY, int endY)
         {
             return _baseColumnHeadHeight * (endY - startY+1);
         }

         private void DrawHeader(HeaderItem item,DataGridViewCellPaintingEventArgs e)
         {
             if (this.ColumnHeadersHeightSizeMode != DataGridViewColumnHeadersHeightSizeMode.DisableResizing)
                 this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
             int lev=this.Headers.GetHeaderLevels();
             lev=(lev-item.EndY)*_baseColumnHeadHeight;

             SolidBrush backgroundBrush = new SolidBrush(e.CellStyle.BackColor);
             SolidBrush lineBrush = new SolidBrush(this.GridColor);
             Pen linePen = new Pen(lineBrush);
             StringFormat foramt = new StringFormat();
             foramt.Alignment = StringAlignment.Center;
             foramt.LineAlignment = StringAlignment.Center;

             Rectangle headRec = new Rectangle(e.CellBounds.Left, lev, ComputeWidth(item.StartX, item.EndX)-1, ComputeHeight(item.StartY, item.EndY)-1);
             e.Graphics.FillRectangle(backgroundBrush, headRec);
             e.Graphics.DrawLine(linePen, headRec.Left, headRec.Bottom, headRec.Right, headRec.Bottom);
             e.Graphics.DrawLine(linePen, headRec.Right, headRec.Top, headRec.Right, headRec.Bottom);
             e.Graphics.DrawString(item.Content, this.ColumnHeadersDefaultCellStyle.Font, Brushes.Black,headRec, foramt);
         }
     }

     public class HeaderItem
     {
         private int _startX;
         private int _startY;
         private int _endX;
         private int _endY;
         private bool _baseHeader;

         public HeaderItem(int startX, int endX, int startY, int endY, string content)
         {
             this._endX = endX;
             this._endY = endY;
             this._startX = startX;
             this._startY = startY;
             this.Content = content;
         }

         public HeaderItem(int x, int y, string content):this(x,x,y,y,content)
         {

         }

         public HeaderItem()
         {

         }

         public static HeaderItem CreateBaseHeader(int x,int y,string content)
         {
             HeaderItem header = new HeaderItem();
             header._endX= header._startX = x;
             header._endY= header._startY = y;
             header._baseHeader = true;
             header.Content = content;
             return header;
         }

         public int StartX
         {
             get { return _startX; }
             set
             {
                 if (value > _endX)
                 {
                     _startX = _endX;
                     return;
                 }
                 if (value < 0) _startX = 0;
                 else _startX = value;
             }
         }

         public int StartY
         {
             get { return _startY; }
             set
             {
                 if (_baseHeader)
                 {
                     _startY = 0;
                     return;
                 }
                 if (value > _endY)
                 {
                     _startY = _endY;
                     return;
                 }
                 if (value < 0) _startY = 0;
                 else _startY = value;
             }
         }

         public int EndX
         {
             get { return _endX; }
             set
             {
                 if (_baseHeader)
                 {
                     _endX = _startX;
                     return;
                 }
                 if (value < _startX)
                 {
                     _endX = _startX;
                     return;
                 }
                 _endX = value;
             }
         }

         public int EndY
         {
             get { return _endY; }
             set
             {
                 if (value < _startY)
                 {
                     _endY = _startY;
                     return;
                 }
                 _endY = value;
             }
         }

         public bool IsBaseHeader
         {get{ return _baseHeader;} }

         public string Content { get; set; }
     }

     public class HeaderCollection
     {
         private List<HeaderItem> _headerList;
         private bool _iniLock;

         public DataGridViewColumnCollection BindCollection{get;set;}

         public HeaderCollection(DataGridViewColumnCollection cols)
         {
             _headerList = new List<HeaderItem>();
             BindCollection=cols;
             _iniLock = false;
         }

         public int GetHeaderLevels()
         {
             int max = 0;
             foreach (HeaderItem item in _headerList)
                 if (item.EndY > max)
                     max = item.EndY;

             return max;
         }

         public List<HeaderItem> GetBaseHeaders()
         {
             List<HeaderItem> list = new List<HeaderItem>();
             foreach (HeaderItem item in _headerList)
                 if (item.IsBaseHeader) list.Add(item);
             return list;
         }

         public HeaderItem GetHeaderByLocation(int x, int y)
         {
             if (!_iniLock) InitHeader();
             HeaderItem result=null;
             List<HeaderItem> temp = new List<HeaderItem>();
             foreach (HeaderItem item in _headerList)
                 if (item.StartX <= x && item.EndX >= x)
                     temp.Add(item);
             foreach (HeaderItem item in temp)
                 if (item.StartY <= y && item.EndY >= y)
                     result = item;

             return result;
         }

         public IEnumerator GetHeaderEnumer()
         {
             return _headerList.GetEnumerator();
         }

         public void AddHeader(HeaderItem header)
         {
             this._headerList.Add(header);
         }

         public void AddHeader(int startX, int endX, int startY, int endY, string content)
         {
             this._headerList.Add(new HeaderItem(startX,endX,startY,endY,content));
         }

         public void AddHeader(int x, int y, string content)
         {
             this._headerList.Add(new HeaderItem(x, y, content));
         }

         public void RemoveHeader(HeaderItem header)
         {
             this._headerList.Remove(header);
         }

         public void RemoveHeader(int x, int y)
         {
            HeaderItem header= GetHeaderByLocation(x, y);
            if (header != null) RemoveHeader(header);
         }

         private void InitHeader()
         {
             _iniLock = true;
             for (int i = 0; i < this.BindCollection.Count; i++)
                 if(this.GetHeaderByLocation(i,0)==null)
                 this._headerList.Add(HeaderItem.CreateBaseHeader(i,0 , this.BindCollection[i].HeaderText));
             _iniLock = false;
         }
     }

下载本文
显示全文
专题