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.
Related
I'm trying to create a function that will add each visible node in a tree to a node array and then return it.
This is the code I have so far, but struggling to figure out how to add them.
Note: The tree has a maximum of 8 nodes.
private Node[] activeClients(AdvTree tree)
{
Node[] activeClients = new Node[8];
foreach (Node client in tree.Nodes)
{
if (client.IsVisible)
{
//Add Visible Node to activeClients Node Array
}
}
return activeClients;
}
May be something like:
var visibleNodes = tree.Nodes.Where(client=>client.IsVisible)
especially if you are talking about small numbers (8 elements) and not compute intensive function, dynamic array (or vector) like List<T>, IEnumerable<T> is a right choice.
And in this way, your code also scales better in the future.
I actually figured out I didn't need a Node Array, but thanks for the help guys.
I used NodeCollection instead and it worked perfect for my needs.
private NodeCollection activeClients(AdvTree tree)
{
NodeCollection activeClients = new NodeCollection();
foreach (Node client in tree.Nodes)
{
if (client.IsVisible)
{
//Add Visible Node to activeClients Node Array
activeClients.Add(client, eTreeAction.Code);
}
}
return activeClients;
}
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(...)
I have multiple TabItems in my TabControl; tabItem1, tabItem2, tabItem3...these are
CloseableTabItem.
If I add a node in tabItem1 and press a button to make a subGraph model for this node, the
same node should appear in tabItem2 with a button; so that
tabItem2-Header = nodeName and nodeName = tabItem1-Header.
if i press the button from the node in tabitem2, tabitem1 should be focused. if i close
tabItem1 and press the same Button tabItem1 should be loaded again(this happen in
SubGraphButton_Click).
Do you see a problem with this code?
private void ChildNode_Click(object sender, RoutedEventArgs args)
{
System.Windows.Controls.Button button = (System.Windows.Controls.Button)sender;
Node node = Part.FindAncestor<Node>(button);
MyNodeData nodeData = node.Data as MyNodeData;
foreach (TabItem item in tabControl.Items)
{
if (nodeData.Text == item.Header.ToString())
{
item.Focus();
}
else if (nodeData.Text != item.Header.ToString())
{
SubGraphButton_Click(sender, args);
}
}
}
private void SubGraphButton_Click(object sender, RoutedEventArgs args)
{
string activeDirectory = #"X:\SubGraph\";
string[] files = Directory.GetFiles(activeDirectory);
foreach (string fileName in files)
{
FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
System.Windows.Controls.Button button = (System.Windows.Controls.Button)sender;
Node node = Part.FindAncestor<Node>(button);
MyNodeData nodeData = node.Data as MyNodeData;
if (node != null)
{
if (nodeData.Text + ".epk" == fileName.Substring(12, fileName.Length - 12) && !tabControl.Items.Contains(tabItem1))
{
tabControl.Items.Add(tabItem1);
tabItem1.Focus();
var model = new MyGraphLinksModel();
model.Modifiable = true;
model.HasUndoManager = true;
activateDiagram(myDiagram1);
activeDiagram.Model = model;
model.Name = fileName.Substring(12, fileName.Length - 12);
model.Name = model.Name.Substring(0, model.Name.Length - 4);
tabItem1.Header = model.Name;
activeDiagram.PartManager.UpdatesRouteDataPoints = false;
StreamReader reader = new StreamReader(file);
string contents = reader.ReadToEnd();
XElement root = XElement.Parse(contents);
activeDiagram.LayoutCompleted += LoadLinkRoutes;
model.Load<MyNodeData, MyLinkData>(root, "MyNodeData", "MyLinkData");
}
}
}
When you modify a collection when it is in the middle of being modified it is rather likely to cause errors. The types of errors, and their likeliness, tend to vary based on what the underlying collection actually is. Modifying a List when iterating it is very likely to give you lots of off by one errors (or off by more than one if you modify it a lot) and potentially out of bounds errors. Modifying a LinkedList could result in null pointer exceptions, infinite loops, accessing non-existent items, etc. but is quite a bit less likely.
Because the chances of problems, in the general case, are rather high, the impact of those problems is also rather high, and the difficulty in diagnosing what actually went wrong (and where) C# chooses to just throw an exception whenever you try to iterate a collection that was modified during the iteration. This way you don't end up with weird, unexpected problems that don't manifest themselves until some time much further down the road then where their root cause is.
There are several different strategies that can be used to avoid this issue:
Iterate over a different collection than the one you really want to modify. In some cases this can simply be done by adding a ToList call on a sequence so that it is moved to a new collection; when doing this the collection being iterated is separate from the one being modified and so there is no error.
You can avoid modifying the actual collection inside of the foreach loop. Common examples of this are creating a List or other collection of "changes to make" whether it's itemsToAdd, itemsToRemove etc. Then you can add/remove/whatever for all of those items after the loop. (This is effective if you are only modifying a small percentage of the size of the collection.)
Certain types of collections can be "iterated" without actually using a traditional iterator (meaning a foreach loop). As an example, you can iterate through a List using a regular for loop instead, and simply modify (increment/decrement) the loop variable whenever you add or remove items. This, when done correctly, tends to be an efficient option, but it's quite easy to make a mistake and get something wrong so while the other options are (marginally) less efficient, they are very good options for non-performance intensive code due to their simplicity.
you can't modify collection you're iterating on.
you can replace the "foreach" loop with simple "for" loop but notice the index you're running on when adding/removing items from the collection.
like this:
for (int i = 0; i < tabControl.Items.Count; i++)
{
TabItem item = tabControl.Items[i];
... // your logic here
}
another option which might be convenient, is instead of adding the items into the tab control.Items collection is getting it as return value, save them in a list and after you've done iterating all the items, insert all the tabs you've created into the items collection so you're not modifying the collection while you're running on it.
You are not permitted to modify a collection (tabControl.Items in this case) while you are enumerating it (which you are doing in your foreach loop) as it will make the enumerator invalid.
The specific line of code which is causing the error is likely to be
// In SubGraphButton_Click
// This line of code is called inside an enumeration of tabControl.Items
// This is not permitted!
tabControl.Items.Add(tabItem1);
Conceptually, your code looks like this:
private void ChildNode_Click(object sender, RoutedEventArgs args)
{
System.Windows.Controls.Button button = (System.Windows.Controls.Button)sender;
Node node = Part.FindAncestor<Node>(button);
MyNodeData nodeData = node.Data as MyNodeData;
foreach (TabItem item in tabControl.Items)
{
if (nodeData.Text == item.Header.ToString())
{
item.Focus();
}
else if (nodeData.Text != item.Header.ToString())
{
// This line will throw an exception
DoSomethingThatModifiesTabControlItemsCollection()
}
}
}
Inside the foreach loop you call SubGraphButton_Click which in turn adds a new node tabControl.Items.Add(tabItem1);
This is not allowed. You can use a for loop instead.
Yes, this line
tabControl.Items.Add(tabItem1);
changes the collection on which you enumerate in the NodeClick
and this is no-no in the enumeration world
Try to loop with a standard for, but in reverse order......
for( int x = tabControl.Items.Count - 1; x>= 0; x--)
{
TabItem item = tabControl.Items[x];
if (nodeData.Text == item.Header.ToString())
{
item.Focus();
}
else if (nodeData.Text != item.Header.ToString())
{
SubGraphButton_Click(sender, args);
}
}
Looping in reverse order avoid to examine the new items added inside the SubGraphButton.
I don't know if this is a desidered effect or not.
When you ForEach over the TabItems in tabControl, you can't do anything inside the ForEach that will cause the tabControl's items collection to change.
This is a limitation of the framework. It is because you are currently iterating over the TabItems.
So inside of your ChildNode_Click function,
Inside of your ForEach
foreach (TabItem item in tabControl.Items)
you make a call to
SubGraphButton_Click(sender, args);
Inside of that function, you make a call to
tabControl.Items.Add(tabItem1);
You can't manipulate the Items collection while inside the ForEach.
I'm currently working on a buggy bit of code that's designed to strip out all the namespaces from an XML document and re-add them in the header. We use it because we ingest very large xml documents and then re-serve them in small fragments, so each item needs to replicate the namespaces in the parent document.
The XML is first loaded ias an XmlDocument and then passed to a function that removes the namespaces:
_fullXml = new XmlDocument();
_fullXml.LoadXml(itemXml);
RemoveNamespaceAttributes(_fullXml.DocumentElement);
The remove function iterates through the whole documents looking for namespaces and removing them. It looks like this:
private void RemoveNamespaceAttributes(XmlNode node){
if (node.Attributes != null)
{
for (int i = node.Attributes.Count - 1; i >= 0; i--)
{
if (node.Attributes[i].Name.Contains(':') || node.Attributes[i].Name == "xmlns")
node.Attributes.Remove(node.Attributes[i]);
}
}
foreach (XmlNode n in node.ChildNodes)
{
RemoveNamespaceAttributes(n);
}
}
However, I've discovered that it doesn't work - it leaves all the namespaces intact.
If you iterate through the code with the debugger then it looks to be doing what it's supposed to - the nodes objects have their namespace attributes removed. But the original _fullXml document remains untouched. I assume this is because the function is looking at a clone of the data passed to it, rather than the original data.
So my first thought was to pass it by ref. But I can't do that because the iterative part of the function inside the foreach loop has a compile error - you can't pass the object n by reference.
Second thought was to pass the whole _fullXml document but that doesn't work either, guessing because it's still a clone.
So it looks like I need to solve the problem of passing the document by ref and then iterating through the nodes to remove all namespaces. This will require re-designing this code fragment obviously, but I can't see a good way to do it. Can anyone help?
Cheers,
Matt
To strip namespaces it could be done like this:
void StripNamespaces(XElement input, XElement output)
{
foreach (XElement child in input.Elements())
{
XElement clone = new XElement(child.Name.LocalName);
output.Add(clone);
StripNamespaces(child, clone);
}
foreach (XAttribute attr in input.Attributes())
{
try
{
output.Add(new XAttribute(attr.Name.LocalName, attr.Value));
}
catch (Exception e)
{
// Decide how to handle duplicate attributes
//if(e.Message.StartsWith("Duplicate attribute"))
//output.Add(new XAttribute(attr.Name.LocalName, attr.Value));
}
}
}
You can call it like so:
XElement result = new XElement("root");
StripNamespaces(NamespaceXml, result);
I'm not 100% sure there aren't failure cases with this but it occurs to me that you can do
string x = Regex.Replace(xml, #"(xmlns:?|xsi:?)(.*?)=""(.*?)""", "");
on the raw xml to get rid of namespaces.
It's probably not the best way to solve this but I thought I'd put it out there.
I have an XElement (myParent) containing multiple levels of children that I wish to extract data from. The elements of interest are at known locations in the parent.
I understand that I am able to get a child element by:
myParent.Element(childName);
or
myParent.Element(level1).Element(childName);
I am having trouble figuring out how to do this if I want to loop through an array offor a list of elements that are at different levels, and looping through the list. For instance, I am interested in getting the following set of elements:
myParent.Element("FieldOutputs").Element("Capacity");
myParent.Element("EngOutputs").Element("Performance")
myParent.Element("EngOutputs").Element("Unit").Element("Efficiency")
How can I define these locations in an array so that I can simply loop through the array?
i.e.
string[] myStringArray = {"FieldOutputs.Capacity", "EngOutputs.Performance", "EngOutputs.Unit.Efficiency"};
for (int i=0; i< myArray.Count(); i++)
{
XElement myElement = myParent.Element(myStringArray);
}
I understand that the method above does not work, but just wanted to show effectively what I am trying to achieve.
Any feedback is appreciated.
Thank you,
Justin
While normally I'm reluctant to suggest using XPath, it's probably the most appropriate approach here, using XPathSelectElement:
string[] paths = { "FieldOutputs/Capacity", "EngOutputs/Performance",
"EngOutputs/Unit/Efficiency"};
foreach (string path in paths)
{
XElement element = parent.XPathSelectElement(path);
if (element != null)
{
// ...
}
}
The Descendants() method is what you're looking for, I believe. For example:
var descendants = myParent.Descendants();
foreach (var e in descendants) {
...
}
http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.descendants.aspx
Edit:
Looking at your question more closely, it looks like you may want to use XPathSelectElements()
var descendants = myParent.XPathSelectElements("./FieldOutputs/Capacity | ./EngOutputs/Performance | ./EngOutputs/Units/Efficency");
http://msdn.microsoft.com/en-us/library/bb351355.aspx