有天在想工作上的事的時候,看着.net原有的DataGridView,想起以前我寫過的一篇文章,總結了一個好的Gird控件應該具備哪些功能,在那里我提及到了分組功能,就像jqGrid那樣,
其實這樣的顯示型式很常見,就在平時郵箱的郵件列表就是按這種分組型式顯示的,按今天、昨天、上周之類,在購物網站的歷史訂單處也可以看見這種Grid表的身影。但是原有的DataGridView並不支持這種分組功能。那只能擴展一下了。
之前寫了一個多維表頭的GirdView,有經驗了,知道搞這種圖形化的東西無非都是用GDI+,上網找了一些文章后有點愣住了,之前畫表頭的是在DataGridView的OnPaint方法里把表頭描繪出來,但是這里畫分組的話就不同了,不是在DataGridViewRow的Paint方法里面處理。
因此要完成這個可分組DataGridView需要對兩個類進行拓展,一個是DataGridView,另一個是DataGirdViewRow。而實現這個分組DataGridView的效果大體步驟就是先把分組的標題行添加到GirdView里面,然后逐個把該組的數據行添加到GridView里面並控制它的顯示狀態,在分組的標題行進行相關的操作來改變數據行顯示狀態達到分組顯示的效果。
下面則逐個類來介紹吧!
GroupGridView繼承DataGridView,以下是它的一些字段和屬性的定義
成員名稱 |
數據類型 |
修飾符 |
描述 |
GroupFieldName |
String |
public |
要分組的字段的名稱,只能是類的屬性名或者是DataTable的列名 |
objDataSource |
Object |
Protected |
使用分組時暫時記錄數據源 |
GroupRowTemplate |
GroupGridViewRow |
Public |
分組行實例的模板 |
isGroupping |
Bool |
Private |
是否使用分組顯示 |
如果要使用這個GroupGridView的話,還要默認地對原本的DataGridView進行一些設置,這些設置我都塞到了構造函數里面,大體上分三類,分別是操作設置,樣式設置和部分屬性或字段的賦值,代碼如下
1 public GroupGridView() 2 { 3 //對GridView的操作的設置 4 this.AllowUserToAddRows = false; 5 this.AllowUserToDeleteRows = false; 6 this.SelectionMode = DataGridViewSelectionMode.FullRowSelect; 7 8 //對GridView的樣式設置 9 this.EditMode = DataGridViewEditMode.EditProgrammatically; 10 this.RowHeadersVisible = false; 11 this.CellBorderStyle = DataGridViewCellBorderStyle.RaisedHorizontal; 12 13 //對實例的部分成員賦值 14 isGroupping = false; 15 GroupRowTemplate = new GroupGridViewRow(); 16 }
其實GroupGridView要做的事就是能增加分組,在單擊和雙擊分組行時作處理,還有就是數據綁定時把數據分組添加到GirdView里。那么就逐個方法來介紹
第一個是增加分組的
1 public GroupGridViewRow CreateGroupGridViewRow(string title) 2 { 3 GroupGridViewRow group = this.GroupRowTemplate.Clone() as GroupGridViewRow; 4 if (group == null) 5 throw new NullReferenceException("組模板為空或者組模板類型不是GroupGridViewRow"); 6 group.Title = title; 7 group.ParentGridView = this; 8 group.CreateCells(this, group.Title); 9 this.Rows.Add(group); 10 group.CollapseGroup(); 11 return group; 12 }
主要是按照模板拷貝一個GroupGridViewRow的實例,設置相應的屬性后就把它添加到GirdView里面。
然后到鼠標對分組行的點擊事件,單擊展開/折疊圖標就展示或隱藏該組的數據。雙擊分組行同樣也達到這種顯示狀態切換的效果。
1 protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e) 2 { 3 if (e.RowIndex == -1 ||e.ColumnIndex==-1|| e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != 1) 4 return; 5 GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow; 6 if (group != null && group.IsIconHit(e)) 7 group.Toggle(); 8 base.OnCellMouseDown(e); 9 } 10 11 protected override void OnCellDoubleClick(DataGridViewCellEventArgs e) 12 { 13 if (e.RowIndex == -1||e.ColumnIndex==-1) 14 return; 15 GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow; 16 if (group != null ) 17 group.Toggle(); 18 base.OnCellDoubleClick(e); 19 }
Toggle和IsIconHit方法在GroupGridViewRow里會定義,在介紹GroupGridViewRow會列出該方法的定義,在單擊時就要通過IsIconHit判斷鼠標是否點擊到了圖片,當然是點擊中圖標才會經行狀態切換,而雙擊則不需要了。無論是雙擊和單擊都要判斷當前鼠標所在的行是分組行,如果是數據行的話就不會作任何操作了。
為了方便點綁定到數據,我重新定義了控件的DataSource屬性。當分組字段信息(GroupFieldName屬性)不存在時就會使用GridView默認的綁定數據,如果存在就會分組篩選出數據,然后逐個組去把行添加進去。
1 public new object DataSource 2 { 3 get 4 { 5 if (isGroupping) return objDataSource; 6 return base.DataSource; 7 } 8 set 9 { 10 if (string.IsNullOrEmpty(GroupFieldName) 11 || string.IsNullOrWhiteSpace(GroupFieldName)) 12 { 13 14 foreach (DataGridViewColumn col in this.Columns) 15 col.SortMode = DataGridViewColumnSortMode.Automatic; 16 base.DataSource = value; 17 isGroupping = false; 18 } 19 else 20 { 21 22 foreach (DataGridViewColumn col in this.Columns) 23 col.SortMode = DataGridViewColumnSortMode.NotSortable; 24 if (value is IEnumerable) 25 BindIEnumerableDataSource(value as IEnumerable); 26 else if (value is DataTable) 27 BindDataTableDataSouce(value as DataTable); 28 else if (value is DataSet) 29 BindDataSetDataSource(value as DataSet); 30 else 31 { 32 throw new NotImplementedException("不支持此類型作數據源"); 33 } 34 objDataSource = value; 35 isGroupping = true; 36 37 } 38 } 39 }
現在能自動綁定的數據源只能是可枚舉的或DataTable,DataSet其實是把里面第一個DataTable作為數據源而已。不同類型的數據有相應的處理方法
1 private void BindIEnumerableDataSource(IEnumerable enumerable) 2 { 3 IEnumerable iends = enumerable; 4 if (iends == null) return; 5 Type dsItemType = null; 6 foreach (object item in iends) 7 dsItemType = item.GetType(); 8 if (iends == null) return; 9 10 PropertyInfo proInfo = dsItemType.GetProperty(GroupFieldName); 11 12 Dictionary<string, List<object>> groupDataSource = new Dictionary<string, List<object>>(); 13 foreach (object item in iends) 14 { 15 string tempStr = proInfo.GetValue(item, null).ToString(); 16 if (!groupDataSource.ContainsKey(tempStr)) 17 groupDataSource[tempStr] = new List<object>(); 18 groupDataSource[tempStr].Add(item); 19 } 20 21 List<string> colFildNames = new List<string>(this.Columns.Count); 22 foreach (DataGridViewColumn col in this.Columns) 23 colFildNames.Add(col.DataPropertyName); 24 GroupGridViewRow group = null; 25 List<object> datas = new List<object>(colFildNames.Count); 26 foreach (KeyValuePair<string, List<object>> gi in groupDataSource) 27 { 28 group = CreateGroupGridViewRow(gi.Key); 29 foreach (object celli in gi.Value) 30 { 31 foreach (string colName in colFildNames) 32 { 33 datas.Add(dsItemType.GetProperty(colName).GetValue(celli, null)); 34 } 35 group.AddRowToGroup(datas.ToArray()); 36 datas.Clear(); 37 } 38 group.CollapseGroup(); 39 } 40 }
對於可枚舉的數據源,我就通過反射把相應的屬性的值都拿出來填到DataGridViewRow里面。
1 private void BindDataTableDataSouce(DataTable table) 2 { 3 Dictionary<string, List<DataRow>> groupDataSource = new Dictionary<string, List<DataRow>>(); 4 foreach (DataRow row in table.Rows) 5 { 6 string tempStr = row[GroupFieldName].ToString(); 7 if (!groupDataSource.ContainsKey(tempStr)) 8 groupDataSource[tempStr] = new List<DataRow>(); 9 groupDataSource[tempStr].Add(row); 10 } 11 12 List<string> colFildNames = new List<string>(this.Columns.Count); 13 foreach (DataGridViewColumn col in this.Columns) 14 colFildNames.Add(col.DataPropertyName); 15 GroupGridViewRow group = null; 16 List<object> datas = new List<object>(colFildNames.Count); 17 foreach (KeyValuePair<string, List<DataRow>> gi in groupDataSource) 18 { 19 group = CreateGroupGridViewRow(gi.Key); 20 foreach (DataRow celli in gi.Value) 21 { 22 foreach (string colName in colFildNames) 23 { 24 datas.Add(celli[colName]); 25 } 26 group.AddRowToGroup(datas.ToArray()); 27 datas.Clear(); 28 } 29 group.CollapseGroup(); 30 } 31 }
DataTable的綁定也類似,更方便的是DataTable不需要用反射了,直接通過行列的訪問就可以了。
GruopGridView介紹就到此結束,下面則介紹另一個類GroupGridViewRow,它是繼承DataGridViewRow。而GDI+的描繪都是在這個方法里面。也是先看看里面的成員
成員名稱 |
數據類型 |
修飾符 |
描述 |
IsExpanded |
bool
|
public
|
分組的狀態,是展開還是折疊 |
Title |
string
|
public
|
分組的標題,通常是分組的數據 |
ParentGridView |
GroupGridView
|
public
|
分組行所在的GridView |
groupRows |
List<DataGridViewRow>
|
private
|
此分組包含的數據行 |
那么一個GroupGridViewRow的要處理的事就有以下幾個,對分組下的數據行的狀態控制,判斷鼠標單擊是否命中圖標,增加一行數據到該分組下,還有最重要的就是描繪出分組行的外觀。
先列舉數據行的狀態控制方法,ExpandGroup()展開分組,CollapseGroup()折疊分組,還有Toggle()切換
1 public void ExpandGroup() 2 { 3 IsExpanded = true; 4 foreach (DataGridViewRow row in groupRows) 5 row.Visible = true; 6 } 7 8 public void CollapseGroup() 9 { 10 IsExpanded = false; 11 foreach (DataGridViewRow row in groupRows) 12 row.Visible = false; 13 } 14 15 public void Toggle() 16 { 17 if (IsExpanded) 18 CollapseGroup(); 19 else 20 ExpandGroup(); 21 }
實際上就是通過遍歷groupRows集合,改變其顯示狀態而已。
判斷單擊圖標的方法如下
1 public bool IsIconHit(DataGridViewCellMouseEventArgs e) 2 { 3 Rectangle groupBound = this.ParentGridView.GetRowDisplayRectangle(e.RowIndex, false); 4 5 if ( 6 e.X > groupBound.Left + 5 && 7 e.X < groupBound.Left + 15 && 8 e.Y > ((groupBound.Height - 1) - 5) / 2-2 && 9 e.Y < ((groupBound.Height - 1) - 5) / 2 + 10-2 10 ) 11 return true; 12 13 return false; 14 }
主要是通過鼠標指針當前所在的坐標是不是在圖標的范圍以內,那個范圍有點不好把握,要通過DataGridView的GetRowDisplayRectangle方法得到分組行的矩形,Left,X這兩個屬性有點分不清了。
增加數據行的方法如下
1 public DataGridViewRow AddRowToGroup(params object[] values) 2 { 3 DataGridViewRow row = this.ParentGridView.RowTemplate.Clone() as DataGridViewRow; 4 if (row == null) throw new NullReferenceException("行模板為空或者組模板類型不是DataGridViewRow"); 5 6 row.CreateCells(this.ParentGridView, values); 7 this.ParentGridView.Rows.Add(row); 8 this.groupRows.Add(row); 9 return row; 10 }
也復雜,通過DataGridView的普通數據行(不是分組行)的目標拷貝,填上數據,然后分別添加到這個分組的數據行集合中和GridView中。
最后到描繪分組行的方法,重寫Paint方法,這個基本上是參考園友老虎哥的代碼的,也作了一些小的調整,解決了水平滾動之后文字和圖標有重影的問題,所以老虎哥看見了不要怪哈!
1 protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle rowBounds, int rowIndex, DataGridViewElementStates rowState, bool isFirstDisplayedRow, bool isLastVisibleRow) 2 { 3 4 int holdWidth = this.ParentGridView.Columns.GetColumnsWidth(DataGridViewElementStates.Visible); 5 Color backgroudColor; 6 if (this.Selected) 7 backgroudColor = this.ParentGridView.DefaultCellStyle.SelectionBackColor; 8 else 9 backgroudColor = this.ParentGridView.DefaultCellStyle.BackColor; 10 using (Brush backgroudBrush = new SolidBrush(backgroudColor)) 11 { 12 graphics.FillRectangle(backgroudBrush, rowBounds.X,rowBounds.Y,holdWidth,rowBounds.Height); 13 } 14 using (Brush bottomLineBrush=new SolidBrush(Color.FromKnownColor( KnownColor.GradientActiveCaption))) 15 { 16 graphics.FillRectangle(bottomLineBrush, rowBounds.Left, rowBounds.Top + rowBounds.Height-2, holdWidth, 2); 17 } 18 19 StringFormat sf = new StringFormat(); 20 sf.LineAlignment = StringAlignment.Center; 21 Font font = new Font(this.ParentGridView.Font, FontStyle.Bold); 22 graphics.DrawString(this.Title, font, Brushes.Black, 23 rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + 20, rowBounds.Top + rowBounds.Height / 2, sf); 24 25 26 int symbolX = rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + 5; 27 int symbolY =rowBounds.Y+ ((rowBounds.Height - 1) - 5) / 2-2; 28 if (Application.RenderWithVisualStyles) 29 { 30 31 VisualStyleRenderer glyphRenderer; 32 if (this.IsExpanded) 33 glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened); 34 else 35 glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed); 36 37 Rectangle glyphRectangle =new Rectangle(symbolX, symbolY, 10, 10); 38 39 glyphRenderer.DrawBackground(graphics, glyphRectangle); 40 41 } 42 else 43 { 44 int h = 8; 45 int w = 8; 46 int x = symbolX; 47 int y = symbolY; 48 graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h); 49 graphics.FillRectangle(new SolidBrush(Color.White), 50 x + 1, y + 1, w - 1, h - 1); 51 52 //畫橫線 53 graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), 54 x + 2, y + 4, x + w - 2, y + 4); 55 56 //畫豎線 57 if (!this.IsExpanded) 58 graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), 59 x + 4, y + 2, x + 4, y + h - 2); 60 } 61 }
我自己寫出來的話還是有點棘手的,繪制對於我自己而言就分了三部分,分組欄的背景邊線描繪,文字的填寫,還有圖標的描繪,而圖標在這里很好的考慮那個顯示效果,分了漸變效果和單色效果。我也該多學習學習。
最后列一下控件的使用,列的設置分組設置,數據綁定如下,CreateColumn是我定義的方法,主要是構造一個新的列,對其進行設置之后就添加到GridView里面。
1 groupGridView21.GroupFieldName = "name"; 2 3 CreateColumn("id", "id", groupGridView21); 4 CreateColumn("invdate", "invdate", groupGridView21); 5 CreateColumn("note", "note", groupGridView21); 6 CreateColumn("amount", "amount", groupGridView21); 7 CreateColumn("tax", "tax", groupGridView21); 8 CreateColumn("total", "total", groupGridView21); 9 CreateColumn("name", "name", groupGridView21); 10 11 groupGridView21.DataSource = datasource;
效果就這樣,數據我完全拿了那個jqGrid的Demo里面的數據
控件的缺點還是跟老虎哥的控件類似,要把顯示的列逐個添加,不支持自動添加列,並且分組顯示的時候不能對列經行排序。最后附上控件的完整源碼,介紹完畢,謝謝!

