Select TreeView node by text - c#

I have a TreeView where I get nodes like:
TreeNode tn = parent.Nodes[indexProject].Nodes[currDesign].Nodes
This is legacy data from old project so I know indexing is bad practice for tree views.
That I want to know is if there is any way to select node by Text, I mean, code above returns something like this in last node:
So I want to get Node who contains FBOM in text field is it possible to do something like:
TreeNode tn = parent.Nodes[indexProject].Nodes[currDesign].Nodes.where(x => x.Text.Contains("FOB"))
I also try :
TreeNode tn = (TreeNode)parent.Nodes[indexProject].Nodes[currDesign].Nodes.Cast<TreeNode>()
.Where(r => r.Text.Contains("FBOM"));
But I get:
System.InvalidCastException: 'Unable to cast object of type
'WhereEnumerableIterator`1[System.Windows.Forms.TreeNode]' to type
'System.Windows.Forms.TreeNode'.'

If you tree view is not the immediate child, you might have to flatten it a bit. For example,
var result = treeView1.FlattenTree().Cast<TreeNode>()
.Where(r => r.Text == "Node3");
Where FlattenTree is defined as.
public static class Extensions
{
public static IEnumerable<TreeNode> FlattenTree(this TreeView source)
{
return FlattenTree(source.Nodes);
}
public static IEnumerable<TreeNode> FlattenTree(this TreeNodeCollection source)
{
return source.Cast<TreeNode>().Concat(source.Cast<TreeNode>().SelectMany(x => FlattenTree(x.Nodes)));
}
}
Considering there is a possibility that there might be duplicate nodes with same text, a collection is returned in above. You can alter the linq to suit you requirements though.

Related

Sort specific Node in TreeView?

