离上一次写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的观察,主要是下面两种情况:
对于第一种情况,我们需要重载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。我们主要需要完成以下几步:
//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