有天在想工作上的事的时候,看着.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进行一些设置,这些设置我都塞到了构造函数里面,大体上分三类,分别是操作设置,样式设置和部分属性或字段的赋值,代码如下

         public GroupGridView()
{
//对GridView的操作的设置
this.AllowUserToAddRows = false;
this.AllowUserToDeleteRows = false;
this.SelectionMode = DataGridViewSelectionMode.FullRowSelect; //对GridView的样式设置
this.EditMode = DataGridViewEditMode.EditProgrammatically;
this.RowHeadersVisible = false;
this.CellBorderStyle = DataGridViewCellBorderStyle.RaisedHorizontal; //对实例的部分成员赋值
isGroupping = false;
GroupRowTemplate = new GroupGridViewRow();
}

  其实GroupGridView要做的事就是能增加分组,在单击和双击分组行时作处理,还有就是数据绑定时把数据分组添加到GirdView里。那么就逐个方法来介绍

第一个是增加分组的

         public GroupGridViewRow CreateGroupGridViewRow(string title)
{
GroupGridViewRow group = this.GroupRowTemplate.Clone() as GroupGridViewRow;
if (group == null)
throw new NullReferenceException("组模板为空或者组模板类型不是GroupGridViewRow");
group.Title = title;
group.ParentGridView = this;
group.CreateCells(this, group.Title);
this.Rows.Add(group);
group.CollapseGroup();
return group;
}

主要是按照模板拷贝一个GroupGridViewRow的实例,设置相应的属性后就把它添加到GirdView里面。

然后到鼠标对分组行的点击事件,单击展开/折叠图标就展示或隐藏该组的数据。双击分组行同样也达到这种显示状态切换的效果。

         protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e)
{
if (e.RowIndex == - ||e.ColumnIndex==-|| e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != )
return;
GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow;
if (group != null && group.IsIconHit(e))
group.Toggle();
base.OnCellMouseDown(e);
} protected override void OnCellDoubleClick(DataGridViewCellEventArgs e)
{
if (e.RowIndex == -||e.ColumnIndex==-)
return;
GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow;
if (group != null )
group.Toggle();
base.OnCellDoubleClick(e);
}

Toggle和IsIconHit方法在GroupGridViewRow里会定义,在介绍GroupGridViewRow会列出该方法的定义,在单击时就要通过IsIconHit判断鼠标是否点击到了图片,当然是点击中图标才会经行状态切换,而双击则不需要了。无论是双击和单击都要判断当前鼠标所在的行是分组行,如果是数据行的话就不会作任何操作了。

为了方便点绑定到数据,我重新定义了控件的DataSource属性。当分组字段信息(GroupFieldName属性)不存在时就会使用GridView默认的绑定数据,如果存在就会分组筛选出数据,然后逐个组去把行添加进去。

         public new object DataSource
{
get
{
if (isGroupping) return objDataSource;
return base.DataSource;
}
set
{
if (string.IsNullOrEmpty(GroupFieldName)
|| string.IsNullOrWhiteSpace(GroupFieldName))
{ foreach (DataGridViewColumn col in this.Columns)
col.SortMode = DataGridViewColumnSortMode.Automatic;
base.DataSource = value;
isGroupping = false;
}
else
{ foreach (DataGridViewColumn col in this.Columns)
col.SortMode = DataGridViewColumnSortMode.NotSortable;
if (value is IEnumerable)
BindIEnumerableDataSource(value as IEnumerable);
else if (value is DataTable)
BindDataTableDataSouce(value as DataTable);
else if (value is DataSet)
BindDataSetDataSource(value as DataSet);
else
{
throw new NotImplementedException("不支持此类型作数据源");
}
objDataSource = value;
isGroupping = true; }
}
}

现在能自动绑定的数据源只能是可枚举的或DataTable,DataSet其实是把里面第一个DataTable作为数据源而已。不同类型的数据有相应的处理方法

         private void BindIEnumerableDataSource(IEnumerable enumerable)
{
IEnumerable iends = enumerable;
if (iends == null) return;
Type dsItemType = null;
foreach (object item in iends)
dsItemType = item.GetType();
if (iends == null) return; PropertyInfo proInfo = dsItemType.GetProperty(GroupFieldName); Dictionary<string, List<object>> groupDataSource = new Dictionary<string, List<object>>();
foreach (object item in iends)
{
string tempStr = proInfo.GetValue(item, null).ToString();
if (!groupDataSource.ContainsKey(tempStr))
groupDataSource[tempStr] = new List<object>();
groupDataSource[tempStr].Add(item);
} List<string> colFildNames = new List<string>(this.Columns.Count);
foreach (DataGridViewColumn col in this.Columns)
colFildNames.Add(col.DataPropertyName);
GroupGridViewRow group = null;
List<object> datas = new List<object>(colFildNames.Count);
foreach (KeyValuePair<string, List<object>> gi in groupDataSource)
{
group = CreateGroupGridViewRow(gi.Key);
foreach (object celli in gi.Value)
{
foreach (string colName in colFildNames)
{
datas.Add(dsItemType.GetProperty(colName).GetValue(celli, null));
}
group.AddRowToGroup(datas.ToArray());
datas.Clear();
}
group.CollapseGroup();
}
}

