概要
- 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エディタとして使えるかもしれません。