I am working on an ASP.Net page, and there is tree view in it. In the tree view some nodes have nested nodes like branches. I have data in a list of custom objects in the following format:
Id, Description, parentId
Right now, I am using a function to recursively add nodes to the tree view. The following is code snippet:
private bool findParentAddNode(string id, string description, string parentid, ref List<CustomTreeNode> treeList)
{
bool isFound = false;
foreach (CustomTreeNode node in treeList)
{
if (node.id == parentid)//if current node is parent node, add in it as its child
{
node.addChild(id, description, parentid);
isFound = true;
break;
}
else if (node.listOfChildNodes != null)//have child nodes
{
isFound = findParentAddNode(id, description, parentid, ref node.listOfChildNodes);
if (isFound)
break;
}
}
return isFound;
}
The above technique works well but, for more then 30K nodes, its performance is slow. Please suggest an algorithm to replace this recursive call with loops.
As it recurses down the tree, the code is doing a linear search over the lists of child nodes.
This means that for randomly distributed parent ids, after adding N nodes to the tree it will on average search N/2 nodes for the parent before adding the N+1th node. So the cost will be O(N²) on the number of nodes.
Instead of a linear scan, create an index of id to node and use that to find the parent quickly. When you create a node and add it to the tree, also add it to a Dictionary<int,CustomTreeNode>. When you want to add a node to parent, find the parent in the index and add it. If addChild returns the child it creates, then the code becomes:
Dictionary<int,CustomTreeNode> index = new Dictionary<int,CustomTreeNode>();
private bool findParentAddNode(string id, string description, string parentid)
{
if ( !nodeIndex.TryGetValue ( parentid, out parentNode ) )
return false;
index[id] = parentNode.addChild(id, description, parentid);
return true;
}
You will need to add the root of the tree to the index before using findParentAddNode.
An iterative version of a breadth-first search should be something like the following:
var rootNodes = new List<CustomTreeNode> { new CustomTreeNode() };
while (rootNodes.Count > 0) {
var nextRoots = new List<CustomTreeNode>();
foreach (var node in rootNodes) {
// process node here
nextRoots.AddRange(node.CustomTreeNode);
}
rootNodes = nextRoots;
}
That said, this isn't tested, and since it's a BFS, isn't optimal w/r/t memory. (Memory use is O(n), not O(log n) compared to DFS or iterative-deepening DFS.)
You can return data in xml format from sql server database using for xml
then bind it to treeview control.
Related
I need to filter Tree on Winforms.
basically the Tree View contain the list from the registry with all the key's on the branch
now,when i run the method to search some values in all the tree, the result I get is just part of the tree and I cant save the branch were connected from the result to the root.
there is any way to save the hierarchy that in the end the result will showed correctly. ?
I tried to put it on Dictionary that contains string with the level,index, and full path. any idea?
this is the search code.the Dictionary basically to show the results. for testing
Dictionary<string, TreeNode> Result = new Dictionary<string, TreeNode>();
private void SearchforNodes(TreeNodeCollection nodes)
{
bool x = true;
while (x)
{
foreach (TreeNode item in nodes)
{
x = ReadAllKeys(item);
}
}
}
bool flag = true;
private bool ReadAllKeys(TreeNode node)
{
foreach (TreeNode item in node.Nodes)
{
if (item.Nodes.Count > 0)
{
ReadAllKeys(item);
}
else
{
var result = SearchKey(item);
if (result != null)
{
if (!Result.Keys.Contains(string.Format("Index: {0} level: {1} Text: {2} FullPathTree: {3} ", result.Index, result.Level, result.Text, result.FullPath)))
{
Result.Add(string.Format("Index: {0} level: {1} Text: {2} FullPathTree: {3} ", result.Index, result.Level, result.Text, result.FullPath), result);
flag = false;
}
else
{
flag = false;
}
}
}
}
return flag;
}
private TreeNode SearchKey(TreeNode node)
{
if (node.Text.ToUpper().Contains(txtSearch.Text.ToUpper()))
{
return node;
}
else
{
return null;
}
}
For what I remember in using the Treeviews, in the class/classes used to contain the data used for the tree we usually added a Parent or a ParentID field so that each node until the leaf knows who is it's parent.
this is useful to set which nodes other than the leaf must be visible.
Our data class was usually in a datatable or a collection but also always put in the tag element of the treenode to be able to have access to all information from the node. (the Tag is an object so you can attach your classes or datarows and then retrieve them with a cast)
If you just use the standard node element in the tree as your data source, I'm sure the node has a Parent information so that you can add to the collection of the visible elements you want to display after filtering all the elements up to the root.
Another thing I found useful when working with tree data is to have a plain collection of all the nodes for example a List updated when building the tree and, use that collection to set the visibility of the node and its parents, ancestors and so on.
I'm not sure this is exactly what you need but I hope it can set you to the right path.
Hello I currently have a TreeView with the following structure:
Root
Child
Root
Child
Root
Child
Child
RootN
ChildN
The TreeView structure can basically have NRootNodes - NChildren and the NRootNodes can have NRoots and NChildren so basically just like Windows Explorer Window.
My current issue that I have is that I have to get all the Parents or Root, in this case Roots / RootN and then I have to Remove all of their Child Nodes, in this case Child / ChildN. In the end I have to have only the Parent Nodes and then Clone them so I can move them to a different location within the TreeView.
RootNodes have a unique Tag - Folder and ChildNodes have another unique Tag - Calculations, as I have said earlier, I have to get rid of all Calculations in the Selected Node so only the Structure of that Selected Node will Remain.
Basically in the end I have to have something like this:
Root
Root
Root
Root
Root
I have a recursive method that "scans" the SelectedNode and gets all the Parents:
public List<TreeNode> CollectParentNodes(TreeNodeCollection parentCollection, List<TreeNode> collectedNodes)
{
foreach (TreeNode node in parentCollection)
{
if (!collectedNodes.Contains(node.Parent))
{
collectedNodes.Add(node.Parent);
parentNodeAdded = true;
}
if (node.Level != 0 && node.Tag.ToString() != Enumerations.NodeType.Calculation.ToString())
collectedNodes.Add(node);
if (node.Nodes.Count > 0)
CollectParentNodes(node.Nodes, collectedNodes);
}
parentNodeAdded = false;
return collectedNodes;
}
In the end I have a List that will hold all the Parents but the problem I'm facing is that that Parents also contain their descendents, in this case the Calculations
I have searched Google and StackOverFlow but I could not find anything of help, I appologize in advance if this has already been answered.
Thank you.
You can create an extension method GetAllNodes for TreeView that return List
Remember using using System.Linq; at top of your code
public static class Extensions
{
public static List<TreeNode> GetAllNodes(this TreeView tree)
{
var firstLevelNodes = tree.Nodes.Cast<TreeNode>();
return firstLevelNodes.SelectMany(x => GetNodes(x)).Concat(firstLevelNodes).ToList();
}
private static IEnumerable<TreeNode> GetNodes(TreeNode node)
{
var nodes = node.Nodes.Cast<TreeNode>();
return nodes.SelectMany(x => GetNodes(x)).Concat(nodes);
}
}
And the usage will be:
var result = this.treeView1.GetAllNodes().Where(x => x.Tag == "FOLDER").ToList();
Remember to add namespace of your extensions class at top of your code wherever you want to use it.
As an example you can set All nodes with tag of Folder to be in Red forecolor:
var result = this.treeView1.GetAllNodes().Where(x => (x.Tag as string) == "FOLDER").ToList();
result.ForEach(x => x.ForeColor = Color.Red);
And here is an Screenshot
This will create a new tree with the selected node as root and which child nodes consists only of nodes that are tagged "Folder".
You need to create a copy constructor (or extension method) to deep copy the node to prevent the manipulation on the node objects to impact your original tree source:
public TreeNode CollectFolderChildNodes(TreeNode selectedNode)
{
if (selectedNode.Tag == "Calculation")
return null;
// Get all the children that are tagged as folder
var childRootNodes = selectedNode.Children.Where((childNode) => childNode.Tag == "Folder";
// Clone root node using a copy constructor
var newRoot = new TreeNode(selectedNode);
newRoot.Children.Clear();
foreach (var childNode in childRootNodes)
{
// Iterate over all children and add them to the new tree
if (childNode.Children.Any())
{
// Repeat steps for the children of the current child.
// Recursion stops when the leaf is reached
newRoot.Children.Add(CollectFolderChildNodes(childNode));
}
else
{
// The current child item is leaf (no children)
newRoot.Children.Add(new TreeNode(childNode));
}
}
return newRoot;
}
I think this should do it, but I didn't tested it. But maybe at least the idea behind it is clear.
But as I mentioned before, maybe it's better to traverse the tree (using same ItemsSource) and set a property (e.g. IsHidingCalculations) to true so that only the folders will show up. You would need to implement an ItemsStyle and use a trigger that sets the items Visibility to Collapsed when your IsHidingCalculations evaluates to true.
To clone a node without its children you can create an extension method like this:
public static TreeNode CloneWithoutChildren(this TreeNode node)
{
return new TreeNode(node.Text, node.ImageIndex, node.SelectedImageIndex)
{
Name = node.Name,
ToolTipText = node.ToolTipText,
Tag = node.Tag,
Checked = node.Checked
}
}
and then:
collectedNodes.Add(node.CloneWithoutChildren());
I have a recursion function that builds a node list from an IEnumerable of about 2000 records. The procedure currently takes around 9 seconds to complete and has become a major performance issue. The function serves to:
a) sort the nodes hierarchically
b) calculate the depth of each node
This is a stripped down example:
public class Node
{
public string Id { get; set; }
public string ParentId { get; set; }
public int Depth { get; set; }
}
private void GetSortedList()
{
// next line pulls the nodes from the DB, not included here to simplify the example
IEnumerable<Node> ie = GetNodes();
var l = new List<Node>();
foreach (Node n in ie)
{
if (string.IsNullOrWhiteSpace(n.ParentId))
{
n.Depth = 1;
l.Add(n);
AddChildNodes(n, l, ie);
}
}
}
private void AddChildNodes(Node parent, List<Node> newNodeList, IEnumerable<Node> ie)
{
foreach (Node n in ie)
{
if (!string.IsNullOrWhiteSpace(n.ParentId) && n.ParentId == parent.Id)
{
n.Depth = parent.Depth + 1;
newNodeList.Add(n);
AddChildNodes(n, newNodeList, ie);
}
}
}
What would be the best way to rewrite this to maximize performance? I've experimented with the yield keyword but I'm not sure that will get me the result I am looking for. I've also read about using a stack but none of the examples I have found use parent IDs (they use child node lists instead), so I am a little confused on how to approach it.
Recursion is not what is causing your performance problem. The real problem is that on each recursive call to AddChildNodes, you traverse the entire list to find the children of the current parent, so your algorithm ends up being O(n^2).
To get around this, you can create a dictionary that, for each node Id, gives a list of all its children. This can be done in a single pass of the list. Then, you can start with the root Id ("") and recursively visit each of its children (i.e. a "depth first traversal"). This will visit each node exactly once. So the entire algorithm is O(n). Code is shown below.
After calling GetSortedList, the sorted result is in result. Note that you could make children and result local variables in GetSortedList and pass them as parameters to DepthFirstTraversal, if you prefer. But that unnecessarily slows down the recursive calls, since those two parameters would always have the same values on each recursive call.
You can get rid of the recursion using stacks, but the performance gain would probably not be worth it.
Dictionary<string, List<Node>> children = null;
List<Node> result = null;
private void GetSortedList()
{
var ie = data;
children = new Dictionary<string,List<Node>>();
// construct the dictionary
foreach (var n in ie)
{
if (!children.ContainsKey(n.ParentId))
{
children[n.ParentId] = new List<Node>();
}
children[n.ParentId].Add(n);
}
// Depth first traversal
result = new List<Node>();
DepthFirstTraversal("", 1);
if (result.Count() != ie.Count())
{
// If there are cycles, some nodes cannot be reached from the root,
// and therefore will not be contained in the result.
throw new Exception("Original list of nodes contains cycles");
}
}
private void DepthFirstTraversal(string parentId, int depth)
{
if (children.ContainsKey(parentId))
{
foreach (var child in children[parentId])
{
child.Depth = depth;
result.Add(child);
DepthFirstTraversal(child.Id, depth + 1);
}
}
}
I'm trying to represent a subway map type thing that has to be drawn progressively (like its growing).
My code all works perfectly but its unreadable. Basically it's a tree structure with recursive nodes and subnodes and my test code looks like this:
Children.Add(new TrackLine(800));
Children[0].Children.Add(new TrackSpot());
Children[0].Children[0].Children.Add(new TrackSplitter());
Children[0].Children[0].Children[0].Children.Add(new TrackRotate(-45));
Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackColorChange(Color.Red));
Children[0].Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackLine(100));
Children[0].Children[0].Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackRotate(45));
Children[0].Children[0].Children[0].Children[0].Children[0].Children[0].Children[0].Children.Add(new TrackLine(200));
Does anyone have any suggestions of how to fix that mess?
You're looking for a way to add it to the deepest child that has no children of it's own?
class Node
{
List<Node> children ;
public void addNode( Node newNode )
{
if( children.Count > 0 )
children[0].addNode( newNode ) ; // recursive call
// to ask first child to add newNode to it
else
children.Add( newNode ) ; // just add it to the children list of THIS node
}
}
To make that code more readable I'd use variables with appropriate names (since I don't know your subject domain, mine are probably not appropriate):
Children.Add(new TrackLine(800));
var lineA = Children[0];
lineA.Children.Add(new TrackSpot());
var spotA = lineA.Children[0];
spotA.Children.Add(new TrackSplitter());
var splitterA = spotA.Children[0];
splitterA.Children.Add(new TrackRotate(-45));
var rotateNeg45 = splitterA.Children[0];
rotateNeg45.Children.Add(new TrackColorChange(Color.Red));
var colorRed = rotateNeg45.Children[0];
colorRed.Add(new TrackLine(100));
var lineB = colorRed.Children[0];
lineB.Children.Add(new TrackRotate(45));
var rotatePos45 = lineB.Children[0];
rotatePos45.Children.Add(new TrackLine(200));
var lineC = rotatePos45.Children[0];
Another approach is to use strings as indexes for Children like this:
Children.Add(new TrackLine(800));
Children["lineA"].Children.Add(new TrackSpot());
Children["lineA"].Children["spotA"].Children.Add(new TrackSplitter());
However I probably would not do it because managing the string constants would be a mess on its own and it would probably require changing Children implementation.
How can one easily iterate through all nodes in a TreeView, examine their .Checked property and then delete all checked nodes?
It seems straightforward, but you aren't supposed to modify a collection through which you are iterating, eliminating the possibility of a "foreach" loop. (The .Nodes.Remove call is modifying the collection.) If this is attempted, the effect is that only about half of the .Checked nodes are removed.
Even if one were to use two passes: first creating a list of temporary indexes, and then removing by index on the second pass -- the indexes would change upon each removal, invaliding the integrity of the index list.
So, what is the most efficient way to do this?
Here is an example of code that looks good, but actually only removes about half of the .Checked nodes.:
foreach (TreeNode parent in treeView.Nodes)
{
if (parent.Checked)
{
treeView.Nodes.Remove(parent);
}
else
{
foreach (TreeNode child in parent.Nodes)
{
if (child.Checked) parent.Nodes.Remove(child);
}
}
}
(Yes, the intention is only to prune nodes from a tree that is two levels deep.)
Try walking through the nodes backwards. That way your index doesn't increase past your node size:
for( int ndx = nodes.Count; ndx > 0; ndx--)
{
TreeNode node = nodes[ndx-1];
if (node.Checked)
{
nodes.Remove(node);
}
// Recurse through the child nodes...
}
This will remove the nodes after enumerating them, and can be used recursively for n-tiers of nodes.
void RemoveCheckedNodes(TreeNodeCollection nodes)
{
List<TreeNode> checkedNodes = new List<TreeNode>();
foreach (TreeNode node in nodes)
{
if (node.Checked)
{
checkedNodes.Add(node);
}
else
{
RemoveCheckedNodes(nodes.ChildNodes);
}
}
foreach (TreeNode checkedNode in checkedNodes)
{
nodes.Remove(checkedNode);
}
}
If you want to do it efficiently you need to keep track of the checked nodes as they are checked. Store the checked tree nodes in a list (and remove them as they are unchecked).
If you have a unique key and a LOT of nodes to keep track of you might consider a dictionary as well. But if you are only dealing with 10-50 it probably wont make a big difference.
Then, instead of looping thru the entire tree you just loop thru your (smaller) list of nodes.
Whilst iterating you could construct a new list of unchecked items and then re-bind your treeview to that new list (discarding the old one).