Delete empty root node in treeView - c#

Let's say I've got tree with 3 categories, each with 3 child nodes. I want to delete root node, when all child nodes gets deleted. I tried something like this:
TreeNode current = treeView1.SelectedNode;
TreeNode parent = treeView1.SelectedNode.Parent;
if (parent.Nodes.Count == 0)
{
parent.Nodes.Remove(current);
}
And I placed it in Form1_Load. Unfortunatelly, when all child nodes are gone nothing happens. Is this code correct? Or maybe I misplaced it and I should place it somewhere else?
edit: My tree looks like this:
Morning
brush teeth
drink coffee
Afternoon
dinner
TV
Night
Sleep
So if I decide to delete "Sleep", I want also delete "Night". But If I decide to delete "TV", I want to keep "Dinner" and "Afternoon".

Try this:
if (treeView1.SelectedNode != null)
{
if (treeView1.SelectedNode.Parent == null) treeView1.SelectedNode.Remove();
else if (treeView1.SelectedNode.Parent.Nodes.Count == 1) treeView1.SelectedNode.Parent.Remove();
else treeView1.SelectedNode.Remove();
}

If the parent is null, then you know that you are on a root node. So that node needs to be removed from the TreeView's Nodes collection directly. Otherwise, you can just remove the selected node from the parent. There's no reason to even look at the Node count.
Now, you also need to check that the current node is not null either; because it's perfectly reasonable to have no node in a tree selected.
TreeNode current = treeView1.SelectedNode;
if(current == null)
return;
TreeNode parent = treeView1.SelectedNode.Parent;
if (parent == null)
{
treeView1.Nodes.Remove(current);
}
else
{
parent.Nodes.Remove(current);
}

Related

C# Remove duplicate child/grandchild/greatgrandchild nodes in a treeview

I have a treeview which is generated dynamically. My issue is that it adds same nodes -it receives the list from another application though wherein the same item is repeated, but i do not want to include the repeated items in my treeview. I have tried to avoid adding the node at the first case; but failed. My code to check if exists is below( tried some):
if(treeViewLeft.Nodes[0].Nodes[index1].Nodes[index2].Nodes.Find(childnode.Name,true).Length>0)
{
treeViewLeft.Nodes[0].Nodes[index1].Nodes[index2].Nodes.Add(childnode);
}
and
if(treeViewLeft.Nodes[0].Nodes[index1].Nodes[index2].Nodes.Contains(childnode.Name))
{
treeViewLeft.Nodes[0].Nodes[index1].Nodes[index2].Nodes.Add(childnode);
}
I think the problem might be that as you are creating child nodes, they are technically different objects even though all of their properties are the same. So node.Contains(childnode) won't return true in this case.
You can instead add a function to loop through all the name properties on a node looking for a match:
public bool CheckNameExists(TreeNode parent, string text)
{
foreach (TreeNode item in parent.Nodes)
{
if (item.Name == text) return true;
}
return false;
}
So then I would take your second example and change it to this:
if(CheckNameExists(treeViewLeft.Nodes[0].Nodes[index1].Nodes[index2], childnode.Name) == false)
{
treeViewLeft.Nodes[0].Nodes[index1].Nodes[index2].Nodes.Add(childnode);
}

Loop to iterate up parent nodes until it finds specific tag