对于可枚举的数据源,我就通过反射把相应的属性的值都拿出来填到DataGridViewRow里面。

         private void BindDataTableDataSouce(DataTable table)
{
Dictionary<string, List<DataRow>> groupDataSource = new Dictionary<string, List<DataRow>>();
foreach (DataRow row in table.Rows)
{
string tempStr = row[GroupFieldName].ToString();
if (!groupDataSource.ContainsKey(tempStr))
groupDataSource[tempStr] = new List<DataRow>();
groupDataSource[tempStr].Add(row);
} List<string> colFildNames = new List<string>(this.Columns.Count);
foreach (DataGridViewColumn col in this.Columns)
colFildNames.Add(col.DataPropertyName);
GroupGridViewRow group = null;
List<object> datas = new List<object>(colFildNames.Count);
foreach (KeyValuePair<string, List<DataRow>> gi in groupDataSource)
{
group = CreateGroupGridViewRow(gi.Key);
foreach (DataRow celli in gi.Value)
{
foreach (string colName in colFildNames)
{
datas.Add(celli[colName]);
}
group.AddRowToGroup(datas.ToArray());
datas.Clear();
}
group.CollapseGroup();
}
}

DataTable的绑定也类似,更方便的是DataTable不需要用反射了,直接通过行列的访问就可以了。

GruopGridView介绍就到此结束,下面则介绍另一个类GroupGridViewRow,它是继承DataGridViewRow。而GDI+的描绘都是在这个方法里面。也是先看看里面的成员

成员名称

数据类型

修饰符

描述

IsExpanded

bool

public

分组的状态,是展开还是折叠

Title

string

public

分组的标题,通常是分组的数据

ParentGridView

GroupGridView

public

分组行所在的GridView

groupRows

List<DataGridViewRow>

private

此分组包含的数据行

  那么一个GroupGridViewRow的要处理的事就有以下几个,对分组下的数据行的状态控制,判断鼠标单击是否命中图标,增加一行数据到该分组下,还有最重要的就是描绘出分组行的外观。

先列举数据行的状态控制方法,ExpandGroup()展开分组,CollapseGroup()折叠分组,还有Toggle()切换

         public void ExpandGroup()
{
IsExpanded = true;
foreach (DataGridViewRow row in groupRows)
row.Visible = true;
} public void CollapseGroup()
{
IsExpanded = false;
foreach (DataGridViewRow row in groupRows)
row.Visible = false;
} public void Toggle()
{
if (IsExpanded)
CollapseGroup();
else
ExpandGroup();
}

实际上就是通过遍历groupRows集合,改变其显示状态而已。

判断单击图标的方法如下

         public bool IsIconHit(DataGridViewCellMouseEventArgs e)
{
Rectangle groupBound = this.ParentGridView.GetRowDisplayRectangle(e.RowIndex, false); if (
e.X > groupBound.Left + &&
e.X < groupBound.Left + &&
e.Y > ((groupBound.Height - ) - ) / - &&
e.Y < ((groupBound.Height - ) - ) / + -
)
return true; return false;
}

主要是通过鼠标指针当前所在的坐标是不是在图标的范围以内,那个范围有点不好把握,要通过DataGridView的GetRowDisplayRectangle方法得到分组行的矩形,Left,X这两个属性有点分不清了。

增加数据行的方法如下

         public DataGridViewRow AddRowToGroup(params object[] values)
{
DataGridViewRow row = this.ParentGridView.RowTemplate.Clone() as DataGridViewRow;
if (row == null) throw new NullReferenceException("行模板为空或者组模板类型不是DataGridViewRow"); row.CreateCells(this.ParentGridView, values);
this.ParentGridView.Rows.Add(row);
this.groupRows.Add(row);
return row;
}

也复杂,通过DataGridView的普通数据行(不是分组行)的目标拷贝,填上数据,然后分别添加到这个分组的数据行集合中和GridView中。

