TreeView - Node not expanding - c#

I have a TreeView with several nodes and if a special node (you will see in code) is deleted, the parent node should be expanded after updating the TreeView.
Here's how I tried it:
public void Remove(){
...
...
else if ((NodeType)n.Tag == NodeType.Attribute) //Here I simply check if it's the "special" parent
{
Commands.CommandAttributeRemove cmd = (Commands.CommandAttributeRemove)mAppData.CommandFactory.Create("AttributeRemove");
cmd.Data = n.Text;
cmd.ObjectClass = mObjectClass;
cmd.ObjectTypeName = n.Parent.Parent.Text;
list.Add(cmd);
mNodeToExpand = mTreeView.SelectedNode.Parent; //THIS LINE IS IMPORTANT... mNodeToExpand is a member variable which I use in UpdateData()
}
...
...
UpdateData();
}
public void UpdateData()
{
… //A lot of not so important stuff happening here (at least not important for what I want, I think)
...
//Update Selected Items (for the case that objects were deleted) and UpdateSelection
OnSelect();
//UpdateSelection();
this.Update();
Now that's interesting stuff:
if (mNodeToExpand != null)
{
mNodeToExpand.Expand();
mNodeToExpand = null;
}
}
This is how I tried to achieve what I want, but the node doesn't expand (it still has other children).
In the Remove() I also tried mTreeView.SelectedNode.Parent.Nodes.Add(new Node("Blabla")); but it doesn't even add a node.
And in the if(mNodeToExpand!=null) I also try to set the selectedNode to mNodeToExpand, but it gives me a NullReferenceException EVEN THOUGH I CHECK IF IT'S NULL IN THE IF. WHY?

This can't freaking be it. FullPath is apparently the path of the node in the treeview, so for example if you have Node2 which is a sub-node of Node1, then the FullPath of Node1 is "Node1" and of Node2 "Node1//Node2" … Now the thing is that FOR SOME WEIRD REASON my mNodeToExpand loses the Information of FullPath and suddenly it's only "Node2" instead of "Node1//Node2"... Because of that it isn't expanded cause the TreeView doesn't find a node with this FullPath… Now what I did is that I stored the FullPath in a string and when I want to expand it I search for the node with this FullPath and then expand what is returned. This is so stupid, cause imagine a list of 1 000 000 nodes. This could take very long. I'm starting to really dislike this Crownwoods.DotNetMagic library but it's already existing Code I'm working on.

Related

Make function dynamic / use Iteration / Make DRY

