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

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

Related

Filter Tree-View with similar word on the TreeNode.Text C#

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.

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

Checking if treeview contains an item

This problem seems simple enough. I have a treeview, let's call it MyTreeView, populated with all of the drive letters, so the treeview looks like this:
A:\
C:\
D:\
F:\
How do I check if the treeview contains a specific item? How does the treeview identify its items?
I have created a MessageBox to show MyTreeView.Items.GetItemAt(1), and it identifies item 1 as:
"System.Windows.Controls.TreeViewItem Header:C:\ Items.Count:1"
Try the easiest thing first, which obviously doesn't work:
if (MyTreeView.Items.Contains(#"C:\")
{
MessageBox.Show(#"Tree contains C:\");
}
The next easiest thing would be to try making a TreeViewItem that looks similar to what I want, which also doesn't work:
TreeViewItem myItem = new TreeViewItem();
myItem.Header = #"C:\";
if (MyTreeView.Items.Contains(myItem)
{
MessageBox.Show("Tree contains " + myItem.ToString());
}
Just to make sure I had the fundamental concept right, I tried some circular logic, which actually does work:
var myItem = MyTreeView.Items.GetItemAt(1);
if (MyTreeView.Items.Contains(myItem)
{
MessageBox.Show("Tree contains " + myItem.ToString());
}
Which outputs:
"Tree contains System.Windows.Controls.TreeViewItem Header:C:\ Items.Count:1"
What am I doing wrong? How do I check if my tree contains something like "C:\" ?
edit:
The code for building the tree is this:
(basically a copy and paste from the internet)
foreach (string myString in Directory.GetLogicalDrives())
{
TreeViewItem item = new TreeViewItem();
item.Header = myString;
item.Tag = myString;
item.FontWeight = FontWeights.Normal;
item.Items.Add(dummyNode); // this is (object)dummyNode = null
item.Expanded += new RoutedEventHandler(DWGFolder_Expanded);
item.Selected += new RoutedEventHandler(DWGFolder_Selected);
// the Expanded event is very similar,
// subitem.Header is the folder name (Testing),
// while subitem.Tag is the full path (C:\Testing)
MyTreeView.Items.Add(item);
}
So basically I'm trying to match TreeViewItem objects.
I believe .Contains() would check for the value by reference since it isn't a simple string object. This requires you to iterate through each of the items until you retrieve the item which matches the header.
LINQ Example
if (MyTreeView.Items.Cast<TreeViewItem>().Any(item => item.Header.ToString() == #"C:\"))
{
MessageBox.Show(#"Tree contains C:\");
}
Contains looks for the exact same instance inside the collection. If you don't have the object you want to check already, you can't use Contains.
But you can use some basic LINQ query... Add the LINQ namespace to your class:
using System.Linq;
If your items are indeed just Strings, then use this query (EDIT - Though, in case they're just Strings, Contains should work, since their equality comparers don't behave like those of regular reference types, but compare by value):
if (MyTreeView.Items.Cast<string>().Any(s => s == #"C:\"))
{
// Do stuff
}
If your items are TreeViewItems, you can use this one:
if (MyTreeView.Items.Cast<TreeViewItem>().Any(i => i.Header.ToString() == #"C:\"))
{
// Do stuff
}
But your items could be any class we don't know, or your header binding could change... Without knowing how you're adding the items to the TreeView, it's hard to give you the best alternative.
EDIT - Keep in mind that this will only search in the first level of the tree. If the item you're looking for is placed somewhere deeper, you'll have to do a recursive search. At that point, maybe just keeping the values stored somewhere from the start would be better.

Adding a child node in the middle of a list of other children

This question is a follow up to this question. Basically what I want to do is add nodes to my treeView in numerical order. Right now I am working with a list of child nodes. Each child has a DisplayName of a numerical value. In my program I have a loop that checks if the node being added has a DisplayName that is less than the DisplayName of the current child in the loop. If the new child's DisplayName is less than the child being checked I would like to add the new node before the already existing one. I am having trouble figuring out a method to do this.
Here is my code:
var node = Data.GetAllChildren(x => x.Children).Distinct().ToList().First(x => x.identify == 'B');
//Get # of children -- if children exist
if (node.Children.Count() > 0)
{
newChildTitle = int.Parse(nodeName.Value); //node to add
for (int i = 0; i < node.Children.Count(); i++)
{
currentChildTitle = int.Parse(node.Children.ElementAt(i).DisplayName.Value); //current node value
if (newChildTitle == currentChildTitle) //Check if location already exists
{
MessageBox.Show("Sorry, that location name already exists under this category.", "Duplicate Location Name", MessageBoxButton.OK, MessageBoxImage.Warning);
break;
}
else if (newChildTitle < currentChildTitle) //If new location value is less, add before current location in Tree
{
//CODE WOULD GO HERE**
break;
}
}
}
else //if no children exist, just add
node.Children.Add(CreateBlockingLocation(model, blockTreeCollection, nodeName));
XAML of the TreeView (the TreeView is bound to an ObservableCollection):
<!-- Tree view items & Functions -->
<TreeView Name="Tree_One" IsEnabled="{Binding DataContext.DataTreeEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" ItemsSource="{Binding DataTree.Data}" />
Data and Properties of the ObservableCollection<HierarchicalVM> that the TreeView is bound to:
private HierarchicalVM _parent;
private ObservableCollection<HierarchicalVM> _children;
public HierarchicalVM()
{
Commands = new ObservableCollection<Command>();
}
//Relationship Properties
public HierarchicalVM Parent
{
get { return _parent; }
set
{
_parent = value;
NotifyPropertyChange(() => Parent);
}
}
public ObservableCollection<HierarchicalVM> Children
{
get { return _children ?? (_children = new ObservableCollection<HierarchicalVM>()); }
set { _children = value; }
}
Current Solution attempt:
//Gets parent item (Data is the complete Tree)
var node = Data.GetAllChildren(x => x.Children).Distinct().ToList().First(x => x.identify == 'B');
if (!node.Children.Any(n => n.numValue == newNumValue))
{
//Error is on this line: **Operator '.' cannot be applied to operand of type 'void'
node.Children = node.Children.Add(newNode).Orderby(n => n.numValue);
}
Ok, assuming you already have a reference to the Parent object (not the DataTreeItem UI Element), there are two ways I can think of to accomplish this. This is off the top of my head with a little bit of checking against MSDN, so I apologize for any bugs.
Parent parent = //however you are getting the parent
if (!parent.Children.Any(n => n.DisplayName == newNode.DisplayName))
{
parent.Children.Add(newNode)
parent.Children = new ObservableCollection<ViewModel>(parent.Children.OrderBy(n => n.DisplayName));
//OR,
parent.Children.InsertItem(parent.Children.IndexOf(parent.Children.OrderBy(n => n.DisplayName).Last(n => n.DisplayName < newNode.DisplayName)), newNode);
}
The first method just adds the new item and resorts the list, which gets you the same result as an in-place insert. The second actually does a in-place insert, but is more complicated because you have to find the index to insert at.
If the list is already sorted you could remove the OrderBy in the second method. This also assumes that DisplayName is an int and a property of each child node (which from the code you posted it isn't, but it should be).
Clearly I don't understand your architecture, so I will explain how I think it should be, and then perhaps you can discover where your code is broken.
We have a object type called Parent, which contains a collection called children, like so:
class Parent<T>
{
ObservableCollection<T> Children;
}
Now we have an collection of Parent called Data in the view model that the TreeView is bound to. Somehow, we are getting a single instance of this Parent class (which definitely shouldn't be void, the fact that your "node.Children" object is should be a HUGE red flag).
If you agree with what I said above, the insert code I posted should work.
Please let me know if I can clarify or add to this answer, or help in any other way.

How to Efficiently Delete Checked Items from a TreeView?

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).

Categories