最后到描绘分组行的方法,重写Paint方法,这个基本上是参考园友老虎哥的代码的,也作了一些小的调整,解决了水平滚动之后文字和图标有重影的问题,所以老虎哥看见了不要怪哈!

         protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle rowBounds, int rowIndex, DataGridViewElementStates rowState, bool isFirstDisplayedRow, bool isLastVisibleRow)
{ int holdWidth = this.ParentGridView.Columns.GetColumnsWidth(DataGridViewElementStates.Visible);
Color backgroudColor;
if (this.Selected)
backgroudColor = this.ParentGridView.DefaultCellStyle.SelectionBackColor;
else
backgroudColor = this.ParentGridView.DefaultCellStyle.BackColor;
using (Brush backgroudBrush = new SolidBrush(backgroudColor))
{
graphics.FillRectangle(backgroudBrush, rowBounds.X,rowBounds.Y,holdWidth,rowBounds.Height);
}
using (Brush bottomLineBrush=new SolidBrush(Color.FromKnownColor( KnownColor.GradientActiveCaption)))
{
graphics.FillRectangle(bottomLineBrush, rowBounds.Left, rowBounds.Top + rowBounds.Height-, holdWidth, );
} StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Center;
Font font = new Font(this.ParentGridView.Font, FontStyle.Bold);
graphics.DrawString(this.Title, font, Brushes.Black,
rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + , rowBounds.Top + rowBounds.Height / , sf); int symbolX = rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + ;
int symbolY =rowBounds.Y+ ((rowBounds.Height - ) - ) / -;
if (Application.RenderWithVisualStyles)
{ VisualStyleRenderer glyphRenderer;
if (this.IsExpanded)
glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
else
glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed); Rectangle glyphRectangle =new Rectangle(symbolX, symbolY, , ); glyphRenderer.DrawBackground(graphics, glyphRectangle); }
else
{
int h = ;
int w = ;
int x = symbolX;
int y = symbolY;
graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h);
graphics.FillRectangle(new SolidBrush(Color.White),
x + , y + , w - , h - ); //画横线
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + , y + , x + w - , y + ); //画竖线
if (!this.IsExpanded)
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + , y + , x + , y + h - );
}
}

我自己写出来的话还是有点棘手的,绘制对于我自己而言就分了三部分,分组栏的背景边线描绘,文字的填写,还有图标的描绘,而图标在这里很好的考虑那个显示效果,分了渐变效果和单色效果。我也该多学习学习。

最后列一下控件的使用,列的设置分组设置,数据绑定如下,CreateColumn是我定义的方法,主要是构造一个新的列,对其进行设置之后就添加到GridView里面。

             groupGridView21.GroupFieldName = "name";

             CreateColumn("id", "id", groupGridView21);
CreateColumn("invdate", "invdate", groupGridView21);
CreateColumn("note", "note", groupGridView21);
CreateColumn("amount", "amount", groupGridView21);
CreateColumn("tax", "tax", groupGridView21);
CreateColumn("total", "total", groupGridView21);
CreateColumn("name", "name", groupGridView21); groupGridView21.DataSource = datasource;

效果就这样,数据我完全拿了那个jqGrid的Demo里面的数据

