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