Click here to Skip to main content
Click here to Skip to main content

A Graph Tree Drawing Control for WPF

By , 23 Feb 2009
 
Sample Image - maximum width is 600 pixels

Introduction

This article includes two very different basic assemblies and a couple of test assemblies for WPF. The first basic assembly is GraphLayout which implements the Reingold-Tilford algorithm, described here, to determine the placement of nodes in a tree structure. This tree is structured as a top down tree with the root at the top and the leaves at the bottom. This algorithm is entirely independent of any actual drawing of those nodes and so can be used for any purpose where such positioning is required. It includes options for vertical justification for nodes, collapsing of nodes, distance between sibling nodes and distance between non-sibling nodes. This assembly is not WPF specific and could easily be used to produce a GDI+ tree drawer (though I haven't included any here). It also includes a list of endpoints for lines which can be drawn as connectors between nodes. Currently, these lines are simply straight lines from the bottom center of the parent node to the top center of the child node, though it would be easy enough to modify them to draw different kinds of connectors.

The second assembly is a WPF control subclassed from Panel which uses GraphLayout to determine where to place its child controls in a tree. Each non-root node has a dependency property for its parent node's name which is how the graph is strung together. Since this is a subclassed panel, the individual nodes can be any type of WPF control desired. This allows the tree to be specified in XAML as demonstrated in the TreeContainerTest test app or in code as demonstrated in the VisLogTree.

It also contains a Silverlight control which does the same thing. The primary difference with the Silverlight control is that the connections have to be added as a Path child to the tree container rather than painting the connections directly onto the control. Silverlight doesn't support the OnRender function for controls where they can be drawn to. I prefer the drawing method used in the WPF control so that there's not this "dangling" path control, but it really doesn't seem to be an option in Silverlight. I just got started with Silverlight a couple of days ago so if anybody knows better, I'd love to hear about it. The GraphLayout DLL had to be recompiled so it would be linked with the Silverlight libraries but otherwise was used unchanged. After figuring out all the little Silverlight quirks, the new Silverlight control worked the first time I tried it testifying to some extent to the universality of GraphLayout.

Background

There are several WPF controls which I could locate which make some effort to produce trees in WPF but so far as I can find, there are no free assemblies/source code which give a proper implementation of the Reingold-Tilford algorithm in a standard WPF fashion (i.e., a subclassed panel which can be implemented in XAML) and it seems like an obvious and valuable thing to do, so I've done it. There are LOTS of extensions which I can think of making here, but this seems pretty valuable as it is so I'm putting it out there and moving on to other things for the moment.

The Reingold-Tilford algorithm can easiest be described as "recursively drawing all child graphs then jamming them all to the left as far as you can, leaving a fixed distance between nodes and finally cleaning up interior nodes which are jammed to the left but can be moved to the right for centering purposes". I know that's not incredibly intuitive - especially the last part - but reading the article cited above gives all the details (albeit not in a terribly clear manner in my estimation, nor in a way that corresponds very directly to my code which I implemented before reading this article and with not much more than the sketchy description given here - carefully slogging through the walkthrough is the only way I really understood it, even when I knew the basics of the algorithm).

I tried to test all the cases, but I might have missed a few - there are lots of trees out there, so please mail me if you find any bugs. For the most part, if I found inconsistencies with the Parenting within the WPF control, I just didn't position the corresponding controls at all so they end up in the upper left of the TreeContainer control. This is so XAML isn't constantly blowing up and showing you nothing because of a thrown exception. If you see nodes in the upper left of your TreeContainer control, go in and check for consistency in your Parent properties.

Using GraphLayout

GraphLayout (which probably should more properly be called TreeLayout although in the future I may add other Graph type layouts to it) provides an ITreeNode interface which represents (you guessed it) the nodes in the tree. GraphLayout.LayeredTreeDraw is the class which calculates all node positions. Its constructor takes an ITreeNode which represents the root of the tree and some global parameters which affect the positioning (minimum distance between nodes, vertical justification, etc.) and sets properties on all the nodes giving their proper positions in the final graphical tree.The ITreeNode interface is fairly simple. It includes TreeWidth and TreeHeight properties which return the width and height of the node. It also includes the boolean Collapsed property which is a readonly property telling whether the node should be collapsed. How this information is kept and set on the node is up to the implementation. The most important property is TreeChildren which returns the children of this node in a TreeNodeGroup collection. TreeNodeGroup is a simple enough class with an Add function to add ITreeNode objects to its collection. Finally, LayeredTreeDraw needs a place to poke its own private information into each node. You must implement PrivateNodeInfo which takes an object, stores it into the node and retrieves it back later.

There are three buffer distances associated with trees. The vertical buffer distance is the minimum distance between the layered rows of the tree. The horizontal buffer distance is the minimal distance between sibling nodes in the graph. The horizontal subtree distance is the minimal distance between nodes which are not direct siblings. This makes some graphs look better by further separating subtrees further than individual siblings.

After constructing the LayeredTreeDraw object, you simply call LayoutTree() on it and then retrieve the information back from it. The X and Y coordinates for a given node are retrieved from a LayeredTreeDraw object called ltd with ltd.X(treeNode) and ltd.Y(treeNode) where "treeNode" is the ITreeNode in question. ltd.PxOverallWidth() and ltd.PxOverallHeight() return the width and height of the resultant tree. ltd.Connections() returns a list of TreeConnection objects. Each object has a parent ITreeNode and a child ITreeNode along with a list of DPoints which give a path of lines from the parent to the child. Since WPF and GDI+ use different definitions for "Point" I decided to implement a very lightweight DPoint structure which always uses doubles for the coordinates and can be used from either WPF or GDI+ (or anywhere else for that matter). Currently, these connectors give a single line between the parent and the child but this wouldn't be hard to change. Really, I was going to provide a set of different types of connectors, but maybe that will be something for the future. As it is, it is possible with different sized vertical nodes that a connector from a short node could overlap with a taller sibling node. One of the things I thought about doing was giving broken lines which would avoid this possibility.

That is pretty much it for GraphLayout. It's simple enough to use but definitely the most complicated part under the covers.

Using TreeContainer

TreeContainer is a WPF control subclassed from Panel which uses GraphLayout to layout its children. To be used properly, we have to know the tree structure of the children. This is handled by having a dependency property called "Root" which is set on the TreeContainer and has the string value of the name of the node which is the root of the tree. The children of the TreeContainer must all be TreeNodes which are content controls which may hold whatever controls you like. The TreeNodes have a TreeParent property which tells which node is their parent in the tree - again, a string value with the name of the parent node. Every TreeNode in the TreeContainer must have a parent property except the root node. There is also a Collapsible and Collapsed property on each TreeNode. If the Collapsible property is set to false for a node, it cannot be collapsed. It it is true, then the value of Collapsed determines whether a node is collapsed or not. In the VisLogTree sample, the nodes are buttons which toggle the collapse of the tree below them when pressed. These properties are all XAML settable.

TreeContainer also contains some utility routines for creating the TreeNodes programmatically. This includes Clear() which clears all nodes from the TreeContainer and routines to add roots or interior nodes. At their simplest, these never have to deal directly with names in which case the code produces internal names. So, for instance, AddNode(Object, TreeNode) wraps the object in a new TreeNode, gives it a name and sets its parent to the TreeNode passed in. The TreeNode produced to wrap the object is returned. See the VisLogTree for an example of how to use these to easily produce the trees in a TreeContainer at runtime.

The TreeNode containers implement the ITreeNode interface and hence are themselves the ITreeNodes necessary for GraphLayout's calculations.

Points of Interest

There are many additions which could easily be added to this control. I've mentioned several in the text already - arbitrary pen for drawing connections, different types of connections, different orientations for the graph (i.e., left to right or bottom to top), a routed event for when nodes get collapsed or uncollapsed, etc. It seems like a valuable control at this point, however, and to be honest, I'm anxious for new territory to cover so I'm putting it out there as is.

The algorithm proved to be much trickier than I had initially supposed. There are a fair number of exceptions and gotchas to the algorithm as I originally read it. It might have been easier to just blindly follow the algorithm as described in Dr. Dobb's article in the hyperlink above, but I didn't have that article ahead of time and there are a few things I'm not fond of in the algorithm as written there.

There are two samples. One is an XAML version of the graph used in the paper mentioned above just to verify that it turned out correctly as described in the paper. The other gives the visual and logical tree for a dialog loosely based on one in "WPF Unleashed" by Adam Nathan, an excellent book. It's the dialog that Nathan uses to illustrate logical and visual trees so you can check that it gives the right information. The nodes in the second one are buttons which toggle whether their subgraphs are collapsed or not. There is also a button to remove or add back in the label at the top of the dialog which I used for debugging and left in there.

A point I wasn't aware of until working on this control is the RegisterName/UnregisterName methods on FrameworkElements. When I first tried to create the tree control programmatically, I naively set the name on my TreeNodes and then used FindName() to locate them later. Wrong! If you do the same, you will be told that no such control name exists. You must use the RegisterName() method to register the name with the parent in order to make this work. Similarly, if you remove a control you really should do an UnregisterName(). I've read a lot on WPF and this is the first time I've ever seen this. It seems like this should be a little more visible in the documentation out there. The utilities on the TreeContainer which allow easy production of the TreeNodes takes care of all this automatically.

History

  • 9-20-2008 First version
  • 2-21-2009 Added Silverlight control

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

darrellp
Web Developer
United States United States
Member
I've been doing programming for close to 30 years now. I started with C at Bell Labs and later went to work for Microsoft for many years. I currently am a partner in Suckerpunch LLC where we produce PlayStation games (www.suckerpunch.com).
 
I am mainly interested in AI, graphics and more mathematical stuff. Dealing with client/server architectures and business software doesn't do that much for me. I love math, computers, hiking, travel, reading, playing piano, fruit jars (yes - I said fruit jars) and photography.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSingle child with two parents cant this be represented in this implementation ?memberkarthikparys29 Jan '13 - 8:11 
Hello, I really admire your work here, was very interesting.
 
My question here is ,
 
Can a child node be reached from more than one parent node.
 
If you take the TreeContainerTest sample, i want the Node G to be reached from both F & N.
 
You answer & suggestion will be really helpful.
 
Also if you can suggest some link for starting on understanding the algorithm behind this, i.e Walkers / Reigngold-Tilford algorithm.
 
Thanks in advance !.
GeneralMy vote of 5membercsharpbd22 Nov '12 - 1:15 
Good...
SuggestionTreeContainer Vertical Layout [modified]membergeheim8115 Oct '12 - 7:27 
I was looking for good ways to change the TreeContainer orientation so it could be displayed Vertically instead of horizontally. At first I tried inverting some coordinates in the positioning algorithm but I got really weird behaviors, then I read about WPF rotate, transform capabilities and was able to flip it this way:
 
Assuming you have defined a TreeContainer like this:
<tc:TreeContainer 
                        HorizontalAlignment="Center" 
                        VerticalAlignment="Center" 
                        x:Name="Tree" 
                        HorizontalBuffer="38" 
                        VerticalBuffer="48" 
                        HorizontalBufferSubtree="40" 
                        VerticalJustification="center" 
                        RenderTransformOrigin="0.5,0.5"/>
 
You can create a Test method in your main window code-behind:
private void BuildTree()
{
        Button btn_Root = new Button();
        btn_Root.Content = "ROOT";
        TreeNode root = Tree.AddRoot(btn_Root);
        for (int i = 0; i < 4; i++)
        {
            Button btn = new Button();
            btn.Content = "Button_l1_" + i;
            TreeNode ch1 = Tree.AddNode(btn, btn.Content.ToString(), root);
            for (int j = 0; j < 2; j++)
            {
                Button btn2 = new Button();
                btn2.Content = "Button_l2_" + i + "_" + j;
                Tree.AddNode(btn2, btn2.Content.ToString(), ch1);
            }
        }
 
        //Rotate Tree first here
        Tree.RenderTransform = new RotateTransform(-90);
        //Then rotate all TreeNode content children
        foreach (TreeNode n in Tree.Children)
        {
           Button btn = n.Content as Button;
           btn.LayoutTransform = new RotateTransform(90);
        }
}
 
 
You will end up with a vertical tree now Smile | :)
 