控件的缺点还是跟老虎哥的控件类似,要把显示的列逐个添加,不支持自动添加列,并且分组显示的时候不能对列经行排序。最后附上控件的完整源码,介绍完毕,谢谢!

     public class GroupGridView:DataGridView
{
public string GroupFieldName { get; set; } protected object objDataSource; public GroupGridViewRow GroupRowTemplate { get;protected set; } private bool isGroupping; public GroupGridView()
{
//对GridView的操作的设置
this.AllowUserToAddRows = false;
this.AllowUserToDeleteRows = false;
this.SelectionMode = DataGridViewSelectionMode.FullRowSelect; //对GridView的样式设置
this.EditMode = DataGridViewEditMode.EditProgrammatically;
this.RowHeadersVisible = false;
this.CellBorderStyle = DataGridViewCellBorderStyle.RaisedHorizontal; //对实例的部分成员赋值
isGroupping = false;
GroupRowTemplate = new GroupGridViewRow();
} public GroupGridViewRow CreateGroupGridViewRow(string title)
{
GroupGridViewRow group = this.GroupRowTemplate.Clone() as GroupGridViewRow;
if (group == null)
throw new NullReferenceException("组模板为空或者组模板类型不是GroupGridViewRow");
group.Title = title;
group.ParentGridView = this;
group.CreateCells(this, group.Title);
this.Rows.Add(group);
//do
//{
// group.Toggle();
//} while (group.IsExpanded);
group.CollapseGroup();
return group;
} public new object DataSource
{
get
{
if (isGroupping) return objDataSource;
return base.DataSource;
}
set
{
if (string.IsNullOrEmpty(GroupFieldName)
|| string.IsNullOrWhiteSpace(GroupFieldName))
{ foreach (DataGridViewColumn col in this.Columns)
col.SortMode = DataGridViewColumnSortMode.Automatic;
base.DataSource = value;
isGroupping = false;
}
else
{ foreach (DataGridViewColumn col in this.Columns)
col.SortMode = DataGridViewColumnSortMode.NotSortable;
if (value is IEnumerable)
BindIEnumerableDataSource(value as IEnumerable);
else if (value is DataTable)
BindDataTableDataSouce(value as DataTable);
else if (value is DataSet)
BindDataSetDataSource(value as DataSet);
else
{
throw new NotImplementedException("不支持此类型作数据源");
}
objDataSource = value;
isGroupping = true; }
}
} protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e)
{
if (e.RowIndex == - ||e.ColumnIndex==-|| e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != )
return;
GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow;
if (group != null && group.IsIconHit(e))
group.Toggle();
base.OnCellMouseDown(e);
} protected override void OnCellDoubleClick(DataGridViewCellEventArgs e)
{
if (e.RowIndex == -||e.ColumnIndex==-)
return;
GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow;
if (group != null )
group.Toggle();
base.OnCellDoubleClick(e);
} private void BindIEnumerableDataSource(IEnumerable enumerable)
{
IEnumerable iends = enumerable;
if (iends == null) return;
Type dsItemType = null;
foreach (object item in iends)
dsItemType = item.GetType();
if (iends == null) return; PropertyInfo proInfo = dsItemType.GetProperty(GroupFieldName); Dictionary<string, List<object>> groupDataSource = new Dictionary<string, List<object>>();
foreach (object item in iends)
{
string tempStr = proInfo.GetValue(item, null).ToString();
if (!groupDataSource.ContainsKey(tempStr))
groupDataSource[tempStr] = new List<object>();
groupDataSource[tempStr].Add(item);
} List<string> colFildNames = new List<string>(this.Columns.Count);
foreach (DataGridViewColumn col in this.Columns)
colFildNames.Add(col.DataPropertyName);
GroupGridViewRow group = null;
List<object> datas = new List<object>(colFildNames.Count);
foreach (KeyValuePair<string, List<object>> gi in groupDataSource)
{
group = CreateGroupGridViewRow(gi.Key);
foreach (object celli in gi.Value)
{
foreach (string colName in colFildNames)
{
datas.Add(dsItemType.GetProperty(colName).GetValue(celli, null));
}
group.AddRowToGroup(datas.ToArray());
datas.Clear();
}
//do
//{
// group.Toggle();
//} while (group.IsExpanded);
group.CollapseGroup();
}
} private void BindDataTableDataSouce(DataTable table)
{
Dictionary<string, List<DataRow>> groupDataSource = new Dictionary<string, List<DataRow>>();
foreach (DataRow row in table.Rows)
{
string tempStr = row[GroupFieldName].ToString();
if (!groupDataSource.ContainsKey(tempStr))
groupDataSource[tempStr] = new List<DataRow>();
groupDataSource[tempStr].Add(row);
} List<string> colFildNames = new List<string>(this.Columns.Count);
foreach (DataGridViewColumn col in this.Columns)
colFildNames.Add(col.DataPropertyName);
GroupGridViewRow group = null;
List<object> datas = new List<object>(colFildNames.Count);
foreach (KeyValuePair<string, List<DataRow>> gi in groupDataSource)
{
group = CreateGroupGridViewRow(gi.Key);
foreach (DataRow celli in gi.Value)
{
foreach (string colName in colFildNames)
{
datas.Add(celli[colName]);
}
group.AddRowToGroup(datas.ToArray());
datas.Clear();
}
//do
//{
// group.Toggle();
//} while (group.IsExpanded);
group.CollapseGroup();
}
} private void BindDataSetDataSource(DataSet dataset)
{
if (dataset == null || dataset.Tables.Count == null) return;
BindDataTableDataSouce(dataset.Tables[]);
}
} public class GroupGridViewRow:DataGridViewRow
{
public bool IsExpanded { get;private set; } public string Title { get; set; } public GroupGridView ParentGridView { get; set; } private List<DataGridViewRow> groupRows { get; set; } public GroupGridViewRow()
{
IsExpanded = false;
groupRows = new List<DataGridViewRow>();
} public void ExpandGroup()
{
IsExpanded = true;
foreach (DataGridViewRow row in groupRows)
row.Visible = true;
} public void CollapseGroup()
{
IsExpanded = false;
foreach (DataGridViewRow row in groupRows)
row.Visible = false;
} public void Toggle()
{
if (IsExpanded)
CollapseGroup();
else
ExpandGroup();
} public bool IsIconHit(DataGridViewCellMouseEventArgs e)
{
Rectangle groupBound = this.ParentGridView.GetRowDisplayRectangle(e.RowIndex, false); if (
e.X > groupBound.Left + &&
e.X < groupBound.Left + &&
e.Y > ((groupBound.Height - ) - ) / - &&
e.Y < ((groupBound.Height - ) - ) / + -
)
return true; return false;
} public DataGridViewRow AddRowToGroup(params object[] values)
{
DataGridViewRow row = this.ParentGridView.RowTemplate.Clone() as DataGridViewRow;
if (row == null) throw new NullReferenceException("行模板为空或者组模板类型不是DataGridViewRow"); row.CreateCells(this.ParentGridView, values);
this.ParentGridView.Rows.Add(row);
this.groupRows.Add(row);
return row;
} protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle rowBounds, int rowIndex, DataGridViewElementStates rowState, bool isFirstDisplayedRow, bool isLastVisibleRow)
{ int holdWidth = this.ParentGridView.Columns.GetColumnsWidth(DataGridViewElementStates.Visible);
Color backgroudColor;
if (this.Selected)
backgroudColor = this.ParentGridView.DefaultCellStyle.SelectionBackColor;
else
backgroudColor = this.ParentGridView.DefaultCellStyle.BackColor;
using (Brush backgroudBrush = new SolidBrush(backgroudColor))
{
graphics.FillRectangle(backgroudBrush, rowBounds.X,rowBounds.Y,holdWidth,rowBounds.Height);
}
using (Brush bottomLineBrush=new SolidBrush(Color.FromKnownColor( KnownColor.GradientActiveCaption)))
{
graphics.FillRectangle(bottomLineBrush, rowBounds.Left, rowBounds.Top + rowBounds.Height-, holdWidth, );
} StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Center;
Font font = new Font(this.ParentGridView.Font, FontStyle.Bold);
graphics.DrawString(this.Title, font, Brushes.Black,
//rowBounds.Left+20,rowBounds.Top+rowBounds.Height/2,sf);
rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + , rowBounds.Top + rowBounds.Height / , sf); int symbolX = rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + ;//rowBounds.X + 5;
int symbolY =rowBounds.Y+ ((rowBounds.Height - ) - ) / -;
if (Application.RenderWithVisualStyles)
{ VisualStyleRenderer glyphRenderer;
if (this.IsExpanded)
glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
else
glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed); Rectangle glyphRectangle =new Rectangle(symbolX, symbolY, , ); glyphRenderer.DrawBackground(graphics, glyphRectangle); }
else
{
int h = ;
int w = ;
int x = symbolX;
int y = symbolY;
graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h);
graphics.FillRectangle(new SolidBrush(Color.White),
x + , y + , w - , h - ); //画横线
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + , y + , x + w - , y + ); //画竖线
if (!this.IsExpanded)
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + , y + , x + , y + h - );
}
}
}

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 == - || e.ColumnIndex == - || e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != )
{
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类了,这个类的改动就是按照上面的代码那样外放了一个方法,那方法就是对组内各行按升序或降序排序。

         public void SortByProtery(DataGridViewColumn proteryName, SortOrder order)
{
int colIndex = this.ParentGridView.Columns.IndexOf(proteryName);
groupRows.Sort(new Comparison<DataGridViewRow>((r1, r2) =>
{ object value1 = r1.Cells[proteryName.Name].Value;
object value2 = r2.Cells[proteryName.Name].Value; if (order == SortOrder.Descending) return CompareDESC(value1, value2);
else return CompareASC(value1, value2); }));
int groupIndex = this.ParentGridView.Rows.IndexOf(this);
foreach (DataGridViewRow row in groupRows)
{
this.ParentGridView.Rows.Remove(row);
this.ParentGridView.Rows.Insert(groupIndex + , row);
}
} private int CompareASC(object obj1, object obj2)
{
decimal decTmep;
if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep))
return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2));
if (obj1 is DateTime && obj2 is DateTime)
return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2));
return string.Compare(obj1.ToString(), obj2.ToString());
} private int CompareDESC(object obj1, object obj2)
{
decimal decTmep;
if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep))
return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2)) * -;
if (obj1 is DateTime && obj2 is DateTime)
return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2)) * -;
return string.Compare(obj1.ToString(), obj2.ToString()) * -;
}

  其实还比较笨拙的,按自定义的排序方法对各个列在List里面拍完序之后就按照List里的新顺序重新插入到GroupGridView里面。好了,排序的功能在这里草草介绍完了,下面则是新的控件代码,除了增加了这个功能之外,还修复了一些小错误。

     public class GroupGridView:DataGridView
{
public string GroupFieldName { get; set; } protected object objDataSource; public GroupGridViewRow GroupRowTemplate { get;protected set; } private bool isGroupping; protected List<GroupGridViewRow> groupCollection; private SortOrder mySortOrder; public GroupGridView()
{
//对GridView的操作的设置
this.AllowUserToAddRows = false;
this.AllowUserToDeleteRows = false;
this.SelectionMode = DataGridViewSelectionMode.FullRowSelect; //对GridView的样式设置
this.EditMode = DataGridViewEditMode.EditProgrammatically;
this.RowHeadersVisible = false;
this.CellBorderStyle = DataGridViewCellBorderStyle.RaisedHorizontal; //对实例的部分成员赋值
isGroupping = false;
GroupRowTemplate = new GroupGridViewRow();
groupCollection = new List<GroupGridViewRow>();
} public GroupGridViewRow CreateGroupGridViewRow(string title)
{
GroupGridViewRow group = this.GroupRowTemplate.Clone() as GroupGridViewRow;
if (group == null)
throw new NullReferenceException("组模板为空或者组模板类型不是GroupGridViewRow");
group.Title = title;
group.ParentGridView = this;
group.CreateCells(this, group.Title);
this.Rows.Add(group);
//do
//{
// group.Toggle();
//} while (group.IsExpanded);
group.CollapseGroup();
return group;
} public new object DataSource
{
get
{
if (isGroupping) return objDataSource;
return base.DataSource;
}
set
{
if (string.IsNullOrEmpty(GroupFieldName)
|| string.IsNullOrWhiteSpace(GroupFieldName))
{ foreach (DataGridViewColumn col in this.Columns)
col.SortMode = DataGridViewColumnSortMode.Automatic;
base.DataSource = value;
isGroupping = false;
}
else
{
//2013-10-13增加排序功能所注释
//foreach (DataGridViewColumn col in this.Columns)
// col.SortMode = DataGridViewColumnSortMode.NotSortable;
this.Rows.Clear();
this.groupCollection.Clear();
if (value is IEnumerable)
BindIEnumerableDataSource(value as IEnumerable);
else if (value is DataTable)
BindDataTableDataSouce(value as DataTable);
else if (value is DataSet)
BindDataSetDataSource(value as DataSet);
else
{
throw new NotImplementedException("不支持此类型作数据源");
}
objDataSource = value;
isGroupping = true; }
}
} protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e)
{
if (e.RowIndex == - || e.ColumnIndex == - || e.Button != System.Windows.Forms.MouseButtons.Left || e.Clicks != )
{
base.OnCellMouseDown(e);
return;
}
GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow;
if (group != null && group.IsIconHit(e))
group.Toggle();
base.OnCellMouseDown(e);
} protected override void OnCellDoubleClick(DataGridViewCellEventArgs e)
{
if (e.RowIndex == -||e.ColumnIndex==-)
return;
GroupGridViewRow group = this.Rows[e.RowIndex] as GroupGridViewRow;
if (group != null )
group.Toggle();
base.OnCellDoubleClick(e);
} 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;
} private void BindIEnumerableDataSource(IEnumerable enumerable)
{
IEnumerable iends = enumerable;
if (iends == null) return;
Type dsItemType = null;
foreach (object item in iends)
dsItemType = item.GetType();
if (iends == null) return; PropertyInfo proInfo = dsItemType.GetProperty(GroupFieldName); Dictionary<string, List<object>> groupDataSource = new Dictionary<string, List<object>>();
foreach (object item in iends)
{
string tempStr = proInfo.GetValue(item, null).ToString();
if (!groupDataSource.ContainsKey(tempStr))
groupDataSource[tempStr] = new List<object>();
groupDataSource[tempStr].Add(item);
} List<string> colFildNames = new List<string>(this.Columns.Count);
foreach (DataGridViewColumn col in this.Columns)
colFildNames.Add(col.DataPropertyName);
GroupGridViewRow group = null;
List<object> datas = new List<object>(colFildNames.Count);
foreach (KeyValuePair<string, List<object>> gi in groupDataSource)
{
group = CreateGroupGridViewRow(gi.Key);
foreach (object celli in gi.Value)
{
foreach (string colName in colFildNames)
{
datas.Add(dsItemType.GetProperty(colName).GetValue(celli, null));
}
group.AddRowToGroup(datas.ToArray());
datas.Clear();
}
//do
//{
// group.Toggle();
//} while (group.IsExpanded);
group.CollapseGroup();
}
} private void BindDataTableDataSouce(DataTable table)
{
Dictionary<string, List<DataRow>> groupDataSource = new Dictionary<string, List<DataRow>>();
foreach (DataRow row in table.Rows)
{
string tempStr = row[GroupFieldName].ToString();
if (!groupDataSource.ContainsKey(tempStr))
groupDataSource[tempStr] = new List<DataRow>();
groupDataSource[tempStr].Add(row);
} List<string> colFildNames = new List<string>(this.Columns.Count);
foreach (DataGridViewColumn col in this.Columns)
colFildNames.Add(col.DataPropertyName);
GroupGridViewRow group = null;
List<object> datas = new List<object>(colFildNames.Count);
foreach (KeyValuePair<string, List<DataRow>> gi in groupDataSource)
{
group = CreateGroupGridViewRow(gi.Key);
foreach (DataRow celli in gi.Value)
{
foreach (string colName in colFildNames)
{
datas.Add(celli[colName]);
}
group.AddRowToGroup(datas.ToArray());
datas.Clear();
}
//do
//{
// group.Toggle();
//} while (group.IsExpanded);
group.CollapseGroup();
}
} private void BindDataSetDataSource(DataSet dataset)
{
if (dataset == null || dataset.Tables.Count == null) return;
BindDataTableDataSouce(dataset.Tables[]);
}
} public class GroupGridViewRow:DataGridViewRow
{
public bool IsExpanded { get;private set; } public string Title { get; set; } public GroupGridView ParentGridView { get; set; } private List<DataGridViewRow> groupRows { get; set; } public GroupGridViewRow()
{
IsExpanded = false;
groupRows = new List<DataGridViewRow>();
} public void ExpandGroup()
{
IsExpanded = true;
foreach (DataGridViewRow row in groupRows)
row.Visible = true;
} public void CollapseGroup()
{
IsExpanded = false;
foreach (DataGridViewRow row in groupRows)
row.Visible = false;
} public void Toggle()
{
if (IsExpanded)
CollapseGroup();
else
ExpandGroup();
} public bool IsIconHit(DataGridViewCellMouseEventArgs e)
{
Rectangle groupBound = this.ParentGridView.GetRowDisplayRectangle(e.RowIndex, false); if (
e.X > groupBound.Left + &&
e.X < groupBound.Left + &&
e.Y > ((groupBound.Height - ) - ) / - &&
e.Y < ((groupBound.Height - ) - ) / + -
)
return true; return false;
} public void SortByProtery(DataGridViewColumn proteryName, SortOrder order)
{
int colIndex = this.ParentGridView.Columns.IndexOf(proteryName);
groupRows.Sort(new Comparison<DataGridViewRow>((r1, r2) =>
{ object value1 = r1.Cells[proteryName.Name].Value;
object value2 = r2.Cells[proteryName.Name].Value;
//object value1 = r1.Cells[colIndex].Value;
//object value2 = r2.Cells[colIndex].Value; if (order == SortOrder.Descending) return CompareDESC(value1, value2);
else return CompareASC(value1, value2); }));
int groupIndex = this.ParentGridView.Rows.IndexOf(this);
foreach (DataGridViewRow row in groupRows)
{
this.ParentGridView.Rows.Remove(row);
this.ParentGridView.Rows.Insert(groupIndex + , row);
}
} private int CompareASC(object obj1, object obj2)
{
//if (obj1 is decimal && obj2 is decimal)//2013-10-14 正确判断数值类型
decimal decTmep;
if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep))
return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2));
if (obj1 is DateTime && obj2 is DateTime)
return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2));
return string.Compare(obj1.ToString(), obj2.ToString());
} private int CompareDESC(object obj1, object obj2)
{
//if (obj1 is decimal && obj2 is decimal)//2013-10-14 正确判断数值类型
decimal decTmep;
if (decimal.TryParse(obj1.ToString(), out decTmep) && decimal.TryParse(obj2.ToString(), out decTmep))
return decimal.Compare(Convert.ToDecimal(obj1), Convert.ToDecimal(obj2)) * -;
if (obj1 is DateTime && obj2 is DateTime)
return DateTime.Compare(Convert.ToDateTime(obj1), Convert.ToDateTime(obj2)) * -;
return string.Compare(obj1.ToString(), obj2.ToString()) * -;
} public DataGridViewRow AddRowToGroup(params object[] values)
{
DataGridViewRow row = this.ParentGridView.RowTemplate.Clone() as DataGridViewRow;
if (row == null) throw new NullReferenceException("行模板为空或者组模板类型不是DataGridViewRow"); row.CreateCells(this.ParentGridView, values);
this.ParentGridView.Rows.Add(row);
this.groupRows.Add(row);
return row;
} protected override void Paint(System.Drawing.Graphics graphics, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle rowBounds, int rowIndex, DataGridViewElementStates rowState, bool isFirstDisplayedRow, bool isLastVisibleRow)
{ int holdWidth = this.ParentGridView.Columns.GetColumnsWidth(DataGridViewElementStates.Visible);
Color backgroudColor;
if (this.Selected)
backgroudColor = this.ParentGridView.DefaultCellStyle.SelectionBackColor;
else
backgroudColor = this.ParentGridView.DefaultCellStyle.BackColor;
using (Brush backgroudBrush = new SolidBrush(backgroudColor))
{
graphics.FillRectangle(backgroudBrush, rowBounds.X,rowBounds.Y,holdWidth,rowBounds.Height);
}
using (Brush bottomLineBrush=new SolidBrush(Color.FromKnownColor( KnownColor.GradientActiveCaption)))
{
graphics.FillRectangle(bottomLineBrush, rowBounds.Left, rowBounds.Top + rowBounds.Height-, holdWidth, );
} StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Center;
Font font = new Font(this.ParentGridView.Font, FontStyle.Bold);
graphics.DrawString(this.Title, font, Brushes.Black,
//rowBounds.Left+20,rowBounds.Top+rowBounds.Height/2,sf);
rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + , rowBounds.Top + rowBounds.Height / , sf); int symbolX = rowBounds.Left - this.ParentGridView.HorizontalScrollingOffset + ;//rowBounds.X + 5;
int symbolY =rowBounds.Y+ ((rowBounds.Height - ) - ) / -;
if (Application.RenderWithVisualStyles)
{ VisualStyleRenderer glyphRenderer;
if (this.IsExpanded)
glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
else
glyphRenderer = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed); Rectangle glyphRectangle =new Rectangle(symbolX, symbolY, , ); glyphRenderer.DrawBackground(graphics, glyphRectangle); }
else
{
int h = ;
int w = ;
int x = symbolX;
int y = symbolY;
graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h);
graphics.FillRectangle(new SolidBrush(Color.White),
x + , y + , w - , h - ); //画横线
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + , y + , x + w - , y + ); //画竖线
if (!this.IsExpanded)
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + , y + , x + , y + h - );
}
}
}

