C# – 「XML – TreeView」間でのデータ相互変換

概要

  • XML文書から読み込んだ情報をツリービューに展開する。
  • TreeViewの内容をXML文書に出力する。
  • TreeViewそのものの操作(ドラッグ&ドロップ等)は「DOBON.NET」さんのサイトにあるサンプル等を 使用しております。

サンプルプロジェクト

動作環境

  • Microsoft .NET Framework 4.6.1

実装例

使用するXML文書サンプル

<?xml version="1.0" encoding="UTF-8"?>

<root name="車" price="">
  <group name="日本車" price="">
    <group name="トヨタ" price="">
      <item name="パッソ" price="950,000" />
      <item name="プリウス" price="2,230,000" />
    </group>
    <group name="日産" price="">
      <item name="マーチ" price="1,100,000" />
      <item name="キューブ" price="1,500,000" />
      <item name="スカイライン" price="3,000,000" />
    </group>
    <group name="スズキ" price="">
      <item name="ワゴンR" price="900,000" />
      <item name="Kei" price="800,000" />
    </group>
  </group>
  <group name="外国車" price="">
    <group name="メルセデス・ベンツ" price="">
      <item name="Aクラス" price="3,000,000" />
      <item name="Cクラス" price="5,000,000" />
    </group>
    <group name="BMW" price="">
      <item name="3シリーズ" price="5,000,000" />
      <item name="7シリーズ" price="12,000,000" />
    </group>
  </group>
</root>

上記のXML文書では、タグ名に「root」、「group」、「item」、属性には「name」、「price」を使用しています。
タグ名 root はツリーのルートを表し、group はグループ、item は項目を表しています。 また、属性では「name」でグループや項目名を、「price」では価格を表示しています。
TreeViewの表示ラベル名には、タグ名ではなく「name」属性の値を使用します。

属性格納用のクラス「NodeInfo」を宣言

namespace XmlTreeView
{
    // 各ノードの情報用クラス
    public class NodeInfo
    {
        public NodeInfo()
        {
            this.Type = "null";
            this.Name = "new name";
            this.Price = "0";
            this.Expand = false;
        }

        /// <summary>
        /// タグ名称 (この名称を元にタグの種類を判断)
        /// </summary>
        public string Type { get; set; }

        /// <summary>
        /// 「name」属性 (TreeViewのラベルはこの値を使用する)
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 「price」属性
        /// </summary>
        public string Price { get; set; }

        /// <summary>
        /// 「expand」属性(XMLファイルの読み書き時にのみ使用)
        /// </summary>
        public bool Expand { get; set; }
    }
}

XmlDocumentインスタンスの宣言

実際使用する際に「new」でインスタンスを作成します。

// XML文書入力用のXmlDocumentインスタンス
private XmlDocument importXmlDocument;

// XML文書出力用のXmlDocumentインスタンス
private XmlDocument exportXmlDocument;

XML文書からノードを再帰的に読み込み、TreeNodeを構築する「RecursiveShowToTreeNode」メソッド

        /// <summary>
        /// Xml文書を再帰的に検査し、TreeNodeを構築
        /// </summary>
        /// <param name="Parentnode"></param>
        /// <param name="ParentTreeNode"></param>
        private void RecursiveShowToTreeNode(XmlNode Parentnode, TreeNode ParentTreeNode)
        {
            foreach (XmlNode childXmlNode in Parentnode.ChildNodes)
            {
                if (childXmlNode.Name != "#text")		// テキストノードへの処理
                {
                    // ツリーノードの追加
                    NodeInfo ni = new NodeInfo();
                    // タグ名を取得
                    switch (childXmlNode.Name)
                    {
                        case "group":
                            ni.Type = "group";
                            break;
                        case "item":
                            ni.Type = "item";
                            break;
                        default:
                            ni.Type = "null";
                            break;
                    }
                    // 「name」属性を取得
                    ni.Name = ((XmlElement)childXmlNode).GetAttribute("name");
                    // 「price」属性を取得
                    ni.Price = ((XmlElement)childXmlNode).GetAttribute("price");
                    // 「Expand」属性を取得
                    if (((XmlElement)childXmlNode).GetAttribute("expand") == "true")
                    {
                        ni.Expand = true;
                    }

                    // TreeNodeを新規作成
                    TreeNode tn = new TreeNode(ni.Name);

                    // アイコン設定(このサンプルにはリソースファイルを入れてないので
                    // 実は機能していませんが)
                    if (ni.Type == "group")
                    {
                        tn.ImageIndex = 2;
                        tn.SelectedImageIndex = 2;
                    }
                    else
                    {
                        tn.ImageIndex = 1;
                        tn.SelectedImageIndex = 1;
                    }

                    // ノードに属性を設定
                    tn.Tag = ni;
                    // ノードを追加
                    ParentTreeNode.Nodes.Add(tn);
                    // 再帰呼び出し
                    RecursiveShowToTreeNode(childXmlNode, tn);
                }
            }
        }