What I'm doing is finding a specific value within an XML document and then I want to iterate upwards through each parent until it finds the parent with a specific tag name.
List<XElement> fieldReferences = new List<XElement>();
fieldReferences.AddRange(element.XPathSelectElements(string.Format("descendant::nameValue[#idref='{0}']", fieldName)));
fieldReferences.AddRange(element.XPathSelectElements(string.Format("descendant::nameValue[#value='{0}']", fieldName)));
string parentIterator = ".Parent";
string attributeValue = ".Attribute('id').Value";
string parentElementName = ".Name";
foreach (var value in fieldReferences)
{
var parentField = string.Format("{0}{1}", parentIterator, parentElementName);
while (value + parentField != "private" || value + parentField != "public")
{
// keep appending .Parent until it finds what it needs
}
//var parentField = value.Parent.Parent.Parent.Parent.Attribute("id").Value;
outputFields.Add(parentField, name.FirstOrDefault());
}
The issue that I'm having is that parentField will always be evaluated as a string so it'll never actually check the .Parent.Name property of value.
I don't work often with C# so I'm sure there's a much easier way to do this so my question is: How can I get my parentField string to evaluate the way I want OR how can I do this in a different way to achieve the same end result?
EDIT: Here's what my xml looks like. The XPAthSelectElement gets the nameValue element and I want to iterate through each parent element until I find the private tag
<private id="I need to iterate upwards through each parent element until I find this one">
<value>
<request>
<nameValues>
<nameValue idref="I found this" />
<nameValue value=""/>
</nameValues>
</request>
</value>
</private>
So you don't actually need to do this many string operations to then go crazy with XPath. Once you found your child target element, you can just use the Parent property on the XElement iteratively until you find the XElement with a private/public tag. So that gives us this code:
foreach (XElement element in fieldReferences)
{
XElement currentElement = element;
while (currentElement.Parent != null)
{
currentElement = currentElement.Parent;
if (currentElement.Name == "private" || currentElement.Name == "public") {
outputFields.Add(currentElement, /* not sure what you want here */);
break;
}
}
}
So currentElement would start out as the element with the nameValue tag from your example. In the while loop, each iteration currentElement changes to its parent node until there is no more parent or currentElement has become a private or a public tag. If the latter is the case, it gets appended to your result.
You can use the XElement.Ancestors function to get a list of all the elements that contain the nodes you found, then just select the ones you want using LINQ. No need for any loops.
var results = fieldReferences.Select(element => element.Ancestors()
.Where(ancestor => ancestor.Name == "public" ||
ancestor.Name == "private")
.FirstOrDefault());
Note that this will go all the way up the tree, and may have issues if there are multiple matching ancestors (or no matching ancestor). Let me know if that is a problem for you, and what result you want in that case, and I can make adjustments.

Remove Child Nodes from Parent Node

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());

How can I set how many levels to expand TreeView nodes?

Doing this code will expand all the main nodes under root.
root
images
files
docs
But I want to make that if I change it from 0 to 1 somehow to change the level so it will expand the next level.
root
images
myimages
files
myfiles
docs
mydocs
foreach (TreeNode tn in treeView1.Nodes)
{
if (tn.Level == 0)
tn.Expand();
}
I tried to add in the foreach:
if (tn.Level == 1)
tn.Expand();
But that's not a solution.
Maybe I need foreach in foreach?
All this code is part of a method that is working under BackgroundWorker that get a list of my FTP server directories and files recursively.
So in real time while it's getting the directories and adding the nodes I want to expand the nodes level.
Because the data structure is recursive, IMHO the most appropriate way to deal with the issue is to traverse it recursively. Something like this:
void ExpandToLevel(TreeNodeCollection nodes, int level)
{
if (level > 0)
{
foreach (TreeNode node in nodes)
{
node.Expand();
ExpandToLevel(node.Nodes, level - 1);
}
}
}
You would call it like this:
ExpandToLevel(treeView1.Nodes, 1);
That would expand just the first level. Pass 2 to expand the first two levels, etc.

Highlighting a TreeNode without using foreach, possible with LINQ?

I have a some TreeViews that only contains parent nodes (like a listview). I have a textbox where user can enter a text, then I want to highlight the node in the treeview which has same text as entered by user. At the moment I use this code:
strring text = textBox.Text.Trim(); //the text entered by user
foreach(TreeNode node in treeView.Nodes)
{
if(node.Text == text) node.BackColor == Color.Green;
}
I hate writing foreach for each treeview. Is there a simple way to say for example:
if(treeView.Nodes.Contains(text)) //do stuff
the Nodes.Contains() only accept a TreeNode object. I was wondering if a better code exist out there!?
Yes, you can do it with LINQ like that:
treeView.Nodes.FirstOrDefault<TreeNode>(node => node.Text == text);
You will get null, if no item was found.
To get all items which contain the text do like that:
IEnumerable<TreeNode> foundItems =
from TreeNode node in tree.Nodes
where node.Text.Contains(searchString) select node;
or to select automatically the first occurence:
TreeNode firstNodeWithText = (from TreeNode node in tree.Nodes
where node.Text.Contains(searchString) select node).FirstOrDefault<TreeNode>();
tree.SelectedNode = firstNodeWithText;
You can try like this
string text = textBox.Text.Trim(); //the text entered by user
foreach(TreeNode node in treeView.Nodes
.Cast<TreeNode>()
.Where(x=>x.Text == text))
node.BackColor == Color.Green;
but, as you can see, it is not that shorter from your code.
PS I just realized that you wanted a solution without using foreach. You should try Fischermen's approach then.

Categories