1 public class GroupGridView:DataGridView 2 { 3 public string GroupFieldName { get; set; } 4 5 protected object objDataSource; 6 7 public GroupGridViewRow GroupRowTemplate { get;protected set; } 8 9 private bool isGroupping; 10 11 public GroupGridView() 12 { 13 //對GridView的操作的設置 14 this.AllowUserToAddRows = false; 15 this.AllowUserToDeleteRows = false; 16 this.SelectionMode = DataGridViewSelectionMode.FullRowSelect; 17 18 //對GridView的樣式設置 19 this.EditMode = DataGridViewEditMode.EditProgrammatically; 20 this.RowHeadersVisible = false; 21 this.CellBorderStyle = DataGridViewCellBorderStyle.RaisedHorizontal; 22 23 //對實例的部分成員賦值 24 isGroupping = false; 25 GroupRowTemplate = new GroupGridViewRow(); 26 } 27 28 public GroupGridViewRow CreateGroupGridViewRow(string title) 29 { 30 GroupGridViewRow group = this.GroupRowTemplate.Clone() as GroupGridViewRow; 31 if (group == null) 32 throw new NullReferenceException("組模板為空或者組模板類型不是GroupGridViewRow"); 33 group.Title = title; 34 group.ParentGridView = this; 35 group.CreateCells(this, group.Title); 36 this.Rows.Add(group); 37 //do 38 //{ 39 // group.Toggle(); 40 //} while (group.IsExpanded); 41 group.CollapseGroup(); 42 return group; 43 } 44 45 public new object DataSource 46 { 47 get 48 { 49 if (isGroupping) return objDataSource; 50 return base.DataSource; 51 } 52 set 53 { 54 if (string.IsNullOrEmpty(GroupFieldName) 55 || string.IsNullOrWhiteSpace(GroupFieldName)) 56 { 57 58 foreach (DataGridViewColumn col in this.Columns) 59 col.SortMode = DataGridViewColumnSortMode.Automatic; 60 base.DataSource = value; 61 isGroupping = false; 62 } 63 else 64 { 65 66 foreach (DataGridViewColumn col in this.Columns) 67 col.SortMode = DataGridViewColumnSortMode.NotSortable; 68 if (value is IEnumerable) 69 BindIEnumerableDataSource(value as IEnumerable); 70 else if (value is DataTable) 71 BindDataTableDataSouce(value as DataTable); 72 else if (value is DataSet) 73 BindDataSetDataSource(value as DataSet); 74 else 75 { 76 throw new NotImplementedException("不支持此類型作數據源"); 77 } 78 objDataSource = value; 79 isGroupping = true; 80 81 } 82 } 83 } 84 85 protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e) 86 { 87 if (e.RowIndex == -1 ||e.ColumnIndex==-1|| e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != 1) 88 return; 89 GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow; 90 if (group != null && group.IsIconHit(e)) 91 group.Toggle(); 92 base.OnCellMouseDown(e); 93 } 94 95 protected override void OnCellDoubleClick(DataGridViewCellEventArgs e) 96 { 97 if (e.RowIndex == -1||e.ColumnIndex==-1) 98 return; 99 GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow; 100 if (group != null ) 101 group.Toggle(); 102 base.OnCellDoubleClick(e); 103 } 104 105 private void BindIEnumerableDataSource(IEnumerable enumerable) 106 { 107 IEnumerable iends = enumerable; 108 if (iends == null) return; 109 Type dsItemType = null; 110 foreach (object item in iends) 111 dsItemType = item.GetType(); 112 if (iends == null) return; 113 114 PropertyInfo proInfo = dsItemType.GetProperty(GroupFieldName); 115 116 Dictionary<string, List<object>> groupDataSource = new Dictionary<string, List<object>>(); 117 foreach (object item in iends) 118 { 119 string tempStr = proInfo.GetValue(item, null).ToString(); 120 if (!groupDataSource.ContainsKey(tempStr)) 121 groupDataSource[tempStr] = new List<object>(); 122 groupDataSource[tempStr].Add(item); 123 } 124 125 List<string> colFildNames = new List<string>(this.Columns.Count); 126 foreach (DataGridViewColumn col in this.Columns) 127 colFildNames.Add(col.DataPropertyName); 128 GroupGridViewRow group = null; 129 List<object> datas = new List<object>(colFildNames.Count); 130 foreach (KeyValuePair<string, List<object>> gi in groupDataSource) 131 { 132 group = CreateGroupGridViewRow(gi.Key); 133 foreach (object celli in gi.Value) 134 { 135 foreach (string colName in colFildNames) 136 { 137 datas.Add(dsItemType.GetProperty(colName).GetValue(celli, null)); 138 } 139 group.AddRowToGroup(datas.ToArray()); 140 datas.Clear(); 141 } 142 //do 143 //{ 144 // group.Toggle(); 145 //} while (group.IsExpanded); 146 group.CollapseGroup(); 147 } 148 } 149 150 private void BindDataTableDataSouce(DataTable table) 151 { 152 Dictionary<string, List<DataRow>> groupDataSource = new Dictionary<string, List<DataRow>>(); 153 foreach (DataRow row in table.Rows) 154 { 155 string tempStr = row[GroupFieldName].ToString(); 156 if (!groupDataSource.ContainsKey(tempStr)) 157 groupDataSource[tempStr] = new List<DataRow>(); 158 groupDataSource[tempStr].Add(row); 159 } 160 161 List<string> colFildNames = new List<string>(this.Columns.Count); 162 foreach (DataGridViewColumn col in this.Columns) 163 colFildNames.Add(col.DataPropertyName); 164 GroupGridViewRow group = null; 165 List<object> datas = new List<object>(colFildNames.Count); 166 foreach (KeyValuePair<string, List<DataRow>> gi in groupDataSource) 167 { 168 group = CreateGroupGridViewRow(gi.Key); 169 foreach (DataRow celli in gi.Value) 170 { 171 foreach (string colName in colFildNames) 172 { 173 datas.Add(celli[colName]); 174 } 175 group.AddRowToGroup(datas.ToArray()); 176 datas.Clear(); 177 } 178 //do 179 //{ 180 // group.Toggle(); 181 //} while (group.IsExpanded); 182 group.CollapseGroup(); 183 } 184 } 185 186 private void BindDataSetDataSource(DataSet dataset) 187 { 188 if (dataset == null || dataset.Tables.Count == null) return; 189 BindDataTableDataSouce(dataset.Tables[0]); 190 } 191 } 192 193 public class GroupGridViewRow:DataGridViewRow 194 { 195 public bool IsExpanded { get;private set; } 196 197 public string Title { get; set; } 198 199 public GroupGridView ParentGridView { get; set; } 200 201 private List<DataGridViewRow> groupRows { get; set; } 202 203 public GroupGridViewRow() 204 { 205 IsExpanded = false; 206 groupRows = new List<DataGridViewRow>(); 207 } 208 209 public void ExpandGroup() 210 { 211 IsExpanded = true; 212 foreach (DataGridViewRow row in groupRows) 213 row.Visible = true; 214 } 215 216 public void CollapseGroup() 217 { 218 IsExpanded = false; 219 foreach (DataGridViewRow row in groupRows) 220 row.Visible = false; 221 } 222 223 public void Toggle() 224 { 225 if (IsExpanded) 226 CollapseGroup(); 227 else 228 ExpandGroup(); 229 } 230 231 public bool IsIconHit(DataGridViewCellMouseEventArgs e) 232 { 233 Rectangle groupBound = this.ParentGridView.GetRowDisplayRectangle(e.RowIndex, false); 234 235 if ( 236 e.X > groupBound.Left + 5 && 237 e.X < groupBound.Left + 15 && 238 e.Y > ((groupBound.Height - 1) - 5) / 2-2 && 239 e.Y < ((groupBound.Height - 1) - 5) / 2 + 10-2 240 ) 241 return true; 242 243 return false; 244 } 245 246 public DataGridViewRow AddRowToGroup(params object[] values) 247 { 248 DataGridViewRow row = this.ParentGridView.RowTemplate.Clone() as DataGridViewRow; 249 if (row == null) throw new NullReferenceException("行模板為空或者組模板類型不是DataGridViewRow"); 250 251 row.CreateCells(this.ParentGridView, values); 252 this.ParentGridView.Rows.Add(row); 253 this.groupRows.Add(row); 254 return row; 255 } 256 257 protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle rowBounds, int rowIndex, DataGridViewElementStates rowState, bool isFirstDisplayedRow, bool isLastVisibleRow) 258 { 259 260 int holdWidth = this.ParentGridView.Columns.GetColumnsWidth(DataGridViewElementStates.Visible); 261 Color backgroudColor; 262 if (this.Selected) 263 backgroudColor = this.ParentGridView.DefaultCellStyle.SelectionBackColor; 264 else 265 backgroudColor = this.ParentGridView.DefaultCellStyle.BackColor; 266 using (Brush backgroudBrush = new SolidBrush(backgroudColor)) 267 { 268 graphics.FillRectangle(backgroudBrush, rowBounds.X,rowBounds.Y,holdWidth,rowBounds.Height); 269 } 270 using (Brush bottomLineBrush=new SolidBrush(Color.FromKnownColor( KnownColor.GradientActiveCaption))) 271 { 272 graphics.FillRectangle(bottomLineBrush, rowBounds.Left, rowBounds.Top + rowBounds.Height-2, holdWidth, 2); 273 } 274 275 StringFormat sf = new StringFormat(); 276 sf.LineAlignment = StringAlignment.Center; 277 Font font = new Font(this.ParentGridView.Font, FontStyle.Bold); 278 graphics.DrawString(this.Title, font, Brushes.Black, 279 //rowBounds.Left+20,rowBounds.Top+rowBounds.Height/2,sf); 280 rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + 20, rowBounds.Top + rowBounds.Height / 2, sf); 281 282 283 int symbolX = rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + 5;//rowBounds.X + 5; 284 int symbolY =rowBounds.Y+ ((rowBounds.Height - 1) - 5) / 2-2; 285 if (Application.RenderWithVisualStyles) 286 { 287 288 VisualStyleRenderer glyphRenderer; 289 if (this.IsExpanded) 290 glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened); 291 else 292 glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed); 293 294 Rectangle glyphRectangle =new Rectangle(symbolX, symbolY, 10, 10); 295 296 glyphRenderer.DrawBackground(graphics, glyphRectangle); 297 298 } 299 else 300 { 301 int h = 8; 302 int w = 8; 303 int x = symbolX; 304 int y = symbolY; 305 graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h); 306 graphics.FillRectangle(new SolidBrush(Color.White), 307 x + 1, y + 1, w - 1, h - 1); 308 309 //畫橫線 310 graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), 311 x + 2, y + 4, x + w - 2, y + 4); 312 313 //畫豎線 314 if (!this.IsExpanded) 315 graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), 316 x + 4, y + 2, x + 4, y + h - 2); 317 } 318 } 319 }
補充部分
上面說的那個可分組GroupGridView有一個缺憾比較致命的,就是不能排序,剛好最近工作用上了,那有工作的壓力逼着就把那個排序的功能都加了上去了。
要加這排序的,GroupGridView和GroupGridVIewRow兩個類都要改,先介紹大概的思想,再分別介紹兩個類。
排序的時候不能按照往常那樣比較排序,因為在GridView里面含有顯示組名的,不包含數據,用它來排序會導致整個GridView會亂的。因此排序的時候需要讓各個組各自排列。
這時GroupGirdView最好就收集一下各個分組行,在排序的時候讓各個分組排列
多增加兩個字段
protected List<GroupGridViewRow> groupCollection; private SortOrder mySortOrder;
在綁定數據源的時候取消對各列的排列限制
//foreach (DataGridViewColumn col in this.Columns) // col.SortMode = DataGridViewColumnSortMode.NotSortable;
單擊單元格時也要開放一下,否則控件會忽略對行號為-1,就是列標題的處理
protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e) { if (e.RowIndex == -1 || e.ColumnIndex == -1 || e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != 1) { base.OnCellMouseDown(e); return; } //……… }
跟排序比較相關的就是重寫這個方法
public override void Sort(DataGridViewColumn dataGridViewColumn, System.ComponentModel.ListSortDirection direction) { //base.Sort(dataGridViewColumn, direction); foreach (GroupGridViewRow row in groupCollection) row.SortByProtery(dataGridViewColumn, mySortOrder); mySortOrder = mySortOrder == System.Windows.Forms.SortOrder.Ascending ? System.Windows.Forms.SortOrder.Descending : System.Windows.Forms.SortOrder.Ascending; }
到GroupGirdViewRow類了,這個類的改動就是按照上面的代碼那樣外放了一個方法,那方法就是對組內各行按升序或降序排序。
1 public void SortByProtery(DataGridViewColumn proteryName, SortOrder order) 2 { 3 int colIndex = this.ParentGridView.Columns.IndexOf(proteryName); 4 groupRows.Sort(new Comparison<DataGridViewRow>((r1, r2) => 5 { 6 7 object value1 = r1.Cells[proteryName.Name].Value; 8 object value2 = r2.Cells[proteryName.Name].Value; 9 10 if (order == SortOrder.Descending) return CompareDESC(value1, value2); 11 else return CompareASC(value1, value2); 12 13 })); 14 int groupIndex = this.ParentGridView.Rows.IndexOf(this); 15 foreach (DataGridViewRow row in groupRows) 16 { 17 this.ParentGridView.Rows.Remove(row); 18 this.ParentGridView.Rows.Insert(groupIndex + 1, row); 19 } 20 } 21 22 private int CompareASC(object obj1, object obj2) 23 { 24 decimal decTmep; 25 if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep)) 26 return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2)); 27 if (obj1 is DateTime && obj2 is DateTime) 28 return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2)); 29 return string.Compare(obj1.ToString(), obj2.ToString()); 30 } 31 32 private int CompareDESC(object obj1, object obj2) 33 { 34 decimal decTmep; 35 if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep)) 36 return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2)) * -1; 37 if (obj1 is DateTime && obj2 is DateTime) 38 return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2)) * -1; 39 return string.Compare(obj1.ToString(), obj2.ToString()) * -1; 40 }
其實還比較笨拙的,按自定義的排序方法對各個列在List里面拍完序之后就按照List里的新順序重新插入到GroupGridView里面。好了,排序的功能在這里草草介紹完了,下面則是新的控件代碼,除了增加了這個功能之外,還修復了一些小錯誤。

