I am trying to construct a TreeView from a Menu. My Code is like this:
public class MenuExtractionUtility
{
public TreeView MenuTraverse(MainMenu mainMenu)
{
TreeView treeView = new TreeView();
TreeNode mainNode = new TreeNode();
foreach (MenuItem mi in mainMenu.MenuItems)
{
System.Diagnostics.Debug.WriteLine(mi.Text);
mainNode.Text = mi.Text;
TreeNode tn = MenuItemTraverse(mi);
mainNode.Nodes.Add(tn);
}
treeView.Nodes.Add(mainNode);
return treeView;
}
private TreeNode MenuItemTraverse(MenuItem menuItem)
{
TreeNode treeNode = new TreeNode();
foreach(MenuItem mi in menuItem.MenuItems)
{
System.Diagnostics.Debug.WriteLine(mi.Text);
treeNode.Text = mi.Text;
TreeNode tr = MenuItemTraverse(mi);
if (tr!=null && tr.Text != "")
{
treeNode.Nodes.Add(tr);
}
}
return treeNode;
}
}
But this is not working.
What can be the problem?
I think there are two problems in the methods. Let's start with the MenuItemTraverse method. You get a MenuItem as input. You declare a TreeNode variable, and assign a new TreeNode instance to it. Then you loop over the menu item's sub items. For each iteration you assign the text from the sub item to the TreeNode (I would assume that you would want the text of the incoming menu item on this TreeNode). To get the intended behaviour you should remove this line from the loop:
treeNode.Text = mi.Text;
...and add this line before the loop:
treeNode.Text = menuItem.Text;
It looks like you have the exact same problem in the MenuTraverse method, so do the same change there. I think that would solve it for you (didn't test the code yet; might have missed something).
Update
I gave it a bit of though, since I felt that the code could probably be simplified a bit, and this is what I came up with. Instead of having two different methods for MainMenu and MenuItem input, this one encapsulates the process into one single method. Also, it takes a TreeNodeCollection, which means that you can have the method inject the menu structure into an already existing (and populated) TreeView control, at any level in the tree.
public class MenuExtractionUtility
{
public static void MenuItemTraverse(TreeNodeCollection parentCollection, Menu.MenuItemCollection menuItems)
{
foreach (MenuItem mi in menuItems)
{
System.Diagnostics.Debug.WriteLine(mi.Text);
TreeNode menuItemNode = parentCollection.Add(mi.Text);
if (mi.MenuItems.Count > 0)
{
MenuItemTraverse(menuItemNode.Nodes, mi.MenuItems);
}
}
}
}
Usage example:
treeView1.Nodes.Clear();
MenuExtractionUtility.MenuItemTraverse(treeView1.Nodes, mainMenu1.MenuItems);
This code was just quickly put together, so you may want to "stabilize" it a bit by adding null checks and similar.
here it is...
public class MenuExtractionUtility
{
public void MenuTraverse(MainMenu mainMenu, TreeView treeView)
{
TreeNode ultimateMainNode = new TreeNode();
ultimateMainNode.Text = "Root";
TreeNode mainNode = null;
foreach (MenuItem mi in mainMenu.MenuItems)
{
if (mi != null && mi.Text != "")
{
mainNode = null;
if (mi.MenuItems.Count <= 0)
{
mainNode = new TreeNode();
mainNode.Text = mi.Text;
}
else if (mi.MenuItems.Count > 0)
{
mainNode = MenuItemTraverse(mi);
}
ultimateMainNode.Nodes.Add(mainNode);
}
}
treeView.Nodes.Add(ultimateMainNode);
}
private TreeNode MenuItemTraverse(MenuItem menuItem)
{
TreeNode treeNode = new TreeNode();
System.Diagnostics.Debug.Write(menuItem.Text+",");
treeNode.Text = menuItem.Text;
foreach (MenuItem mi in menuItem.MenuItems)
{
if (mi != null && mi.Text != "")
{
TreeNode tr = MenuItemTraverse(mi);
if (tr != null && tr.Text != "")
{
treeNode.Nodes.Add(tr);
}
}
}
return treeNode;
}
Related
EDIT:
Right thanks for helping earlier, I have been using and the step into and step over and it looks to be working but the nodes are not being deleted and I'm not sure why.
I actually use 5 arguments for the BST but just using the one for testing purposes. It compares and finds if it has any children no problem. Just wont set it to null.
only testing nodes with 0 or 1 children.
main
Tree aTree = new Tree();
aTree.InsertNode("a");
aTree.InsertNode("s");
aTree.InsertNode("3");
aTree.InsertNode("1");
aTree.InsertNode("p");
aTree.PreorderTraversal();
aTree.RemoveNode("p");
aTree.RemoveNode("3");
aTree.PreorderTraversal();
Console.ReadKey();
My Delete Methods are:
Tree Node
public void Remove(TreeNode root, TreeNode Delete) {
if (Data == null) {
}
if (Delete.Data.CompareTo(root.Data) < 0) {
root.nodeLeft.Remove(root.nodeLeft, Delete);
}
if (Delete.Data.CompareTo(root.Data) > 0) {
root.nodeRight.Remove(root.nodeRight, Delete);
}
if (Delete.Data == root.Data) {
//No child nodes
if (root.nodeLeft == null && root.nodeRight == null) {
root = null;
}
else if (root.nodeLeft == null)
{
TreeNode temp = root;
root = root.nodeRight;
root.nodeRight = null;
temp = null;
}
//No right child
else if (root.nodeRight == null)
{
TreeNode temp = root;
root = root.nodeLeft;
root.nodeLeft = null;
temp = null;
}
//Has both child nodes
else
{
TreeNode min = minvalue(root.nodeRight);
root.Data = min.Data;
root.nodeRight.Remove(root.nodeRight, min);
}
}
}
Find Min
public TreeNode minvalue(TreeNode node)
{
TreeNode current = node;
/* loop down to find the leftmost leaf */
while (current.nodeLeft != null)
{
current = current.nodeLeft;
}
return current;
}
Tree
public void RemoveNode(string Nation)
{
TreeNode Delete = new TreeNode(Nation);
root.Remove(root, Delete);
}
Remove is of return type void, but you're trying to assign it to root.nodeLeft and root.nodeRight, causing your type conversion error.
In general your Remove function needs to return the root of the sub-tree as the result, as in
public void Remove(TreeNode root, TreeNode Delete) {
if (Data == null) {
return null;
}
if (Delete.Data.CompareTo(root.Data) < 0) {
root.nodeLeft = (root.nodeLeft.Remove(root.nodeLeft, Delete));
return root;
}
... and so on.
Otherwise, since your nodes don't refer to their parents, there would be no way for the parent to know that the child node is gone, or that a new node is now at the root of the sub-tree.
hi how i can set a image for my treeView nodes... I have a parent and a child node.
here is my code:
private void btnShowLicstate_Click(object sender, EventArgs e)
{
treeLic.Nodes.Clear();
string command = "\"C:\\lmxendutil.exe\" -licstatxml -host lwserv005 -port 6200";
string output = ExecuteCommand(command);
string final_output = output.Substring(90, output.Length - 90);
XmlReader xr = XmlReader.Create(new StringReader(final_output));
var xDoc = XDocument.Load(xr);
TreeNode root = new TreeNode();
LoadTree(xDoc.Root.Element("LICENSE_PATH"), root);
treeLic.Nodes.Add(root);
treeLic.ImageList = imageList1;
}
public void LoadTree(XElement root, TreeNode rootNode)
{
foreach (var e in root.Elements().Where(e => e.Attribute("NAME") != null))
{
var node = new TreeNode(e.Attribute("NAME").Value);
rootNode.Nodes.Add(node);
if (e.Name == "FEATURE")
{
node.SelectedImageIndex = 1;
}
else if (e.Name == "USER")
{
node.SelectedImageIndex = 0;
}
LoadTree(e, node);
}
}
my problem is that i have everyone the same picture but i want for FEATURE the index 1 and for USER the Index 2 but why it don't work ? :(
You should use ImageIndex property instead of SelectedImageIndex.
The first one is the index from ImageList for node in unselected state and the second one is applied when you select node using mouse, keyboard or through code.
Like this:
TreeNode[] treeNodes = treeView.Nodes.Find(searchString, true);
but I want it to search in the text field instead of the name field.
I am not aware of any inbuilt method but you may use LINQ
TreeNode[] treeNodes = treeView.Nodes
.Cast<TreeNode>()
.Where(r => r.Text == "yourText")
.ToArray();
To search all tree nodes (not only the direct child nodes) you can use the extension method below
var nodes = treeView1.FlattenTree()
.Where(n => n.Text == "sometext")
.ToList();
--
public static class SOExtension
{
public static IEnumerable<TreeNode> FlattenTree(this TreeView tv)
{
return FlattenTree(tv.Nodes);
}
public static IEnumerable<TreeNode> FlattenTree(this TreeNodeCollection coll)
{
return coll.Cast<TreeNode>()
.Concat(coll.Cast<TreeNode>()
.SelectMany(x => FlattenTree(x.Nodes)));
}
}
If I understand you correctly (you last question was very confusing), you can write a find method yourself as follows
public static TreeNode[] Find(this TreeNode motherNode, string findNodeText)
{
List<TreeNode> nodeList = new List<TreeNode>();
foreach (TreeNode childNode in motherNode.Nodes)
if (childNode.Text.Equals(findNodeText, StringComparison.CurrentCulture))
nodeList.Add(childNode);
return nodeList.ToArray<TreeNode>();
}
This method can be used like
TreeView myTreeView = new TreeView();
foreach (TreeNode node in myTreeView.Nodes)
{
TreeNode[] childNodes = node.Find("Text");
// Do something...
}
I hope this helps.
The following code only shows the nodes which matches the search criteria.
Copy the following code in the search event
private void tbxSearch_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
trvMenu.BeginUpdate();
if (tbxSearch.Text.Length > 0)
{
for (int i = trvMenu.Nodes.Count; i > 0 ; i--)
{
NodeFiltering(trvMenu.Nodes[i - 1], tbxSearch.Text);
}
}
trvMenu.EndUpdate();
}
Then create the serch & filter function
private bool NodeFiltering(TreeNode Nodo,string Texto)
{
bool resultado = false;
if (Nodo.Nodes.Count == 0)
{
if (Nodo.Text.ToUpper().Contains(Texto.ToUpper()))
{
resultado = true;
}
else
{
Nodo.Remove();
}
}
else
{
for (int i = Nodo.Nodes.Count; i > 0; i--)
{
if (NodeFiltering(Nodo.Nodes[i - 1], Texto))
resultado = true;
}
if (!resultado)
Nodo.Remove();
}
return resultado;
}
This code is pretty nice for creating Treeview menus with many levels.
I have data that looks like the below:
a.b.c.d.e.f.g
b.c.d.e.f.g.h.x
c.d.e.q.s.n.m.y
a.b.c
I need to take this data and turn each and every level into a node in a treeview. So the tree looks something like:
a
b
c
d
e
...
b
c
d
....
if for example at the same leve there is another a, elements under this should be added as nodes to that branch. I have thought of the following:
Parse each line that is qualified by the dot character for each element and create an ordered list.
For each item in the list add it as a node in the current location.
Before adding check to make sure another item at the same level does not exist with the same name.
Add the next element until all items in the list are done, next elements being child to the first added item of the list.
I hope I was clear and let me know if it needs further clarification.
You can change the Node class to have the checks, if you want a list of children nodes, etc, add that as a HashSet, so you can easily make the check for uniqueness. Add a method in the Node class to do the AddChild and do the check on the HashSet.
public class Main
{
public Main()
{
string treeStr = "";
string[] strArr = { "a.b.c.d.e.f.g", "b.c.d.e.f.g.h.x" };
List<Node> nodes = new List<Node>();
Node currentNode;
foreach (var str in strArr)
{
string[] split = str.Split('.');
currentNode = null;
for (int i = 0; i < split.Length; i++)
{
var newNode = new Node { Value = str };
if (currentNode != null)
{
currentNode.Child = newNode;
}
else
{
nodes.Add(newNode);
}
currentNode = newNode;
}
}
}
}
public class Node
{
public string Value { get; set; }
public Node Child { get; set; }
}
I'm assuming the existence of the methods CreateRootNode and AddChildNode.
void ParseToTreeview(IEnumerable<string> data) {
foreach (var line in data) {
var names = line.Split('.');
for (var i = 0; i < names.Length; i++) {
TreeNode node = null;
if (i == 0)
node = CreateRootNode(name:names[i]);
else
node = AddChildNode(name:names[i], parentNode:node);
}
}
}
A recursive method to add all of these is what you need. Here's a sample:
Use:
string[] yourListOfData = { "a.b.c.d.e.f.g", "b.c.d.e.f.g.h.x", "c.d.e.q.s.n.m.y", "a.b.c" };
foreach(string x in yourListOfData)
PopulateTreeView(x, myTreeView.Nodes[0]);
Sample Method:
public void PopulateTreeView(string values, TreeNode parentNode )
{
string nodeValue = values;
string additionalData = values.Substring(value.Length - (value.Length - 2));
try
{
if (!string.IsNullOrEmpty(nodeValue))
{
TreeNode myNode = new TreeNode(nodeValue);
parentNode.Nodes.Add(myNode);
PopulateTreeView(additionalData, myNode);
}
} catch ( UnauthorizedAccessException ) {
parentNode.Nodes.Add( "Access denied" );
} // end catch
}
NOTE: code above is not tested, might need tweaking
Does anyone know of an algorithm that will merge treenodes in the following way?
treeA
\ child a
\node(abc)
\ child b
\node(xyz)
+
treeB
\ child a
\node(qrs)
\ child b
\node(xyz)
\node(pdq)
\ child c
\node(pdq)
= // do merge
treeMerged
\ child a
\node(abc)
\node(qrs)
\ child b
\node(xyz)
\node(pdq)
\ child c
\node(pdq)
Any help would be greatly appreciated.
Well, once I actually took the time to think about it, the solution turns out to be far more simple than I anticipated. (I've posted the critical part of the code below)
private TreeNode DoMerge(TreeNode source, TreeNode target) {
if (source == null || target == null) return null;
foreach (TreeNode n in source.Nodes) {
// see if there is a match in target
var match = FindNode(n, target.Nodes); // match paths
if (match == null) { // no match was found so add n to the target
target.Nodes.Add(n);
} else {
// a match was found so add the children of match
DoMerge(n, match);
}
}
return target;
}
Still interested to know if someone has a better solution?
Ok, I'll admit, when I first started messing with this, I didn't think it would be too hard, so I figured I'll try to do it using LINQ. It came out to be nuts, but it works. I'm SURE there are more elegant and efficient algorithms, but here it is!
First, I have a ToEnumerable extension method on the TreeNodeCollection class:
public static class TreeNodeCollectionExtensions
{
public static IEnumerable<TreeNode> ToEnumerable(this TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
yield return node;
}
}
}
Then, I implement a custom comparer:
public class TreeNodeComparer : IEqualityComparer
{
public bool Equals(TreeNode x, TreeNode y)
{
return x.Text == y.Text;
}
public int GetHashCode(TreeNode obj)
{
return obj.Text.GetHashCode();
}
}
And finally, the crazyness:
private TreeView MergeTreeViews(TreeView tv1, TreeView tv2)
{
var result = new TreeView();
foreach (TreeNode node in tv2.Nodes)
{
result.Nodes.Add(node.Clone() as TreeNode);
}
foreach (TreeNode node in tv1.Nodes)
{
var nodeOnOtherSide = result.Nodes.ToEnumerable()
.SingleOrDefault(tr => tr.Text == node.Text);
if (nodeOnOtherSide == null)
{
TreeNode clone = node.Clone() as TreeNode;
result.Nodes.Add(clone);
}
else
{
var n = node.Nodes.ToEnumerable()
.Where(t => !(nodeOnOtherSide.Nodes.ToEnumerable()
.Contains(t, new TreeNodeComparer())));
foreach (TreeNode subNode in n)
{
TreeNode clone = subNode.Clone() as TreeNode;
nodeOnOtherSide.Nodes.Add(clone);
}
}
}
return result;
}
The way I coded it was that it returns a third "merged" treeView. You can change the code, so that it takes a third treeview as a parameter, so that you can pass in a treeView you may already have.
Again, I'm SURE there are better way to do this, but it SHOULD work.
One more thing I'd like to point out, this will only work for a TreeView that is two layers deep.
I came up with this recursive example, works perfect in C# (have been using it myself), note that you'll need to find a way to convert TreeNode.Nodes to an array:
public static TreeNode[] mergeTrees(TreeNode[] target, TreeNode[] source)
{
if (source == null || source.Length == 0)
{
return target;
}
if (target == null || target.Length == 0)
{
return source;
}
bool found;
foreach (TreeNode s in source)
{
found = false;
foreach (TreeNode t in target)
{
if (s.Text.CompareTo(t.Text) == 0)
{
found = true;
TreeNode[] updatedNodes = mergeTrees(Util.treeView2Array(t.Nodes), Util.treeView2Array(s.Nodes));
t.Nodes.Clear();
t.Nodes.AddRange(updatedNodes);
break;
}
}
if (!found)
{
TreeNode[] newNodes = new TreeNode[target.Length + 1];
Array.Copy(target, newNodes, target.Length);
newNodes[target.Length] = s;
target = newNodes;
}
}
return target;
}
If you are using the Node.Name attribute to set the actual path of the item, then a merge is somewhat simple.
First, create a TreeNodeCollection extension like so (this is needed to have a Case Sensitive Find() method for the TreeNodeCollection, which ensures that the Unique Path can be unique by Case as well) :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace TreeViewApp
{
public static class TreeNodeCollectionExtensions
{
public static TreeNode[] FindExact(this TreeNodeCollection coll, string keytofind)
{
TreeNode[] retval;
if (String.IsNullOrWhiteSpace(keytofind) || coll == null)
{
retval = new TreeNode[0];
}
else
{
TreeNode[] badfinds = coll.Find(keytofind, true);
List<TreeNode> goodfinds = new List<TreeNode>();
foreach (TreeNode bad in badfinds)
{
if (bad.Name == keytofind)
goodfinds.Add(bad);
}
retval = goodfinds.ToArray();
}
return retval;
}
}
}
Second, fill a treeview with your Source nodes...
Thrid, fill a treeview with your Target nodes...
Fourth, create an empty treeview.
and then, it's as simple as this:
private void btn_Merge_Click(object sender, EventArgs e)
{
//first merge
foreach (TreeNode sourceNode in this.treeview_Source.Nodes)
{
FindOrAdd(sourceNode, ref this.treeview_Merged);
}
//second merge
foreach (TreeNode targetNode in this.treeview_Target.Nodes)
{
FindOrAdd(targetNode, ref this.treeview_Merged);
}
}
private void FindOrAdd(TreeNode FindMe, ref TreeView InHere)
{
TreeNode[] found = InHere.Nodes.FindExact(FindMe.Name);
//if the node is not found, add it at the proper location.
if (found.Length == 0)
{
if (FindMe.Parent != null)
{
TreeNode[] foundParent = InHere.Nodes.FindExact(FindMe.Parent.Name);
if (foundParent.Length == 0)
InHere.Nodes.Add((TreeNode)FindMe.Clone());
else
foundParent[0].Nodes.Add((TreeNode)FindMe.Clone());
}
else
InHere.Nodes.Add((TreeNode)FindMe.Clone());
}
else
{
//if the item was found, check all children.
foreach (TreeNode child in FindMe.Nodes)
FindOrAdd(child, ref InHere);
}
}
Once again, this solution only works if you have unique paths... with the extension, it also accounts for uniqueness at the case level.
I posted this here in the hopes of helping someone who, like me, had to search for a solution for days on end without success and had to build my own.