Я пытаюсь переписать свое приложение 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>





Что ж, было бы проще, если бы ваша иерархия элементов была больше похожа на ...
<node type = "forest">
<node type = "tree">
...
... а не вашу текущую схему.
Как есть, вам понадобится 4 HierarchicalDataTemplate, по одному для каждого иерархического элемента, включая корень, и один 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 с TextBoxes на каждом узле, но когда дело доходит до редактирования только одного узла за раз в отдельном, единственном TextBox или другом элементе управления, вы должны привязать его к текущему выбранному элементу TreeView. Вы также можете изменить указанные выше TextBoxes на TextBlocks, поскольку нажатие на 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 на экранированную строку. Затем мы должны их исправить.
Если XML представлен в виде строки, угловые скобки не будут содержать сущностей (они начинаются с &). Это можно изменить, поскольку свойство Document возвращает XmlDocument. Я отредактирую и добавлю код для выполнения CDATA в элементы данных.
Отлично - работает. Производительность действительно плохая для размера документов, с которыми я работаю, но вместо обновления каждого узла я добавлю флаг IsDirty и обновлю только узлы, которые были отредактированы.
Мне также не нужен корневой узел, поэтому я изменил XPath в ItemsSource TreeView на «XPath = forest» вместо «XPath =». который отлично работает.
Я понимаю, что это очень старые вопросы и ответы, но этот ответ мне очень помог сегодня, поэтому я хотел поблагодарить вас. Мне любопытно, однако, как бы отличалось решение, если бы XML был деревом node, как вы предложили?
@Naikrovek: Нормализация. Каждый элемент имеет одинаковые атрибуты, поэтому они могут быть одним и тем же типом данных. Тогда вы не ограничены определенной глубиной вашей структуры данных или вам не придется иметь дело как с листьями, так и с ветвями внутри ветвей. У вас просто есть узлы внутри узлов. Теперь у него могут быть причины для такого дизайна, но это означает наличие нескольких шаблонов HierarchicalDataTemplates вместо одного.
Обоснование (к лучшему или к худшему) описано в статье после прыжка. Как и во многих других решениях, это было несколько произвольно. В веб-версии ForestPad, которую я сейчас создаю с помощью ASP.NET WebAPI и AngularJS, я использую один и тот же тип данных для каждого узла с неограниченной глубиной. Ссылка на старую статью ForestPad: codeproject.com/Articles/7255/…
У нас была аналогичная проблема. Вы можете найти чтение эта статья полезным. Мы использовали описанный паттерн ViewModel, и он все упростил.
Спасибо, Джоэл, это работает. Хотя есть один вопрос. Я окружаю содержимое элемента данных разделом CDATA, чтобы можно было хранить Xml. Есть ли способ контролировать, как XmlDataProvider записывает элемент данных?