Copy all treeView parent and children to another treeView c# WinForms - c#

I am trying to copy the entire tree (exactly all nodes) of a treeview (completely) to another treeview using this code:
TreeNodeCollection myTreeNodeCollection = treeView1.Nodes;
TreeNode[] myTreeNodeArray = new TreeNode[treeView1.Nodes.Count];
treeView1.Nodes.CopyTo(myTreeNodeArray, 0);
treeView2.Nodes.AddRange(myTreeNodeArray);
But this does not allow me to do so, it asks to either delete the nodes in source treeview or use it Clone! How can I do that? I dont want my source treeview to lose anything in this process.
** UPDATE **
Ok guys I found a complicated code (for me!!) but how can I use this?
public static T DeepTreeCopy<T>(T obj)
{
object result = null;
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms); ms.Close();
}
return (T)result;
}

try this
public void Copy(TreeView treeview1, TreeView treeview2)
{
TreeNode newTn;
foreach (TreeNode tn in treeview1.Nodes)
{
newTn = new TreeNode(tn.Text, tn.Value);
CopyChilds(newTn, tn);
treeview2.Nodes.Add(newTn);
}
}
public void CopyChilds(TreeNode parent, TreeNode willCopied)
{
TreeNode newTn;
foreach (TreeNode tn in willCopied.ChildNodes)
{
newTn = new TreeNode(tn.Text, tn.Value);
parent.ChildNodes.Add(newTn);
}
}
My regards

Like MohD's answer, but with recursion to get all nodes. (Nodes of childnodes)
public void CopyTreeNodes(TreeView treeview1, TreeView treeview2)
{
TreeNode newTn;
foreach (TreeNode tn in treeview1.Nodes)
{
newTn = new TreeNode(tn.Text, tn.ImageIndex, tn.SelectedImageIndex);
CopyChildren(newTn, tn);
treeview2.Nodes.Add(newTn);
}
}
public void CopyChildren(TreeNode parent, TreeNode original)
{
TreeNode newTn;
foreach (TreeNode tn in original.Nodes)
{
newTn = new TreeNode(tn.Text, tn.ImageIndex, tn.SelectedImageIndex);
parent.Nodes.Add(newTn);
CopyChildren(newTn, tn);
}
}

Using the node.Clone() function works as well...
foreach (TreeNode node in treeViewSource.Nodes)
{
treeViewTarget.Nodes.Add((TreeNode)node.Clone());
}
Adding a root node will help ensure the "PlusMinus" functionality is viewable.
TreeNode rootNode = new TreeNode("Root Node");
treeViewTarget.Nodes.Add(rootNode);
foreach (TreeNode node in treeViewSource.Nodes)
{
rootNode.Nodes.Add((TreeNode)node.Clone());
}
rootNode.Expand();

Thank you guys for the Answers. Combining all answers this worked for me. Even if the treeview has multiple levels.
public void CopyTreeNodes(TreeView treeview1, TreeView treeview2)
{
TreeNode newTn;
foreach (TreeNode tn in treeview1.Nodes)
{
newTn = new TreeNode(tn.Text, tn.Value);
CopyChildren(newTn, tn);
treeview2.Nodes.Add(newTn);
}
}
public void CopyChildren(TreeNode parent, TreeNode original)
{
TreeNode newTn;
foreach (TreeNode tn in original.ChildNodes)
{
newTn = new TreeNode(tn.Text, tn.Value);
parent.ChildNodes.Add(newTn);
CopyChildren(newTn, tn);
}
}

You can use this at the desired level
public void RootCopy(TreeView treeview1, TreeView treeview2)
{
TreeNode newNode;
foreach (TreeNode tnode in treeview1.Nodes)
{
newNode = new TreeNode(tnode.Text);
treeview2.Nodes.Add(newNode);
if (tnode.Nodes.Count != 0)
{
int _1index = tnode.Index;
ChildsCopyLevel2(_1index, treeview1, treeview2);
}
}
}
public void ChildsCopyLevel2(int index1, TreeView TV1, TreeView TV2)
{
foreach (TreeNode Tnode in TV1.Nodes[index1].Nodes)
{
string Childtext = Tnode.Text;
TV2.Nodes[index1].Nodes.Add(Childtext);
if (Tnode.Nodes.Count != 0)
{// ChildsCopyLevel3(Tnode.Nodes.Count, TV1, TV2);
int _2index = Tnode.Index;
ChildsCopyLevel3(index1, _2index, TV1, TV2);
}
}
}
public void ChildsCopyLevel3(int index1, int index2, TreeView TV1, TreeView TV2)
{
foreach (TreeNode Tnode in TV1.Nodes[index1].Nodes[index2].Nodes)
{
string Childtext = Tnode.Text;
TV2.Nodes[index1].Nodes[index2].Nodes.Add(Childtext);
}
}