I want to make this function dynamic/Dry
rather than having to do multiple loops, do one loop /recursively
step1: The function receives a path
from that path it creates a Dir Model for the parent directory,
if the parent has sub directories, it adds them as models, to the parent subdirectory list,
step2: for each of the sub directories it repeats what it did in step 1, with the child being the parent to its sub directories if it has them,
this should repeat as long as a subdirectory to a parent has subdirectories,
finally, it returns the main Directory with its sub directories, as models with their own subdirectory models
public string RepoAsJson(string repoPath)
{
string path = #"C:\parentDir\subDir\subSubDir";
string[] parentDir = Directory.GetDirectories(path);
RepoDirModel model = new RepoDirModel();
foreach (string dir in parentDir){
RepoDirModel model1 = new RepoDirModel();
model1.Name = rket.Split(Path.DirectorySeparatorChar).Last();
model.SubDirectories.Add(model1);
foreach (string sub in Directory.GetDirectories(rket))
{
RepoDirModel model2 = new RepoDirModel();
model2.Name = sub.Split(Path.DirectorySeparatorChar).Last();
model1.SubDirectories.Add(model2);
foreach (string subSub in Directory.GetDirectories(sub))
{
RepoDirModel model3 = new RepoDirModel();
model3.Name = subSub.Split(Path.DirectorySeparatorChar).Last();
model2.SubDirectories.Add(model3);
}
}
}
return JsonSerializer.Serialize(model);
}
Remove nested loops / use Iteration / Make function dynamic
Since this seems to be some sort of course work / homework / training (maybe?), I am not going to post a working solution, but rather give you some hints.
First of all: What is the common "thing" your existing code does?
It gets the list of subdirectories from a specific path string and adds those into a tree structure.
That's it. So the first thing you might want to do is "extract" this functionality into a function or method, that only works on its parameters (no outside fields, "global vars" or something like that). You pass it the node to expand and the path to analyse and it will take care of one step.
Now, if you want to go into recursion, you need some additional things:
Do you want to go deep first or wide first?
When do you stop?
The second question is rather easy here: If there are no children (i.e. subdirectories) of the current subject, we are done - no further recursion.
The answer to the first one may not be so relevant in this setting, but think about it anyway. If for example you are looking for a specific leaf or node in the tree it might be relevant if it is more probable that you will find it quicker going deep first or go level by level.
But anyway: If you want to go deep first, you'd do something like this (Pseudo code)
RecursiveMethod( Element parent, ModelNode parentNode )
{
foreach (child of parent)
{
newNode = AddToModel(parentNode, child)
RecursiveMethod( child, newNode ) // <-- Note: Recursion IN the loop
}
}
And if you want to go wide something like
RecursiveMethod( List<Element> parents, Model m )
{
newParents = new List<Element>();
foreach( parent in parents )
{
foreach (child of parent)
{
AddToModel(m, parent, child)
newParents.Add(child);
}
}
// Note: Recursion AFTER the loop - in some cases it may even happen _before_
if( newParents.Length > 0 ) RecursiveMethod( newParents, m );
}
If you want an iterative solution, you have again those two questions, you just do a little different:
// you need some sort of datastructure, for "width first" we can use a FIFO queue
queue = new Queue(); // Not _the_ .NET Queue, just a FIFO for demo
// we start with the root...
queue.Enqueue( path );
// Then you do that one step as long as there is something in the queue
while( queue.Length > 0 )
{
current = queue.Dequeue(); // Get the first element in queue
foreach( child of current ) // No child -> no new elements in queue, length decreases
{
// If there are children =>
// Do what you need to do
InsertIntoModel(child); // Whatever that means in particular
// and put them into the queue for later processing
// queue length increases
queue.Enqueue(child);
}
}
If you want to go deep first, you can chose another mode of insertion and/or a different data structure. (For example a Stack, that is LIFO, creating width-first behavior)

Delete item in nested collections of Nth level