Give it a try.
 
Ideally the TreeContainer should have a DependencyProperty to define the orientation
and shouldn't be to hard to implement, but for now I think this gets us closer to a good solution.
 
If someone knows how to achieve the same by modifying the positioning algorithm itself i'd be very interested to know...

modified 15 Oct '12 - 13:35.

SuggestionHorizontal and vertical connectors instead of diagonal (direct TreeConnections) [modified]membergeheim8123 Jul '12 - 8:22 
I wanted the TreeContainer control to show horizontal and vertical TreeConnections as in most standard Org Chart diagrams. In order to do that I modified:
 
Class: LayeredTreeDraw.cs Method: DetermineFinalPositions
 
The idea: My idea was to add the extra points to the TreeConnection so it was rendered as horizontal and vertical lines in case a given parent node had more than one child node. Fortunately TreeContainer.OnRender method worked fine without changes. It deals with multiple TreeConnection points by default.
 
Drawback: The only drawback I've found with this approach is that some lines
are drawn on top of each other causing some connections to appear bolder in the overlapped line segments.. Other than that it does what I wanted. If anyone has a solution to this line overlap I'd be very grateful if you could share it.
 
        private void DetermineFinalPositions(ITreeNode tn, int iLayer, double pxFromTop, double pxParentFromLeft)
        {
            double pxRowHeight = _lstLayerHeight[iLayer];
            LayeredTreeInfo lti = Info(tn);
            double pxBottom;
            DPoint dptOrigin;
 
            lti.pxFromTop = pxFromTop + CalcJustify(tn.TreeHeight, pxRowHeight);
            pxBottom = lti.pxFromTop + tn.TreeHeight;
            if (pxBottom > PxOverallHeight)
            {
                PxOverallHeight = pxBottom;
            }
            lti.pxFromLeft = lti.pxLeftPosRelativeToParent + pxParentFromLeft;
            dptOrigin = new DPoint(lti.pxFromLeft + tn.TreeWidth / 2, lti.pxFromTop + tn.TreeHeight);
            iLayer++;
            TreeNodeGroup tng = GetChildren(tn);
            foreach (ITreeNode tnCur in tng)
            {
                List<DPoint> lstcpt = new List<DPoint>();
                LayeredTreeInfo ltiCur = Info(tnCur);
                lstcpt.Add(dptOrigin);
                DetermineFinalPositions(tnCur, iLayer, pxFromTop + pxRowHeight + _pxBufferVertical, lti.pxFromLeft);
                //If parent node has only one child then no changes here, just a normal TreeConnection
                if (tng.Count == 1)
                {
                    lstcpt.Add(new DPoint(ltiCur.pxFromLeft + tnCur.TreeWidth/2, ltiCur.pxFromTop));
                    _lsttcn.Add(new TreeConnection(tn, tnCur, lstcpt));
                }
                else
                {
                    //If parent node has more than one child then add the extra connection points
                    double halfHeight = (ltiCur.pxFromTop - dptOrigin.Y)/2;
                    DPoint p2 = new DPoint(dptOrigin.X, dptOrigin.Y + halfHeight);
                    lstcpt.Add(p2);
                    DPoint p3 = new DPoint(ltiCur.pxFromLeft + tnCur.TreeWidth / 2, dptOrigin.Y + halfHeight);
                    lstcpt.Add(p3);
                    DPoint p4 = new DPoint(ltiCur.pxFromLeft + tnCur.TreeWidth / 2, ltiCur.pxFromTop +5);
                    lstcpt.Add(p4);
                    _lsttcn.Add(new TreeConnection(tn, tnCur, lstcpt));
                }
            }
        }
 
