Well, it would be easier if your element hierarchy was more like...
<node type="forest">
<node type="tree">
...
...rather than your current schema.
As-is, you'll need 4 HierarchicalDataTemplate
s, one for each hierarchical element including the root, and one DataTemplate
for leaf
elements:
<Window.Resources>
<HierarchicalDataTemplate
DataType="forestPad"
ItemsSource="{Binding XPath=forest}">
<TextBlock
Text="a forestpad" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="forest"
ItemsSource="{Binding XPath=tree}">
<TextBox
Text="{Binding XPath=data}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="tree"
ItemsSource="{Binding XPath=branch}">
<TextBox
Text="{Binding XPath=data}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="branch"
ItemsSource="{Binding XPath=leaf}">
<TextBox
Text="{Binding XPath=data}" />
</HierarchicalDataTemplate>
<DataTemplate
DataType="leaf">
<TextBox
Text="{Binding XPath=data}" />
</DataTemplate>
<XmlDataProvider
x:Key="dataxml"
XPath="forestPad" Source="D:fp.xml">
</XmlDataProvider>
</Window.Resources>
You can instead set the Source
of the XmlDataProvider
programmatically:
dp = this.FindResource( "dataxml" ) as XmlDataProvider;
dp.Source = new Uri( @"D:fp.xml" );
Also, re-saving your edits is as easy as this:
dp.Document.Save( dp.Source.LocalPath );
The TreeView
itself needs a Name
and an ItemsSource
bonded to the XmlDataProvider
:
<TreeView
Name="treeview"
ItemsSource="{Binding Source={StaticResource dataxml}, XPath=.}">
I this example, I did TwoWay
binding with TextBox
es on each node, but when it comes to editing just one node at a time in a separate, single TextBox
or other control, you would be binding it to the currently selected item of the TreeView
. You would also change the above TextBox
es to TextBlock
s, as clicking in the TextBox
does not actually select the corresponding TreeViewItem
.
<TextBox
DataContext="{Binding ElementName=treeview, Path=SelectedItem}"
Text="{Binding XPath=data, UpdateSourceTrigger=PropertyChanged}"/>
The reason you must use two Binding
s is that you cannot use Path
and XPath
together.
Edit:
Timothy Lee Russell asked about saving CDATA to the data elements. First, a little on InnerXml
and InnerText
.
Behind the scenes, XmlDataProvider
is using an XmlDocument
, with it's tree of XmlNodes
. When a string such as "stuff" is assigned to the InnerXml
property of an XmlNode
, then those tags are really tags. No escaping is done when getting or setting InnerXml
, and it is parsed as XML.
However, if it is instead assigned to the InnerText
property, the angle brackets will be escaped with entities < and >. The reverse happens when the value is retreived. Entities (like <) are resolved back into characters (like <).
Therefore, if the strings we store in the data elements contain XML, entities have been escaped, and we need to undo that simply by retrieving InnerText
before adding a CDATA section as the node's child...
XmlDocument doc = dp.Document;
XmlNodeList nodes = doc.SelectNodes( "//data" );
foreach ( XmlNode node in nodes ) {
string data = node.InnerText;
node.InnerText = "";
XmlCDataSection cdata = doc.CreateCDataSection( data );
node.AppendChild( cdata );
}
doc.Save( dp.Source.LocalPath );
If the node already has a CDATA section and the value has not been changed in any way, then it still has a CDATA section and we essentially replace it with the same. However, through our binding, if we change the value of the data elements contents, it replaces the CDATA in favor of an escaped string. Then we have to fix them.