I would like to know if there is any method to sort (A-Z) a specific node of a TreeView.
The node I want to order is the node "Node1 \ z"
To display it like this:
H
N
Y
Z
Thank you
Your question is how to apply an alpha sort (A-Z) to a specific single node in a TreeView.
There are many ways to do this and the comments mention some good ones. Here is a solution that efficiently locates the target node using its fully-qualified path in the tree hierarchy. If found, it applies an individualized sort method (specified at runtime) to its children. But first, it must ensure that the TreeView will permit such reordering by setting its Sorted property to false. An advantage of this approach is that it avoids making a call the Sort method for the entire TreeView because that would add an unnecessary layer of complexity.
Find the target node
Given a rule for the TreeView that the fully-qualified path to any node (for example, #"Node1\z") is unique, the target can be obtained very efficiently by making an extension for TreeView that returns the specified node (or null if not found).
public static TreeNode Find(this TreeView treeView, string path)
{
var parse = path.Split('\\');
var nodes = treeView.Nodes;
TreeNode node = null;
foreach (var text in parse)
{
node = nodes.Cast<TreeNode>().FirstOrDefault(node => node.Text == text);
if (node == null) break;
nodes = node.Nodes;
}
return node;
}
Sort children of target node
This extension for TreeNode sorts its children according to the comparer Func passed in as an argument.
public static void Sort(
this TreeNode node,
Func<TreeNode, TreeNode, int> sorter)
{
// Make sure the TreeView will allow reordering
if (node.TreeView != null)
{
node.TreeView.Sorted = false;
}
// Copy the nodes to a list
var list = node.Nodes.Cast<TreeNode>().ToList();
// Sort the list however the `Sorter` says to.
list.Sort((a, b) => sorter(a, b));
// Clear the 'old' order
node.Nodes.Clear();
// Install the 'new' order
foreach (var sorted in list)
{
node.Nodes.Add(sorted);
}
}
Complete method to sort (A-Z) a specific node of a TreeView.
So, to answer the question Is there any method to sort a specific node of a TreeView from A-Z let's say we manufacture such a method by making an extension for TreeView that does just that. For the path argument specify the entire hierarchal path like #"Node1" or #"Node1\z".
public static bool SortIfNodeFound(
this TreeView treeView,
string path,
Func<TreeNode, TreeNode, int> sorter)
{
var node = treeView.Find(path);
node?.Sort(sorter);
return node != null;
}
Once you call the TreeView.Sort method, you actually apply the sorted TREEVIEWSTATE through the setter of the hidden Sorted property. The control by that sorts all the nodes using the default sorter, the ascending alphabetical sort. Any attempts afterwards to sort the nodes in a different way fail. You'll note that nothing happens when you remove, sort, and reinsert child nodes of a specific node because once you insert them, the default sorter will interfere and revert your different sort. Again, all the mentioned is when you call the .Sort method before any other sorting routines.
To override this behavior, you need to provide a custom node sorter for the TreeView.TreeViewNodeSorter property. An example that allows you to sort the tree or children of node in ascending or descending orders.
public class TreeNodeComparer : IComparer
{
public TreeNodeComparer(SortOrder sortOrder = SortOrder.Ascending) : base()
{
SortOrder = sortOrder;
}
public int Compare(object x, object y)
{
var xn = x as TreeNode;
var yn = y as TreeNode;
switch (SortOrder)
{
case SortOrder.Descending:
return string.Compare(xn.Text, yn.Text) * -1;
case SortOrder.Ascending:
return string.Compare(xn.Text, yn.Text);
default:
return 1;
}
}
public SortOrder SortOrder { get; set; } = SortOrder.Ascending;
}
Note return 1; here in case SortOrder.None is necessary to not revert sorting the child nodes of a specific node.
A couple of extension methods for the TreeView and TreeNode types.
public static class TreeViewExtensions
{
public static void Sort(this TreeView self, SortOrder order = SortOrder.Ascending)
{
self.TreeViewNodeSorter = new TreeNodeComparer(order);
self.Sort();
}
public static void Sort(this TreeNode self, SortOrder order = SortOrder.Ascending)
{
List<TreeNode> tmp;
TreeView tv = self.TreeView;
if (order == SortOrder.Descending)
tmp = self.Nodes.Cast<TreeNode>().OrderByDescending(n => n.Text).ToList();
else
tmp = self.Nodes.Cast<TreeNode>().OrderBy(n => n.Text).ToList();
var sorter = tv.TreeViewNodeSorter as TreeNodeComparer ?? new TreeNodeComparer();
sorter.SortOrder = SortOrder.None;
tv.TreeViewNodeSorter = sorter;
tv.BeginUpdate();
self.Nodes.Clear();
self.Nodes.AddRange(tmp.ToArray());
tv.EndUpdate();
}
}
You can call them as follows:
// To sort the whole thing...
YourTreeView.Sort(SortOrder.Descending);
// Or the children of the selected node for example...
YourTreeView.SelectedNode.Sort(SortOrder.Ascending);

Get list of all checked nodes and its subnodes in treeview

I have a treeview list check boxes and the list contains nodes, subnodes and in some cases subnode of subnode. When user check some items i want to get list of selected items.
On this why I get only selcted items of main node:
foreach (System.Windows.Forms.TreeNode aNode in tvSastavnica.Nodes)
{
if (aNode.Checked == true)
{
Console.WriteLine(aNode.Text);
}
}
How to travers through whole treeview and get checked items in subnodes?
If you like LINQ, you can create an extension method that traverses the whole treeview:
internal static IEnumerable<TreeNode> Descendants(this TreeNodeCollection c)
{
foreach (var node in c.OfType<TreeNode>())
{
yield return node;
foreach (var child in node.Nodes.Descendants())
{
yield return child;
}
}
}
Then you can perform every operations you want using LINQ. In your case, getting a list of selected nodes is easy:
var selectedNodes = myTreeView.Nodes.Descendants()
.Where(n => n.Checked)
.Select(n => n.Text)
.ToList();
An advantage of this approach is it is generic.
However, because the Descendant() method traverses the whole tree, it might be a bit less efficient than the answer given by #mybirthname because it only cares about nodes that are checked with their parents. I dont known if your use case includes this constraint.
EDIT: Now #mybirthname answer has been edited, it is doing the same. Now you have the loop and the LINQ solution, both are recursive.
public void GetCheckedNodes(TreeNodeCollection nodes)
{
foreach(System.Windows.Forms.TreeNode aNode in nodes)
{
//edit
if(!aNode.Checked)
continue;
Console.WriteLine(aNode.Text);
if(aNode.Nodes.Count != 0)
GetCheckedNodes(aNode.Nodes);
}
}
You don't make look back into the child notes, using recursion you can do it.
You need method like this ! In your code just call once GetCheckedNodes(tvSastavnica.Nodes) and all checked nodes should be displayed !
my way:
void LookupChecks(TreeNodeCollection nodes, List<TreeNode> list)
{
foreach (TreeNode node in nodes)
{
if (node.Checked)
list.Add(node);
LookupChecks(node.Nodes, list);
}
}
useage:
var list = new List<TreeNode>();
LookupChecks(TreeView.Nodes, list);

What .NET framework collection class is for modeling trees? [duplicate]

I want to store an organisation chart in a collection. I think a tree data structure will be best suited to my needs, as I need to add multiple nodes to one node.
LinkedList only provides adding one node to another node, if I understand it correctly.
I have looked at C5 treeset collection, but it doesn't seem to have Add() method to add more than 2 nodes to one node.
I have also looked at Treeview class from Windows Forms library, but I do not want to add Windows forms dll to my project, since I am building a service layer application. (or is it fine?)
I do not want to write my own tree collection class, if there is already one provided by 3rd party?
Any suggestion please?
Thanks
Something like this can be a starting point. By using generics this one can hold a tree of anything
class TreeNode<T>
{
List<TreeNode<T>> Children = new List<TreeNode<T>>();
T Item {get;set;}
public TreeNode (T item)
{
Item = item;
}
public TreeNode<T> AddChild(T item)
{
TreeNode<T> nodeItem = new TreeNode<T>(item);
Children.Add(nodeItem);
return nodeItem;
}
}
A sample which holds a tree of strings
string root = "root";
TreeNode<string> myTreeRoot = new TreeNode<string>(root);
var first = myTreeRoot.AddChild("first child");
var second = myTreeRoot.AddChild("second child");
var grandChild = first.AddChild("first child's child");

LINQ to SQL- Check if object has selected child, grandchild etc

I've been looking for an answer everywhere, but can't find anything. I have two tables, Media and Keywords, which have a many to many relationship. Now the Keywords table is quite simple - it has a ID, Name and ParentFK column that relates to ID column (it's a tree structure).
The user can assign any single keyword to the media file, which means that he can select a leaf without selecting the root or branch.
Now I have to be able to determine if a root keyword has any child, grandchild etc. which is assigned to a media object, but I have to do it from the root.
Any help will be appreciated.
Just look for any entry, which has the given ParentFK set with your ID.
public static bool HasChild(int id) {
return
db.Keywords.Any(item => item.Parent == id);
}
public static bool HasGrandChilds(int id) {
return
db.Keywords.Where(item => item.Parent == id).Any(item => HasChild(item.ID);
}
A more generic way:
public static bool HasGrandChilds(int id, int depth) {
var lst = new List<Keywords>();
for (var i = 0; i < depth - 1; i++) {
if (i == 0)
{
//Initial search at first loop run
lst = db.Keywords.Where(item => item.ParentId == id);
}
else
{
//Search all entries, where the parent is in our given possible parents
lst = db.Keywords.Where(item => lst.Any(k => k.Id == item.Parent));
}
if (!lst.Any())
{
//If no more children where found, the searched depth doesn't exist
return false;
}
}
return true;
}
From your current schema I can't think of a better solution than the following:
Issue a query to retrieve a list of all children of the root.
Issue queries to retrieve a list of all children of the children from the previous step.
So on, recursively to create a list of all descendants of the root.
Next query the DB for all media objects that have any of the keywords in the list.
But the above algorithm will entail multiple calls to the DB. You can make it in a single query of you refine your schema a little. I would suggest that you keep for each keyword not only its parent FK, but also its root FK. This way you could issue a single query to get all objects that have a keyword whose root FK is the desired one.

What collection to store a tree structure?

I want to store an organisation chart in a collection. I think a tree data structure will be best suited to my needs, as I need to add multiple nodes to one node.
LinkedList only provides adding one node to another node, if I understand it correctly.
I have looked at C5 treeset collection, but it doesn't seem to have Add() method to add more than 2 nodes to one node.
I have also looked at Treeview class from Windows Forms library, but I do not want to add Windows forms dll to my project, since I am building a service layer application. (or is it fine?)
I do not want to write my own tree collection class, if there is already one provided by 3rd party?
Any suggestion please?
Thanks
Something like this can be a starting point. By using generics this one can hold a tree of anything
class TreeNode<T>
{
List<TreeNode<T>> Children = new List<TreeNode<T>>();
T Item {get;set;}
public TreeNode (T item)
{
Item = item;
}
public TreeNode<T> AddChild(T item)
{
TreeNode<T> nodeItem = new TreeNode<T>(item);
Children.Add(nodeItem);
return nodeItem;
}
}
A sample which holds a tree of strings
string root = "root";
TreeNode<string> myTreeRoot = new TreeNode<string>(root);
var first = myTreeRoot.AddChild("first child");
var second = myTreeRoot.AddChild("second child");
var grandChild = first.AddChild("first child's child");

Categories

Resources