离上一次写blog已经有两年多了,惭愧惭愧。。。
前段时间开发一个记账软件记账本,需要一个类似于Outlook的邮件分组显示功能。网上找了好久也没有找到称心的,于是乎就自己动手做了一个,现在做一些简单的总结。此文章同时发布于我的独立blog: http://www.zitiger.com/
这个新的控件我把他叫做GroupGridView,主要实现了下面两个功能:
- 把所有数据进行分组显示
- 在右侧显示每个组的附加信息

对于第一个需求网上很容易找到解决方案,但这些方案不能满足第二个需求,于是我就开始创建这个GroupGridView
整个控件主要包括两个类:GroupGridView 和GroupGridGroup。
我们先来看一下GroupGridView类。GroupGridView类继承自DataGridView,为了让GroupGridView看起来像Outlook的分组显示,我们要多对DataGridView进行一些改造:
// Control when edit occurs because edit mode shouldn’t start when expanding/collapsing
this.EditMode = DataGridViewEditMode.EditProgrammatically;
// This sample does not support adding or deleting rows by the user.
// From http://www.zitiger.com/
this.AllowUserToAddRows = false;
this.AllowUserToDeleteRows = false;
//We do not need the row header
this.RowHeadersVisible = false;
//we only need the horizontal border line like it in outlook
this.CellBorderStyle = DataGridViewCellBorderStyle.RaisedHorizontal;
//only allow the full row select
this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
接着我们要暴露一个方法CreateGroup用来创建Group
public GroupGridGroup CreateGroup(String title, String summary)
{
//at least one column and only fire when the first group
if (this.Columns.Count > 0 && this.Groups.Count == 0)
{
//Give the enought padding for each row
// From http://www.zitiger.com/
Padding padding = this.Columns[0].DefaultCellStyle.Padding;
padding.Left += 4;
this.Columns[0].DefaultCellStyle.Padding = padding;
//We do not allow to sort.
for (int i = 0; i < this.Columns.Count; i++)
{
this.Columns[i].SortMode = DataGridViewColumnSortMode.NotSortable;
}
}
GroupGridGroup group = (GroupGridGroup)this.GroupTemplate.Clone();
group.Title = title;
group.Summary = summary;
group.GroupGridView = this;
group.CreateCells(this, new String[] { title, summary });
//Add the group to datagridview.rows
//so the group can be displayed in the datagridview
this.Rows.Add(group);
this.Groups.Add(group);
return group;
}
接着,我们要开始处理Group的折叠展开事件了。什么时候我们要对Group进行展开/隐藏操作呢? 通过对outlook的观察,主要是下面两种情况:
- 当用户双击Group行
- 用户点击Group的+/- 符号时
对于第一种情况,我们需要重载DataGridView的OnCellDoubleClick事件,如果用户双击的是GroupGridGroup,那么需要对这个Group的行进行折叠展开操作
软件同时发布于我的blog: http://www.zitiger.com/,转载请注明出处
protected override void OnCellDoubleClick(DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0) return;
// From http://www.zitiger.com/
if (Rows[e.RowIndex] is GroupGridGroup)//if the clicked row is the group
{
GroupGridGroup group = this.Rows[e.RowIndex] as GroupGridGroup;
group.Toggle();
}
base.OnCellDoubleClick(e);
}
对于第二种情况,我们需要重载OnCellMouseDown,代码和OnCellDoubleClick相似,只不过我们需要额外判断用户点击的是否是+/-符号
// the OnCellMouseDown is overriden so the control can check to see if the
// user clicked the + or - sign of the group-row
protected override void OnCellMouseDown(DataGridViewCellMouseEventArgs e)
{
//If use does not click on a row
if (e.RowIndex < 0) return;
// From http://www.zitiger.com/
if (Rows[e.RowIndex] is GroupGridGroup) //if the clicked row is the group
{
GroupGridGroup group = this.Rows[e.RowIndex] as GroupGridGroup;
if (IsIconHit(e)) //if the user clicked the sing + or -
{
group.Toggle();
}
}
base.OnCellMouseDown(e);
}
至此,整个GroupGridView的代码我们已经剖析完成了,我们接着来看看我们的主角GroupGridGroup的实现过程。GroupGridGroup继承自DataGridView, 通过重载Paint函数来绘制我们的Group。
我们需要暴露CreateRow方法,让外界可以通过GroupGridGroup.CreateRow来创建属于这个Group的Row。
软件同时发布于我的blog: http://www.zitiger.com/,转载请注明出处
public DataGridViewRow CreateRow(params Object[] values)
{
DataGridViewRow row = this.GroupGridView.RowTemplate.Clone() as DataGridViewRow;
row.CreateCells(this.GroupGridView, values);
this.GroupGridView.Rows.Add(row);
// add row to childRows
this.childRows.Add(row);
return row;
}
接着我们来看看如何进行折叠和展开操作
//Collapse this group, we need to hide the rows belongs to this group
public void Collapse()
{
this.IsExpanded = false;
//Hide all rows in this group
foreach (DataGridViewRow row in this.childRows)
{
row.Visible = false;
}
}
//Expand this group, we need to show the rows belongs to this group
public void Expand()
{
this.IsExpanded = true;
//Show all rows in this group
foreach (DataGridViewRow row in this.childRows)
{
row.Visible = true;
}
}
最后,也是最重要的任务:通过重载Paint来绘制Group。我们主要需要完成以下几步:
- 绘制Group的背景
- 绘制文字
- 绘制Group的底边线
- 绘制+ – 符号
//Override the Paint, the most important part of this control
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle rowBounds,
int rowIndex, DataGridViewElementStates rowState,
bool isFirstDisplayedRow, bool isLastVisibleRow)
{
GroupGridView grid = (GroupGridView)this.DataGridView;
// From http://www.zitiger.com/
int gridwidth = grid.Columns.GetColumnsWidth(DataGridViewElementStates.Displayed);
Color backgroundColor;
if (this.Selected) //If it is selected
backgroundColor = grid.DefaultCellStyle.SelectionBackColor;
else
backgroundColor = grid.DefaultCellStyle.BackColor;
//draw the background
using (Brush backgroundBrush = new SolidBrush(backgroundColor))
{
graphics.FillRectangle(backgroundBrush,
rowBounds.Left - grid.HorizontalScrollingOffset,
rowBounds.Top, gridwidth, rowBounds.Height - 1);
}
//draw text, using the current grid font
Font font = new Font(this.DataGridView.Font, FontStyle.Bold);
//Draw the title
graphics.DrawString(this.Title, font, Brushes.Black,
-grid.HorizontalScrollingOffset + 23, rowBounds.Bottom - 18);
//mesure the string to get the height and width of the string
SizeF stringSize = graphics.MeasureString(this.Summary, font);
//caculate the x of position for the Summary
float contentX = rowBounds.X + gridwidth - stringSize.Width - 8;
//caculate the y of position for the Summary
float contentY = rowBounds.Y + (rowBounds.Height - stringSize.Height) / 2;
//Draw the summary string
graphics.DrawString(this.Summary, font, Brushes.Black, contentX, contentY);
//For bottom line of group
using (Brush bottomBrush =
new SolidBrush(Color.FromKnownColor(KnownColor.GradientActiveCaption)))
{
//draw bottom line of the group
graphics.FillRectangle(bottomBrush, rowBounds.Left + grid.HorizontalScrollingOffset
, rowBounds.Bottom - 2, gridwidth - 1, 2);
}
//the Rectangle for the +/- symbol
Rectangle glyphRect = new Rectangle(rowBounds.X + 5, rowBounds.Y, 5, rowBounds.Height - 1);
//Draw the + or - sign on the left of the group
// Ensure that visual styles are supported.
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(glyphRect.X, glyphRect.Y + (glyphRect.Height / 2) - 4, 10, 10);
// Paint group +/- glyph
glyphRenderer.DrawBackground(graphics, glyphRectangle);
}
else //e.g. for win 2000 or classic theme in win Xp
{
int h = 8;
int w = 8;
int x = glyphRect.X;
int y = glyphRect.Y + (glyphRect.Height / 2) - 4;
//The below two lines will draw the box of +/- symbols
graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h);
graphics.FillRectangle(new SolidBrush(Color.White),
x + 1, y + 1, w - 1, h - 1);
//Draw the -
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + 2, y + 4, x + w - 2, y + 4);
//Draw the |
// From http://www.zitiger.com/
if (!this.IsExpanded)
graphics.DrawLine(new Pen(new SolidBrush(Color.Black)),
x + 4, y + 2, x + 4, y + h - 2);
}
}
这个控件的缺点是不能通过datasource进行数据绑定只能手动进行。同时发布于我的blog: http://www.zitiger.com/,转载请注明出处,同时也欢迎访问我的记账本http://www.jzben.com