How can I get all expanded nodes in treeview? - c#

I have a program that contains a TreeView. All of my nodes except for the root and two nodes under the root are loaded from a database.
When the user adds data to the database it must be automatically added to the TreeView. I can successfully do this by clearing all the nodes, add the default nodes and add all the data including the new one to my TreeView, but all nodes of the new TreeView are collapsed.
Our client wants to retain all expanded nodes but still add the new data he just added. Is there any way to know all expanded nodes and expand it again once collapsed or refreshed? Thank you for any response.

Hi As i understand you want to save you tree view map after you refresh tree view (by adding new data or even remove some) you want to expand all expanded nodes the other are collapsed by default.
Solution is:
1) save expanded tree view nodes before refresh
2) refresh tree view data (notice that if you are removing a node , remove it from saved list as well)
3) set tree view map which saved before
saving tree view map (only expanded nodes)->
This Code Look through a tree view node collection and saves expanded nodes Names in a string list
List<string> collectExpandedNodes(TreeNodeCollection Nodes)
{
List<string> _lst = new List<string>();
foreach (TreeNode checknode in Nodes)
{
if (checknode.IsExpanded)
_lst.Add(checknode.Name);
if (checknode.Nodes.Count > 0)
_lst.AddRange(collectExpandedNodes(checknode.Nodes));
}
return _lst;
}
Now you have collected expanded nodes name in a list and you want to gain back your tree view appearance you need 2 functions a function which retrieves node by a name and a function which expand selected node and it's parents the following codes does :
This function retrieves a pointer to selected node Name if node is exist in Tree Node Collection
TreeNode FindNodeByName(TreeNodeCollection NodesCollection , string Name)
{
TreeNode returnNode = null; // Default value to return
foreach (TreeNode checkNode in NodesCollection)
{
if (checkNode.Name == Name) //checks if this node name is correct
returnNode = checkNode;
else if (checkNode.Nodes.Count > 0 ) //node has child
{
returnNode = FindNodeByName(checkNode.Nodes , Name);
}
if (returnNode != null) //check if founded do not continue and break
{
return returnNode;
}
}
//not found
return returnNode;
}
and This Function Expand node and it's parents
void expandNodePath(TreeNode node)
{
if (node == null)
return;
if (node.Level != 0) //check if it is not root
{
node.Expand();
expandNodePath(node.Parent);
}
else
{
node.Expand(); // this is root
}
}
and the following show you the usage of the functions
private void button4_Click(object sender, EventArgs e)
{
//saving expanded nodes
List<string> ExpandedNodes = new List<string>();
ExpandedNodes = collectExpandedNodes(treeView1.Nodes);
//resetting tree view nodes status to colapsed
treeView1.CollapseAll();
//Restore it back
if (ExpandedNodes.Count > 0)
{
TreeNode IamExpandedNode;
for (int i = 0; i < ExpandedNodes.Count;i++ )
{
IamExpandedNode = FindNodeByName(treeView1.Nodes, ExpandedNodes[i]);
expandNodePath(IamExpandedNode);
}
}
}

For expanding all the nodes use below code
treeView1.ExpandAll();
For expanding selected node use below code
treeView1.SelectedNode.ExpandAll();
For expanding particular node use below code
treeView1.Nodes[Index].Expand();

Assume the Nodename is unique.
Using an Database the nodename can be the unique rowid of an table
The state of the tree (List) can simply saved with an Formatter (e.g. BinaryFormatter)
if User want to save status
Only save the expandedstate
private List<string> SaveTreeState(TreeNodeCollection nodes)
{
List<string> nodeStates = new List<string>();
foreach (TreeNode node in nodes)
{
if (node.IsExpanded) nodeStates.Add(node.Name);
nodeStates.AddRange(SaveTreeState(node.Nodes));
}
return (nodeStates);
}
Let the treeview do the work finding the nodes for restore
private void RestoreTreeState(TreeView tree, List<string> treeState)
{
foreach (string NodeName in treeState)
{
TreeNode[] NodeList = treeView1.Nodes.Find(NodeName, true);
if (NodeList.Length > 0) // only if node after reload is avail
NodeList[0].Expand();
}
}
Using:
List<string> StateList = SaveTreeState(treeView1.Nodes);
... // reload
RestoreTreeState(treeView1, StateList);

To simply expand nodes you can try this following code
private void button1_Click(object sender, EventArgs e)
{
treeView1.Nodes.Add(new TreeNode("New Node",
new TreeNode[2] { new TreeNode("Node1"), new TreeNode("Node2") }));
treeView1.Nodes[1].Expand();
}
Hope that helps

