欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

DataGridView多维表头的实现方法

程序员文章站 2024-03-02 16:22:40
背景 对于.net 原本提供的datagridview控件,制作成如下形式的表格是毫无压力的。 但是如果把表格改了一下,变成如下形式 传统的datagridvi...

背景

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

DataGridView多维表头的实现方法

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

DataGridView多维表头的实现方法

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

DataGridView多维表头的实现方法

那得自己扩展了,不过最后还是用了一个控件库的报表控件,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轴正方向是垂直向上。如下图所示

DataGridView多维表头的实现方法

之所以要对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。这个坐标是忽略了整个表头合并单元格的情况,例如

DataGridView多维表头的实现方法

上面这幅图,如果输入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, "成绩");

效果图如下所示

 

DataGridView多维表头的实现方法

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

复制代码 代码如下:


     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;
         }
     }