【问题标题】:Recursive adding XML into TreeView递归地将 XML 添加到 TreeView
【发布时间】:2015-03-05 18:15:06
【问题描述】:

我正在尝试使用 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 的人可以帮助查看代码错误的地方。

【问题讨论】:

  • @dbc 是的。你记下的 TreeView 类。

标签: c# xml winforms linq treeview


【解决方案1】:

您在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);
    ...
}

【讨论】:

    【解决方案2】:

    我建议递归

    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"));
    

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

    【讨论】:

      【解决方案3】:

      这很复杂的原因是您的树节点层次结构与您的 XML 层次结构不对应 1-1。在这种情况下,我建议引入中间类来提供基本 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)));
      

      结果:

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

      【讨论】:

        猜你喜欢
        • 2016-08-12
        • 1970-01-01
        • 2019-02-26
        • 1970-01-01
        • 2015-08-13
        • 2017-12-22
        • 2022-01-16
        • 2014-05-09
        • 2011-02-09
        相关资源
        最近更新 更多