Related

Converting tree view to byte[], then back again

I'm trying to convert a treeview to a byte array and then back again. So far when the form loads, it will load the structure of my documents. Then as far as I know, it will convert it to a byte array and back but I'm not sure how to convert the byte array back to the tree view.
Here is my code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
string filepath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
ListDirectory(treeView1, filepath);
}
private static void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();
var stack = new Stack<TreeNode>();
var rootDirectory = new DirectoryInfo(path);
var node = new TreeNode(rootDirectory.Name) { Tag = rootDirectory };
stack.Push(node);
while (stack.Count > 0)
{
var currentNode = stack.Pop();
var directoryInfo = (DirectoryInfo)currentNode.Tag;
foreach (var directory in directoryInfo.GetDirectories())
{
var childDirectoryNode = new TreeNode(directory.Name) { Tag = directory };
currentNode.Nodes.Add(childDirectoryNode);
stack.Push(childDirectoryNode);
}
foreach (var file in directoryInfo.GetFiles())
currentNode.Nodes.Add(new TreeNode(file.Name));
}
treeView.Nodes.Add(node);
}
private Byte[] SerilizeQueryFilters()
{
BinaryFormatter bf = new BinaryFormatter();
List<TreeNode> list = new List<TreeNode>();
foreach(TreeNode node in treeView1.Nodes)
{
list.Add(node);
}
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, list);
return ms.GetBuffer();
}
}
private void DeSerilizeQueryFilters(byte[] items)
{
BinaryFormatter bf = new BinaryFormatter();
try
{
using (MemoryStream ms = new MemoryStream())
{
ms.Write(items, 0, items.Length);
ms.Position = 0;
_list = bf.Deserialize(ms) as List<TreeNode>;
treeView2.Nodes.AddRange(_list.ToArray());
}
}
catch(Exception e)
{
Console.WriteLine(e.StackTrace);
}
}
private void button1_Click(object sender, EventArgs e)
{
byte[] data = SerilizeQueryFilters();
DeSerilizeQueryFilters(data);
}
}
So the bit that's throwing an error at the moment is
_list = bf.Deserialize(ms) as List<TreeNode>;
and I get this error:
System.Runtime.Serialization.SerializationException
Does anyone have any ideas?
The solution was easy using the recommendation of #Fildor I stop using BinaryFormatter and now I use JSON and using the recommendation of #madreflection
I create a custom class to save the data of the TreeNode.
Custom TreeNode Class
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TreeViewStuff
{
public class Node
{
public int id;
public string text = "";
public int parentId = 0;
public bool cheked = false;
}
}
Deserialize/Serialize TreeView Class
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TreeViewStuff
{
public class TreeviewPersist
{
public static string strJson;
private static TreeView treeView_;
static public string ToJson(TreeView treeView)
{
List<Node> nodes = new List<Node>();
foreach(TreeNode node in treeView.Nodes)
{
SerializeTree(nodes, node);
}
nodes.RemoveAt(0);
return JsonConvert.SerializeObject(nodes);
}
public delegate void FunctionDelegate();
public static void FromJson(string strJson_, TreeView treeView)
{
strJson = strJson_;
treeView_ = treeView;
treeView.BeginInvoke(new FunctionDelegate(FromJson));
}
private static void FromJson()
{
treeView_.Nodes.Clear();
List<Node> nodes = JsonConvert.DeserializeObject<List<Node>>(strJson);
foreach(Node node in nodes)
{
if (node.parentId == 0)
{
TreeNode treeNode = treeView_.Nodes.Add(node.text);
treeNode.Name = $"{node.id}";
treeNode.Checked = node.cheked;
}
else
{
TreeNode[] foundNodes = treeView_.Nodes.Find($"{node.parentId}", true);
if (foundNodes.Length > 0)
{
TreeNode treeNode = foundNodes[0].Nodes.Add(node.text);
treeNode.Checked = node.cheked;
treeNode.Name = $"{node.id}";
}
}
}
}
static private void SerializeTree(List<Node> nodes, TreeNode treeNode)
{
Node node = new Node();
bool suces = Int32.TryParse(treeNode.Name, out node.id);
TreeNode parent = treeNode.Parent;
if (parent != null)
{
suces = Int32.TryParse(parent.Name, out node.parentId);
}
else
{
node.parentId = 0;
}
node.text = treeNode.Text;
node.cheked = treeNode.Checked;
nodes.Add(node);
foreach (TreeNode tn in treeNode.Nodes)
{
SerializeTree(nodes, tn);
}
}
}
}
Directories/Files to TreeView Method
private static void ListDirectory(TreeView treeView, string path)
{
treeView.Nodes.Clear();
int id = 1;
var stack = new Stack<TreeNode>();
var rootDirectory = new DirectoryInfo(path);
var node = new TreeNode(rootDirectory.Name) { Tag = rootDirectory };
stack.Push(node);
while (stack.Count > 0)
{
var currentNode = stack.Pop();
var directoryInfo = (DirectoryInfo)currentNode.Tag;
foreach (var directory in directoryInfo.GetDirectories())
{
var childDirectoryNode = new TreeNode(directory.Name) { Tag = directory };
childDirectoryNode.Name = $"{id++}";
currentNode.Nodes.Add(childDirectoryNode);
stack.Push(childDirectoryNode);
}
foreach (var file in directoryInfo.GetFiles())
{
TreeNode treeNode = new TreeNode(file.Name);
treeNode.Name = $"{id++}";
currentNode.Nodes.Add(new TreeNode(file.Name));
}
}
treeView.Nodes.Add(node);
}
To make this work each TreeNode needs to have a Unique Id that is going to be saved in the name of the TreeNode as I do in the Directories/Files to TreeView Method. Thanks #Fildor and #madreflection for your help!

Remove parent nodes without childs

My procedure works fine with a Do-While but I think it must do a lot of iterations.
Is there a faster way to remove nodes without childs? Remember if you remove a node and it is a single node from its parent node then it must be removed later again.
int counter=0;
private void EliminaParentsWithoutChilds()
{
do
{
counter= 0;
foreach (TreeNode node in treeView1.Nodes)
{
RemoveEmptyNodes(node);
}
}
while (counter > 0);
}
private void RemoveEmptyNodes(TreeNode node)
{
if (node.Nodes.Count > 0)
{
foreach (TreeNode childNode in node.Nodes)
{
RemoveEmptyNodes(childNode);
}
}
else
{
node.Remove();
counter++;
}
}
You can create a method that tell you when a node is empty or has only one child in all descendants:
private static bool IsUniqueOrEmptyNode(TreeNode node)
{
return node.Nodes.Count == 0 ||
(node.Nodes.Count == 1 && IsUniqueOrEmptyNode(node.Nodes[0]));
}
Next method remove only the top parent that hasn't childs or that contains only one a single direct child:
private static void RemoveEmptyNodes(TreeNodeCollection nodes)
{
for (int i = nodes.Count - 1; i >= 0; i--)
{
if (IsUniqueOrEmptyNode(nodes[i]))
{
nodes.RemoveAt(i);
}
else
{
RemoveEmptyNodes(nodes[i].Nodes);
}
}
}
And a helper method to apply to a TreeView:
private static void RemoveEmptyNodes(TreeView tree)
{
RemoveEmptyNodes(tree.Nodes);
}

Beginner: TreeView with checkboxes and recursion in Winforms