Update: The issue with the rendering of connection lines has been solved thanks to user @Member 8767345 who shared the one-liner fix. Here his explanation:
 
"The problem with the drawing of the lines is caused because they are drawn in logical pixels. The antialiasing system spreads the line over multiple pixels if it doesn't align with physical device pixels, making them blurry and sometimes bolder."
 
This can be fixed by setting the EdgeMode to aliasedt in the TreeContainer.OnRender method:

base.OnRender(dc);
if (Connections != null)
{
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
...
 
When setting that EdgeMode the TreeConnections rendering is now very clean and smooth
Thanks,
Adolfo


modified 27 Sep '12 - 9:00.

GeneralRe: Horizontal and vertical connectors instead of diagonal (direct TreeConnections)memberMember 876734527 Sep '12 - 2:31 
Thanks for sharing this modification - exactly what I needed.
The problem with the drawing of the lines is caused because they are drawn in logical pixels. The antialiasing system spreads the line over multiple pixels if it doesn't align with physical device pixels, making them blurry and sometimes bolder.
 
This can be fixed by setting the EdgeMode to aliasedt in the TreeContainer.OnRender method:
 
base.OnRender(dc);
if (Connections != null)
{
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
...
GeneralRe: Horizontal and vertical connectors instead of diagonal (direct TreeConnections)membergeheim8127 Sep '12 - 2:53 
Wow that single line made the difference!! Wink | ;) Node connections look much more cleaner and smoother now.
 
Thanks so much! I thought the fix was going to be much more elaborated.
Glad you liked my idea.
 
Cheers,
Adolfo
QuestionObject Data Binding support..membergeheim8112 Mar '12 - 5:01 
Is it possible to use the HierarchyDataTemplate to bind the tree to a business object?
 
Thanks!
QuestionRandom horizontal spacing issuememberOzzah841 Dec '11 - 13:46 
Hi darrellp, first of all let me say that I really love this code. Before I stumbled on this I was having huge headaches figuring out how to determine the X and Y positions of nodes on a tree.
 
I am only using your tree layout code and using my own code to draw the nodes with GDI.
 
When I have just a few items in a few levels on my tree like in your examples, the positions are perfect. However as my tree becomes very large and very wide, your algorithm seems to add occasional extra horizontal space between nodes - sometimes quite a lot of space - for unknown reason. Have you (or anyone else?) seen this behaviour?
 
If I can't figure this out then I might have to re-write your layout algorithm :(
AnswerRe: Random horizontal spacing issuememberdarrellp1 Dec '11 - 19:36 
I'm embarrassed to admit that I saw the same thing. I figured it out but I honestly wasn't motivated enough to fix it at the time. Unless the tree gets really wide it won't appear, but you're right - if it gets wide enough it can pop up. I'd like to tell you that I'll go back and figure out the details but I'm off on some other stuff so if it happens at all, it's probably going to be a while. If you want to mail me with any questions, I'll try and remember what I can about the code. I don't think it's too awfully bad at least as far as understanding goes. Then again, I'm a biased observer.
GeneralRe: Random horizontal spacing issuememberOzzah844 Dec '11 - 12:32 
Ahh, ok. I'll take a deeper look at your code and see what I can find.
 
Incidentally, I downloaded the paper "A Node-positioning Algorithm for General Trees" by John Q. Walker II (1990). It describes a technique for drawing these sorts of trees, and mentions that there are some deficiencies in the Tilford algorithm, though from the description in the paper I don't think it's related to this horizontal spacing bug.
AnswerRe: Random horizontal spacing issuememberdarrellp1 Dec '11 - 19:38 
Oh - and P.S. - I'm glad you're using the library - seems like everybody was using the WPF code, but the library should make it much more applicable to other situations as you're demonstrating (modulo the embarrassing problem you unfortunately brought up! Frown | :( ).
QuestionHorizontal CentrememberJez Lukins22 Jul '11 - 3:54 
Great bit of code and I can appreciate the effort as I tried doing a "simple" tree structure myself.
 
My question, which I think is different from the previous one on horizontal alignment, is that I would like the nodes to stay in the same position when collapsing and un-collapsing. Put simply, if the root node is centered when everything is not collapsed, it should stay put when they are collapsed. My SilverTreeContainer is being bound to a ContentControl and I have everything set to centre.
 
Do you have any advice?
AnswerRe: Horizontal Centrememberdarrellp22 Jul '11 - 12:18 
I guess I don't quite understand the question. When you collapse a branch, the definition of "centering" (or at least what I think of as "centering") changes. You could certainly change it to just calculate values as though the entier graph was being drawn and then not draw the collapsed parts. The resulting graph would look out of kilter, but if that's what you're looking for I don't think it's hard. I haven't got the code in front of me but I'm pretty sure that there's an attachable property for whether a node is collapsed or not. Just look up where that property is used and keep the part that avoids drawing the children and eliminate the part which recalculates based on missing children.
 
Since I'm not 100% sure what you're asking for, I'm not sure whether this answers your question or not.
GeneralRe: Horizontal CentrememberJez Lukins25 Jul '11 - 2:22 
Thanks for this Darrell, I will look into it, but if you are not sure you understand it's probably becase I didn't explain it very well.
 
Imagine a node with two children which is centered in the screen. When I collapse this node, it "jumps" to the left because there is no longer any need to be as wide as it was. I should explain that my SilverTreeContainer is bound to a ContentControl which is itself in a ScrollViewer with everything set to Stretch. I think the easy way to explain this is that I want the top node to be centered at all times.
GeneralRe: Horizontal Centre [modified]membermetonym15 Mar '12 - 4:27 
Great library, thanks Darrell!
 
I have the same question and been trying to figure out how and where to change the code for two hours now, but I can't find where "the part that avoids drawing the children" and "the part which recalculates based on missing children" are exactly.
 
It seems that the Collapsed property of TreeNode leads to the children of the collapsed nodes being ignored everywhere, because LayeredTreeDraw.GetChildren() only returns non-collapsed nodes.
The TreeNode.VisibleDescendants list is used to set the visibility of the collapsed TreeNodes.
 
If I construct the tree with all nodes, by having LayeredTreeDraw.GetChildren() return all nodes and have LayeredTreeDraw.GetChildren() return all nodes, and only set the collapsed nodes to invisible inside TreeNode.CollapsePropertyChange, the Panel's Measure function will ignore them by setting their DesiredSize to (0,0).
 
If someone has figured out how to proceed from there, I'd love to hear it.

-- modified 15 Mar '12 - 11:57.
GeneralIMPORTANT: Fix for connection lines not being redrawn [modified]memberdarrellp22 Apr '11 - 4:34 
I'm not sure what changed in 4.0 to cause OnRender to not be called when the control's measure and arrange are being changed, but it breaks my tree control. In 3.5, collapsing a TreeNode caused the parent TreeContainer's measure and arrange to be affected. These, in turn, caused a render of the parent and the connecting lines were drawn in the OnRender() that was called as a result. In 4.0, apparently changing the measure and arrange does not automatically invoke a rerender so my connection lines weren't being redrawn properly. Here's a fix:
 
In TreeContainer.MeasureOverride() put a call to InvalidateVisual() as the first thing. This is better than my previous fix since it's more general.
 
This forces the OnRender() function of the parent to be called which draws the connection lines properly. I guess I'll eventually get around to putting this up on CodeProject afresh, but for the moment, this note will have to do.

modified on Friday, April 22, 2011 11:10 AM

GeneralRe: IMPORTANT: Fix for connection lines not being redrawn [modified]membergeheim8112 Mar '12 - 4:41 
That did the trick! thanks for the fix and thanks for sharing this cool project
GeneralInvalidOperationExceptionmemberezgar20 Apr '11 - 17:15 
Hi, im getting this InvalidOperationException when i want to use the control; im working in C# with winforms, i use a elementHost to host the tree container, but when i want to add a root i get this:
 
InvalidOperationException
The NameScope to register the Name '_TreeNode0' was not found
 
The problem is in your metod SetName:
 
private void SetName(TreeNode tn, string strName)
{
	tn.Name = strName;
	RegisterName(strName, tn);
}
 
In the line RegisterName(strName, tn);
 
I don´t really know anything about wpf so im not sure what is the problem, i have done this code in base to VisLogTree example you give, this is my code:
 
TreeContainer.TreeContainer tree = new TreeContainer.TreeContainer();
System.Windows.Controls.Button button = new System.Windows.Controls.Button();
button.Content = "root";
button.Name = "root";
tree.AddRoot(button);
 
Any kind of help would be great, thanks.
GeneralRe: InvalidOperationExceptionmemberdarrellp21 Apr '11 - 4:03 
I believe that the difference is that my TreeContainer is already placed in a window while yours isn't. That window provides a namescope for the names added. If you look at the docs for RegisterName you'll see that "the implementation will check successive parent elements until it finds the applicable NameScope implementation, which is found by finding an element that implements INameScope." Since your TreeContainer has no parent elements, this search is going to fail and you're going to get the error message you wrote about. It makes sense if you stop and think about it. These names aren't just "names" that anybody, anywhere can reference. It's not a "name" in the sense of a variable name in C#. It's a name that, for instance, animations can use to locate items and it has to be done within this context of a namescope which is attached to a container.
 
It raises an interesting question which I don't think I've ever thought about - perhaps TreeContainer can/should implement INameScope. I'm not 100% sure of the ramifications so I'm hesitant to just jump in and do it, but it bears some thought. It might hinder doing animations, for instance, on the tree nodes.
 
You can read more here:
http://msdn.microsoft.com/en-us/library/ms746659.aspx[^]
Generalwriting info on connectorsmembergozdeu858 Mar '11 - 22:34 
Is it possible to add text on connectors?
GeneralRe: writing info on connectorsmemberdarrellp9 Mar '11 - 3:54 
Not as things stand, but it's only code. Look for the part where the connectors are drawn and add some code to draw text. The main question will be about positioning the text so it's obvious which text belongs to which connector but once you make a decision on that the rest should be pretty easy.
QuestionAppreciate the article but I'm having a little trouble... [modified: corrected typos]memberHarriBanerjee10 Feb '11 - 7:54 
Hi,
 
Firstly, it goes without saying, this is a brilliant article and I'm sure you're code is going to help me with my 3rd year University project Smile | :)
 
