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!
Related
My program reads an XML file for movies, and shows the data using a class called Movie and a list called movieList. You are able to display the information of a selected one, add one and modify/delete.
Data display
The information displaying works just fine, however when I add a new item and select it, the data shown is the exact same as the 1st movie in the list. If I add another movie, it will show the same data as the 2nd movie. The only time the program will show the correct info for the newly added movies is if I close the program and reopen it. I've tried many things but none have really worked out. The ListView display is fine, but getting the actual data from the selected item in the movieList is not. Method appendNodeToXML works as intended for writing the inputted data and transferring it to the XML file. Feels like it could just be a simple fix.
Incorrect data display
Code:
private void MovieListView_SelectedIndexChanged(object sender, EventArgs e)
{
// If an item is selected
if (movieListView.SelectedItems.Count > 0)
{
// Declare index selected from ListView to variable
int selectedIndex = movieListView.SelectedIndices[0];
Movie selectedMovie = new Movie();
selectedMovie = movieList[selectedIndex];
// Show index of selected item
titleMovieTextBox.Text = selectedMovie.Title;
genreMovieTextBox.Text = selectedMovie.Genre;
yearMovieTextBox.Text = selectedMovie.Year.ToString();
lengthMovieTextBox.Text = selectedMovie.Length;
directorMovieTextBox.Text = selectedMovie.Director;
ratingMovieTextBox.Text = selectedMovie.Rating.ToString();
imageMovieTextBox.Text = selectedMovie.ImagePath;
}
private void AddMovieButton_Click(object sender, EventArgs e)
{
if (genreAddMovieTextBox.Text != "" || titleAddMovieTextBox.Text != "" || yearAddMovieTextBox.Text != "" || lengthAddMovieTextBox.Text != ""
|| directorAddMovieTextBox.Text != "" || ratingAddMovieTextBox.Text != "" || imagePathAddMovieTextBox.Text != "")
{
// Clear ListView to prepare for new added item
movieListView.Items.Clear();
// Method to append all movie nodes to the XML file
appendNodeToXMLFile("movies.xml");
// Indicate what XML file to read
readXMLFile("movies.xml");
// Method to clear TextBoxes
ClearTextBoxes();
// Set focus
genreAddMovieTextBox.Focus();
// Refresh genre
GetGenre();
}
else
{
MessageBox.Show("Not all entries are filled.");
}
}
private void appendNodeToXMLFile(string filePath)
{
XmlDocument xmlDoc = new XmlDocument();
if (File.Exists(filePath))
{
// XML file loaded
xmlDoc.Load(filePath);
// rootNode to memory
XmlElement rootNode = xmlDoc.DocumentElement;
// XML nodes created for movie and its attributes
XmlElement addMovie = xmlDoc.CreateElement("movie");
XmlElement addTitle = xmlDoc.CreateElement("title");
XmlElement addYear = xmlDoc.CreateElement("year");
XmlElement addLength = xmlDoc.CreateElement("length");
XmlElement addDirector = xmlDoc.CreateElement("director");
XmlElement addRating = xmlDoc.CreateElement("audienceRating");
XmlElement addImagePath = xmlDoc.CreateElement("imageFilePath");
// Create textNode element
XmlText newTextNode;
// Set attribute for genre from TextBox
addMovie.SetAttribute("genre", genreAddMovieTextBox.Text);
// Use new node for title
newTextNode = xmlDoc.CreateTextNode(titleAddMovieTextBox.Text);
// Add inputted information to text node
addTitle.AppendChild(newTextNode);
// Use new node for year
newTextNode = xmlDoc.CreateTextNode(yearAddMovieTextBox.Text);
// Add inputted information to text node
addYear.AppendChild(newTextNode);
// Use new node for length
newTextNode = xmlDoc.CreateTextNode(lengthAddMovieTextBox.Text);
// Add inputted information to text node
addLength.AppendChild(newTextNode);
// Use new node for director
newTextNode = xmlDoc.CreateTextNode(directorAddMovieTextBox.Text);
// Add inputted information to text node
addDirector.AppendChild(newTextNode);
// Use new node for director
newTextNode = xmlDoc.CreateTextNode(ratingAddMovieTextBox.Text);
// Add inputted information to text node
addRating.AppendChild(newTextNode);
// Use new node for director
newTextNode = xmlDoc.CreateTextNode(imagePathAddMovieTextBox.Text);
// Add inputted information to text node
addImagePath.AppendChild(newTextNode);
// Send each movie attribute to addMovie node
addMovie.AppendChild(addTitle);
addMovie.AppendChild(addYear);
addMovie.AppendChild(addLength);
addMovie.AppendChild(addDirector);
addMovie.AppendChild(addRating);
addMovie.AppendChild(addImagePath);
// Send movie node to main movies node
rootNode.AppendChild(addMovie);
// Save file
xmlDoc.Save(filePath);
}
else
{
MessageBox.Show("The file " + filePath + " does not exist.");
}
}
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
Hello all!
I have the following use case:
My Winform-App is validating a prarticular XML against the XSD and displays the errors (if any) in a listbox (this works fine). In addition to that I load the validated XML into a treeview.
Now, what I would like to achieve is to give the user the ability to doubleklick the error in the listbox to have then the treeview selecting the relevant element which caused the error.
I'm a little stuck with this actually. I now the row number of the error, but I can't select the relevant node in the treeview by using the row number.
Does anybody have an idea how I can achieve this?
Any hint is very much appreciated :)
kind regards
UPDATE:
Code filling the treeview:
doc.Load(XMLDocPath);
XmlNodeTree root = new XmlNodeTree(0, doc.LastChild);
treeDGUXml.Nodes.Add(root);
FillTreeView(root.Nodes, doc.LastChild.ChildNodes);
And the method:
private void FillTreeView(TreeNodeCollection c, XmlNodeList l)
{
if (l == null)
{
return;
}
foreach (XmlNode e in l)
{
XmlNodeTree n = new XmlNodeTree(nRow, e);
c.Add(n);
FillTreeView(n.Nodes, e.ChildNodes);
}
And the Class:
public class XmlNodeTree : TreeNode
{
private XmlNode mNode;
public XmlNode Node
{
get { return mNode; }
}
public XmlNodeTree(int rownumber, XmlNode node)
{
mNode = node;
if (node.NodeType == XmlNodeType.Text)
{
Text = node.InnerText;
}
else
{
Text = rownumber.ToString() + " - " + node.Name;
nRow++;
}
if (node.Attributes != null)
{
foreach (XmlAttribute a in node.Attributes)
{
Text += " " + a.OuterXml;
}
}
}
}
Thank you very much for the hints. I decided to have another approach without treeview. I load the XML into a richtext box with appropriate coloring. This way I can select the relevant text. The user cannot collapse the xml structure, but this now is better than nothing ;) Thank you anyway for your suggestions! –
I am attempting to create a tool from WPF that creates a simple XML document by inputting data into a text box, then clicking a button to enter that data into the XML document. Other than the root element, I will have a step element with a child sub step element such as this:
<root>
<step id="1">
<P>some instructions</p>
<step id="1.1">
<p>some additional instructions</p>
</step>
</step>
<step id="2">
<p>second set of instructions</p>
<step id="2.1">
<p>additional instructions for step 2</p>
</step>
</step>
I have been able to add the parent steps, however all of my sub steps fall under the first parent step:
<root>
<step id="1">
<step id="1.1">
<step id="2.1">
<step id="3.1">
<step id="2">
<step id="3">
I am trying to use XPath to insert my sub steps into the proper parent-steps. However I am receiving the error:
"XAttribute does not contain a definition for 'add' and no extension method 'add' accepting a first argument type 'XAttribute' could be found."
I have searched extensively and am unable to come up with a solution, hopefully this makes sense and someone could give me a hand. Thank you in advance. Here is my code:
using System.Windows;
using System.Xml.Linq;
using System.Xml.XPath;
namespace XMLwpfTest1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
XDocument doc = new XDocument(
new XElement("Content",
new XElement("Title", "DM Title"))
);
string par = "par-";
int counter = 1;
int counter2 = 1;
string stepNumber = "";
public MainWindow()
{
InitializeComponent();
}
private void create_Click(object sender, RoutedEventArgs e)
{
doc.Element("Content").Add(
new XElement("proceduralStep",
new XAttribute("id", par + counter.ToString("D4")),
new XElement("para",
dataBox.Text)));
stepNumber = par + counter.ToString("D4");
counter += 1;
dataBox.Clear();
counter2 = 1;
}
private void createSubStep_Click(object sender, RoutedEventArgs e)
{
var addStep = doc.XPathSelectElement("Content/proceduralStep").Attribute(stepNumber);
addStep.add(
new XElement("proceduralStep",
new XAttribute("id", stepNumber + "-" + counter2.ToString("D4")),
new XElement("para",
subDataBox.Text)));
counter2 += 1;
subDataBox.Clear();
}
private void save_Click(object sender, RoutedEventArgs e)
{
doc.Save(fileName.Text + ".xml");
savedLabel.Visibility = Visibility.Visible;
}
}
}
Your problem comes at this line:
var addStep = doc.XPathSelectElement("Content/proceduralStep").Attribute(stepNumber);
It looks like you want to select the <proceduralStep> whose id attribute has value stepNumber. To do this with XPath, you need to use the syntax [#attributeName='attributeValue'], i.e.:
var addStep = doc.XPathSelectElement(string.Format("Content/proceduralStep[#id='{0}']", stepNumber));
Note that, if stepNumber were a user-entered string, you would need to be careful to prevent XPath injection, for instance by following the instructions here.
Alternatively, you could do this using by appending a Where expression:
var addStep = doc.XPathSelectElements("Content/proceduralStep")
.Where(e => (string)e.Attribute("id") == stepNumber)
.FirstOrDefault();
XPath injection is not an issue with this approach.
(The method you are currently calling, XElement.Attribute(XName), returns the attribute with the specified name, which you do not want.)
Then add() needs to be correctly capitalized as XElement.Add():
addStep.Add(
new XElement("proceduralStep",
new XAttribute("id", stepNumber + "-" + counter2.ToString("D4")),
new XElement("para", subDataBox.Text)));
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();
}
}