It's very simple. Below, you can see my recursive version:
//List of storage ids of expanded nodes
List<int> expandedNodeIds = new List<int>();
//call recursive fun for our tree
CollectExpandedNodes(tree.Nodes);
//recursive fun for collect expanded node ids
private void CollectExpandedNodes(TreeListNodes nodes)
{
foreach (TreeListNode node in nodes)
{
if (node.Expanded) expandedNodeIds.Add(node.Id);
if (node.HasChildren) CollectExpandedNodes(node.Nodes);
}
}

I know this post is old but if the tree is deep it might not be a good idea to use a recursive traversal of the tree. Since I have not seen any anwser using a non-recursive way here is a solution for doing getting expanded nodes without impacting performance.
public static IEnumerable<TreeNodeAdv> CollectExpandedNodes(this TreeNodeAdv root)
{
Stack<TreeNodeAdv> s = new Stack<TreeNodeAdv>();
s.Push(root);
while (s.Count > 0)
{
TreeNodeAdv n = s.Pop();
if (n.IsExpanded)
yield return n;
foreach (var child in n.Children.ToArray().Reverse())
{
s.Push(child);
}
}
}
To use this method, you can do the following:
foreach (TreeNodeAdv expandedNode in yourTreeView.Root.CollectExpandedNodes())
{
//Do processing on the expanded node or add in list.
}
This extension method uses Deep-First traversal in post-order along with the yield keyword to generate an IEnumerable collection.

Related

How to insert a tree into another tree in C#

I have two trees like these:
I want to insert the second tree into the first tree in a node that has the same name of its root and attach the children of this node to the left most child of the second tree.
I tried:
PTree attachPoint = chain.Find(x => x.Val == RTree.Val);
if (attachPoint != null)
{
foreach (var c in attachPoint.Childs)
{
RTree.Left.Childs.Add(c);
}
attachPoint = RTree;
}
else
{
RTree.Left.Childs.Add(root);
root = RTree;
}
Here, RTree is the second tree and root points to the root of first tree and chain holds the branch from root "A" to "D". But it seems the desired tree is not built. Did i do it correctly?
It would have been easier to help if you have included the essential parts of your PTree class. Here is what I could suggest you based on the posted code:
PTree attachPoint = chain.Find(x => x.Val == RTree.Val);
if (attachPoint != null)
{
foreach (var c in attachPoint.Childs)
{
RTree.Left.Childs.Add(c);
}
// Here you either have to find the attachPoint parent and
// replace attachPoint with RTree in parent.Childs,
// or make attachPoint.Childs to be RTree.Childs as below
attachPoint.Childs.Clear();
foreach (var c in RTree.Childs)
{
attachPoint.Childs.Add(c);
}
}
else
{
RTree.Left.Childs.Add(root);
root = RTree;
}
attachPoint (and root?) are just local variables so attachPoint = RTree; will not affect the structure of the tree. You need to search the left tree to find the parent node of the insert point and then modify the parent node so that parent.Right = attachPoint;

Limit query to ignore elements descendants