GroupGridView和GroupGridViewRow

能分组的GridView的更多相关文章

  1. Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果

    版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/1873 ...

  2. Windows 8实例教程系列 - 数据绑定高级实例

    原文:Windows 8实例教程系列 - 数据绑定高级实例 上篇Windows 8实例教程系列 - 数据绑定基础实例中,介绍Windows 8应用开发数据绑定基础,其中包括一些简单的数据绑定控件的使用 ...

  3. 扩展GridView控件——为内容项添加拖放及分组功能

    引言 相信大家对GridView都不陌生,是非常有用的控件,用于平铺有序的显示多个内容项.打开任何WinRT应用或者是微软合作商的网站,都会在APP中发现GridView的使用.“Tiles”提供了一 ...

  4. Group GridView:用于.Net的分组显示的GridView

    我的项目需要一个可以分组显示的GridView,我不会写,上网找了一圈,最终在国外的网站上找到的这个,比较符合我的要求,但它的分页得重写,它写了能分页,但我发现它的分页功能事实上并没有实现,也不知道是 ...

  5. Dev GridView 获取选中分组下的所有数据行 z

    现在要在DevExpress 的GridView 中实现这样一个功能.就是判断当前的选中行是否是分组行,如果是的话就要获取该分组下的所有数据信息. 如下图(当选中红框中的分组行事.程序要获取该分组下的 ...

  6. 重新想象 Windows 8 Store Apps (12) - 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示

    原文:重新想象 Windows 8 Store Apps (12) - 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示 [源码下载] 重新想象 Windows 8 Store Ap ...

  7. GridControl/GridView的分组操作

    今天在模块编写中碰到了对表格的分组,特意在这里把它记录下来. 一.背景:Dev14.1.3,GridControl,.NET4.0+C# 二.过程 1.GridControl设计 一共添加4列:在下面 ...

  8. Winform中GridView分组排序实现功能

    由于客户最近要扩充公司的业务,之前基于Winform+web开发混合式的系统已经不能满足他们的需求,需要从新对系统进行分区处理. 考虑到系统模块里面用到的GridView视图比较多,我就结合了DevE ...

  9. 对Dev的GridControl/GridView控件进行分组并展开操作

    今天在模块编写中碰到了对表格的分组,特意在这里把它记录下来. 一.背景:Dev14.1.3,GridControl,.NET4.0+C# 二.过程 1.GridControl设计 一共添加4列:在下面 ...

随机推荐

  1. [HIMCM暑期班]第3课:一个博弈问题

    在一个街道平面图上,住着n个住户.有两个贩卖热狗的商贩,各自想要在街区里摆设一个小摊.每天住户都会去离他家50米范围内的最近的摊点消费.问: 1. 如果两位小贩摆设小摊的顺序有先后(设A先摆,然后B再 ...

  2. 实现两个MySQL数据库之间的主从同步

    一.    概述MySQL从3.23.15版本以后提供数据库复制(replication)功能,利用该功能可以实现两个数据库同步.主从模式.互相备份模式的功能二.    环境操作系统:Linux 2. ...

  3. 64位Windows下安装Redis教程

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/104.html?1455868495 Redis对于Linux​是官方支持 ...

  4. Kafka与Logstash的数据采集对接 —— 看图说话,从运行机制到部署

    基于Logstash跑通Kafka还是需要注意很多东西,最重要的就是理解Kafka的原理. Logstash工作原理 由于Kafka采用解耦的设计思想,并非原始的发布订阅,生产者负责产生消息,直接推送 ...

  5. CAR

    24.编写一个Car类,具有String类型的属性品牌,具有功能drive: 定义其子类Aodi和Benchi,具有属性:价格.型号:具有功能:变速: 定义主类E,在其main方法中分别创建Aodi和 ...

  6. Atititi.名字 姓名 name 起名naming spec 的构成结构规范v2 qc2.docx

    Atititi.名字 姓名 name 起名naming spec 的构成结构规范v2 qc2.docx 1.1. 职业名 官职等 amir 阿米尔 放前面1 1.2. 本名1 1.3. 父名,祖名,一 ...

  7. Servlet过滤器,Servlet过滤器创建和配置

    第一:Servlet的过滤器的创建和配置,创建一个过滤器对象需要实现javax.servlet.Filter接口,同时实现Filter的3个方法.        第一方法是过滤器中的init()方法用 ...

  8. js防止客户端多触发

    代码: /***防止多触发**id 必须唯一*fn 回掉函数*wait 延迟多长时间**使用例子:* ToPreventMoreTrigger('id', function () {//注意 id 是 ...

  9. localStorage使用

    localStorage使用 需要注意的是,HTML5本地存储只能存字符串,任何格式存储的时候都会被自动转为字符串,所以读取的时候,需要自己进行类型的转换. 支持的情况如上图,IE在8.0的时候就支持 ...

  10. Android线程处理

    对JAVA的线程相信大家都有一定的认识,本篇就让我们一起探讨一下Android中的线程问题,对于线程和进程的区别我就不再赘述,有兴趣的小童鞋可以百度一下,讲解的非常详细,相信大家经常可以听到关于线程的 ...