Customizing the DataGridView to support expanding/collapsing (ala TreeGridView) - Mark Rideout's Blog - Site Home - MSDN Blogs

Customizing the DataGridView to support expanding/collapsing (ala TreeGridView)

Customizing the DataGridView to support expanding/collapsing (ala TreeGridView)

Rate This

One of the first things that I wanted to customize with the DataGridView is to make it support hierarchical data

If you read my first blog post you'll find out that I'm not a developer (anymore). Even though I'm not a developer I still like to take features and customize them to do something really cool. As far as the DataGridView goes, customizing it to support hierarchical data is a much larger task since the structure of the DGV doesn't lend itself to having different column sets, so, I decided I'd settled for a tree like structure.

Think of a TreeView combined with a ListView and that is basically what I wanted to go with.

NOTE: This code is not supported by Microsoft and is provided "as-is". This code it not meant to show "best practices", but just showing a concept.

NOTE: This control is written to require Visual Styles to be enabled on your computer. You'll need to modify it if you want to run the TreeGridView without visual styles. Some people have been able to modify the code to run without Visual Styles. See this post for details.

Original Code: http://www.windowsforms.net/blogs/markrideout/treegridview.zip

Here is a picture:

Anyway. The basic part of creating a DataGridView that can expand and collapse is to dynamically add and remove rows. That was the easy part. To make this really usable and extendable, I decided to add a lot code and make this easier to use. Here are some details:

Design

I wanted to ensure that the design of the TreeGridView supported normal TreeView type properties and features, so creating necessary classes to create the “tree view” experience wa necessary (see object model for more details).

Custom Painting – Painting an image in a cell is easy, but ensuring that the text from the DataGridViewTextBoxCell didn’t overlap the image took a bit of work. Using the Padding feature of the DataGridViewCellStyle, I add padding to the left side of the cell to account for the text and the indentation. This padding affects the painting and behavior of the text box cell, so editing a text cell correctly positions the editing control.

Siting/Unsiting a node – Since I don’t want to set padding and styling except when a node is actually displayed. When the node is displayed or in the grid, it is sited. When the node is sited I set all the necessary properties.

Unbound – Since expanding and collapsing is based upon dynamically adding and removing rows from the grid, I decided that unbound mode would be the best way to go with this. I’ve hidden the “databinding” properties and the virtual mode property since this doesn’t support those features.

Edit Mode – One thing that I had to deal with is that double-clicking a cell enters edit mode. This double-click occurs regardless of the padding, so double-click on the +\- symbol causes the control to enter edit mode. Edit also enters if you single click on a cell that already has focus. So, to deal with this I turn edit mode to be enabled only through programmatic means. I have code to handle the F2 key to enter edit mode. There are other ways to solve this, but I went with the F2 approach.

Object model structure

TreeGridNode - Just like a tree view, I wanted to have the concept of a node. I made the nodes class derive from a DataGridViewRow since a node in the list is the same as a row, just with a bit more info.

Here are some properties:

Nodes – Again, like the treeview, a node has children, so there is a Nodes property that returns child nodes. One of the challenges in coding this is to know when a node is actually a row or when it is just a node. A node is a row when it is in the grid, otherwise it is just a node.

IsSited – A node is “sited” when it is contained in the grid as a row. The Sited property is true in this case. There are a set of protected virtual methods on the TreeGridView class (SiteNode and UnSiteNode).

ImageIndex – Image index for the node’s image. Only used when an ImageList is associated with the TreeGridView.

Image – Image associated with the node. Sets or gets the image. When an ImageList is associated with the TreeGridView and an ImageIndex is set then this returns an image from the ImageList. You can set the Image property to an image if you aren’t using an ImageList.

Cells – Returns the cells for the given node. This wasn’t easy to do since accessing the cells for a node (or row) when the node isn’t sited. Using the DataGridView’s CreateCells method I can get the correct cells collection when the node isn’t in the grid.

TreeGridCell/Column – This is a special DataGridView cell that derives from the DataGridViewTextBoxCell. The main thing that this custom cell class does is to customize the cell drawing to make it look like a tree node. That means that it draws the node’s image and the +/- icons and the tree lines. The custom cell also is where a node detects when you click the mouse to expand or collapse a node. NOTE: A lot more work can be done to correctly detect that the mouse is directly over the +/- image. Right now I’m not doing that.

TreeGridView – This class derives from the DataGridView control. Many things are done in this class. Nodes are sited/unsited in the grid as actual rows. Somce DataGridView Properties are hidden since they do not apply.

Here are some properties:

VirtualNodes – One of the common things done with a normal TreeView is to dynamically add child nodes when the user is expanding the parent. With the normal TreeView usres add temp child nodes to get the + sign and support expanding, then remove the temp node later. With the VirtualNodes property, the TreeGridView always displays a + sign next to a node, even if the node doesn’t have any children. Then, by handling the Expanding event you can dynamically add child nodes.

ImageList – ImageList associated with the TreeGridView

Nodes – Identifies the root nodes.

ShowLines – Determines if the TreeGridView shows lines between nodes.

CurrentNode – Identifies the node that has focus.

Here are some events:

Expanding – Again, like the treeview, this event occurs before a node is starting to expand. You can add nodes in this event to fill in more details of a child node collection. When the VirtualNodes property is true, the TreeGridView will display the node with a + sign next to it even when it doesn’t have any children. Handling the Expanding event is where you would dynamically add new child nodes.

Expanded – After a node is expanded.

Collapsing – Before a node is being collapsed

Collapsed – After a node has been collapsed.

I’ve included a simple test app that creates a news group reader looking list that supports expanding and collapsing.

Let me know what you think and how you've used this in your project!

-mark

  • I am looking for an implementation of a DataGrid-TreeView hybrid. actually same idea like above but should be a DataGrid.

  • Thanks for the TreeGridView, it's what i'm looking for. But i face some abnormal behavior for this control.

    I have populated TreeGridView with 2 levels of nodes, (i.e. level 1 refers to parent node, level 2 refers to child node).

    While i'm trying to dynamically remove the last child node from the current parent and add it to another parent, the TreeGridView show the child node as the first row while its parent as the last row. May I know why? How to rectify it?

    However, I know the hierarchy is correct because when I double click the new parent node at the last row, it'll collapse the childnode even though it's at first row. But why the child is not displayed as the next row under the parent?

    Really appreciate if anyone can help. thanks a lot.

  • Thanks for the TreeGridView, it's what i'm looking for. But i face some abnormal behavior for this control.

    I have populated TreeGridView with 2 levels of nodes, (i.e. level 1 refers to parent node, level 2 refers to child node).

    While i'm trying to dynamically remove the last child node from the current parent and add it to another parent, the TreeGridView show the child node as the first row while its parent as the last row. May I know why? How to rectify it?

    However, I know the hierarchy is correct because when I double click the new parent node at the last row, it'll collapse the childnode even though it's at first row. But why the child is not displayed as the next row under the parent?

    Really appreciate if anyone can help. thanks a lot.

  • Found the solution for problem of displaying child node at first row by putting an extra line in TreeGridNode.cs

    Within the method of :    internal protected virtual bool AddChildNode(TreeGridNode node)

    after line of :     node._grid = this._grid;

    add a line of:    node._index = this.childrenNodes.Count - 1;

    Example:

    internal protected virtual bool AddChildNode(TreeGridNode node)

    {

    node._parent = this;

    node._grid = this._grid;

           node._index = this.childrenNodes.Count - 1;

    // ensure that all children of this node has their grid set

    if (this._grid != null)

    UpdateChildNodes(node);

    if ((this._isSited || this.IsRoot) && this.IsExpanded && !node._isSited)

    this._grid.SiteNode(node);

    return true;

    }

  • hello i really thank you for this example!!!

    very good

    im sorry but i have a one question:

    ¿this example have design in web application using asp.net?

    i need design this example but in web application

    i really need help for this!!!

  • This is a very useful component; thanks very much for creating it and to everybody else for contributing to it's evolution.  One thing though; is there are reason for the UnSiteAll() call that is being made in the Dispose() implementation of the TreeGridView class?  This ends up taking a fair bit of time when there are alot of items and I can't see that there is anything that it needs to be doing (wrt un-managed resources etc.).

  • Hi, thanks for your code. That's what I really need now. It's awesome. :)

  • Hi Mark,

    Thanks for sharing this tree grid view.

    In the note, you have mentioned that code change has to be made if visual styles are not used.

    Can u help me with the necessary code change i need to make as i don't want to use any visual styles.

    If change my systems Visual Effects, i get a problem using the tree grid view.

    Can u please let me know the changes i have to make in the code?

    Waiting for your reply.

    Thanks,

    Nayna

  • With regard to Visual Effects, check out the solution at:

    social.msdn.microsoft.com/.../b1167539-1f93-4d30-87cd-04f25626e78d

  • How i can know what item is selected in the treeview column?

    Thanks

  • I too ran into the problem described by Aster on 16 Jan 2009 12:58 AM (page 11 of these comments).

    When the first column is a DataGridViewTextBoxColumn (or anything not assignable to a TreeGridColumn), the following exception is raised:

    "Unable to cast object of type 'System.Windows.Forms.DataGridViewTextBoxCell' to type 'AdvancedDataGridView.TreeGridCell'."

    While Aster's solution will work, my solution would instead be:

    In TreeGridNode.cs at line 262 (Or find the following statement):

    <---

    if (cell.GetType().IsAssignableFrom(typeof(TreeGridCell)))

    <---

    Replace it with this if statement:

    <---

    if (typeof(TreeGridCell).IsAssignableFrom(cell.GetType()))

    <---

    The problem is the TreeGridCell object is being assigned *from* the cell, not the other way around.  I believe this was the original intent of the coder.

    P.S. Mark, thanks for sharing this customized control!

  • Hey,

    Does someone solves the problem of selection when the control is instanciate.

    The SelectionChanged event is well fired but the TreeGridNode that fired the event is "inexistant".

    After, I have change the selection and re-select the first row (manually by clicking with the mouse), all works correctly.

    Thanks for answers

    Geoff

  • Hi.. Am doing Windows application..In datagridview i have 4 columns. Student Name as Column1, present as checkbox,Absent as checkbox,leave as chechbox.. I have to click present or absent or leave for respective student name, Here i have to bind student name from backend, Am not getting how to bind data of one column from backend to one column of datagridview.. please help me.. if possible please mail me.. shrusharan@gmail.com.. Thanks in advance

  • Thanks a lot for this control - we're using it to display a list of aggregated search results (total, average time, max time, etc.), where each result contains a bunch of versions, that when expanded display the same information for just that version. This is exactly the sort of display we needed.

    Of course, I needed to get sorting working first, and I fiddled around a bit getting the keyboard to respond more expectedly, but this definitely saved me a load of time compared to having to implement a control like this from scratch. Thanks for making it available!

    (It does strike me as vaguely silly that it requires non-classic visual style to run properly out of the box, when it was so trivial to fix, but whatever. It was also trivial to fix.)

  • GREAT work!

    I've found another small problem. When Removing child nodes, there has to be a reindexing too. So the routine should look like (TreeGridNode.cs):

    internal protected virtual bool RemoveChildNode(TreeGridNode node)

    {

       if ((this.IsRoot || this._isSited) && this.IsExpanded )

       {

    //We only unsite out child node if we are sited and expanded.

           this._grid.UnSiteNode(node);

       }

       //Reindexing...

        for (int i = node.Index; i < node.Parent.Nodes.Count; i++)

            this.Nodes[i].Index = i-1;

        node._grid = null;

        node._parent = null;

        return true;

    }

Page 15 of 16 (239 items) «1213141516
Leave a Comment
  • Please add 2 and 2 and type the answer here:
  • Post