博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多维表头的DataGridView
阅读量:4353 次
发布时间:2019-06-07

本文共 19324 字,大约阅读时间需要 64 分钟。

背景

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

 

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

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

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

 

准备

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

用到的方法包括

Graphics.FillRectangle //填充一个矩形

Graphics.DrawLine //画一条线

Graphics.DrawString  //写字符串

 

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

HeaderItem的定义如下

1     public class HeaderItem  2     {  3         private int _startX;//起始横坐标  4         private int _startY;//起始纵坐标  5         private int _endX; //终止横坐标  6         private int _endY; //终止纵坐标  7         private bool _baseHeader; //是否基础表头  8   9         public HeaderItem(int startX, int endX, int startY, int endY, string content) 10         { 11             this._endX = endX; 12             this._endY = endY; 13             this._startX = startX; 14             this._startY = startY; 15             this.Content = content; 16         } 17  18         public HeaderItem(int x, int y, string content):this(x,x,y,y,content) 19         {  20              21         } 22  23         public HeaderItem() 24         {  25          26         } 27  28         public static HeaderItem CreateBaseHeader(int x,int y,string content) 29         { 30             HeaderItem header = new HeaderItem(); 31             header._endX= header._startX = x; 32             header._endY= header._startY = y; 33             header._baseHeader = true; 34             header.Content = content; 35             return header; 36         } 37  38         public int StartX 39         { 40             get { return _startX; } 41             set  42             { 43                 if (value > _endX) 44                 { 45                     _startX = _endX; 46                     return; 47                 } 48                 if (value < 0) _startX = 0; 49                 else _startX = value; 50             } 51         } 52  53         public int StartY 54         { 55             get { return _startY; } 56             set 57             { 58                 if (_baseHeader) 59                 { 60                     _startY = 0; 61                     return; 62                 } 63                 if (value > _endY) 64                 { 65                     _startY = _endY; 66                     return; 67                 } 68                 if (value < 0) _startY = 0; 69                 else _startY = value; 70             } 71         } 72  73         public int EndX 74         { 75             get { return _endX; } 76             set  77             { 78                 if (_baseHeader) 79                 { 80                     _endX = _startX; 81                     return; 82                 } 83                 if (value < _startX) 84                 { 85                     _endX = _startX; 86                     return; 87                 } 88                 _endX = value;  89             } 90         } 91  92         public int EndY 93         { 94             get { return _endY; } 95             set  96             { 97                 if (value < _startY) 98                 { 99                     _endY = _startY;100                     return;101                 }102                 _endY = value; 103             }104         }105 106         public bool IsBaseHeader107         {
get{ return _baseHeader;} }108 109 public string Content { get; set; }110 }

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

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

 

另外所有列头单元格的集合HeaderCollection的定义如下

1     public class HeaderCollection 2     { 3         private List
_headerList; 4 private bool _iniLock; 5 6 public DataGridViewColumnCollection BindCollection{
get;set;} 7 8 public HeaderCollection(DataGridViewColumnCollection cols) 9 {10 _headerList = new List
();11 BindCollection=cols;12 _iniLock = false;13 }14 15 public int GetHeaderLevels()16 {17 int max = 0;18 foreach (HeaderItem item in _headerList)19 if (item.EndY > max)20 max = item.EndY;21 22 return max;23 }24 25 public List
GetBaseHeaders()26 {27 List
list = new List
();28 foreach (HeaderItem item in _headerList)29 if (item.IsBaseHeader) list.Add(item);30 return list;31 }32 33 public HeaderItem GetHeaderByLocation(int x, int y) //先进行X坐标遍历,再进行Y坐标遍历。查找出包含输入坐标的表头单元格实例34 {35 if (!_iniLock) InitHeader();36 HeaderItem result=null;37 List
temp = new List
();38 foreach (HeaderItem item in _headerList)39 if (item.StartX <= x && item.EndX >= x)40 temp.Add(item);41 foreach (HeaderItem item in temp)42 if (item.StartY <= y && item.EndY >= y)43 result = item;44 45 return result;46 }47 48 public IEnumerator GetHeaderEnumer()49 {50 return _headerList.GetEnumerator();51 }52 53 public void AddHeader(HeaderItem header)54 {55 this._headerList.Add(header);56 }57 58 public void AddHeader(int startX, int endX, int startY, int endY, string content)59 {60 this._headerList.Add(new HeaderItem(startX,endX,startY,endY,content));61 }62 63 public void AddHeader(int x, int y, string content)64 {65 this._headerList.Add(new HeaderItem(x, y, content));66 }67 68 public void RemoveHeader(HeaderItem header)69 {70 this._headerList.Remove(header);71 }72 73 public void RemoveHeader(int x, int y)74 {75 HeaderItem header= GetHeaderByLocation(x, y);76 if (header != null) RemoveHeader(header);77 }78 79 private void InitHeader()80 {81 _iniLock = true;82 for (int i = 0; i < this.BindCollection.Count; i++)83 if(this.GetHeaderByLocation(i,0)==null)84 this._headerList.Add(HeaderItem.CreateBaseHeader(i,0 , this.BindCollection[i].HeaderText));85 _iniLock = false;86 }87 }

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

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

 