I'm having trouble trying to delete a item inside a tree structured object.
My object is as below
TreeNode
{
string name;
ObservableCollection<TreeNode> Children;
}
I thought if I recursively process through the tree and find my node and delete it but I ran into trouble.
I did something along the lines of
Updated:
DeleteNode(ObservableCollection<TreeNode> children, TreeNode nodetodelete)
{
if(children.remove(nodetodelete))
{
return;
}
else
{
foreach(var child in children)
{
DeleteNode(child, nodetodelete);
}
}
}
I realize while I was writing the code that I would eventually run into manipulation exception while iterating through a collection that has a chance of being changed.
I could build a giant change of for loops since I know exactly the max deep length(which I did for a place holder) but that seems really bad. . . .
Can anyone point me in a better general direction. I kind of wonder if my data structure is the cause of this.
Update:
This will look awful and kinda of code smell but I got the recursion to "work"
by throw a exception when I find my node.
DeleteNode(children, nodetodelete)
{
if(children.remove(nodetodelete)
{
throw FoundException();
}
else
{
foreach(var child in children)
{
DeleteNode(child, nodetodelete)
}
}
}
Is there any other way of breaking out of a recursion.
I would deal with this by making a small change to my design (assuming the snippet in your question is pseudocode for a class):
TreeNode
{
string name;
TreeNode Parent;
ObservableCollection<TreeNode> Children;
public void Delete()
{
Parent.Children.Remove(this);
}
}
This makes a little bit more work for you maintaining an extra reference when manipulating your object graph, but saves you a lot of effort and code when doing things like deletes as you can see above.
You haven't shown how you're constructing TreeNodes, but I'd make the parent and a collection for the children arguments of the constructor.
You can safely iterate over the collection of children nodes and remove them, as long as you don't change the original collection. This can be done by creating an array of the collection and iterating over that instead.
DeleteNode(ObservableCollection<TreeNode> children, TreeNode nodetodelete)
{
if (children.remove(nodetodelete))
{
return;
}
else
{
foreach (var child in children.ToArray())
{
// If anything is deleted in the collection, it will not break the iteration here, as we are iterating over an Array and not "children"
DeleteNode(child, nodetodelete);
}
}
}
This will create a new collection for you to iterate over. If a child node is deleted from children, the foreach loop will not throw an exception. That is because the original collection was changed, while we iterate over a secondary collection.

A special C# Tree algorithm in Umbraco CMS

I'm creating a special tree algorithm and I need a bit of help with the code that I currently have, but before you take a look on it please let me explain what it really is meant to do.
I have a tree structure and I'm interacting with a node (any of the nodes in the tree(these nodes are Umbraco CMS classes)) so upon interaction I render the tree up to the top (to the root) and obtain these values in a global collection (List<Node> in this particular case). So far, it's ok, but then upon other interaction with another node I must check the list if it already contains the parents of the clicked node if it does contain every parent and it doesn't contain this node then the interaction is on the lowest level (I hope you are still with me?).
Unfortunately calling the Contains() function in Umbraco CMS doesn't check if the list already contains the values which makes the list add the same values all over again even through I added the Contains() function for the check.
Can anyone give me hand here if he has already met such a problem? I exchanged the Contains() function for the Except and Union functions, and they yield the same result - they do contain duplicates.
var currentValue = (string)CurrentPage.technologies;
List<Node> globalNodeList = new List<Node>();
string[] result = currentValue.Split(',');
foreach (var item in result)
{
var node = new Node(int.Parse(item));
if (globalNodeList.Count > 0)
{
List<Node> nodeParents = new List<Node>();
if (node.Parent != null)
{
while (node != null)
{
if (!nodeParents.Contains(node))
{
nodeParents.Add(node);
}
node = (Node)node.Parent;
}
}
else { globalNodeList.Add(node); }
if (nodeParents.Count > 0)
{
var differences = globalNodeList.Except<Node>(globalNodeList);
globalNodeList = globalNodeList.Union<Node>(differences).ToList<Node>();
}
}
else
{
if (node.Parent != null)
{
while (node != null)
{
globalNodeList.Add(node);
node = (Node)node.Parent;
}
}
else
{
globalNodeList.Add(node);
}
}
}
}
If I understand your question, you only want to see if a particular node is an ancestor of an other node. If so, just (string) check the Path property of the node. The path property is a comma separated string. No need to build the list yourself.
Just myNode.Path.Contains(",1001") will work.
Small remarks.
If you are using Umbraco 6, use the IPublishedContent instead of Node.
If you would build a list like you do, I would rather take you can provide the Umbraco helper with multiple Id's and let umbraco build the list (from cache).
For the second remark, you are able to do this:
var myList = Umbraco.Content(1001,1002,1003);
or with a array/list
var myList = Umbraco.Content(someNode.Path.Split(','));
and because you are crawling up to the root, you might need to add a .Reverse()
More information about the UmbracoHelper can be found in the documentation: http://our.umbraco.org/documentation/Reference/Querying/UmbracoHelper/
If you are using Umbraco 4 you can use #Library.NodesById(...)

C# - XML - Removing the nodes without using recursion

