Save TreeView as xml with attributes and elements - c#

I have a Windows Forms TreeView that needs to be saved as a xml file.
The structure of the TreeView is like this:
- Parent
- Child 1 (Any value)
- Child 2
- Child 1 (Any value for a child)
Every TreeNode that has children needs to be saved as a element, and every TreeNode that does not have children needs to be saved as a attribute to it's parent TreeNode
This means the above would result in the following xml:
<?xml version="1.0" encoding="utf-8"?>
<Parent Child1="Any value">
<Child2 Child1="Any value for a child" />
</Parent>
I tried using the following code, but it did not work when the TreeNodes with no children where below the TreeNodes with children and I couldn't really figure out a good way of doing it.
public void SerializeTreeView(TreeView treeView, string fileName)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter textWriter = XmlWriter.Create(fileName, settings);
// Writing the xml declaration tag
textWriter.WriteStartDocument();
// Save the nodes, recursive method
SaveNodes(treeView.Nodes, textWriter);
// End the xml document
textWriter.WriteEndDocument();
textWriter.Close();
}
private void SaveNodes(TreeNodeCollection nodesCollection, XmlWriter textWriter)
{
for (int i = 0; i < nodesCollection.Count; i++)
{
TreeNode node = nodesCollection[i];
if (node.Nodes.Count > 0)
{
textWriter.WriteStartElement(node.Name);
}
else
{
textWriter.WriteAttributeString(node.Name, "Attribute value");
}
if (node.Nodes.Count > 0)
SaveNodes(node.Nodes, textWriter);
if (node.Nodes.Count > 0)
textWriter.WriteEndElement();
}
}
EDIT:
The problem with the current code is that if I add a TreeNode that has any children and is ABOVE a TreeNode with no children, it gives me the following error:
Token StartAttribute in state Element Content would result in an invalid XML document.
This happens at:
textWriter.WriteAttributeString(node.Name, "Attribute value");
I solved it by sorting the TreeView by child node count (Meaning that the TreeNode with NO children will ALWAYS be below a TreeNode without children)
The solution works, but I would like to figure out why the error occurred and how to fix it.

How are you filling the TreeView? Are you actually giving the object keys or just titles? Try replacing the two occurences of "node.Name" with "node.Text" (and, for reasons of code sanity, refactoring the multiple checks for Count > 0):
EDIT: ok, try this:
private void SaveNodes(TreeNodeCollection nodesCollection, XmlWriter textWriter)
{
foreach (var node in nodesCollection.OfType<TreeNode>().Where(x => x.Nodes.Count == 0))
textWriter.WriteAttributeString(node.Name, "Attribute value");
foreach (var node in nodesCollection.OfType<TreeNode>().Where(x => x.Nodes.Count > 0))
{
textWriter.WriteStartElement(node.Name);
SaveNodes(node.Nodes, textWriter);
textWriter.WriteEndElement();
}
}

Related

WPF Access XML in <Window.Resource> from Code