「XmlNode」は、属性をそのまま取得するメソッドはないため、「XmlElement」にキャストする必要があります。

「TreeNode」には、「Tag」プロパティがあります。この「Tag」プロパティには「object」型、つまり どんな型でも設定することが出来ます。そこで、この「Tag」プロパティにノード情報クラス「NodeInfo」クラスの インスタンスを格納しています。当然、この情報を取得する場合、「object」型から「NodeInfo」型にキャストする必要があります。 (この後出てきます)

「NodeInfo」クラスの「Expand」プロパティで、TreeViewの展開情報を設定しますが、実際にTreeView上のノードを 展開するのは最後に行います。ノードの追加中に展開を行っても、反映されません。

XML文書を読み込み、上記の「RecursiveShowToTreeNode」メソッドを使用して、TreeViewに表示

        /// <summary>
        /// ファイルを開く(1つのみ)
        /// </summary>
        public void ImportXml()
        {
            // ファイルオープン用ダイアログ表示
            OpenFileDialog openFileDialog1 = new OpenFileDialog();

            openFileDialog1.Title = "リストファイルを選択してください";
            openFileDialog1.FileName = "*.xml";
            openFileDialog1.Filter = "XMLファイル|*.xml|すべてのファイル|*.*";
            openFileDialog1.FilterIndex = 0;

            openFileDialog1.RestoreDirectory = true;
            openFileDialog1.CheckFileExists = true;
            openFileDialog1.CheckPathExists = true;
            openFileDialog1.DereferenceLinks = true;

            openFileDialog1.DefaultExt = "XML";
            openFileDialog1.AddExtension = true;

            openFileDialog1.Multiselect = false;

            // ダイアログを表示
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                // TreeViewの初期化
                treeView1.Nodes.Clear();
                // 入力用 XmlDocument インスタンス初期化
                importXmlDocument = new XmlDocument();
                // XML文書の読み込み
                importXmlDocument.Load(openFileDialog1.FileName);
                // XML文書のルートノード取得
                XmlNode rootXmlNode = importXmlDocument.DocumentElement;

                // ルート用のノード情報を取得
                NodeInfo ni = new NodeInfo();
                //タグ要素名を取得
                ni.Type = ((XmlElement)rootXmlNode).Name;
                // 属性「name」を取得
                ni.Name = ((XmlElement)rootXmlNode).GetAttribute("name");
                // 属性「price」を取得
                ni.Price = ((XmlElement)rootXmlNode).GetAttribute("price");
                // 「Expand」属性を取得
                if (((XmlElement)rootXmlNode).GetAttribute("expand") == "true")
                {
                    ni.Expand = true;
                }

                // TreeView用のルートノード作成(ラベル名「name」属性)
                TreeNode rootTreeNode = new TreeNode(ni.Name);
                // ルートノードの情報にXMLから取得した情報を設定
                rootTreeNode.Tag = ni;

                // TreeViewにルートノード追加
                treeView1.Nodes.Add(rootTreeNode);
                // 再帰的にXmlノードを読み込み、TreeView構築
                RecursiveShowToTreeNode(rootXmlNode, rootTreeNode);

                // TreeNodeの各ノードの展開設定は、XMLの読み込み完了後に行う
                // 「Expand」属性を取得
                if (ni.Expand == true)
                {
                    rootTreeNode.Expand();
                }
                // 必要なノードを展開
                UpdateExpand(rootTreeNode);


            }
            openFileDialog1 = null;
        }

TreeView上で、必要なノードを展開する

        /// <summary>
        /// 「TreeNode」を辿り、「Tag」プロパティの「Expand」属性が「on」のグループを展開
        /// </summary>
        /// <param name="RootTreeNode"></param>
        private void UpdateExpand(TreeNode RootTreeNode)
        {
            foreach (TreeNode childTreeNode in RootTreeNode.Nodes)
            {
                NodeInfo ni = (NodeInfo)childTreeNode.Tag;
                if (ni.Type == "group" && ni.Expand == true)
                {
                    childTreeNode.Expand();
                }
                // 再帰呼び出し
                UpdateExpand(childTreeNode);
            }
        }

XML文書への書き出しも基本は同じで、やることが逆になる

        /// <summary>
        /// タグ要素名取得メソッド
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        private static string GetTagType(TreeNode node)
        {
            if (node != null)
            {
                NodeInfo ni = new NodeInfo();
                ni = (NodeInfo)node.Tag;
                return ni.Type;
            }
            else
            {
                return null;
            }
        }

XML文書の再帰読み込みの逆、TreeNodeの再帰読み込みメソッド

        /// <summary>
        /// TreeNodeを再帰的に検査し、XmlDocumentを構築
        /// </summary>
        /// <param name="Parentnode"></param>
        /// <param name="ParentXmlNode"></param>
        private void RecursiveShowToXml(TreeNode Parentnode, XmlNode ParentXmlNode)
        {
            foreach (TreeNode childTreeNode in Parentnode.Nodes)
            {
                // TreeNodeのタグ情報に格納された属性を取得
                NodeInfo ni = new NodeInfo();
                ni = (NodeInfo)childTreeNode.Tag;

                // XMLのノードを新規作成
                XmlElement xe = exportXmlDocument.CreateElement(ni.Type);
                // 「name」属性を設定
                xe.SetAttribute("name", ni.Name);
                // 「price」属性を設定
                xe.SetAttribute("price", ni.Price);
                // ノードがルートかグループの場合、「Expand」属性を設定
                if (ni.Type == "root" || ni.Type == "group")
                {
                    // 「expand」属性書き出し
                    if (childTreeNode.IsExpanded)
                    {
                        xe.SetAttribute("expand", "true");
                    }
                }


                // XmlNodeに追加
                ParentXmlNode.AppendChild(xe);
                // 再帰呼び出し
                RecursiveShowToXml(childTreeNode, xe);
            }
        }

「XmlElement」を新規作成する場合、XmlDocumentクラスの「CreateElement」メソッドを使用します。 タグ名として「TagType」の値を指定していますが、そのままでは大文字なので、小文字に変換しています。(XML文書のルール上)

「XmlElement」に属性を設定するには、「SetAttribute」メソッドを使用します。第1引数で属性名、第2引数で値を設定します。

TreeNodeを読み込み、上記の「RecursiveShowToXml」メソッドを使用して、XML文書に書き出し

/// <summary>
        /// XMLファイル出力
        /// </summary>
        public void ExportXml()
        {
            SaveFileDialog saveFileDialog1 = new SaveFileDialog();

            saveFileDialog1.Title = "リストファイルを選択してください";
            saveFileDialog1.FileName = "*.xml";
            saveFileDialog1.Filter = "XMLファイル|*.xml|すべてのファイル|*.*";
            saveFileDialog1.FilterIndex = 0;

            saveFileDialog1.RestoreDirectory = false;
            saveFileDialog1.OverwritePrompt = true;

            saveFileDialog1.DefaultExt = "XML";
            saveFileDialog1.AddExtension = true;

            // ダイアログを表示
            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                // 出力用 XmlDocument インスタンスの初期化
                exportXmlDocument = new XmlDocument();

                // XMLヘッダ出力(バージョン 1.0, エンコード UTF-8)
                XmlDeclaration xmlDeclaration = exportXmlDocument.CreateXmlDeclaration("1.0", "UTF-8", null);
                exportXmlDocument.AppendChild(xmlDeclaration);

                // ヘッダの後に1行改行(好みの問題です)
                XmlWhitespace xmlWhitespace = exportXmlDocument.CreateWhitespace(System.Environment.NewLine);
                exportXmlDocument.AppendChild(xmlWhitespace);

                // ルートノードの情報を初期化
                NodeInfo ni = new NodeInfo();
                ni = (NodeInfo)treeView1.Nodes[0].Tag;
                // タグ要素名設定
                XmlElement xe = exportXmlDocument.CreateElement(ni.Type);
                // 「name」属性書き出し
                xe.SetAttribute("name", ni.Name);
                // 「price」属性書き出し
                xe.SetAttribute("price", ni.Price);
                // 「expand」属性書き出し
                if (treeView1.Nodes[0].IsExpanded)
                {
                    xe.SetAttribute("expand", "true");
                }
                // ルートノードをXmlDocumentに追加
                exportXmlDocument.AppendChild(xe);

                // 再帰的にツリーノードを読み込み、XmlDocument構築
                RecursiveShowToXml(treeView1.Nodes[0], xe);

                // ファイルに出力
                exportXmlDocument.Save(saveFileDialog1.FileName);
            }
        }

TreeView上のノードが展開されているかどうかは、「TreeNode」クラスの「IsExpanded」プロパティで確認できます。

「XmlDeclaration」は、XML文書のヘッダ部分の宣言を出力します。「XmlWhitespace」は、空白や改行を出力できます。 ヘッダと本文の間を1行あけたかったので、今回使用しました。

サンプルではTreeViewに簡単な 編集機能を実装しましたので、もうちょい改造すれば簡易XMLエディタとして使えるかもしれません。