But... I've just downloaded the .zip and run the solution in Visual Studio 2010 but there seems to be some problem with the horizontal alignment of nodes - if I debug the solution and click to collapse the root node (labelled StackPanel) the nodes disappear but the vertices persist pointing from/to where the nodes were and the root node aligns itself to the left of the panel.
 
Just wondering if this is a problem on my machine/VS 2010?
 
Thanks for reading,
Harri
 
Note: Just noticed that "TreeContainerTest"'s App.xaml throws a problem message at me when I try to open it saying that there is an undefined CLR namespace in the XAML line xmlns:local="clr-namespace:TreeContainer"
Not sure whether that might have anything to do with it?
modified on Thursday, February 10, 2011 7:20 PM

AnswerRe: Appreciate the artible but I'm having a little trouble...memberdarrellp10 Feb '11 - 8:44 
Hmm...
 
I really don't know what the problem is. If I was there, I could probably figure it out but long distance debugging through email is pretty tough. It kinda sounds like your screen isn't refreshing properly.
 
The error message you're getting says it can't find the TreeContainer library when it's trying to parse your XAML. That could mean that it wasn't built properly or that it can't find it in the place it expects.
 
I just can't say too much at this distance. If you've got somebody else who knows something about CLR there, maybe they can take a look.
 
Darrell
GeneralRe: Appreciate the artible but I'm having a little trouble...memberRazputin23 Feb '11 - 5:27 
Hi,
I'm having the same problem it works fine until i resize the control/window then for whatever reason when the Collapsed property is changed it doesn't re-render the control.. just does measure and arrange.
The quick and dirty way around this is to invalidate the treecontainer which will force a re-render but its not nice..
I'm gonna dig around a bit and let you know if I can find out whats happening.
 
Excellent article btw well written and very useful
GeneralRe: Appreciate the artible but I'm having a little trouble...memberdarrellp23 Feb '11 - 7:22 
That would be great! I'd love to help, but everything is working fine up here so it's pretty tough to solve a problem you can't see. I'm guessing some sort of odd difference in the specific hardware you're using though WPF should work the same on all hardware. I assume the silverlight version on my website works okay for you? I think I mention the website in the article, but here's a link:
 
http://www.darrellplank.com/Experiments/Silverlight/SilverTreeTest/SilverTreeTest.html
GeneralRe: Appreciate the artible but I'm having a little trouble...memberRazputin28 Feb '11 - 1:30 
yeah that works haven't had time to investgate the problem in WPF yet but I've noticed that it only happens when I resize the panel above a certain threshold. Typically if there are only a few nodes and and a large panel so lots of empty space, after the panel is resized for whatever reason it will no longer re-render when the Collapsed property is changed.
GeneralRe: Appreciate the artible but I'm having a little trouble...memberdarrellp28 Feb '11 - 4:54 
About all I can tell you is that the "WPF" portion of it is really a pretty thin shell over all the arranging machinery underneath and you could probably throw it away and write it again from scratch without that much trouble. After all, all you need in WPF is somebody to tell you where to put things. After that, there's not that much to do - just put them there which is what the small shell I wrote does. You could scrap the XAML part and the custom control part and just call the arranging assembly directly from inside your code - if nothing else just for testing purposes. It should be really simple to do.
GeneralRe: Appreciate the artible but I'm having a little trouble...memberRazputin7 Mar '11 - 0:58 
yeah sure was just checking you hadn't identified this problem.. looks like a WPF 'feature' to me probably to do with how dependencyproperty metadata is managed internally, seeing as though this defines the behaviour when the value is changed.. or it could just be that I've not read enough.. if I do get time to look into it I will, for now its not a biggy.
GeneralRe: Appreciate the artible but I'm having a little trouble...membergeheim8116 Mar '12 - 10:32 
Hi Darrell, could you please point me to the source code of the SilverTree version you have in your website: http://www.darrellplank.com/Experiments/Silverlight/SilverTreeTest/SilverTreeTest.html[^]
The zip file in codeproject does not contain the same version.
 
Thanks and great work by the way..
GeneralRe: Appreciate the artible but I'm having a little trouble...memberdarrellp22 Apr '11 - 5:14 
See my "IMPORTANT" comment for a fix.
AnswerRe: Appreciate the article but I'm having a little trouble... [modified: corrected typos]memberdarrellp22 Apr '11 - 4:00 
Okay, I'm seeing the problem. For some reason, OnRender (where the connection lines are drawn) is not being called when a collapse occurred. This used to happen in 3.0 so something has changed under the hood. I have the collapse property's metadata say that it affects the parent's measure and arrange and you'd think that if their measure and arrange changed they'd have to rerender and apparently they did under 3.5 but not under 4.0. I'm not sure why this is. There's no "AffectsParentRender" metadata type so I'll have to look into this. Aargh!
AnswerRe: Appreciate the article but I'm having a little trouble... [modified: corrected typos]memberdarrellp22 Apr '11 - 4:27 
Okay, I'm not sure why things changed, but here's a fix. In TreeNode.CollapsePropertyChanged() put the following lines at the end of the if block:
 
	TreeContainer tc = tn.Parent as TreeContainer;
	if (tc != null)
	{
		tc.InvalidateVisual();
	}
 
This forces the OnRender() function of the parent to be called which draws the connection lines properly. I guess I'll eventually get around to putting this up on CodeProject afresh.
GeneralGreat articlememberJCox_Prog6 Feb '11 - 4:01 
This article is a great starting place for showing how to employ a layout algorithm with WPF.
 
Thanks.
GeneralRe: Great articlememberdarrellp10 Feb '11 - 8:37 
Thanks!
GeneralDocumentation and testmembermoon102420 Jan '11 - 3:49 
Hello,
 
I was wonderring if there is any documentation or unit tests.
 
Regards.
GeneralRe: Documentation and testmemberdarrellp20 Jan '11 - 5:15 
The CodeProject page and the sources are the documentation. I don't remember whether I put in unit tests or not. Look at the source code and find out.
QuestionWindows Forms?memberBrian C. Hart, Ph.D.26 Oct '10 - 10:20 
I am using Windows Forms. Do you have any insight as to how I might implement a Graph Tree using Windows Forms and C#?
Sincerely Yours,
Brian Hart

AnswerRe: Windows Forms?memberdarrellp26 Oct '10 - 12:42 
I'd just use the underlying library and tell it how big each node is and how they're connected. It will tell you where to place each node. I think it tells you where to draw the connecting lines also but it's been a long time since I wrote this. Draw your nodes on a sizing panel, make it the right size and you should be good to go. Of course it's your responsibility to determine the size of your nodes and draw them since I don't know anything about them, but that's the easy part. The hard part is arranging the which the library function here provides.
QuestionNice - but could it use MVVM?memberdaniel radford9 Dec '09 - 6:00 
I'm trying to see how this could be adapted to use WPF binding to bind to a hierarchical view model in the same way that the tree view does instead of having to manually create the structure in code. How easy/hard would this be?
AnswerRe: Nice - but could it use MVVM?memberdarrellp9 Dec '09 - 6:19 
Well, you can build it in Xaml - doesn't have to be in code. I know that doesn't help much in MVVM - at least not if it's dynamic. I don't really know about databinding on a custom control. I've never done it and can't really say I know much about it off the top of my head. If you do know something about it then you're a much better person to answer your own question than I am I'm afraid. Might be a fun project to dig into sometime, though. Sorry I'm of no help here. The control itself is pretty simple so if you know something about it, I doubt it would be too awfully difficult. Really, the article is more about the algorithm than about the particular implementation in WPF which is a fairly thin layer.
GeneralCyclesmemberMaze22 Sep '09 - 7:56 
Hi Darrel,
 
Can I have cycles in your graph control?
 
Thank you
 
who dares wins!

GeneralRe: Cyclesmemberdarrellp22 Sep '09 - 15:11 
Sorry - strictly works with trees.
Questionhow to draw horizontal Graph tree????memberShailRaj17 Jun '09 - 21:31 
hello darrell,
 
your graph tree is vertical(top to bottom), but my need is to draw horizontal tree(left to right)
i tried some ways but didn't get the proper solution can u help me???????
 

one more thing that i want is, to add my icons in the node???
 
i m drawing rectangle instead of UI Element and need to draw icon in it..
how to do that????
 

 
Raj
Questionconnect to Databasememberyhali17 Jun '09 - 20:09 
Dear darrellp
Kindly please
Do you have any Graph Tree connect to Database like family tree in Silverlight?
 
Please share it with us
 
many thanks
QuestionWalker's algorithm?memberKen Domino9 Mar '09 - 5:15 
Hi Darrell,
 
Not to be too picky, but this looks like Walker's algorithm (A node-positioning algorithm for general trees, p 685-705, John Q. Walker II, Software: Practice and Experience, July 1990), not the Reingold/Tilford algorithm (Tidier Drawings of Trees
Reingold, E.M.; Tilford, J.S.; IEEE Transactions on Software Engineering, Volume SE-7, Issue 2, March 1981 Page(s):223 - 228). In your description, the link you gave points to an article in DDJ written by Walker himself.
 
Also, you might want to check out the newer algorithm by Buchheim et al. (Drawing rooted trees in linear time, p 651-665, Christoph Buchheim, Michael Jünger, Sebastian Leipert, Software: Practice and Experience, May 2006). It's better organized (especially in how it computes the left and right contours used in determining shift distances), and you can adapt it more easily to other tree layouts, including something that works like an RT layout for general trees.
 
Ken Domino
AnswerRe: Walker's algorithm?memberdarrellp9 Mar '09 - 6:57 
Hey - thanks for the info! You're absolutely right - I did implement Walker's algorithm - guess I just wasn't clear in my own head on where the distinction lay. The article you mention does a MUCH better job of describing stuff than the one by Walker in my opinion, even when summarizing the various historical algorithms. I never saw mention of the "threads" in any of the original articles but realized that they were necessary so just implemented it that way on my own. It seemed to me that many of the details such as this were "swept under the rug" in those original articles. Buchheim's article calls them out directly.
 
Anyway, I'm looking at his article and will consider altering the algorithm. I'm in the process of fixing some stuff up and consolidating my Silverlight version with the normal WPF version so will be making a revision soon anyway. At the very least, I'll change the name in the text to Walker. I'll have to see how many complications Buchheim introduces (haven't gotten through the entire article yet). Regardless, I much prefer the newer article to the old ones I had been looking at.
 
Thanks again!
 
Darrell
AnswerRe: Walker's algorithm?memberdarrellp10 Mar '09 - 18:51 
I've been reading Buchheim's paper sporadically and I think I essentially have an equivalent solution. His optimization is to avoid having to calculate which two immediate child nodes we have to spread out nodes evenly between when we come to a conflict at a lower level. He seems to be doing it by keeping pointers in the child nodes. I keep an array of which child node is the ancestor node for every node in the right contour generated thus far. Since the right contour is just one node per maximum level in any of our child subtree forest, this is just an array of one index for a responsible child node for ever level of the child forest. When I have a conflict on level N, I just look up level N in the array and I know immediately which child node we have to space between. I don't think I ever fully grokked Walker's given algorithms (perhaps because I never read Tilford's original paper which I take it now, introduced a lot of the terminology Walker tacitly assumes such as modifiers) so I mainly went by his careful walkthrough and I think in that walkthrough, he glosses over all this by saying something like "space children c through e equally". I had to figure out on my own how he might come up with "c through e" and I think this is how I did it and just assumed that he had done something similar. Keeping track of this array is linear since each node at most changes one element of this array one time. This solves the problem of finding the ancestor nodes which is by far the bulk of the non-linearity I think since it must be done whenever there is a conflict and is really time consuming to calculate. The other thing the Buchheim does is to avoid shifting trees more than once but this seems like a real corner case where successive children have to have larger subtrees at increasing levels - you have to kind of stand on your head to get this to happen at all. I may do it, but I think it would require a bit more of a change than I'm ready to put in for such a rare case. Thanks for the article, regardless. It really helped me understand some things I didn't really know before.
 
Darrell
AnswerRe: Walker's algorithm?memberztrgh21 Jun '09 - 11:37 
Hello,
Got a link or a way to read Buchheim's article?
GeneralRe: Walker's algorithm?memberKen Domino21 Jun '09 - 14:29 
The article is:
 
Buchheim, C., Junger, M. and Leipert, S. Drawing rooted trees in linear time.
SOFTWARE PRACTICE AND EXPERIENCE, 36, 6 2006), 651.
 
http://www3.interscience.wiley.com/journal/112418290/abstract[^]
 
There is a minor typo in the algorithm, but it works. I have an implementation in C# if you are interested. The algorithm may be difficult to understand without the paper, and it assume that you understand Walker's algorithm, including the meaning of Prelim, Modifier, and Thread attributes, contours and nearest common ancestors. The layout is exactly the same between Buchheim and Walker.
QuestionCurious as to the benefits?memberroland rodriguez10 Feb '09 - 10:16 
Hi Darrell,
 
This looks great. I did something similar by subclassing a standard WPF treeview control and applying a new control template here: http://wpfblog.info/2008/05/27/wpf-treeview-org-chart-a-bit-of-refactoring/[^]
 
If feels like you're doing the same thing albeit with a bit more complexity. Is that the case or am I missing some great benefit of your technique that could enhance my own? Wink | ;-)
 
Great article either way and a very clever implementation!
 
Regards,
 
Roland

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 23 Feb 2009
Article Copyright 2008 by darrellp
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid