Двусторонняя привязка данных 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>
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
23 553
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Что ж, было бы проще, если бы ваша иерархия элементов была больше похожа на ...

<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 на экранированную строку. Затем мы должны их исправить.

Спасибо, Джоэл, это работает. Хотя есть один вопрос. Я окружаю содержимое элемента данных разделом CDATA, чтобы можно было хранить Xml. Есть ли способ контролировать, как XmlDataProvider записывает элемент данных?

Timothy Lee Russell 10.10.2008 02:09

Если XML представлен в виде строки, угловые скобки не будут содержать сущностей (они начинаются с &). Это можно изменить, поскольку свойство Document возвращает XmlDocument. Я отредактирую и добавлю код для выполнения CDATA в элементы данных.

Joel B Fant 10.10.2008 17:45

Отлично - работает. Производительность действительно плохая для размера документов, с которыми я работаю, но вместо обновления каждого узла я добавлю флаг IsDirty и обновлю только узлы, которые были отредактированы.

Timothy Lee Russell 10.10.2008 21:12

Мне также не нужен корневой узел, поэтому я изменил XPath в ItemsSource TreeView на «XPath = forest» вместо «XPath =». который отлично работает.

Timothy Lee Russell 10.10.2008 21:15

Я понимаю, что это очень старые вопросы и ответы, но этот ответ мне очень помог сегодня, поэтому я хотел поблагодарить вас. Мне любопытно, однако, как бы отличалось решение, если бы XML был деревом node, как вы предложили?

Naikrovek 06.03.2013 05:24

@Naikrovek: Нормализация. Каждый элемент имеет одинаковые атрибуты, поэтому они могут быть одним и тем же типом данных. Тогда вы не ограничены определенной глубиной вашей структуры данных или вам не придется иметь дело как с листьями, так и с ветвями внутри ветвей. У вас просто есть узлы внутри узлов. Теперь у него могут быть причины для такого дизайна, но это означает наличие нескольких шаблонов HierarchicalDataTemplates вместо одного.

Joel B Fant 06.03.2013 23:49

Обоснование (к лучшему или к худшему) описано в статье после прыжка. Как и во многих других решениях, это было несколько произвольно. В веб-версии ForestPad, которую я сейчас создаю с помощью ASP.NET WebAPI и AngularJS, я использую один и тот же тип данных для каждого узла с неограниченной глубиной. Ссылка на старую статью ForestPad: codeproject.com/Articles/7255/…

Timothy Lee Russell 10.01.2014 01:19

У нас была аналогичная проблема. Вы можете найти чтение эта статья полезным. Мы использовали описанный паттерн ViewModel, и он все упростил.

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