I have the following recursive method which takes the an XHTML document and marks nodes based on certain conditions and It is called like below for a number of HTML contents:-
XmlDocument document = new XmlDocument();
document.LoadXml(xmlAsString);
PrepNodesForDeletion(document.DocumentElement, document.DocumentElement);
The method definition is below
/// <summary>
/// Recursive function to identify and mark all unnecessary nodes so that they can be removed from the document.
/// </summary>
/// <param name="nodeToCompareAgainst">The node that we are recursively comparing all of its descendant nodes against</param>
/// <param name="nodeInQuestion">The node whose children we are comparing against the "nodeToCompareAgainst" node</param>
static void PrepNodesForDeletion(XmlNode nodeToCompareAgainst, XmlNode nodeInQuestion)
{
if (infinityIndex++ > 100000)
{
throw;
}
foreach (XmlNode childNode in nodeInQuestion.ChildNodes)
{
// make sure we compare all of the childNodes descendants to the nodeToCompareAgainst
PrepNodesForDeletion(nodeToCompareAgainst, childNode);
if (AreNamesSame(nodeToCompareAgainst, childNode) && AllAttributesPresent(nodeToCompareAgainst, childNode))
{
// the function AnyAttributesWithDifferingValues assumes that all attributes are present between the two nodes
if (AnyAttributesWithDifferingValues(nodeToCompareAgainst, childNode) && InnerTextIsSame(nodeToCompareAgainst, childNode))
{
MarkNodeForDeletion(nodeToCompareAgainst);
}
else if (!AnyAttributesWithDifferingValues(nodeToCompareAgainst, childNode))
{
MarkNodeForDeletion(childNode);
}
}
// make sure we compare all of the childNodes descendants to the childNode
PrepNodesForDeletion(childNode, childNode);
}
}
And then the following method which would delete the marked node:-
static void RemoveMarkedNodes(XmlDocument document)
{
// in order for us to make sure we remove everything we meant to remove, we need to do this in a while loop
// for instance, if the original xml is = <a><a><b><a/></b></a><a/></a>
// this should result in the xml being passed into this function as:
// <a><b><a DeleteNode="TRUE" /></b><a DeleteNode="TRUE"><b><a DeleteNode="TRUE" /></b></a><a DeleteNode="TRUE" /></a>
// then this function (without the while) will not delete the last <a/>, even though it is marked for deletion
// if we incorporate a while loop, then we can insure all nodes marked for deletion are removed
// TODO: understand the reason for this -- see http://groups.google.com/group/microsoft.public.dotnet.xml/browse_thread/thread/25df058a4efb5698/7dd0a8b71739216c?lnk=st&q=xmlnode+removechild+recursive&rnum=2&hl=en#7dd0a8b71739216c
XmlNodeList nodesToDelete = document.SelectNodes("//*[#DeleteNode='TRUE']");
while (nodesToDelete.Count > 0)
{
foreach (XmlNode nodeToDelete in nodesToDelete)
{
nodeToDelete.ParentNode.RemoveChild(nodeToDelete);
}
nodesToDelete = document.SelectNodes("//*[#DeleteNode='TRUE']");
}
}
When I use the PrepNodesForDeletion method without the infinityIndex counter, I get OutOfMemoryException for few HTML contents. However, If I use infinityIndex counter, It may not be deleting nodes for some HTML contents.
Could anybody suggest any way to remove recursion. Also I am not familiar with the HtmlAgility pack. So, If this can be done using that, could somebody provide some code sample.
Well, if I understand your algorithm correctly you want to do this:
For each node in the tree compare it against all its child nodes in a non-recursive fashion, correct?
// walk the tree in DFS
public void XmlTreeWalk(XmlNode root, Action<XmlNode, XmlNode> action)
{
var nodesToCompare = new Stack<XmlNode>();
foreach (XmlNode child in root.ChildNodes)
{
nodesToCompare.Push(child);
}
while (nodesToCompare.Count > 0)
{
var top = nodesToCompare.Pop();
action(root, top);
foreach (XmlNode child in top.ChildNodes)
{
nodesToCompare.Push(child);
}
}
}
// for each node: prepare all its children for deletion
public void PrepareForDeletion(XmlNode root)
{
XmlTreeWalk(root, (r, c) => PrepareSubtreeForDeletion(r, c));
}
// for each node, compare all its children against the toCompare node
private void PrepareSubtreeForDeletion(XmlNode toCompare, XmlNode root)
{
XmlTreeWalk(root, (unused, current) => MarkNodeForDeletion(toCompare, current));
}
// your delete logic
public void MarkNodeForDeletion(XmlNode toCompare, XmlNode toCompareAgains)
{
...
}
What this should do is: Walk the tree top to bottom and for each node walk the subtree of that node comparing all children against this node.
I haven't tested it so it might contain bugs but the idea should be clear. Apparently this algorithm is O(n^2).
To remove recursion, the childs and parents must know about each other.
Then you can traverse say down the right leg from the root parent, until you reach the right most bottom leg.
And then from there, go up one, then down left one, and then down right until bottom. Repeat up one, down left, and then right as far as possible, etc. until you have looped over the entire tree structure.
I'm not sure on what you are attempting to do, to suggest how to use this method on your problem.
Your problem is that you have badly formed XML and as a direct result your DOM is a mess. What I think you are going to have to do is to use a SAX parser (which must exist for .net) and implement the logic to fix the DOM yourself which appears to be what you re trying to do.
This method isn't recursive but is going to require you to do some work that you didn't realize that you needed to do.
also note that you are getting an out of memory exception and not a stack overflow exception which reinforces the idea that too much recursion is not your problem per se.

TreeNodeCollection reference problem

First off we have the almighty code!
List nodes = new List();
TreeNode Temp = new TreeNode();
TreeNodeCollection nodeList = treeViewTab4DirectoryTree.Nodes;
while (nodeList.Count != 0)
{
Temp = nodeList[0];
while (Temp.FirstNode != null)
{
Temp = Temp.FirstNode;
}
if (!nodes.Contains(Temp.FullPath))
{
nodes.Add(Temp.Text);
}
nodeList.Remove(Temp);
}
Now the problem: I have written the code above with the intent of creating a List containing the text from all the nodes in the tree. That works perfectly. The problem I am having is when I remove the nodes from my variable they are also being removed from the actual list. The question would be how can I make a copy of the list of nodes so I can play with them without messing with the ACTUAL list. How do I make a copy of it without just making a reference to it? Any help will be greatly appreciated!
Your problem arises because "nodeList" is a reference to treeViewTab4DirectoryTree.Nodes, rather than a copy of it.
The solution depends entirely on the what type of TreeNodeCollection you're using (WinForms, ASP.net, something else?), as you'll need to look for a .Copy(), .Clone(), .ToArray() method or similar to take a copy of the contents of the collection, rather than a reference to the existing collection.
If, for example, you're using asp.net and thus the System.Web.UI.WebControls.TreeNodeCollection, you could use the .CopyTo method in a way similar to this:
TreeNode[] x = null;
treeViewTab4DirectoryTree.Nodes.CopyTo(x, 0);
Updated to show stack based approach:
List<String> result = new List<String>();
Stack<IEnumerator> nodeColls = new Stack<IEnumerator>();
IEnumerator nodes = treeViewTab4DirectoryTree.Nodes.GetEnumerator();
nodeColls.Push(null);
while (nodes != null)
{
while (nodes.MoveNext())
{
result.add(nodes.Current.FullPath);
if (nodes.Current.FirstNode != null)
{
nodeColls.Push(nodes);
nodes = nodes.Current.Nodes.GetEnumerator();
}
}
nodes = nodeColls.Pop();
}
The code below does not work as was mentioned in comments, because it doesn't traverse the entire tree, but only takes the first leaf node of each top-level branch.
I actually thought the original code (in the question) did so too, because I thought the Remove would actually remove the top-level node after finding the first leaf node under it; but instead, it tries to remove the leaf node from the collection of top-level nodes, and just ignores it if it can't find it.
Original post, non-functioning code
First of all, why do you need to remove the items from your list?
List<string> nodes = new List<string>();
foreach (TreeNode tn in treeViewTab4DirectoryTree.Nodes)
{
TreeNode temp = tn;
while (Temp.FirstNode != null)
{
Temp = Temp.FirstNode;
}
if (!nodes.Contains(Temp.FullPath))
{
nodes.Add(Temp.Text);
}
}
To answer your concrete question, assuming the Nodes collection implements IEnumerable, use:
List<TreeNode> nodeList = new List<TreeNode>(treeViewTab4DirectoryTree.Nodes);
If you do decide to stick with your while loop, you can save an instatiation by changing
TreeNode Temp = new TreeNode();
to
TreeNode Temp = null;
... you're never actually using the object you create, at least in the part of the code you've shown.

Categories

Resources