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

  • Looks wery good, but can I set focus to a special row? The "SelectedRows"is ReadOnly...
  • This control is very cool. Exactly what I was looking for. My only question is: I want two node columns, but I can't figure out how to reference the second columns nodes.
  • This is a really nice way of "combining" a tree and a grid. Thanks for sharing the code.

    I have a question about doing this differently. Earlier, I tried to do the same thing with the earlier .NET 1.0. Instead of painting a tree structure over cells, I tried to create a composite component with a TreeView and a DataGrid on the same control, separated by a splitter. When the tree was expanded/collapsed, the DataTable associated with it was set to be altered.

    It worked except for one pesky problem that I simply could not get rid of. The tree nodes and the grid rows never quite aligned. If one could manage that, one could achieve the same effect as you did with lesser coding, it seems. Do you know of any way to do this? Thanks, and thanks again for your code.
  • how to bind using dataset?
  • Hi, I really love the treegridview, but now i need to reorder nodes in it. (swap two nodes on the same level) I'd like to do this, without rebuliding the whole nodeset under the parent. Can you give me some ideas how can i make it? thankyou
  • Your treegridview have bug if VisualStyle is not enabled on your windows.
    Can you fix it? I really need your code. Thanks your code
  • Hi Mark,

    I am trying to get hold of you for a slightly different problem. I need to understand the DataGridView event model more deeply but I am having trouble locating information I need. Much of the information out there is for bound data sources and deals a lot with the appearance and display of the view. I am working with an unbound grid that requires extensive event customization. I have developed a custom GridCell and have defined some events for that GridCell, but I am not sure how to listen for these at the DataGridView level. How do I register listeners for custom events? Can you reference any good links .. I have combed through a lot of articles on MSDN and else where but so far no luck.
  • Niiice control.

    Consider enablin' databindin'.

    One of other cons - not workin' w/o VisualStyles. Here is the fixxx:

    in "TreeGridView.cs" delete the followin' two lines:

    ------8<------
    internal VisualStyleRenderer rOpen = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
    internal VisualStyleRenderer rClosed = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed);
    ------8<------

    in "TreeGridCell.cs" find the line "if (node.HasChildren || node._grid.VirtualNodes)" and change it so that it looks like that:

    ------8<------
               if (node.HasChildren || node._grid.VirtualNodes)
               {
                   // Ensure that visual styles are supported.
                   if (Application.RenderWithVisualStyles)
                   {
                       VisualStyleRenderer rOpen = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
                       VisualStyleRenderer rClosed = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed);

                       // Paint node glyphs
                       if (node.IsExpanded)
                           //node._grid.rOpen.DrawBackground(graphics, new Rectangle(glyphRect.X, glyphRect.Y + (glyphRect.Height / 2) - 4, 10, 10));
                           rOpen.DrawBackground(graphics, new Rectangle(glyphRect.X, glyphRect.Y + (glyphRect.Height / 2) - 4, 10, 10));
                       else
                           //node._grid.rClosed.DrawBackground(graphics, new Rectangle(glyphRect.X, glyphRect.Y + (glyphRect.Height / 2) - 4, 10, 10));
                           rClosed.DrawBackground(graphics, new Rectangle(glyphRect.X, glyphRect.Y + (glyphRect.Height / 2) - 4, 10, 10));
                   }
                   else
                   {
                       int h = 8;
                       int w = 8;
                       int x = glyphRect.X;
                       int y = glyphRect.Y + (glyphRect.Height / 2) - 4;
                       //MessageBox.Show("x = " + x.ToString() + ", y= " + y.ToString());

                       graphics.DrawRectangle(new Pen(SystemBrushes.ControlDark), x, y, w, h);
                       graphics.FillRectangle(new SolidBrush(Color.White), x + 1, y + 1, w - 1, h - 1);
                       graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), x + 2, y + 4, x + w - 2, y + 4);

                       if (!node.IsExpanded)
                           graphics.DrawLine(new Pen(new SolidBrush(Color.Black)), x + 4, y + 2, x + 4, y + h - 2);
                   }
               }
    ------8<------

    haha!
  • I really like the News Reader GUI look! EXACTLY what I want for my hierarchical task list application.

    However, as the previous post, the only 'thing' that's missing is the data binding... But, the good thing is that its not that much work to make it happen...

    I'm thinking of doing this myself, if it's not "too late" - someone else has done that already...?

    Thanks!

  • Beautiful. Move it to SourceForge (or somewhere)!
  • This looks really promising, however, I am havng problems even on the smallest sample:
    Put the grid control on a Form, added a column and on the FormLoad, called Nodes.Add("test"). I get the following:

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

    on line 264 of TreeGridNode.cs (in the cells_CollectionChanged function)

    Surely, I am doing soethin wrong.... Ay pointers please
  • Mark, I really like your control, but what I am trying to accomplish I am finding rather difficult with any of the grids at my disposal, nor am I able to find a clean solution. I am able to use the DataGridView and the C1FlexGrid, I am unable to purchase any others based upon budgetary constraints. I am hoping you can help with a suggestion or where a free control that can accomplish my needs.

    The Issue:
    I need to display a complex hierarchy of differentiating data in multiple stepped grids. Here is the hierarchy :

    Order
    ----GRID:LineItem(s)
    --------GRID:LineItem Price Assignment(s) (discounts, specials, etc.)
    --------GRID:LineItem Option(s)
    ------------GRID:Option Price Assignment(s) (discounts, specials, etc.)

    Each one of these grids has different columns, and data. I do not really want to use the standard - seperate grids to display the master - detail data. I would prefer if this was treed somehow.
  • Nice.  Two questions:
    1) How might one implement sorting columns?
    2) Headers don't seem to "click" or show the thin orange bar on the bottom on hover.
  • Mark

    A few years ago I wrote an application in VS2003 Managed C++ using a similar control called ContainerListView which is a combined TreeView and ListView control.  I am in the process of updating the application using VS2005 and CLR/C++ and am looking at your control as a replacement for the ContainerListView control.

    I was able to add your control to my ToolBox after rebuilding the dll (got a .ctor error with the downloaded dll).  However, when I try to create a node using either the designer or in code I get the following error:

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

    This occurs at line number 264 in TreeGridNode.cs.  The line is:

    treeCell = (TreeGridCell)cell;

    I don’t speak C# very well and am not sure how to fix the problem.  I suspect that because I’m writing in C++ this may just be the tip of a compiler iceberg.  Any thoughts or suggestions?

    Thanks

    John
    john.bollwerk.ctr@scott.af.mil
  • You Rock!!!  So great to have people that contribute like this.

    Thanks!
Page 2 of 16 (239 items) 12345»
Leave a Comment
  • Please add 3 and 3 and type the answer here:
  • Post