源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

DataGridView多维表头的实现方法

  • 时间:2021-09-14 09:23 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:DataGridView多维表头的实现方法
背景 对于.NET 原本提供的DataGridView控件,制作成如下形式的表格是毫无压力的。 [img]http://img.1sucai.cn/uploads/article/2018010710/20180107100134_0_82850.png[/img] 但是如果把表格改了一下,变成如下形式 [img]http://img.1sucai.cn/uploads/article/2018010710/20180107100134_1_54576.png[/img] 传统的DataGridView就做不到了,如果扩展一下还是行的,有不少网友也扩展了DataGridView控件,不过有些也只能制作出二维的表头。或者使用第三方的控件,之前也用过DevExpress的BoundGridView。不过在没有可使用的第三方控件的情况下,做到下面的效果,就有点麻烦了。 [img]http://img.1sucai.cn/uploads/article/2018010710/20180107100135_2_84388.png[/img] 那得自己扩展了,不过最后还是用了一个控件库的报表控件,Telerik的Reporting。不过我自己还是扩展了DataGridView,使之能制作出上面的报表。 准备 学习了一些网友的代码,原来制作这个多维表头都是利用GDI+对DataGirdView的表头进行重绘。 用到的方法包括 Graphics.FillRectangle //填充一个矩形 Graphics.DrawLine //画一条线 Graphics.DrawString  //写字符串 此外为了方便组织表头,本人还定义了一个表头的数据结构 HeaderItem 和 HeaderCollection 分别作为每个表头单元格的数据实体和整个表头的集合。 HeaderItem的定义如下
[u]复制代码[/u] 代码如下:
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轴正方向是垂直向上。如下图所示 [img]http://img.1sucai.cn/uploads/article/2018010710/20180107100135_3_74569.png[/img] 之所以要对GridView中原始的列头进行特别处理,是因为这里的起止坐标和终止坐标都可以设置,而原始列头的起始纵坐标(StartY)只能是0,终止横坐标(EndX)必须与起始横坐标(StartY)相等。 另外所有列头单元格的集合HeaderCollection的定义如下
[u]复制代码[/u] 代码如下:
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。这个坐标是忽略了整个表头合并单元格的情况,例如 [img]http://img.1sucai.cn/uploads/article/2018010710/20180107100136_4_85705.png[/img] 上面这幅图,如果输入0,0 返回的是灰色区域,输入2,1 或3,2 或 5,1返回的都是橙色的区域。 扩展控件 到真正扩展控件了,最核心的是重写 OnCellPainting 方法,这个其实是与表格单元格重绘时触发事件绑定的方法,通过参数 DataGridViewCellPaintingEventArgs 的 ColumnIndex 和 RowIndex 属性可以知道当前重绘的是哪个单元格,于是就通过HeaderCollection获取要绘制的表头单元格的信息进行重绘,对已经重绘的单元格会进行标记,以防重复绘制。
[u]复制代码[/u] 代码如下:
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里面
[u]复制代码[/u] 代码如下:
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维,最终只是一维。 这里用到的一些辅助方法如下,分别是通过坐标计算出高度和宽度。
[u]复制代码[/u] 代码如下:
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每一列设好绑定的字段,否则自动添加的列是做不出效果来的。
[u]复制代码[/u] 代码如下:
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, "成绩");
效果图如下所示   [img]http://img.1sucai.cn/uploads/article/2018010710/20180107100136_5_66400.png[/img] 总的来说自我感觉有点小题大做,但想不出有什么更好的办法,各位如果觉得以上说的有什么不好的,欢迎拍砖;如果发现以上有什么说错了,恳请批评指正;如果觉得好的,请支持一下。谢谢!最后附上整个控件的源码 控件的完整代码
[u]复制代码[/u] 代码如下:
     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;          }      }
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部