first post and a new coder so bare with me if you need more info than I am giving. I am trying to create a treeview with checkboxes in a hierarchy (see pic). My issue is i want to create some sort of recursion which deselects and selects child nodes when parent nodes are checked or vice versa.
I am using VS with winforms and been googling for 2 days on how to do this, unfortunately the examples online are either too advanced for me or dont work. I found a Tutorial on how to do this exactly with indeterminate checkboxes as well which would be a big bonus but it is for WPF.
I managed to create buttons which are able to (un)check all buttons with some examples online. Please can someone guide, a beginner who is finding programming AMAZING so far, in the right direction :)
private void button_checkAllNodes_Click(object sender, EventArgs e)
{
checkAllNodes(treeView1.Nodes);
}
private void button_uncheckAllNodes_Click(object sender, EventArgs e)
{
UncheckAllNodes(treeView1.Nodes);
}
public void checkAllNodes(TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
node.Checked = true;
checkChildren(node, true);
}
}
public void UncheckAllNodes(TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
node.Checked = false;
checkChildren(node, false);
}
}
private void checkChildren(TreeNode rootNode, bool isChecked)
{
foreach (TreeNode node in rootNode.Nodes)
{
checkChildren(node, isChecked);
node.Checked = isChecked;
}
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
}
Picture Treeview with (un)check All buttons
Let's create a couple of extension methods for the TreeNode type, one that gets all the children of a node, and another that gets it's parents.
// Within your project's namespace...
static class TreeViewExtensions
{
public static IEnumerable<TreeNode> Children(this TreeNode node)
{
foreach (TreeNode n in node.Nodes)
{
yield return n;
foreach (TreeNode child in Children(n))
yield return child;
}
}
public static IEnumerable<TreeNode> Parents(this TreeNode node)
{
var p = node.Parent;
while (p != null)
{
yield return p;
p = p.Parent;
}
}
}
Now, all what you need to do is to handle the TreeView.AfterCheck event to toggle the Checked property of the nodes that the extension methods yield.
// +
using System.Linq;
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action == TreeViewAction.Unknown) return;
foreach (TreeNode n in e.Node.Children())
n.Checked = e.Node.Checked;
// Comment this if you don't need it.
foreach (TreeNode p in e.Node.Parents())
p.Checked = p.Nodes.OfType<TreeNode>().Any(n => n.Checked);
}
Soon, you'll notice sometimes this solution won't work as it should when you click rapidly over the check boxes since they don't receive the mouse double click messages by default. Then, follow this post or this to solve this problem. For now click slowly.
If you prefer though to use buttons to toggle the check state, then delete the AfterCheck handler and do instead:
private void btnCheckAll_Click(object sender, EventArgs e)
{
ToggleCheck(treeView1.SelectedNode, true);
}
private void btnUncheckAll_Click(object sender, EventArgs e)
{
ToggleCheck(treeView1.SelectedNode, false);
}
private void ToggleCheck(TreeNode node, bool state)
{
node.Checked = state;
foreach (TreeNode n in node.Children())
n.Checked = state;
// Optional...
foreach (TreeNode n in node.Parents())
n.Checked = state;
}
I agree with #jdweng , you are using recursion in checkChildren(). The base case is missing.
In recursion checkChildren , add base case before
foreach (TreeNode node in rootNode.Nodes)
if node is Null : rootNode=isChecked

TreeView with CheckBoxes in c#