I'm trying to query the following XML to build some objects which copy the folder hierarchy outlined in the XML.
<ShareList>
<Title>Documantis</Title>
<Url>/sites/dev/Documantis/Forms/AllItems.aspx</Url>
<Guid>fed8f456-efa9-4fe5-8b97-46734a3040b6</Guid>
<HasUniqueScopes>False</HasUniqueScopes>
<RootFolder>/sites/dev</RootFolder>
<Children>
<ShareListItem>
<Title>First</Title>
<Url>Documantis/First</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
<Children>
<ShareListItem>
<Title>Second</Title>
<Url>Documantis/First/Second</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<ParentGuid>22b2a7e9-a42e-497f-aad3-8caa85f6ac6d</ParentGuid>
</ShareListItem>
</Children>
</ShareListItem>
<ShareListItem>
<Title>Folda</Title>
<Url>Documantis/Folda</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
</ShareListItem>
</Children>
</ShareList>
I'm having trouble finding a way to return one level of the <ShareListItem> elements at a time, with my current code it returns all the ShareListItems in one List which doesn't represent the hierarchy accurately.
XmlDocument doc = new XmlDocument();
doc.LoadXml(sharepointXml);
XElement root;
using (XmlReader xr = new XmlNodeReader(doc)) { root = XElement.Load(xr); }
var result = from child in root.DescendantsAndSelf("ShareList") //.Elements("ShareList") // Descendants("ShareList")
select child;
foreach (XElement xml in result)
{
// Build ListItem from results
ShareList list = new ShareList()
{
Title = xml.Element("Title").Value,
Url = xml.Element("Url").Value,
Guid = xml.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(xml.Element("HasUniqueScopes").Value),
RootFolder = xml.Element("RootFolder").Value,
};
if (xml.Element("Children") != null)
{
var subResult = from child in xml.Element("Children").Descendants("ShareListItem")
select child;
foreach (XElement subXml in subResult)
{
// results here are flat and don't show depth of nodes
}
//list.Children =
}
I could recursively infer the hierarchy's structure from the URL Element, however I already have it represented in XML so I would rather learn how to return this through a query.
Edit:
Here's what I ended up using
public List<ShareList> HandleLists(XElement levelRoot)
{
List<ShareList> lists = new List<ShareList>();
var results = from list in levelRoot.DescendantsAndSelf("ShareList")
select list;
foreach (var list in results)
{
var children = list.Element("Children");
if (children == null)
return null;
ShareList shareList = new ShareList()
{
Title = list.Element("Title").Value,
Url = list.Element("Url").Value,
Guid = list.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(list.Element("HasUniqueScopes").Value),
RootFolder = list.Element("RootFolder").Value,
// Recursively find ListItem folders
Children = HandleSubfolders(list)
};
lists.Add(shareList);
}
return lists;
}
public List<ShareListItem> HandleSubfolders(XElement levelRoot)
{
List<ShareListItem> subfolders = new List<ShareListItem>();
// All nodes deeper than current
var children = levelRoot.Element("Children");
if (children == null)
return null;
// Subfolders
var items = children.Elements("ShareListItem");
foreach (var item in items)
{
ShareListItem listItem = new ShareListItem()
{
Title = item.Element("Title").Value,
Url = item.Element("Url").Value,
HasUniqueRole = Convert.ToBoolean(item.Element("HasUniqueRole").Value),
IsSubfolder = Convert.ToBoolean(item.Element("IsSubFolder").Value),
PermissionMask = item.Element("PermissionMask").Value,
PermissionMaskName = item.Element("PermissionMaskName").Value,
// Recursively find ListItem subfolders
Children = HandleSubfolders(item)
};
// Add subfolder to Children collection
subfolders.Add(listItem);
}
return subfolders;
}
You would want to use recursion here.
Create a method that handles one level of the hierarchy and calls itself with the next level.
public void HandleLevel(XElement levelRoot)
{
PerformAction(levelRoot);
var children = levelRoot.Element("Children");
if(children == null)
return;
var items = children.Elements("ShareListItem");
foreach(var item in item)
{
// Handle child's children:
HandleLevel(item);
}
}
PerformAction is the code that actually does, whatever you want to do for each document.
The way the code is currently structured, this action is also executed for the root document /sites/dev/Documantis/Forms/AllItems.aspx.
If you don't want this simply move PerformAction into the foreach loop and pass item instead of levelRoot.
BTW: Your initialization of the root element is very strange.
You can simply use this:
var root = XDocument.Parse(sharepointXml).Root;
The initial call to HandleLevel would simply look like this:
HandleLevel(root);
A good way of producing the results your after is by using XPath (here's a good primer if you need it).
Once you've got your XML into an XmlDocument you can return different bits of it by using an XPathNavigator, like this:
var xmlNavigator = xmlDocument.CreateNavigator();
var outerQuery = xmlNavigator.Select("ShareList/Children/ShareListItem");
while (outerQuery.MoveNext()) {
Console.WriteLine(outerQuery.Current.SelectSingleNode("Title").Value);
var innerQuery = outerQuery.Current.Select("Children/ShareListItem");
while (innerQuery.MoveNext()) {
Console.WriteLine(" - " + innerQuery.Current.SelectSingleNode("Title").Value);
}
}
In the code above, we query the XML for all ShareListItem nodes within Children nodes of the root ShareList node, and store the resultant XPathNodeIterator in the variable outerQuery. We then iterate over all the nodes found, and run an operation as well as another XPath query on each to retrieve child nodes to process. The code above produces the following output:
First
- Second
Folda
Which I think it what you're after. Obviously, you can use recursion if necessary if your XML can be nested more deeply than this.
One way of doing this is by creating Classes to represent the hierarchy like so:
public class ShareList {
...
public List<ShareList> Children { get; set; }
}
In your code, refactor the traversing part into a method that accepts a Sharelist node and traverse it, calling itself for each Children node:
private Sharelist RecurseHierarchy(XElement sharelistNode, ShareList parent)
{
// your traversing code goes here
// get your data and create a new Sharelist object
// if it has a children node, traverse it and call this same method on the child
// Sharelist nodes
parent.Title = sharelistNode.Element("Title").Value;
var children = sharelistNode.Element("Children");
if (children != null)
{
var items = children.Elements("ShareListItem");
foreach(var listItem in items)
{
ShareList childShareList = new ShareList();
parent.Children.Add(childShareList);
RecurseHierarchy(listItem, childShareList);
}
}
// Just in case we want to chain some method
return parent;
}
To call it initially, you will have to pass in the root node and a new ShareList object.

Arrange TreeView by getting file paths?

I have this code:
public void AddNode(string Node)
{
try
{
treeView.Nodes.Add(Node);
treeView.Refresh();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
Very simple as you see, this method gets file path. like C:\Windows\notepad.exe
Now i want the TreeView to show it like FileSystem..
-C:\
+Windows
And if i click the '+' it gets like this:
-C:\
-Windows
notepad.exe
Here is what i get now from sending theses pathes to the method above:
How can i do that it will arrange the nodes?
If I were you, I would split the input string onto substrings, using the string.Split method and then search for the right node to insert the relevant part of a node. I mean, that before adding a node, you should check whether node C:\ and its child node (Windows) exist.
Here is my code:
...
AddString(#"C:\Windows\Notepad.exe");
AddString(#"C:\Windows\TestFolder\test.exe");
AddString(#"C:\Program Files");
AddString(#"C:\Program Files\Microsoft");
AddString(#"C:\test.exe");
...
private void AddString(string name) {
string[] names = name.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
TreeNode node = null;
for(int i = 0; i < names.Length; i++) {
TreeNodeCollection nodes = node == null? treeView1.Nodes: node.Nodes;
node = FindNode(nodes, names[i]);
if(node == null)
node = nodes.Add(names[i]);
}
}
private TreeNode FindNode(TreeNodeCollection nodes, string p) {
for(int i = 0; i < nodes.Count; i++)
if(nodes[i].Text.ToLower(CultureInfo.CurrentCulture) == p.ToLower(CultureInfo.CurrentCulture))
return nodes[i];
return null;
}
If you are in windows forms (and I guess so), you can implement the IComparer class and use the TreeView.TreeViewNodeSorter property:
public class NodeSorter : IComparer
{
// Compare the length of the strings, or the strings
// themselves, if they are the same length.
public int Compare(object x, object y)
{
TreeNode tx = x as TreeNode;
TreeNode ty = y as TreeNode;
// Compare the length of the strings, returning the difference.
if (tx.Text.Length != ty.Text.Length)
return tx.Text.Length - ty.Text.Length;
// If they are the same length, call Compare.
return string.Compare(tx.Text, ty.Text);
}
}
Is the issue that the parents and children aren't being differentiated?
Each one of the nodes in the tree also has a Nodes property, which represents the collection of its children. Your AddNode routine needs to be changed so you can specify the parent node to whom you want to add a child node. Like:
TreeNode parent = //some node
parent.Nodes.Add(newChildNode);
If you want it to just populate the paths and figure out the parent-child relationships itself, you're going to have to write some code to parse the paths, and identify the parent node based on the path segments.
Try taking a look at this Filesystem TreeView. It should do exactly what you are looking for.

C# Treeview checking if node exists

I'm trying to populate a treeview from an XmlDocument.
The Root of the tree is set as 'Scripts' and from the root the next level should be 'Departments' which is within the XML script. I can get data from the XML document no problem. My question is when looping through the XmlDocument and adding nodes to the root node, I want to ensure that if a department is already within the treeview then it is not added again. I should also add that each Department also has a list of scripts that need to be child nodes of the department.
My code so far is:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(scriptInformation);
TreeNode t1;
TreeNode rootNode = new TreeNode("Script View");
treeView1.Nodes.Add(rootNode);
foreach (XmlNode node in xDoc.SelectNodes("//row"))
{
t1 = new TreeNode(node["DEPARTMENT"].InnerXml);
//How to check if node already exists in treeview?
}
Thanks.
if(treeView1.Nodes.ContainsKey("DEPARTMENT")){
//...
}
EDIT: Recursive method:
bool exists = false;
foreach (TreeNode node in treeView1.Nodes) {
if (NodeExists(node, "DEPARTMENT"))
exists = true;
}
private bool NodeExists(TreeNode node, string key) {
foreach (TreeNode subNode in node.Nodes) {
if (subNode.Text == key) {
return true;
}
if (node.Nodes.Count > 0) {
NodeExists(node, key);
}
}
return false;
}
Depending upon the size of your XML file, you could consider using an associated List for fast lookup. As you add each node to the TreeView also add it to the List.
If your XML document has a set structure where 'Departments' will always be indexed at 1;
ie:
index:[0] Scripts
index:[1] Department
index:[2] Script
index:[1] Department2
index:[2] Script
Then you could encapsulate the following code into a method where 'name' is a string parameter and the return type is boolean.
foreach (TreeNode node in uxTreeView.Nodes[0].Nodes) {
if (node.Name.ToLower() == name.ToLower()) {
return true;
}
}
return false;
The idea is you would call that function each time you encounter a 'Department' node in your Xml, before creating the TreeNode.
Full example:
private bool DepartmentNodeExists(string name) {
foreach (TreeNode node in uxTreeView.Nodes[0].Nodes) {
if (node.Name.ToLower() == name.ToLower()) {
return true;
}
}
return false;
}
Lastly, the easy way:
private bool DepartmentNodeExists(string name) {
if (uxTreeView.Nodes[0].ContainsKey(name)) {
return true;
}
else {
return false;
}
}
These are all just refactored and encapsulated into their own named methods, you of course could just call:
if (uxTreeView.Nodes[0].ContainsKey(name)) {
// do not create TreeNode
}
...during your parsing of your XML. PS. These examples all assume that you have the first root node in the TreeView already created and added to the TreeView.
http://www.vbdotnetforums.com/listviews-treeviews/13278-treeview-search.html#post39625
http://forums.asp.net/t/1645725.aspx/1?Check+if+child+Node+exists+on+treeview
You can do something like this:
TreeNode parentNode = t1.Parent;
if (parentNode != null}
{
if(parentNode.Nodes.Cast<TreeNode>().ToList().Find(t => t.Text.Equals(node["DEPARTMENT"].InnerXml) == null)
{
//Add node
}
}
else
{
bool isFound = true;
if (treeView1.Nodes.Cast<TreeNode>().ToList().Find(t => t.Text.Equals(node["DEPARTMENT"].InnerXml) == null)
{
isFound = false;
}
if(!isFound)
{
//Add node
}
}
Not sure about the document structure...
Couldn't you use Linq to Xml, load the document and get the distinct row ( row = department?) and consider only those elements to create a TreeNode? It is more efficient than trying to find if a node with such a text has already been added.
ex:
var rows = ( from row in XDocument.Load(document).Root.Elements("row")
select row
).Distinct(new SampleElementComparerOnNameAttribute());
Here the EqualityComparer is made on the "name" attribute value assuming the doc structure to be
<rows><row name='dep1'><script>script1</script><script>script2</script></row><row name='dep1'><script>script3</script><script>script4</script></row></rows>
I use,
string department = node["DEPARTMENT"].InnerXml;
TreeNode node = parentNode.Nodes[department] ?? parentNode.Nodes.Add(department, department);
That line guarantees that a lookup of the value department will be done first, if not found it creates it. You have to do the double entry in Add() so it will have a key value you can do the lookup with the .Nodes[department].
It depends on the structure of your input. Since you don't show how exactly you add your subnodes I can only point you towards either the Contains or the ContainsKey method of the Nodes property, either of the treeView1 itself, or of any subnodes you add. You should use an overload of the Add method to specify a key name to simplify lookup.

Is it possible to add two nodes at a time dynamically to a treeview

I am having a tree-view on my main form with initially some nodes as follows
ACH
|-> some.txt
|->FileHeader
|->BatchHeader
Now at this point i will have to add 2 child nodes at a time to BatchHeader. This nodes i will pass as strings from child forms
My sample code that i added some nodes is as follows
public void loadingDatafrom(string filename, bool str)
{
if (Append.oldbatchcontrol != filename)
{
if (tvwACH.SelectedNode.Text == "FileHeader")
{
tvwACH.SelectedNode.Nodes.Add(filename);
}
if (tvwACH.SelectedNode.Text == "BatchHeader" && filecontrolvariables.m_gridclick == false)
{
tvwACH.SelectedNode.Nodes.Add(filename);
**I got this idea tvwach.SelectedNode.Lastnode.Nodes.Add("Node");**
}
}
}
Can any one give an idea to add 2 nodes as child to the existing node ..
You can simply call the Add method twice, for example:
TreeNode node = tvwACH.SelectedNode;
node.Nodes.Add(filename);
node.Nodes.Add("Node");
If you meant you want to add two levels of nodes, try this:
TreeNode newNode = new TreeNode("NewNode");
newNode.Nodes.Add("SubNode");
tvwACH.SelectedNode.Nodes.Add(newNode);

Categories

Resources