This is one of the tutorials on Microsoft about data binding with XML. I am using the source code provided on the web page.
In the application. It creates an XML in Window.Resources and data binding with other controls.
Xaml
<Window.Resources>
<!-- Books provider and inline data -->
<ObjectDataProvider x:Key="LoadedBooks" ObjectType="{x:Type linq:XElement}" MethodName="Parse">
<ObjectDataProvider.MethodParameters>
<system:String xml:space="preserve">
<![CDATA[
<books xmlns="http://www.mybooks.com">
<book id="0">book zero</book>
<book id="1">book one</book>
<book id="2">book two</book>
<book id="3">book three</book>
</books>
]]>
</system:String>
<linq:LoadOptions>PreserveWhitespace</linq:LoadOptions>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
...
</Window.Resouces>
// Other UI data binding code...
<StackPanel> ....
The XAML runs perfectly. Control can display the XML data correctly. However, in the code behind, it should load the XML data as XElement. So I can use a button click event to add/remove node.
CS
public partial class L2XDBForm : Window
{
XNamespace mybooks = "http://www.mybooks.com";
XElement bookList;
public L2XDBForm()
{
InitializeComponent();
// load xml data
bookList = (XElement) this.FindResource("LoadedBooks");
}
void OnAddBook(object sender, EventArgs e)
{
if (String.IsNullOrEmpty(tbAddID.Text) ||
String.IsNullOrEmpty(tbAddValue.Text))
{
MessageBox.Show("Please supply both a Book ID and a Value!", "Entry Error!");
return;
}
XElement newBook = new XElement(
mybooks + "book",
new XAttribute("id", tbAddID.Text),
tbAddValue.Text);
bookList.Add(" ", newBook, "\r\n");
}
// other event handler...
However, it shows System.InvalidCastException:“Unable to cast object of type 'System.Windows.Data.ObjectDataProvider' to type 'System.Xml.Linq.XElement'.”
It seems I cannot get Window.Resources xml object from code bookList = (XElement) this.FindResource("LoadedBooks");
And move on to the Remove button to delete node.
Remove Node from XML
void OnRemoveBook(object sender, EventArgs e)
{
int index = lbBooks.SelectedIndex;
if (index < 0) return;
XElement selBook = (XElement)lbBooks.SelectedItem;
//Get next node before removing element.
XNode nextNode = selBook.NextNode;
selBook.Remove();
//Remove any matching newline node.
if (nextNode != null && nextNode.ToString().Trim().Equals(""))
{ nextNode.Remove(); }
//Set selected item.
if (lbBooks.Items.Count > 0)
{ lbBooks.SelectedItem = lbBooks.Items[index > 0 ? index - 1 : 0]; }
}
This code works well. I can transfer a selected item as an XElement. So my question is, is there a better way to add the node or access the XML from Window.Resource? Should I transfer all the items in Listbox to an XElement object?
Thanks!

How to populate WinForms TreeView from xml file, regardless the number of children and grandchildren

Sorry if this has been posted before, but all the solutions i could find only addressed XML files with on layer of children.
I have a form displaying a treeview. Instead of having to edit directly in the code, i want to create an XML file, and populate my treeview from that. My XML is similar to this:
<Root>
<Element>
<ChildElement>
<GrandChildElement></GrandChildElement>
<GrandChildElement></GrandChildElement>
</ChildElement>
<ChildElement>
<GrandChildElement></GrandChildElement>
<GrandChildElement></GrandChildElement>
</ChildElement>
</Element>
<Element>
<ChildElement>
<GrandChildElement></GrandChildElement>
<GrandChildElement></GrandChildElement>
</ChildElement>
<ChildElement>
<GrandChildElement></GrandChildElement>
<GrandChildElement></GrandChildElement>
</ChildElement>
</Element>
</Root>
The GrandChildElements, is the clickable nodes in my treeview, which can be used to select different things. The rest is used to visually categorize them.
So far i haven't managed to find a way to include the GrandChildElements. Populating it without the GrandChildElements, can be done through recursion like this:
private void treeView_Load(object sender, EventArgs e)
{
XmlDocument xmldoc = new XmlDocument();
System.IO.FileStream fs = new System.IO.FileStream("treeNodes.xml", FileMode.Open, FileAccess.Read);
xmldoc.Load(fs);
XmlNode xmlnode = xmldoc.ChildNodes[1];
tvKortskab.Nodes.Clear();
tvKortskab.Nodes.Add(new TreeNode(xmldoc.DocumentElement.Name));
TreeNode tNode = tvKortskab.Nodes[0];
AddNode(xmlnode, tNode);
}
private void AddNode(XmlNode inXmlNode, TreeNode inTreeNode)
{
XmlNode xNode;
TreeNode tNode;
XmlNodeList childNodes;
XmlNodeList subChildNodes;
if (inXmlNode.HasChildNodes)
{
childNodes = inXmlNode.ChildNodes;
for (int i = 0; i <= childNodes.Count - 1; i++)
{
xNode = childNodes[i];
inTreeNode.Nodes.Add(new TreeNode(xNode.Name));
tNode = inTreeNode.Nodes[i];
AddNode(xNode, tNode);
}
else
{
inTreeNode.Text = inXmlNode.InnerText.ToString();
}
}
I have tried nesting a loop inside that, checking if the childNodes had children, and then adding those. That worked, but also added a bunch of empty and duplicate nodes.
Your idea was correct and you did the right thing.
But at the else statement you did something wrong.
You say, that the "GrandChildElement" should show the InnerText and this text is empty, so you get an empty element. You need to use the Name property instead.
if (inXmlNode.HasChildNodes)
{
childNodes = inXmlNode.ChildNodes;
for (int i = 0; i <= childNodes.Count - 1; i++)
{
xNode = childNodes[i];
inTreeNode.Nodes.Add(new TreeNode(xNode.Name));
tNode = inTreeNode.Nodes[i];
AddNode(xNode, tNode);
}
}
else
{
inTreeNode.Text = inXmlNode.Name;
}
I hope I understand what you meant.
Since your GrandChildren nodes are empty,following line would show an empty value there.
inTreeNode.Text = inXmlNode.InnerText.ToString();
One possible solution is to show the node name if innerText is empty.
inTreeNode.Text = string.IsNullOrEmpty(inXmlNode.InnerText)? inXmlNode.Name : inXmlNode.InnerText.ToString();
PS: Btw, you had a possible typo here
XmlNode xmlnode = xmldoc.ChildNodes[1];
This needs to be
XmlNode xmlnode = xmldoc.ChildNodes[0];
Earlier Output
New Output

Copy all Selected (Checked) TreeNodes from one treeview to another (include unchecked parents) C#

I've created a directory and file browser TreeView on the left side. I want the users to be able to browse the tree and check the files and directories that they would like to move to another treeview.
The other TreeView is a user control I found online called TreeViewColumn. I will be using that control to allow the user to add other data (categories, attributes) to the files and folders selected.
The trouble I'm running into is two-fold, I need to recursively add all children (I can figure this out) but I need to add unchecked parents to checked children (to preserve the hierarchy).
private void IterateTreeNodes(TreeNode originalNode, TreeNode rootNode)
{
//Take the node passed through and loop through all children
foreach (TreeNode childNode in originalNode.Nodes)
{
// Create a new instance of the node, will need to add it to the recursion as a root item
// AND if checked it needs to get added to the new TreeView.
TreeNode newNode = new TreeNode(childNode.Text);
newNode.Tag = childNode.Tag;
newNode.Name = childNode.Name;
newNode.Checked = childNode.Checked;
if (childNode.Checked)
{
// Now we know this is checked, but what if the parent of this item was NOT checked.
//We need to head back up the tree to find the first parent that exists in the tree and add the hierarchy.
if (tvSelectedItems.TreeView.Nodes.ContainsKey(rootNode.Name)) // Means the parent exist?
{
tvSelectedItems.TreeView.SelectedNode = rootNode;
tvSelectedItems.TreeView.SelectedNode.Nodes.Add(newNode);
}
else
{
AddParents(childNode);
// Find the parent(s) and add them to the tree with their CheckState matching the original node's state
// When all parents have been added, add the current item.
}
}
IterateTreeNodes(childNode, newNode);
}
}
private TreeNode AddParents(TreeNode node)
{
if (node.Parent != null)
{
//tvDirectory.Nodes.Find(node.Name, false);
}
return null;
}
Could anyone help with this code so that it recursively adds checked nodes (and their parent, regardless of checked state). I need to maintain directory hierarchy.
Thanks for any help!
A rather (not-so-clean and not-preferred) solution could be to clone the tree first, and then remove unchecked branches.
Else, when you are adding a node, write a recursive method to traverse through node's parent node until you hit the root. And simply optimize it by checking if childNode.parent already exists, just ignore the branch and move on. i.e. Backtrack to root node.
I got it working. I was already aware of what #Yahya was saying, I was hoping for an easier way / better approach.
The code below is certainly not optimal, I will continue improving it on my end but at this point it is looking through a treeview on the left and copying all of the checked items (and their parents - regardless of CheckedState) to a treeview on the right.
Hopefully this helps someone and thanks for answering #Yahya.
I'm open to improvements but keep in mind this is a one-time use utility.
private void cmdMoveRight_Click(object sender, EventArgs e)
{
tvSelectedItems.TreeView.Nodes.Clear();
// Traverse first level Tree Nodes
foreach (TreeNode originalNode in tvDirectory.Nodes)
{
TreeNode newNode = new TreeNode(originalNode.Text);
newNode.Name = originalNode.Name;
newNode.Tag = originalNode.Tag;
newNode.Checked = originalNode.Checked;
//Only add to the new treeview if the node is checked
if (originalNode.Checked)
{
tvSelectedItems.TreeView.Nodes.Find(originalNode.Parent.Name,true)[0].Nodes.Add(newNode);
}
//Start recursion - this will be called for each first level node - there should technically only be 1 "ROOT" node.
IterateTreeNodes(originalNode, newNode);
}
}
private void IterateTreeNodes(TreeNode originalNode, TreeNode rootNode)
{
//Take the node passed through and loop through all children
foreach (TreeNode childNode in originalNode.Nodes)
{
// Create a new instance of the node, will need to add it to the recursion as a root item
// AND if checked it needs to get added to the new TreeView.
TreeNode newNode = new TreeNode(childNode.Text);
newNode.Tag = childNode.Tag;
newNode.Name = childNode.Name;
newNode.Checked = childNode.Checked;
if (childNode.Checked)
{
// Now we know this is checked, but what if the parent of this item was NOT checked.
//We need to head back up the tree to find the first parent that exists in the tree and add the hierarchy.
TreeNode[] nodestest = tvSelectedItems.TreeView.Nodes.Find(childNode.Parent.Name, true);
if (nodestest.Length > 0)
{
tvSelectedItems.TreeView.Nodes.Find(childNode.Parent.Name,true)[0].Nodes.Add(newNode);
}
else
{
AddParents(childNode);// Find the parent(s) and add them to the tree with their CheckState matching the original node's state
}
}
//recurse
IterateTreeNodes(childNode, newNode);
}
}
private void AddParents(TreeNode node)
{
if (node.Parent != null)// Check if parent is null (would mean we're looking at the root item
{
TreeNode[] nodestest = tvSelectedItems.TreeView.Nodes.Find(node.Parent.Name, true);
if (nodestest.Length > 0)
{
TreeNode[] nodes = tvDirectory.Nodes.Find(node.Name, true);
TreeNode newNode = new TreeNode(nodes[0].Text);
newNode.Name = nodes[0].Name;
newNode.Tag = nodes[0].Tag;
newNode.Checked = nodes[0].Checked;
tvSelectedItems.TreeView.Nodes[node.Parent.Name].Nodes.Add(newNode);
}
else
{
AddParents(node.Parent);
TreeNode newNode = new TreeNode(node.Text);
newNode.Name = node.Name;
newNode.Tag = node.Tag;
newNode.Checked = node.Checked;
tvSelectedItems.TreeView.Nodes.Find(node.Parent.Name,true)[0].Nodes.Add(newNode);
}
}
else // deal with root node
{
TreeNode rootNode = new TreeNode(node.Text);
rootNode.Name = node.Name;
rootNode.Tag = node.Tag;
rootNode.Checked = node.Checked;
tvSelectedItems.TreeView.Nodes.Add(rootNode);
}
}

get folder to treeview

I am using the following code to get the contents of a folder into a TreeView. But the current code always adds the contents to the root of the TreeView. It does not add them as child nodes of their parent folder's node.
Can you help me?
void Recurse(string path)
{
DirectoryInfo info = new DirectoryInfo(path);
TreeNode root = new TreeNode(info.Name);
string[] sub = Directory.GetDirectories(info.FullName);
TreeNode node = new TreeNode();
MailTree.Nodes.Add(root);
if (sub.Length == 0) {
}
else
{
foreach(string i in sub)
{
DirectoryInfo subinfo = new DirectoryInfo(i);
root.Nodes.Add(subinfo.Name);
Recurse(i);
}
//MailTree.Nodes.Add(root);
}
}
You should be passing a root node as part of your Rescure method, something like Rescure(string path, TreeNode currentRoot).
Now, you can call currentRoot.Nodes.Add(root) in place of MailTree.Nodes.Add(root), which will ensure that the brances are added only to the current level. You also need to change your call in the loop to Rescure(i, root).
Finally, your initial call to Rescure should include a reference to a pre-created root node, so something like Rescure(initialDirectory, initialRootNode).
One thing I would add is that your method and variable names should be changed to reflect their meaning. Yes, you are recursing, but why? A better name for the method might be TraverseDirectory. Similarly, rather than foreach(string i in sub), why not foreach(string directoryName in sub)? Having clear code is almost as important as having correct code.
The recursive part of your function is always adding the child nodes to the root. You need to add in the "parent node" as a parameter of your recursive function. Something like this:
void Recurse(string path, TreeNode parentNode)
{
DirectoryInfo info = new DirectoryInfo(path);
TreeNode node = new TreeNode(info.Name);
if (parentNode == null)
MailTree.Nodes.Add(node);
else
parentNode.Nodes.Add(node);
string[] sub = Directory.GetDirectories(path);
if (sub.Length != 0)
{
foreach(string i in sub)
{
Recurse(i, node);
}
}
}
I can't se any error in the code at a first glance, but I can suggesto to take another approach: the one you show is too expensive. Just fill a level on the tree, and put some dummy item as a leaf in each nodes you add. Then intercept the NodeExpanding event, remove the dummy node, and add the subnodes ( applying recursively the same strategy of adding the dummy child nodes )

populate a tree view with an xml file using xElement using c#

Im using c#.net windows form application. I have a xml file .I need to populate this xml file into a tree view. I can do that using "xml document" but i need to do it using xElement.
Please refer to the article Populating a TreeView Control from XML, which says
This post describes how to populate a WinForms TreeView control from an XML file assuming you are targeting the .NET Framework Version 3.5.
XElement doc = XElement.Load("testXML.xml");
TreeNode stateNode;
TreeNode regionNode;
foreach (XElement state in doc.Descendants("State"))
{
stateNode = treeView1.Nodes.Add(state.Attribute("name").Value);
foreach (XElement region in state.Descendants("Region"))
{
regionNode =
stateNode.Nodes.Add(region.Attribute("name").Value);
foreach (XElement area in region.Descendants("Area"))
{
regionNode.Nodes.Add(area.Attribute("name").Value);
}
}
}
The following Class loads your .xml into an XElement collection then iterates over it, adding nodes according to their position in the original document and using XML node attributes for tree node naming:
using System.Windows.Forms;
using System.Xml.Linq;
namespace _Solution.Class
{
class Load
{
public void tree_pop(TreeView treeview)
{
treeview.Nodes.Clear();
XElement doc = XElement.Load("xml_file.xml");
TreeNode root = new TreeNode("root");
treeview.Nodes.Add(root);
add_nodes(root, doc);
treeview.ExpandAll();
}
private void add_nodes(TreeNode t_node, XElement x_node)
{
foreach (XElement node in x_node.Elements())
{
TreeNode n_node = t_node.Nodes.Add(node.Attribute("name").Value);
add_nodes(n_node, node);
if (n_node.Nodes.Count == 0) n_node.EnsureVisible();
}
}
}
}
Instantiate and call as follows:
Load load = new Load();
private void Form1_Load(object sender, EventArgs e)
{
load.tree_pop(treeview);
}
What's happening in tree_pop is that you're clearing your treeview of any nodes that may already exist, loading your .xml document into an XElement collection, establishing your treeview's root node, calling add_nodes to iterate over the XElement collection then expanding the treeview nodes.
What's happening in add_nodes is that you're iterating over the XElement collection as passed from tree_pop, creating and adding a new node to the treeview then searching each XElement for children and, if found, adding those to the node you just added to the tree.
For copy / paste, remember to adjust your Class namespace to match that of your solution.
Try the following:
var data = XElement.Load(source.xml);
foreach (XElement single in data.Elements())
{
treeNode.Nodes.Add(single.Element("name of element").Value);
}

Categories