Двусторонняя привязка данных 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 HierarchicalDataTemplates, один для каждого иерархического элемента, включая корень, и один 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>

Это будет отображаться как:

Другие вопросы по тегам