1 public class GroupGridView:DataGridView 2 { 3 public string GroupFieldName { get; set; } 4 5 protected object objDataSource; 6 7 public GroupGridViewRow GroupRowTemplate { get;protected set; } 8 9 private bool isGroupping; 10 11 protected List<GroupGridViewRow> groupCollection; 12 13 private SortOrder mySortOrder; 14 15 public GroupGridView() 16 { 17 //對GridView的操作的設置 18 this.AllowUserToAddRows = false; 19 this.AllowUserToDeleteRows = false; 20 this.SelectionMode = DataGridViewSelectionMode.FullRowSelect; 21 22 //對GridView的樣式設置 23 this.EditMode = DataGridViewEditMode.EditProgrammatically; 24 this.RowHeadersVisible = false; 25 this.CellBorderStyle = DataGridViewCellBorderStyle.RaisedHorizontal; 26 27 //對實例的部分成員賦值 28 isGroupping = false; 29 GroupRowTemplate = new GroupGridViewRow(); 30 groupCollection = new List<GroupGridViewRow>(); 31 } 32 33 public GroupGridViewRow CreateGroupGridViewRow(string title) 34 { 35 GroupGridViewRow group = this.GroupRowTemplate.Clone() as GroupGridViewRow; 36 if (group == null) 37 throw new NullReferenceException("組模板為空或者組模板類型不是GroupGridViewRow"); 38 group.Title = title; 39 group.ParentGridView = this; 40 group.CreateCells(this, group.Title); 41 this.Rows.Add(group); 42 //do 43 //{ 44 // group.Toggle(); 45 //} while (group.IsExpanded); 46 group.CollapseGroup(); 47 return group; 48 } 49 50 public new object DataSource 51 { 52 get 53 { 54 if (isGroupping) return objDataSource; 55 return base.DataSource; 56 } 57 set 58 { 59 if (string.IsNullOrEmpty(GroupFieldName) 60 || string.IsNullOrWhiteSpace(GroupFieldName)) 61 { 62 63 foreach (DataGridViewColumn col in this.Columns) 64 col.SortMode = DataGridViewColumnSortMode.Automatic; 65 base.DataSource = value; 66 isGroupping = false; 67 } 68 else 69 { 70 //2013-10-13增加排序功能所注釋 71 //foreach (DataGridViewColumn col in this.Columns) 72 // col.SortMode = DataGridViewColumnSortMode.NotSortable; 73 this.Rows.Clear(); 74 this.groupCollection.Clear(); 75 if (value is IEnumerable) 76 BindIEnumerableDataSource(value as IEnumerable); 77 else if (value is DataTable) 78 BindDataTableDataSouce(value as DataTable); 79 else if (value is DataSet) 80 BindDataSetDataSource(value as DataSet); 81 else 82 { 83 throw new NotImplementedException("不支持此類型作數據源"); 84 } 85 objDataSource = value; 86 isGroupping = true; 87 88 } 89 } 90 } 91 92 protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e) 93 { 94 if (e.RowIndex == -1 || e.ColumnIndex == -1 || e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != 1) 95 { 96 base.OnCellMouseDown(e); 97 return; 98 } 99 GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow; 100 if (group != null && group.IsIconHit(e)) 101 group.Toggle(); 102 base.OnCellMouseDown(e); 103 } 104 105 protected override void OnCellDoubleClick(DataGridViewCellEventArgs e) 106 { 107 if (e.RowIndex == -1||e.ColumnIndex==-1) 108 return; 109 GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow; 110 if (group != null ) 111 group.Toggle(); 112 base.OnCellDoubleClick(e); 113 } 114 115 public override void Sort(DataGridViewColumn dataGridViewColumn, System.ComponentModel.ListSortDirection direction) 116 { 117 //base.Sort(dataGridViewColumn, direction); 118 foreach (GroupGridViewRow row in groupCollection) 119 row.SortByProtery(dataGridViewColumn, mySortOrder); 120 mySortOrder = mySortOrder == System.Windows.Forms.SortOrder.Ascending ? 121 System.Windows.Forms.SortOrder.Descending : 122 System.Windows.Forms.SortOrder.Ascending; 123 } 124 125 private void BindIEnumerableDataSource(IEnumerable enumerable) 126 { 127 IEnumerable iends = enumerable; 128 if (iends == null) return; 129 Type dsItemType = null; 130 foreach (object item in iends) 131 dsItemType = item.GetType(); 132 if (iends == null) return; 133 134 PropertyInfo proInfo = dsItemType.GetProperty(GroupFieldName); 135 136 Dictionary<string, List<object>> groupDataSource = new Dictionary<string, List<object>>(); 137 foreach (object item in iends) 138 { 139 string tempStr = proInfo.GetValue(item, null).ToString(); 140 if (!groupDataSource.ContainsKey(tempStr)) 141 groupDataSource[tempStr] = new List<object>(); 142 groupDataSource[tempStr].Add(item); 143 } 144 145 List<string> colFildNames = new List<string>(this.Columns.Count); 146 foreach (DataGridViewColumn col in this.Columns) 147 colFildNames.Add(col.DataPropertyName); 148 GroupGridViewRow group = null; 149 List<object> datas = new List<object>(colFildNames.Count); 150 foreach (KeyValuePair<string, List<object>> gi in groupDataSource) 151 { 152 group = CreateGroupGridViewRow(gi.Key); 153 foreach (object celli in gi.Value) 154 { 155 foreach (string colName in colFildNames) 156 { 157 datas.Add(dsItemType.GetProperty(colName).GetValue(celli, null)); 158 } 159 group.AddRowToGroup(datas.ToArray()); 160 datas.Clear(); 161 } 162 //do 163 //{ 164 // group.Toggle(); 165 //} while (group.IsExpanded); 166 group.CollapseGroup(); 167 } 168 } 169 170 private void BindDataTableDataSouce(DataTable table) 171 { 172 Dictionary<string, List<DataRow>> groupDataSource = new Dictionary<string, List<DataRow>>(); 173 foreach (DataRow row in table.Rows) 174 { 175 string tempStr = row[GroupFieldName].ToString(); 176 if (!groupDataSource.ContainsKey(tempStr)) 177 groupDataSource[tempStr] = new List<DataRow>(); 178 groupDataSource[tempStr].Add(row); 179 } 180 181 List<string> colFildNames = new List<string>(this.Columns.Count); 182 foreach (DataGridViewColumn col in this.Columns) 183 colFildNames.Add(col.DataPropertyName); 184 GroupGridViewRow group = null; 185 List<object> datas = new List<object>(colFildNames.Count); 186 foreach (KeyValuePair<string, List<DataRow>> gi in groupDataSource) 187 { 188 group = CreateGroupGridViewRow(gi.Key); 189 foreach (DataRow celli in gi.Value) 190 { 191 foreach (string colName in colFildNames) 192 { 193 datas.Add(celli[colName]); 194 } 195 group.AddRowToGroup(datas.ToArray()); 196 datas.Clear(); 197 } 198 //do 199 //{ 200 // group.Toggle(); 201 //} while (group.IsExpanded); 202 group.CollapseGroup(); 203 } 204 } 205 206 private void BindDataSetDataSource(DataSet dataset) 207 { 208 if (dataset == null || dataset.Tables.Count == null) return; 209 BindDataTableDataSouce(dataset.Tables[0]); 210 } 211 } 212 213 public class GroupGridViewRow:DataGridViewRow 214 { 215 public bool IsExpanded { get;private set; } 216 217 public string Title { get; set; } 218 219 public GroupGridView ParentGridView { get; set; } 220 221 private List<DataGridViewRow> groupRows { get; set; } 222 223 public GroupGridViewRow() 224 { 225 IsExpanded = false; 226 groupRows = new List<DataGridViewRow>(); 227 } 228 229 public void ExpandGroup() 230 { 231 IsExpanded = true; 232 foreach (DataGridViewRow row in groupRows) 233 row.Visible = true; 234 } 235 236 public void CollapseGroup() 237 { 238 IsExpanded = false; 239 foreach (DataGridViewRow row in groupRows) 240 row.Visible = false; 241 } 242 243 public void Toggle() 244 { 245 if (IsExpanded) 246 CollapseGroup(); 247 else 248 ExpandGroup(); 249 } 250 251 public bool IsIconHit(DataGridViewCellMouseEventArgs e) 252 { 253 Rectangle groupBound = this.ParentGridView.GetRowDisplayRectangle(e.RowIndex, false); 254 255 if ( 256 e.X > groupBound.Left + 5 && 257 e.X < groupBound.Left + 15 && 258 e.Y > ((groupBound.Height - 1) - 5) / 2-2 && 259 e.Y < ((groupBound.Height - 1) - 5) / 2 + 10-2 260 ) 261 return true; 262 263 return false; 264 } 265 266 public void SortByProtery(DataGridViewColumn proteryName, SortOrder order) 267 { 268 int colIndex = this.ParentGridView.Columns.IndexOf(proteryName); 269 groupRows.Sort(new Comparison<DataGridViewRow>((r1, r2) => 270 { 271 272 object value1 = r1.Cells[proteryName.Name].Value; 273 object value2 = r2.Cells[proteryName.Name].Value; 274 //object value1 = r1.Cells[colIndex].Value; 275 //object value2 = r2.Cells[colIndex].Value; 276 277 if (order == SortOrder.Descending) return CompareDESC(value1, value2); 278 else return CompareASC(value1, value2); 279 280 })); 281 int groupIndex = this.ParentGridView.Rows.IndexOf(this); 282 foreach (DataGridViewRow row in groupRows) 283 { 284 this.ParentGridView.Rows.Remove(row); 285 this.ParentGridView.Rows.Insert(groupIndex + 1, row); 286 } 287 } 288 289 private int CompareASC(object obj1, object obj2) 290 { 291 //if (obj1 is decimal && obj2 is decimal)//2013-10-14 正確判斷數值類型 292 decimal decTmep; 293 if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep)) 294 return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2)); 295 if (obj1 is DateTime && obj2 is DateTime) 296 return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2)); 297 return string.Compare(obj1.ToString(), obj2.ToString()); 298 } 299 300 private int CompareDESC(object obj1, object obj2) 301 { 302 //if (obj1 is decimal && obj2 is decimal)//2013-10-14 正確判斷數值類型 303 decimal decTmep; 304 if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep)) 305 return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2)) * -1; 306 if (obj1 is DateTime && obj2 is DateTime) 307 return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2)) * -1; 308 return string.Compare(obj1.ToString(), obj2.ToString()) * -1; 309 } 310 311 public DataGridViewRow AddRowToGroup(params object[] values) 312 { 313 DataGridViewRow row = this.ParentGridView.RowTemplate.Clone() as DataGridViewRow; 314 if (row == null) throw new NullReferenceException("行模板為空或者組模板類型不是DataGridViewRow"); 315 316 row.CreateCells(this.ParentGridView, values); 317 this.ParentGridView.Rows.Add(row); 318 this.groupRows.Add(row); 319 return row; 320 } 321 322 protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle rowBounds, int rowIndex, DataGridViewElementStates rowState, bool isFirstDisplayedRow, bool isLastVisibleRow) 323 { 324 325 int holdWidth = this.ParentGridView.Columns.GetColumnsWidth(DataGridViewElementStates.Visible); 326 Color backgroudColor; 327 if (this.Selected) 328 backgroudColor = this.ParentGridView.DefaultCellStyle.SelectionBackColor; 329 else 330 backgroudColor = this.ParentGridView.DefaultCellStyle.BackColor; 331 using (Brush backgroudBrush = new SolidBrush(backgroudColor)) 332 { 333 graphics.FillRectangle(backgroudBrush, rowBounds.X,rowBounds.Y,holdWidth,rowBounds.Height); 334 } 335 using (Brush bottomLineBrush=new SolidBrush(Color.FromKnownColor( KnownColor.GradientActiveCaption))) 336 { 337 graphics.FillRectangle(bottomLineBrush, rowBounds.Left, rowBounds.Top + rowBounds.Height-2, holdWidth, 2); 338 } 339 340 StringFormat sf = new StringFormat(); 341 sf.LineAlignment = StringAlignment.Center; 342 Font font = new Font(this.ParentGridView.Font, FontStyle.Bold); 343 graphics.DrawString(this.Title, font, Brushes.Black, 344 //rowBounds.Left+20,rowBounds.Top+rowBounds.Height/2,sf); 345 rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + 20, rowBounds.Top + rowBounds.Height / 2, sf); 346 347 348 int symbolX = rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + 5;//rowBounds.X + 5; 349 int symbolY =rowBounds.Y+ ((rowBounds.Height - 1) - 5) / 2-2; 350 if (Application.RenderWithVisualStyles) 351 { 352 353 VisualStyleRenderer glyphRenderer; 354 if (this.IsExpanded) 355 glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened); 356 else 357 glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed); 358 359 Rectangle glyphRectangle =new Rectangle(symbolX, symbolY, 10, 10); 360 361 glyphRenderer.DrawBackground(graphics, glyphRectangle); 362 363 } 364 else 365 { 366 int h = 8; 367 int w = 8; 368 int x = symbolX; 369 int y = symbolY; 370 graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h); 371 graphics.FillRectangle(new SolidBrush(Color.White), 372 x + 1, y + 1, w - 1, h - 1); 373 374 //畫橫線 375 graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), 376 x + 2, y + 4, x + w - 2, y + 4); 377 378 //畫豎線 379 if (!this.IsExpanded) 380 graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), 381 x + 4, y + 2, x + 4, y + h - 2); 382 } 383 } 384 }