能分組的GridView


  有天在想工作上的事的時候,看着.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和GroupGridViewRow

 

補充部分

  上面說的那個可分組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     }
GroupGridView和GroupGridViewRow

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM