C# Xml 数据到 WPF TreeView 的双向绑定

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/188001/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 17:15:10  来源:igfitidea点击:

Two-way binding of Xml data to the WPF TreeView

c#wpfxmldata-bindingtreeview

提问by Timothy Lee Russell

I am attempting to rewrite my ForestPadapplication utilizing WPF for the presentation layer. In WinForms, I am populating each node programmatically but I would like to take advantage of the databinding capabilities of WPF, if possible.

我正在尝试使用 WPF 为表示层重写我的ForestPad应用程序。在 WinForms 中,我以编程方式填充每个节点,但如果可能,我想利用 WPF 的数据绑定功能。

In general, what is the best way to two-way databind the WPF TreeView to an Xml document?

一般来说,将 WPF TreeView 双向数据绑定到 Xml 文档的最佳方法是什么?

A generic solution is fine but for reference, the structure of the Xml document that I am trying to bind to looks like this:

通用解决方案很好,但作为参考,我尝试绑定的 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>

采纳答案by Joel B Fant

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 HierarchicalDataTemplates, one for each hierarchical element including the root, and one DataTemplatefor leafelements:

按原样,您将需要 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>

You can instead set the Sourceof the XmlDataProviderprogrammatically:

您可以改为SourceXmlDataProvider编程方式设置 的:

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 TreeViewitself needs a Nameand an ItemsSourcebonded to the XmlDataProvider:

TreeView自身需要NameItemsSource结合到XmlDataProvider

<TreeView
    Name="treeview"
    ItemsSource="{Binding Source={StaticResource dataxml}, XPath=.}">

I this example, I did TwoWaybinding with TextBoxes on each node, but when it comes to editing just one node at a time in a separate, single TextBoxor other control, you would be binding it to the currently selected item of the TreeView. You would also change the above TextBoxes to TextBlocks, as clicking in the TextBoxdoes not actually select the corresponding TreeViewItem.

在这个例子中,我在每个节点上TwoWay使用TextBoxes 进行了绑定,但是当在单独的、单个TextBox或其他控件中一次只编辑一个节点时,您会将其绑定到TreeView. 您还可以将上面的TextBoxes更改为TextBlocks,因为单击TextBox并实际上并未选择相应的TreeViewItem.

<TextBox
    DataContext="{Binding ElementName=treeview, Path=SelectedItem}"
    Text="{Binding XPath=data, UpdateSourceTrigger=PropertyChanged}"/>

The reason you must use two Bindings is that you cannot use Pathand XPathtogether.

必须使用两个Bindings的原因是不能将PathandXPath一起使用。

Edit:

编辑:

Timothy Lee Russell asked about saving CDATA to the data elements. First, a little on InnerXmland InnerText.

Timothy Lee Russell 询问了将 CDATA 保存到数据元素的问题。首先,有点关于InnerXmlInnerText

Behind the scenes, XmlDataProvideris using an XmlDocument, with it's tree of XmlNodes. When a string such as "stuff" is assigned to the InnerXmlproperty of an XmlNode, then those tags are really tags. No escaping is done when getting or setting InnerXml, and it is parsed as XML.

在幕后,XmlDataProvider使用的是XmlDocument,它是XmlNodes. 当将诸如“stuff”之类的字符串分配给InnerXmlan的属性时XmlNode,这些标签就是真正的标签。获取或设置时不进行转义InnerXml,并解析为 XML。

However, if it is instead assigned to the InnerTextproperty, the angle brackets will be escaped with entities &lt; and &gt;. The reverse happens when the value is retreived. Entities (like &lt;) are resolved back into characters (like <).

但是,如果它改为分配给InnerText属性,则尖括号将被转义为实体 < 和 >。取回该值时会发生相反的情况。实体(如 <)被解析回字符(如 <)。

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 InnerTextbefore adding a CDATA section as the node's child...

因此,如果我们存储在数据元素中的字符串包含 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 );

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.

如果节点已经有一个 CDATA 部分并且值没有以任何方式改变,那么它仍然有一个 CDATA 部分,我们基本上用相同的部分替换它。但是,通过我们的绑定,如果我们更改数据元素内容的值,它会替换 CDATA 以支持转义字符串。然后我们必须修复它们。

回答by Shaun Bowe

We had a similar issue. You may find reading this articlehelpful. We used the ViewModel pattern described and it simplified everything.

我们有一个类似的问题。您可能会发现阅读这篇文章很有帮助。我们使用了所描述的 ViewModel 模式,它简化了一切。