将XML递归添加到TreeView中

时间:2015-03-05 18:15:07

标签: c# xml winforms linq treeview

我尝试使用C#将节点的XML文件导入到TreeView中的相同节点结构中。我发现了许多使用单个节点结构的示例,但是在遍历XML文件并使用它填充TreeView时遇到了很多问题。这是XML文件的示例:

<?xml version="1.0"?>
<xmlRoot>
<ProductGroup>
    <Group> 
      <GroupName>Soda</GroupName>
        <Classifications>
            <Classification>
                <ClassificationName>Regular</ClassificationName>
                    <Containers>
                        <Container>
                            <ContainerType>Can</ContainerType>
                            <ContainerName>SmallCan</ContainerName>
                        </Container>
                        <Container>
                            <ContainerType>bottle</ContainerType>
                            <ContainerName>SmallBottle</ContainerName>
                        </Container>
                    </Containers>
            </Classification>
            <Classification>
                <ClassificationName>Diet</ClassificationName>
                    <Containers>
                        <Container>
                            <ContainerType>Can</ContainerType>
                            <ContainerName>SmallCan</ContainerName>
                        </Container>
                    </Containers>
            </Classification>
        </Classifications>
    </Group>
    <Group> 
      <GroupName>Water</GroupName>
        <Classifications>
            <Classification>
                <ClassificationName>Regular</ClassificationName>
                    <Containers>
                        <Container>
                            <ContainerType>Bottle</ContainerType>
                            <ContainerName>EcoBottle</ContainerName>
                        </Container>
                    </Containers>
            </Classification>
        </Classifications>
    </Group>
</ProductGroup>
</xmlRoot>

我尝试过这样的事情:

treProducts.Nodes.Clear();
XDocument xdoc = XDocument.Load("ProductDocument.xml");
foreach (XElement groupElement in xdoc.Descendants("Group"))
{
    treProducts.Nodes.Add(groupElement.Element("GroupName").Value);
    treProducts.SelectedNode = treProducts.Nodes[groupElement.Element("GroupName").Value];
    foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
    {
        treProducts.SelectedNode.Nodes.Add(groupElement.Element("ClassificationName").Value);
        treProducts.SelectedNode = treProducts.Nodes[groupElement.Element("ClassificationName").Value];
        foreach (XElement ContainerElement in groupElement.Descendants("Container"))
        {
            treProducts.SelectedNode.Nodes.Add(ContainerElement.Element("ContainerName").Value);
        }
    }
}

我试图让树显示:

Soda
    Regular
        SmallCan
        SmallBottle
    Diet
        SmallCan
Water
    Regular
        EcoBottle

...但树只显示苏打水,它似乎跳过其余的,除非我注释掉嵌套的foreach语句,它将显示苏打和水。 我使用的语法有问题,我想知道更好地了解Linq的人是否可以帮助查看代码错误的位置。

3 个答案:

答案 0 :(得分:0)

您在Classification元素的循环中使用了错误的变量。将groupElement替换为循环内的ClassificationElement

更改:

foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
    // groupElement.Element("ClassificationName") is null:
    treProducts.SelectedNode.Nodes.Add(groupElement.Element("ClassificationName").Value);
    ...
}

foreach (XElement ClassificationElement in groupElement.Descendants("Classification"))
{
treProducts.SelectedNode.Nodes.Add(ClassificationElement.Element("ClassificationName").Value);
    ...
}

答案 1 :(得分:0)

这很复杂的原因是您的树节点层次结构与您的XML层次结构不对应。在这种情况下,我建议引入中间类来提供基本XML模型数据的视图。在WPF中,这些类将是视图模型,但窗口形成TreeView doesn't support data binding。尽管如此,视图模型的抽象在这里很有用。

首先,一些基本的视图模型接口和类将TreeNodeXElement层次结构组合在一起:

public interface ITreeNodeViewModel
{
    string Name { get; }

    string Text { get; }

    object Tag { get; }

    object Model { get; }

    IEnumerable<ITreeNodeViewModel> Children { get; }
}

public abstract class TreeNodeViewModel<T> : ITreeNodeViewModel
{
    readonly T model;

    public TreeNodeViewModel(T model)
    {
        this.model = model;
    }

    public T Model { get { return model; } }

    #region ITreeNodeProxy Members

    public abstract string Name { get; }

    public abstract string Text { get; }

    public virtual object Tag { get { return this; } } 

    public abstract IEnumerable<ITreeNodeViewModel> Children { get; }

    #endregion

    #region ITreeNodeViewModel Members

    object ITreeNodeViewModel.Model
    {
        get { return Model; }
    }

    #endregion
}

public abstract class XElementTreeNodeViewModel : TreeNodeViewModel<XElement>
{
    public XElementTreeNodeViewModel(XElement node) : base(node) {
        if (node == null)
            throw new ArgumentNullException();
    }

