背景
對于.NET 原本提供的DataGridView控件,制作成如下形式的表格是毫無壓力的。
但是如果把表格改了一下,變成如下形式
傳統(tǒng)的DataGridView就做不到了,如果擴(kuò)展一下還是行的,有不少網(wǎng)友也擴(kuò)展了DataGridView控件,不過有些也只能制作出二維的表頭?;蛘呤褂玫谌降目丶?,之前也用過DevExpress的BoundGridView。不過在沒有可使用的第三方控件的情況下,做到下面的效果,就有點(diǎn)麻煩了。
那得自己擴(kuò)展了,不過最后還是用了一個控件庫的報表控件,Telerik的Reporting。不過我自己還是擴(kuò)展了DataGridView,使之能制作出上面的報表。
準(zhǔn)備
學(xué)習(xí)了一些網(wǎng)友的代碼,原來制作這個多維表頭都是利用GDI+對DataGirdView的表頭進(jìn)行重繪。
用到的方法包括
Graphics.FillRectangle //填充一個矩形
Graphics.DrawLine //畫一條線
Graphics.DrawString //寫字符串
此外為了方便組織表頭,本人還定義了一個表頭的數(shù)據(jù)結(jié)構(gòu) HeaderItem 和 HeaderCollection 分別作為每個表頭單元格的數(shù)據(jù)實體和整個表頭的集合。
HeaderItem的定義如下
復(fù)制代碼 代碼如下:
public class HeaderItem
{
private int _startX;//起始橫坐標(biāo)
private int _startY;//起始縱坐標(biāo)
private int _endX; //終止橫坐標(biāo)
private int _endY; //終止縱坐標(biāo)
private bool _baseHeader; //是否基礎(chǔ)表頭
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; }
}
設(shè)計思想是利用數(shù)學(xué)的直角坐標(biāo)系,給每個表頭單元格定位并劃定其大小。與計算機(jī)顯示的坐標(biāo)定位不同,這里的原點(diǎn)是跟數(shù)學(xué)的一樣放在左下角,X軸正方向是水平向右,Y軸正方向是垂直向上。如下圖所示
之所以要對GridView中原始的列頭進(jìn)行特別處理,是因為這里的起止坐標(biāo)和終止坐標(biāo)都可以設(shè)置,而原始列頭的起始縱坐標(biāo)(StartY)只能是0,終止橫坐標(biāo)(EndX)必須與起始橫坐標(biāo)(StartY)相等。
另外所有列頭單元格的集合HeaderCollection的定義如下
復(fù)制代碼 代碼如下:
public class HeaderCollection
{
private ListHeaderItem> _headerList;
private bool _iniLock;
public DataGridViewColumnCollection BindCollection{get;set;}
public HeaderCollection(DataGridViewColumnCollection cols)
{
_headerList = new ListHeaderItem>();
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 ListHeaderItem> GetBaseHeaders()
{
ListHeaderItem> list = new ListHeaderItem>();
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;
ListHeaderItem> temp = new ListHeaderItem>();
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 方法,這個方法可以通過給定的坐標(biāo)獲取那個坐標(biāo)的HeaderItem。這個坐標(biāo)是忽略了整個表頭合并單元格的情況,例如
上面這幅圖,如果輸入0,0 返回的是灰色區(qū)域,輸入2,1 或3,2 或 5,1返回的都是橙色的區(qū)域。
擴(kuò)展控件
到真正擴(kuò)展控件了,最核心的是重寫 OnCellPainting 方法,這個其實是與表格單元格重繪時觸發(fā)事件綁定的方法,通過參數(shù) DataGridViewCellPaintingEventArgs 的 ColumnIndex 和 RowIndex 屬性可以知道當(dāng)前重繪的是哪個單元格,于是就通過HeaderCollection獲取要繪制的表頭單元格的信息進(jìn)行重繪,對已經(jīng)重繪的單元格會進(jìn)行標(biāo)記,以防重復(fù)繪制。
復(fù)制代碼 代碼如下:
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;
}
上面的代碼中,最初是先判斷當(dāng)前要重繪的單元格是不是表頭部分,如果不是則調(diào)用原本的OnCellPainting方法。 e.Handled=true; 比較關(guān)鍵,有了這句代碼,重繪才能生效。
繪制單元格的過程封裝在方法DrawHeader里面
復(fù)制代碼 代碼如下:
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);
}
填充矩形時,記得要給矩形的常和寬減去一個像素,這樣才不會與相鄰的矩形重疊區(qū)域?qū)е戮匦蔚倪吘€顯示不出來。還有這里的要設(shè)置 ColumnHeadersHeightSizeMode 屬性,如果不把它設(shè)成 DisableResizing ,那么表頭的高度是改變不了的,這樣即使設(shè)置了二維,三維,n維,最終只是一維。
這里用到的一些輔助方法如下,分別是通過坐標(biāo)計算出高度和寬度。
復(fù)制代碼 代碼如下:
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);
}
給一段使用的實例代碼,這里要預(yù)先給DataGridView每一列設(shè)好綁定的字段,否則自動添加的列是做不出效果來的。
復(fù)制代碼 代碼如下:
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, "數(shù)學(xué)");
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, "成績");
效果圖如下所示
總的來說自我感覺有點(diǎn)小題大做,但想不出有什么更好的辦法,各位如果覺得以上說的有什么不好的,歡迎拍磚;如果發(fā)現(xiàn)以上有什么說錯了,懇請批評指正;如果覺得好的,請支持一下。謝謝!最后附上整個控件的源碼
控件的完整代碼
復(fù)制代碼 代碼如下:
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 ListHeaderItem> _headerList;
private bool _iniLock;
public DataGridViewColumnCollection BindCollection{get;set;}
public HeaderCollection(DataGridViewColumnCollection cols)
{
_headerList = new ListHeaderItem>();
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 ListHeaderItem> GetBaseHeaders()
{
ListHeaderItem> list = new ListHeaderItem>();
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;
ListHeaderItem> temp = new ListHeaderItem>();
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;
}
}
您可能感興趣的文章:- asp.net DataGridView導(dǎo)出到Excel的三個方法[親測]
- DataGridView自動調(diào)整行高和行寬
- DataGridView中綁定DataTable數(shù)據(jù)及相關(guān)操作實現(xiàn)代碼
- C# DataGridView添加新行的2個方法
- c#讀取xml文件到datagridview實例
- WinForm中DataGridView添加,刪除,修改操作具體方法
- c#利用Excel直接讀取數(shù)據(jù)到DataGridView
- C# DatagridView常用操作匯總
- Winform在DataGridView中顯示圖片
- Winform讓DataGridView左側(cè)顯示圖片