GroupGridView:支持分组的DataGridView

离上一次写blog已经有两年多了,惭愧惭愧。。。

 

前段时间开发一个记账软件记账本,需要一个类似于Outlook的邮件分组显示功能。网上找了好久也没有找到称心的,于是乎就自己动手做了一个,现在做一些简单的总结。此文章同时发布于我的独立blog: http://www.zitiger.com/

这个新的控件我把他叫做GroupGridView,主要实现了下面两个功能:

  • 把所有数据进行分组显示
  • 在右侧显示每个组的附加信息

GroupGridView:支持分组的DataGridView_第1张图片

 对于第一个需求网上很容易找到解决方案,但这些方案不能满足第二个需求,于是我就开始创建这个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。我们主要需要完成以下几步:

  1. 绘制Group的背景
  2. 绘制文字
  3. 绘制Group的底边线
  4. 绘制+ – 符号
//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

 

你可能感兴趣的:(GroupGridView:支持分组的DataGridView)