    public XNamespace Namespace { get { return Model.Name.Namespace; } }

    public override string Name
    {
        get { return Model.Name.ToString();  }
    }
}

接下来,几个扩展类:

public static class TreeViewExtensions
{
    public static void PopulateNodes(this TreeView treeView, IEnumerable<ITreeNodeViewModel> viewNodes)
    {
        treeView.BeginUpdate();
        try
        {
            treeView.Nodes.PopulateNodes(viewNodes);
        }
        finally
        {
            treeView.EndUpdate();
        }
    }

    public static void PopulateNodes(this TreeNodeCollection nodes, IEnumerable<ITreeNodeViewModel> viewNodes)
    {
        nodes.Clear();
        if (viewNodes == null)
            return;
        foreach (var viewNode in viewNodes)
        {
            var name = viewNode.Name;
            var text = viewNode.Text;
            if (string.IsNullOrEmpty(text))
                text = name;
            var node = new TreeNode { Name = name, Text = text, Tag = viewNode.Tag };
            nodes.Add(node);
            PopulateNodes(node.Nodes, viewNode.Children);
            node.Expand();
        }
    }
}

public static class XObjectExtensions
{
    public static string TextValue(this XContainer node)
    {
        if (node == null)
            return null;
        //return string.Concat(node.Nodes().OfType<XText>().Select(tx => tx.Value));  c# 4.0
        return node.Nodes().OfType<XText>().Select(tx => tx.Value).Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
    }

    public static IEnumerable<XElement> Elements(this IEnumerable<XElement> elements, XName name)
    {
        return elements.SelectMany(el => el.Elements(name));
    }
}

接下来,查看树的三个级别的模型:

class ContainerViewModel : XElementTreeNodeViewModel
{
    public ContainerViewModel(XElement node) : base(node) { }

    public override string Text
    {
        get
        {
            return Model.Element(Namespace + "ContainerName").TextValue();
        }
    }

    public override IEnumerable<ITreeNodeViewModel> Children
    {
        get { return Enumerable.Empty<ITreeNodeViewModel>(); }
    }
}

class ClassificationViewModel : XElementTreeNodeViewModel
{
    public ClassificationViewModel(XElement node) : base(node) { }

    public override string Text
    {
        get
        {
            return Model.Element(Namespace + "ClassificationName").TextValue();
        }
    }

    public override IEnumerable<ITreeNodeViewModel> Children
    {
        get
        {
            return Model.Elements(Namespace + "Containers").Elements<XElement>(Namespace + "Container").Select(xn => (ITreeNodeViewModel)new ContainerViewModel(xn));
        }
    }
}

class GroupViewModel : XElementTreeNodeViewModel
{
    public GroupViewModel(XElement node) : base(node) { }

    public override string Text
    {
        get
        {
            return Model.Element(Namespace + "GroupName").TextValue();
        }
    }

    public override IEnumerable<ITreeNodeViewModel> Children
    {
        get
        {
            return Model.Elements(Namespace + "Classifications").Elements<XElement>(Namespace + "Classification").Select(xn => (ITreeNodeViewModel)new ClassificationViewModel(xn));
        }
    }
}

现在,构建树变得非常简单:

        var xdoc = XDocument.Load("ProductDocument.xml");
        var ns = xdoc.Root.Name.Namespace;
        treeView1.PopulateNodes(xdoc.Root.Elements(ns + "ProductGroup").Elements(ns + "Group").Select(xn => (ITreeNodeViewModel)new GroupViewModel(xn)));

结果:

enter image description here

稍后,如果您希望向树中添加编辑功能,可以将适当的方法添加到ITreeNodeViewModel - 例如,Text的setter方法。由于ITreeNodeViewModel已将自己保存在TreeNode.Tag中,因此可以使用相应的方法。

答案 2 :(得分:0)

我建议递归

void AddNodes(XElement parentElement, TreeNode parent = null)
{
    Queue<XElement> queue = new Queue<XElement>(parentElement.Elements());
    while (queue.Count > 0)
    {
        TreeNode child = parent;
        XElement element = queue.Dequeue();
        if (!element.HasElements)
        {
            string value = element.Value;
            element = (XElement)element.NextNode;
            if (null != element && !element.HasElements)
                value = element.Value;

            if (null == parent)
                treeView1.Nodes.Add(child = new TreeNode(value));
            else
                parent.Nodes.Add(child = new TreeNode(value));
            child.Expand();
            element = queue.Dequeue();
        }
        AddNodes(element, child);
    }
}

AddNodes(XElement.Load("ProductDocument.xml"));

enter image description here

注意:如果您的XML结构可能会发生变化,那么dbc的答案可能会更好,但是如果按照目前的情况给出它,并且它不会改变 - 那么这会使它正确快速进入树中,没有太大的开销。

相关问题