I have a tree view with checkboxes in c#, I want that when the user checks one node all the nodes that there are on the levels below automatic checked also.
Does anyone know about way to do that without run with recorsive fnction on all the tree each time that the user checks some node?
Thanks
//this function returns the treeView.
public TreeView GetTreeView()
{
getSubject();
// fill the treeview with all subjects.
foreach (Subject subject in subjects)
{
//for each root subject fill all the his children.
if (subject.subjestId == subject.parentSubject)
{
TreeNode node = new TreeNode(subject.subjectString, subject.subjestId, subject.subjestId);
addChild(node, subject.subjestId);
tv.Nodes.Add(node);
}
}
return tv;
}
// for each subject return sub subjects.
private void addChild(TreeNode node, int parentId)
{
foreach (Subject subject in subjects)
{
if (subject.parentSubject == parentId && subject.parentSubject != subject.subjestId)
{
TreeNode childNode = new TreeNode(subject.subjectString, subject.subjestId, subject.subjestId);
addChild(childNode, subject.subjestId);
node.Nodes.Add(childNode);
}
}
}
Recursion. Like this:
bool busy = false;
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e) {
if (busy) return;
busy = true;
try {
checkNodes(e.Node, e.Node.Checked);
}
finally {
busy = false;
}
}
private void checkNodes(TreeNode node, bool check) {
foreach (TreeNode child in node.Nodes) {
child.Checked = check;
checkNodes(child, check);
}
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e) {
foreach (TreeNode child in e.Node.Nodes) {
child.Checked = e.Node.Checked;
}
}
This is a better solution
private void trvMenuList_AfterCheck(object sender, TreeViewEventArgs e)
{
SetChildrenChecked(e.Node, e.Node.Checked);
}
private void SetChildrenChecked(TreeNode treeNode, bool checkedState)
{
foreach (TreeNode item in treeNode.Nodes)
{
if (item.Checked != checkedState)
{
item.Checked = checkedState;
}
SetChildrenChecked(item, item.Checked);
}
}
As a number of the answers state, create a recursive 'set checked to children' function, then call it AfterCheck on the tree.
The framework unfortunately gives you a call back to AfterCheck even if you set the check value in code, and although this may not be noticeable in small trees adds a massive amount of exponential extra work for your app to do. To avoid it, filter AfterCheck to only fire your new function if it has been triggered by user.
private void tree_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown)
{
SetChildrenChecked(e.Node);
}
}
private void SetChildrenChecked(TreeNode treeNode)
{
foreach (TreeNode item in treeNode.Nodes)
{
if (item.Checked != treeNode.Checked)
{
item.Checked = treeNode.Checked;
}
if (item.Nodes.Count > 0)
{
SetChildrenChecked(item);
}
}
}
I expanded on the answer a little; by updating the parent as well. [The DisplayException method inside the catch is just a popup window that I always use; you can do your own]
private bool _busy = false;
private void treeViewPassFail_AfterCheck(object sender, TreeViewEventArgs e)
{
try
{
if (_busy)
{
return;
}
_busy = true;
CheckNodes(e.Node, e.Node.Checked);
CheckParent(e.Node.Parent);
}
catch(Exception ex)
{
DisplayException(ex);
}
finally
{
_busy = false;
}
}
private void CheckNodes(TreeNode node, bool check)
{
foreach(TreeNode child in node.Nodes)
{
child.Checked = check;
CheckNodes(child, check);
}
}
private void CheckParent(TreeNode parent)
{
if (parent != null)
{
bool allChecked = true;
foreach (TreeNode node in parent.Nodes)
{
allChecked &= node.Checked;
}
parent.Checked = allChecked;
}
}
If you want to do it in WinForms then I think you have to do it manually by recursion - I don't know any better way.

c# object loses reference when removed from list

I have a very strange issue. Basically I have created a class called TreeNode that represents a node in a tree. I then create the tree by adding all the nodes to a List.
class TreeNode
{
private TreeNode parent, lChild, rChild;
private int key, val;
public int Key
{
get { return key; }
set { key = value; }
}
public int Val
{
get { return val; }
set { val = value; }
}
public TreeNode Parent
{
get { return parent; }
set { parent = value; }
}
public TreeNode LChild
{
get { return lChild; }
}
public TreeNode RChild
{
get { return rChild; }
}
public TreeNode(int k, int v)
{
key = k;
val = v;
}
public void SetChild(TreeNode leftChild, TreeNode rightChild)
{
this.lChild = leftChild;
this.rChild = rightChild;
}
public bool isLeaf()
{
if (this.lChild == null && this.rChild == null)
{
return true;
} else
{
return false;
}
}
public bool isParent()
{
if (this.parent == null)
{
return true;
}
else
{
return false;
}
}
public void SetParent(TreeNode Parent)
{
this.parent = Parent;
}
}
So if i put a breakpoint just after the creation of the tree and hover over the list in Visual Studio I can see the structure of the tree - with all the references working perfectly from the root down to the leaves.
If however I do the following:
TreeNode test = newTree[newTree.Count - 1];
please note:
private List<TreeNode> newTree = new List<TreeNode>();
which returns the root node - and hover over again I can do down one level (i.e. left child or right child) but these children do not have any references for their children after that.
I'm wondering if I'm losing the reference in memory to the other nodes in the list as the test node is not part of the list?
Any help would be greatly appreciated.
Thanks
Tom
you sure you don't have (note no space between new and tree in your code)
TreeNode test = new Tree[newTree.Count - 1];
Which would create a new empty array of tree (probably not what you intended), and leave your original tree un-rooted and inaccessible.
Can you make sure your code is correct, please?
Seems as though I found the issue - I wasn't correctly updating some of the parent nodes with their relevant child nodes - problem solved.
Thanks for your help
Tom

Categories

Resources