扩展控件

到真正扩展控件了,最核心的是重写 OnCellPainting 方法,这个其实是与表格单元格重绘时触发事件绑定的方法,通过参数 DataGridViewCellPaintingEventArgs 的 ColumnIndex 和 RowIndex 属性可以知道当前重绘的是哪个单元格,于是就通过HeaderCollection获取要绘制的表头单元格的信息进行重绘,对已经重绘的单元格会进行标记,以防重复绘制。

1         protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e) 2         { 3             if (e.ColumnIndex == -1 || e.RowIndex != -1) 4             { 5                 base.OnCellPainting(e); 6                 return; 7             } 8             int lev=this.Headers.GetHeaderLevels(); 9             this.ColumnHeadersHeight = (lev + 1) * _baseColumnHeadHeight;10             for (int i = 0; i <= lev; i++) //到达某一列后,遍历各行,查找出还没绘制的表头进行绘制11             {12                 HeaderItem tempHeader= this.Headers.GetHeaderByLocation(e.ColumnIndex, i);13                 if (tempHeader==null|| i != tempHeader.EndY || e.ColumnIndex != tempHeader.StartX) continue;14                 DrawHeader(tempHeader, e);15             }16             e.Handled = true;17         }

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

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

1         private void DrawHeader(HeaderItem item,DataGridViewCellPaintingEventArgs e) 2         { 3             if (this.ColumnHeadersHeightSizeMode != DataGridViewColumnHeadersHeightSizeMode.DisableResizing) 4                 this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; 5             int lev=this.Headers.GetHeaderLevels();  //获取整个表头的总行数 6             lev=(lev-item.EndY)*_baseColumnHeadHeight;   //重新设置表头的行高 7  8             SolidBrush backgroundBrush = new SolidBrush(e.CellStyle.BackColor); 9             SolidBrush lineBrush = new SolidBrush(this.GridColor);10             Pen linePen = new Pen(lineBrush);11             StringFormat foramt = new StringFormat();12             foramt.Alignment = StringAlignment.Center;13             foramt.LineAlignment = StringAlignment.Center;14 15             Rectangle headRec = new Rectangle(e.CellBounds.Left, lev, ComputeWidth(item.StartX, item.EndX)-1, ComputeHeight(item.StartY, item.EndY)-1);16             e.Graphics.FillRectangle(backgroundBrush, headRec);  //填充矩形17             e.Graphics.DrawLine(linePen, headRec.Left, headRec.Bottom, headRec.Right, headRec.Bottom); //画单元格的底线18             e.Graphics.DrawLine(linePen, headRec.Right, headRec.Top, headRec.Right, headRec.Bottom);  //画单元格的右边线19             e.Graphics.DrawString(item.Content, this.ColumnHeadersDefaultCellStyle.Font, Brushes.Black,headRec, foramt);  //填写表头标题20         }

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

 

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

1         private int ComputeWidth(int startX, int endX) 2         { 3             int width = 0; 4             for (int i = startX; i <= endX; i++) 5                 width+= this.Columns[i].Width; 6             return width; 7         } 8  9         private int ComputeHeight(int startY, int endY)10         {11             return _baseColumnHeadHeight * (endY - startY+1);12         }

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

1             HeaderItem item= this.boundGridView1.Headers.GetHeaderByLocation(0, 0);  //获取包括坐标(0,0)的单元格 2             item.EndY = 2; 3             item = this.boundGridView1.Headers.GetHeaderByLocation(9,0 ); 4             item.EndY = 2; 5             item = this.boundGridView1.Headers.GetHeaderByLocation(10, 0); 6             item.EndY = 2; 7             item = this.boundGridView1.Headers.GetHeaderByLocation(11, 0); 8             item.EndY = 2; 9 10             this.boundGridView1.Headers.AddHeader(1, 2, 1, 1, "语文"); //增加表头,起始坐标(1,1) ,终止坐标(2,1) 内容"语文"11             this.boundGridView1.Headers.AddHeader(3, 4, 1, 1, "数学");  //增加表头,起始坐标(3,1) ,终止坐标(4,1) 内容"数学" 12             this.boundGridView1.Headers.AddHeader(5, 6, 1, 1, "英语");  //增加表头,起始坐标(5,1) ,终止坐标(6,1) 内容"英语" 13             this.boundGridView1.Headers.AddHeader(7, 8, 1, 1, "X科");  //增加表头,起始坐标(7,1) ,终止坐标(8,1) 内容"X科" 14             this.boundGridView1.Headers.AddHeader(1, 8, 2, 2, "成绩");  //增加表头,起始坐标(1,2) ,终止坐标(8,2) 内容"成绩"

效果图如下所示

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

控件的完整代码
1     public class BoundGridView : DataGridView  2     {  3         private int _baseColumnHeadHeight;  4   5         public BoundGridView():base()  6         {  7             this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;  8             _baseColumnHeadHeight = this.ColumnHeadersHeight;  9             this.Headers = new HeaderCollection(this.Columns); 10         } 11  12         public HeaderCollection Headers{ get;private set; } 13  14         protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e) 15         { 16             if (e.ColumnIndex == -1 || e.RowIndex != -1) 17             { 18                 base.OnCellPainting(e); 19                 return; 20             } 21             int lev=this.Headers.GetHeaderLevels(); 22             this.ColumnHeadersHeight = (lev + 1) * _baseColumnHeadHeight; 23             for (int i = 0; i <= lev; i++) 24             { 25                 HeaderItem tempHeader= this.Headers.GetHeaderByLocation(e.ColumnIndex, i); 26                 if (tempHeader==null|| i != tempHeader.EndY || e.ColumnIndex != tempHeader.StartX) continue; 27                 DrawHeader(tempHeader, e); 28             } 29             e.Handled = true; 30         } 31  32         private int ComputeWidth(int startX, int endX) 33         { 34             int width = 0; 35             for (int i = startX; i <= endX; i++) 36                 width+= this.Columns[i].Width; 37             return width; 38         } 39  40         private int ComputeHeight(int startY, int endY) 41         { 42             return _baseColumnHeadHeight * (endY - startY+1); 43         } 44  45         private void DrawHeader(HeaderItem item,DataGridViewCellPaintingEventArgs e) 46         { 47             if (this.ColumnHeadersHeightSizeMode != DataGridViewColumnHeadersHeightSizeMode.DisableResizing) 48                 this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; 49             int lev=this.Headers.GetHeaderLevels(); 50             lev=(lev-item.EndY)*_baseColumnHeadHeight; 51  52             SolidBrush backgroundBrush = new SolidBrush(e.CellStyle.BackColor); 53             SolidBrush lineBrush = new SolidBrush(this.GridColor); 54             Pen linePen = new Pen(lineBrush); 55             StringFormat foramt = new StringFormat(); 56             foramt.Alignment = StringAlignment.Center; 57             foramt.LineAlignment = StringAlignment.Center; 58  59             Rectangle headRec = new Rectangle(e.CellBounds.Left, lev, ComputeWidth(item.StartX, item.EndX)-1, ComputeHeight(item.StartY, item.EndY)-1); 60             e.Graphics.FillRectangle(backgroundBrush, headRec); 61             e.Graphics.DrawLine(linePen, headRec.Left, headRec.Bottom, headRec.Right, headRec.Bottom); 62             e.Graphics.DrawLine(linePen, headRec.Right, headRec.Top, headRec.Right, headRec.Bottom); 63             e.Graphics.DrawString(item.Content, this.ColumnHeadersDefaultCellStyle.Font, Brushes.Black,headRec, foramt); 64         } 65     } 66  67     public class HeaderItem 68     { 69         private int _startX; 70         private int _startY; 71         private int _endX; 72         private int _endY; 73         private bool _baseHeader; 74  75         public HeaderItem(int startX, int endX, int startY, int endY, string content) 76         { 77             this._endX = endX; 78             this._endY = endY; 79             this._startX = startX; 80             this._startY = startY; 81             this.Content = content; 82         } 83  84         public HeaderItem(int x, int y, string content):this(x,x,y,y,content) 85         {  86              87         } 88  89         public HeaderItem() 90         {  91          92         } 93  94         public static HeaderItem CreateBaseHeader(int x,int y,string content) 95         { 96             HeaderItem header = new HeaderItem(); 97             header._endX= header._startX = x; 98             header._endY= header._startY = y; 99             header._baseHeader = true;100             header.Content = content;101             return header;102         }103 104         public int StartX105         {106             get { return _startX; }107             set 108             {109                 if (value > _endX)110                 {111                     _startX = _endX;112                     return;113                 }114                 if (value < 0) _startX = 0;115                 else _startX = value;116             }117         }118 119         public int StartY120         {121             get { return _startY; }122             set123             {124                 if (_baseHeader)125                 {126                     _startY = 0;127                     return;128                 }129                 if (value > _endY)130                 {131                     _startY = _endY;132                     return;133                 }134                 if (value < 0) _startY = 0;135                 else _startY = value;136             }137         }138 139         public int EndX140         {141             get { return _endX; }142             set 143             {144                 if (_baseHeader)145                 {146                     _endX = _startX;147                     return;148                 }149                 if (value < _startX)150                 {151                     _endX = _startX;152                     return;153                 }154                 _endX = value; 155             }156         }157 158         public int EndY159         {160             get { return _endY; }161             set 162             {163                 if (value < _startY)164                 {165                     _endY = _startY;166                     return;167                 }168                 _endY = value; 169             }170         }171 172         public bool IsBaseHeader173         {
get{ return _baseHeader;} }174 175 public string Content { get; set; }176 }177 178 public class HeaderCollection179 {180 private List
_headerList;181 private bool _iniLock;182 183 public DataGridViewColumnCollection BindCollection{
get;set;}184 185 public HeaderCollection(DataGridViewColumnCollection cols)186 {187 _headerList = new List
();188 BindCollection=cols;189 _iniLock = false;190 }191 192 public int GetHeaderLevels()193 {194 int max = 0;195 foreach (HeaderItem item in _headerList)196 if (item.EndY > max)197 max = item.EndY;198 199 return max;200 }201 202 public List
GetBaseHeaders()203 {204 List
list = new List
();205 foreach (HeaderItem item in _headerList)206 if (item.IsBaseHeader) list.Add(item);207 return list;208 }209 210 public HeaderItem GetHeaderByLocation(int x, int y)211 {212 if (!_iniLock) InitHeader();213 HeaderItem result=null;214 List
temp = new List
();215 foreach (HeaderItem item in _headerList)216 if (item.StartX <= x && item.EndX >= x)217 temp.Add(item);218 foreach (HeaderItem item in temp)219 if (item.StartY <= y && item.EndY >= y)220 result = item;221 222 return result;223 }224 225 public IEnumerator GetHeaderEnumer()226 {227 return _headerList.GetEnumerator();228 }229 230 public void AddHeader(HeaderItem header)231 {232 this._headerList.Add(header);233 }234 235 public void AddHeader(int startX, int endX, int startY, int endY, string content)236 {237 this._headerList.Add(new HeaderItem(startX,endX,startY,endY,content));238 }239 240 public void AddHeader(int x, int y, string content)241 {242 this._headerList.Add(new HeaderItem(x, y, content));243 }244 245 public void RemoveHeader(HeaderItem header)246 {247 this._headerList.Remove(header);248 }249 250 public void RemoveHeader(int x, int y)251 {252 HeaderItem header= GetHeaderByLocation(x, y);253 if (header != null) RemoveHeader(header);254 }255 256 private void InitHeader()257 {258 _iniLock = true;259 for (int i = 0; i < this.BindCollection.Count; i++)260 if(this.GetHeaderByLocation(i,0)==null)261 this._headerList.Add(HeaderItem.CreateBaseHeader(i,0 , this.BindCollection[i].HeaderText));262 _iniLock = false;263 }264 }

 

转载于:https://www.cnblogs.com/HopeGi/archive/2013/04/03/2982837.html

你可能感兴趣的文章
78,90,Subsets,46,47,Permutations,39,40 DFS 大合集
查看>>
在Static控件上显示图片
查看>>
ORACLE 数据库基本参数查询
查看>>
iOS - CAReplicatorLayer 的运用
查看>>
iOS 8自动调整UITableView和UICollectionView布局
查看>>
哈佛精神
查看>>
c++
查看>>
微信开发
查看>>
《代码整洁之道》
查看>>
基于框架的应用系统开发的质量属性
查看>>
noip普及组考纲+样题合集——初级篇(OIer必看)
查看>>
windos下安装pgAdmin
查看>>
1.Hello World - Console
查看>>
基于jquery判断浏览器版本过低代码
查看>>
因式分解
查看>>
Java获取随机数的3种方法
查看>>
AJAX发送参数到后台,前台火狐debug报undefine
查看>>
luogu4240 毒瘤之神的考验(毒瘤乌斯反演)
查看>>
Setting Ruby on Rails Environment in MacOSX
查看>>
C#中数据的批量插入和更新 转载自21教程网
查看>>