C# - XMLをTreeViewに表示する (XElementクラスで探索して力技でTreeNodeに登録)

# まえがき

arxmlを解析してなにかする便利ツールをつくろうと思ったが、XMLが大きすぎてノード関係性がサッパリわからないので、見やすくする＆検索できるようにしたかった。

# 画面キャプチャ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/e5e8e433-d609-2147-6145-0287480e325f.png)

# 入力サンプルデータ

```xml:Sample.xml

<?xml version="1.0" encoding="utf-8"?>
<SampleClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <SampleFieldPoint>
    <X>10</X>
    <Y>20</Y>
  </SampleFieldPoint>
  <SampleFieldSize>
    <Width>30</Width>
    <Height>40</Height>
  </SampleFieldSize>
</SampleClass>
```

# ソースコード

```cs:XmlTreeView.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Drawing;
using System.Windows.Forms;


class XmlTreeView : Form
{
    TextBox txtTreePath;
    TextBox txtSearchKeyword;
    ListView lsv;
    TreeView trv;

    XmlTreeView(string path)
    {
        ClientSize = new Size(1200,500);
        
        XElement xml = XElement.Load(path);
        
        SplitContainer spl1;
        SplitContainer spl2;
        Controls.Add(spl1 = new SplitContainer(){
            Dock = DockStyle.Fill,
            Orientation = Orientation.Horizontal,
            FixedPanel = FixedPanel.Panel1,
            Panel1MinSize = 50,
            Panel2MinSize = 50,
        });
        spl1.Panel2.Controls.Add(spl2 = new SplitContainer(){
            Dock = DockStyle.Fill,
            Orientation = Orientation.Horizontal,
            // FixedPanel = FixedPanel.Panel1,
            Panel1MinSize = 50,
            Panel2MinSize = 50,
        });
        
        Load += (s,e)=>{
            spl1.SplitterDistance =  45;
            spl2.SplitterDistance =  150;
        };

        spl1.Panel1.Controls.Add( txtTreePath = new TextBox(){
            Location = new Point(0, 0),
            Width = 1000,
            ReadOnly = true,
        });

        Button btnSearch;
        spl1.Panel1.Controls.Add( btnSearch = new Button(){
            Location = new Point(0, 22),
            Width = 70,
            Text = "Search",
        });
        btnSearch.Click += (s,e)=>{SearchNode();};
        
        spl1.Panel1.Controls.Add( txtSearchKeyword = new TextBox(){
            Location = new Point(80, 25),
            Width = 620,
        });
        txtSearchKeyword.KeyDown += (s,e)=>{
            if ( e.KeyData == Keys.Enter ) { SearchNode(); }
        };


        spl2.Panel1.Controls.Add(lsv = new ListView(){
            Dock = DockStyle.Fill,
			View = View.Details,
			MultiSelect = false,
			HideSelection = false,
			FullRowSelect = true,
			GridLines = true,
        });
        lsv.Columns.AddRange(new ColumnHeader[]{
            new ColumnHeader(){Name="Type"     , Text="Type"         , Width=  70, TextAlign=HorizontalAlignment.Left},
            new ColumnHeader(){Name="Name"     , Text="Name or Value", Width=  70, TextAlign=HorizontalAlignment.Left},
            new ColumnHeader(){Name="Location" , Text="Location"     , Width=1000, TextAlign=HorizontalAlignment.Left},
        });
        lsv.DoubleClick += (s,e)=>{Lsv_DoubleClick();};

        spl2.Panel2.Controls.Add( trv = new TreeView(){
            Dock = DockStyle.Fill,
            HideSelection = false,
        });
        trv.AfterSelect += Trv_AfterSelect;


        var rootNode = new TreeNode(xml.Name.LocalName);
        rootNode.Name = rootNode.Text;
        trv.Nodes.Add(rootNode);
        XmlAddNodes(rootNode, xml);
    }
    // elemの子をnodeに追加する
    void XmlAddNodes(TreeNode node, XElement elem)
    {
        var t = elem.Elements(); // 直下の子ノードたち
        bool hasChild=false;
        foreach ( XElement elemChild in t)
        {
            hasChild=true;
            string s = elemChild.Name.LocalName;
            var nodeChild = new TreeNode(s);
            nodeChild.Name = s;
            node.Nodes.Add(nodeChild);
            XmlAddNodes(nodeChild, elemChild);
        }
        if(!hasChild){
            node.Text += " = " + elem.Value;
            node.Tag   = elem.Value;
        }
    }
    

    void Trv_AfterSelect(object sender, TreeViewEventArgs e)
    {
        txtTreePath.Text = GetLocationText(e.Node);
    }
    
    string GetLocationText(TreeNode node)
    {
        string s = node.Text;
        
        while(node.Parent!=null){
            node = node.Parent;
            s = node.Text + " > " + s;
        }
        
        return s;
    }


    void SearchNode()
    {
        string key = txtSearchKeyword.Text;
        
        if(key==""){return;}
        
        lsv.Items.Clear();
        lsv.BeginUpdate();
        
        var nodes = trv.Nodes.Find(key, true);
        foreach( var node in nodes ) {
            var item = new ListViewItem(new string[]{"TagName", node.Text, GetLocationText(node)});
            item.Tag = node;
            lsv.Items.Add(item);
            
        }
        
        lsv.EndUpdate();
    }
    
    void Lsv_DoubleClick()
    {
        if (lsv.SelectedItems.Count!=1){
            return;
        }
        
        var node = (TreeNode)lsv.SelectedItems[0].Tag;
        node.EnsureVisible();
        trv.SelectedNode = node;
    }
    
    [STAThread]
    static void Main(string[] args)
    {
        //Application.Run(new XmlTreeView(@"hogehoge.xml"));
        if (args.Length==1){
            Application.Run(new XmlTreeView(args[0]));
        }
    }
}
```