Двусторонняя привязка данных Xml к WPF TreeView
Я пытаюсь переписать мое приложение ForestPad, используя WPF для уровня представления. В WinForms я заполняю каждый узел программно, но я бы хотел воспользоваться возможностями связывания данных WPF, если это возможно.
В общем, каков наилучший способ двустороннего связывания данных WPF TreeView с документом XML?
Универсальное решение хорошо, но для справки, структура документа Xml, к которому я пытаюсь привязаться, выглядит следующим образом:
<?xml version="1.0" encoding="utf-8"?>
<forestPad
guid="6c9325de-dfbe-4878-9d91-1a9f1a7696b0"
created="5/14/2004 1:05:10 AM"
updated="5/14/2004 1:07:41 AM">
<forest
name="A forest node"
guid="b441a196-7468-47c8-a010-7ff83429a37b"
created="01/01/2003 1:00:00 AM"
updated="5/14/2004 1:06:15 AM">
<data>
<![CDATA[A forest node
This is the text of the forest node.]]>
</data>
<tree
name="A tree node"
guid="768eae66-e9df-4999-b950-01fa9be1a5cf"
created="5/14/2004 1:05:38 AM"
updated="5/14/2004 1:06:11 AM">
<data>
<![CDATA[A tree node
This is the text of the tree node.]]>
</data>
<branch
name="A branch node"
guid="be4b0993-d4e4-4249-8aa5-fa9c940ae2be"
created="5/14/2004 1:06:00 AM"
updated="5/14/2004 1:06:24 AM">
<data>
<![CDATA[A branch node
This is the text of the branch node.]]></data>
<leaf
name="A leaf node"
guid="9c76ff4e-3ae2-450e-b1d2-232b687214aa"
created="5/14/2004 1:06:26 AM"
updated="5/14/2004 1:06:38 AM">
<data>
<![CDATA[A leaf node
This is the text of the leaf node.]]>
</data>
</leaf>
</branch>
</tree>
</forest>
</forestPad>
3 ответа
Ну, было бы проще, если бы ваша иерархия элементов была больше похожа на...
<node type="forest">
<node type="tree">
...
... а не ваша текущая схема.
Вам нужно 4 HierarchicalDataTemplate
s, один для каждого иерархического элемента, включая корень, и один DataTemplate
за leaf
элементы:
<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>
Вместо этого вы можете установить Source
из XmlDataProvider
программно:
dp = this.FindResource( "dataxml" ) as XmlDataProvider;
dp.Source = new Uri( @"D:\fp.xml" );
Кроме того, восстановить ваши изменения так же просто, как это:
dp.Document.Save( dp.Source.LocalPath );
TreeView
сама нуждается в Name
и ItemsSource
связаны с XmlDataProvider
:
<TreeView
Name="treeview"
ItemsSource="{Binding Source={StaticResource dataxml}, XPath=.}">
Я этот пример, я сделал TwoWay
связывание с TextBox
на каждом узле, но когда дело доходит до редактирования только одного узла за раз в отдельном TextBox
или другой элемент управления, вы бы связали его с выбранным в данный момент элементом TreeView
, Вы также изменили бы выше TextBox
как TextBlock
с, как нажав на TextBox
на самом деле не выбирает соответствующий TreeViewItem
,
<TextBox
DataContext="{Binding ElementName=treeview, Path=SelectedItem}"
Text="{Binding XPath=data, UpdateSourceTrigger=PropertyChanged}"/>
Причина, по которой вы должны использовать два Binding
это то, что вы не можете использовать Path
а также XPath
все вместе.
Редактировать:
Тимоти Ли Рассел спросил о сохранении CDATA в элементах данных. Сначала немного о InnerXml
а также InnerText
,
За кулисами, XmlDataProvider
использует XmlDocument
с деревом XmlNodes
, Когда строка, такая как "вещи" назначена InnerXml
свойство XmlNode
тогда эти теги действительно являются тегами. При получении или настройке экранирование не выполняется InnerXml
и анализируется как XML.
Однако, если он вместо этого назначен InnerText
свойство, угловые скобки будут экранированы объектами & lt; и & gt;. Обратное происходит, когда значение получено. Объекты (например, & lt;) преобразуются обратно в символы (например, <).
Поэтому, если строки, которые мы храним в элементах данных, содержат XML, сущности были экранированы, и нам нужно отменить это, просто получив InnerText
перед добавлением раздела CDATA в качестве дочернего узла...
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 );
Если узел уже имеет раздел CDATA и значение не было изменено каким-либо образом, то у него все еще есть раздел CDATA, и мы по существу заменим его тем же. Однако через нашу привязку, если мы изменим значение содержимого элементов данных, он заменит CDATA в пользу экранированной строки. Тогда мы должны исправить их.
У нас была похожая проблема. Вы можете найти чтение этой статьи полезным. Мы использовали описанный шаблон ViewModel, и он все упростил.
Я знаю, что это старый пост, но есть более элегантное решение. Вы действительно можете использовать один
HierarchicalDataTemplate
, если вы используете выражение XPath, которое выбирает все узлы, которые вы хотите использовать в шаблоне:
XPath=tree|branch|leaf
.
<HierarchicalDataTemplate x:Key="forestTemplate"
ItemsSource="{Binding XPath=tree|branch|leaf}">
<TextBlock Text="{Binding XPath=data}" />
</HierarchicalDataTemplate>
Вот полный
Page
пример с XData, встроенным в
XmlDataProvider1
:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<XmlDataProvider x:Key="sampleForestPad" XPath="forestPad/forest">
<x:XData>
<forestPad xmlns=""
guid="6c9325de-dfbe-4878-9d91-1a9f1a7696b0"
created="5/14/2004 1:05:10 AM"
updated="5/14/2004 1:07:41 AM">
<forest
name="A forest node"
guid="b441a196-7468-47c8-a010-7ff83429a37b"
created="01/01/2003 1:00:00 AM"
updated="5/14/2004 1:06:15 AM">
<data>
<![CDATA[A forest node
This is the text of the forest node.]]>
</data>
<tree
name="A tree node"
guid="768eae66-e9df-4999-b950-01fa9be1a5cf"
created="5/14/2004 1:05:38 AM"
updated="5/14/2004 1:06:11 AM">
<data>
<![CDATA[A tree node
This is the text of the tree node.]]>
</data>
<branch
name="A branch node"
guid="be4b0993-d4e4-4249-8aa5-fa9c940ae2be"
created="5/14/2004 1:06:00 AM"
updated="5/14/2004 1:06:24 AM">
<data>
<![CDATA[A branch node
This is the text of the branch node.]]></data>
<leaf
name="A leaf node"
guid="9c76ff4e-3ae2-450e-b1d2-232b687214aa"
created="5/14/2004 1:06:26 AM"
updated="5/14/2004 1:06:38 AM">
<data>
<![CDATA[A leaf node
This is the text of the leaf node.]]>
</data>
</leaf>
</branch>
</tree>
</forest>
</forestPad>
</x:XData>
</XmlDataProvider>
<HierarchicalDataTemplate x:Key="forestTemplate"
ItemsSource="{Binding XPath=tree|branch|leaf}">
<TextBlock Text="{Binding XPath=data}" />
</HierarchicalDataTemplate>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</Page.Resources>
<TreeView ItemsSource="{Binding Source={StaticResource sampleForestPad}}"
ItemTemplate="{StaticResource forestTemplate}"/>
</Page>
Это будет отображаться как: