Styling a treeview to create an org chart has been done before.
Josh Smith did it like this
Miguel Saez did it like this.
Both use simple styles to transform a treeview to a horizontally laid out org chart. The only thing missing are connection lines between the nodes. In addition, both developers use a grid in the TreeViewItem control template to create the horizontal layout. This is fine, but I didn’t like having to specify the rows of the nodes and item presenters. Why? Cuz I’m lazy. I simplify things with a couple of StackPanels.
The Concept
Here’s what we’re aiming for:
The source code for this project is here: WPFBlog.Info.OrgChart Example Source Code
I’ll explain the key concepts regarding the source code but you’ll probably want to download it and have a look through it before we begin.
The general concept is as follows:
- We style the treeview to create an org chart without connectors.
- We subclass the treeview and override the OnRender routine.
- We look for all the treeview items and then use some VisualTreeHelper hackery to find the visual element that represents a node, in this case a Border.
- When we find the node element, we calculate it’s center Point.
- We store all the Points in an internal tree data structure that mimicks the organizational structure of the logical tree but only contains Point information.
- Then we walk the tree and draw connector lines from Parent to Child using a Pen object exposed as a dependency property.
The Data
Although you can tailor this example to use the data structure of your choosing, I use a simple hierarchical Node structure that can contain anything. Here’s what it looks like:
Using this structure I create a tree structure when the window is loaded and bind it to the window’s data context for later use by our treeview:
The TreeView Control
In order to draw connector lines we need to be able to inspect the data of our treeview and our treeview needs a Pen to be able to draw with. Note that this could be done outside of a standard treeview, but naturally encapsulating the logic within our own control means we get to reuse this anyplace we could use a standard treeview.
Our custom treeview class is defined like so:
The portion of our logic we’re concerned with is in the OnRender routine. We reuse our simple node structure to create an internal tree structure of Point data representing the center locations of all of our nodes that are the treeview’s items. We do this in the GetTreeViewItemLocations routine. Then we take our tree structure and use the DrawConnections routine to walk that tree, drawing a simple line between parent nodes and each of it’s children using a Pen passed in via a dependency property.
That’s it!
The XAML
This is the extent of our window’s XAML without resources which we’ll go over next:
Our hierarchical data template is a fairly straightforward representation of our Node class which looks like this:
In order to achieve a horizontal org chart like layout we style the TreeViewItem:
We simply set the TreeViewItem’s ContentPresenter on top of the ItemsPresenter using a vertical StackPanel, then we layout the items horizontally in the ItemsPanelTemplate using a horizontal StackPanel. The only other item of importance is the Pen resource which is passed into the custom treeview declaration’s ConnectorPen dependency property without which you’d see the horizontal org chart but without the connector lines.
Conclusion
So that’s pretty much it. By no means would this be a complete control example. But using this logic is a good foundation for a more robust org chart control. Things to do would be to wrap this in a user control that calls InvalidateVisual() to redraw the tree when nodes are added/removed or the parent container is resized or scrolled. Provide a container that allows for scrolling through large trees (have a look at Sacha Barber’s code for an example). Adding style triggers for the TreeViewItem to expand and collapse nodes (I leave as an exercise for you, it’s really simple to do ;-). Perhaps a zoom/unzoom feature, again for large trees. I’m one of those lazy architects who hates having to do in code what could be done only in XAML but this is one example of a good reason to jump into code to solve a visual problem not easily handled by XAML alone.
