TreeView コントロールはデータ項目をツリー状に表示するためのもので、コントロールを使うことによって、銘柄の分類と追加や削除が簡単になるので、下記のようなツールを作ってみます。

機能としては、TreeViewノードの追加、削除、編集、データの保存と読み込みです。
動作確認環境
- Windows10
- Visual studio Community 2017
- ターゲットフレームワーク .NET Framework 4.6.1
Windows フォームの追加とプロパティ
親ノード(カテゴリー名)編集用として下記のようなWindowsフォームを作成します。ファイル名をinputCategory.csとしてWindowsフォームを追加します。テキストボックスとボタンを以下にように配置します。カテゴリー名編集ボタンをクリックした時に、親フォームから呼び出されるように実装します。

コントロール | プロパティ | 値 |
Form1 | FormBorderStyle | FixedDialog |
Form1 | MaximizeBox | False |
Form1 | MinimizeBox | False |
Form1 | StartPosition | CenterParent |
PanActiveDBAc.csの追加
Pan Active Market Databaseにアクセスするためのクラスを追加します。「ローソク足チャートを描いてみる」で作成したPanActiveDBAc.csを使用します。
コントロールの配置とプロパティ
メインフォームのコントロールの配置とプロパティを以下のように設定します。

コントロール | プロパティ | 値 |
TreeView | Anchor | Top, Bottom, Left |
参照設定
ここまででソリューションエクスプローラは以下のようになります。

inputCategory.cs
TreeView の プロパティLabelEditを True に設定すると、ノードを編集できるのですが、カテゴリーノード(親ノード)だけを編集可能にしたいので、LabelEditを Falseに設定して(デフォルト)、わざわざ編集ダイアログを作りました。(簡単に編集できないようするという意味があります)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CodeTreeView
{
public partial class inputCategory : Form
{
private string _inputText = "";
private string _titleText = "";
public inputCategory()
{
InitializeComponent();
}
public string inputText
{
set
{
_inputText = value;
}
get
{
return (_inputText);
}
}
public string titleText
{
set
{
_titleText = value;
}
get
{
return (_titleText);
}
}
private void inputCategory_Load(object sender, EventArgs e)
{
this.Text = _titleText;
textBox1.Text = _inputText;
}
private void btnOK_Click(object sender, EventArgs e)
{
this._inputText = textBox1.Text;
// 戻り値をOKに設定する
this.DialogResult = DialogResult.OK;
this.Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
// 戻り値をCancelに設定する
this.DialogResult = DialogResult.Cancel;
this.Close();
}
}
}
Form1.cs
選択されているノードの取得
treeView1.SelectedNodeで選択されているツリーノードを取得します。ノードが選択されていない場合は、nullを返します。
TreeNode ParentNode = treeView1.SelectedNode;
下記のように、「国内ETF」が選択されている場合は、「国内ETF」ノードが返ってきます。

下記のように、ノードが何も選択されていなければ、nullが返ってきます。

ツリー ノードの深さを取得します
TreeNodeクラスのLevelは、ツリー ノードの深さを取得します。
if (ParentNode.Level == 0)

ノードの追加
ParentNode.Nodes.Add(node)でノードを動的に追加します。node.Nameはキーとして使用するので、入力されたテキスト値を設定しています。最初の行のnew TreeNode(textBox1.Text)では、Nameは設定されない。このキーは2重登録されているかContainsKey()でチェックするためのものです。
TreeNode node = new TreeNode(textBox1.Text);
// 銘柄名の検索
if (allCodeNames.dict.ContainsKey(textBox1.Text))
{
// コード名の設定
node.Name = textBox1.Text; // Keyとして登録
// 表示テキストの設定
node.Text = node.Name + " " + allCodeNames.dict[textBox1.Text];
if (ParentNode.Nodes.ContainsKey(node.Name) == false)
{
// 同じコードがなければ、ノードに追加する
ParentNode.Nodes.Add(node);
ParentNode.Expand(); // 展開する
treeView1.Focus(); //追加した項目にフォーカスを当てたい
textBox1.Text = "";
}
}
else
{
MessageBox.Show("存在しないコードです", "エラー");
}
選択中のノードの移動
一つ上へ移動
treeView1.Nodes.RemoveAt(index);
treeView1.Nodes.Insert(index - 1, treeNode);
treeView1.SelectedNode = treeNode;
一つ下へ移動
treeView1.Nodes.RemoveAt(index);
treeView1.Nodes.Insert(index + 1, treeNode);
treeView1.SelectedNode = treeNode;
XMLで無効な文字をエスケープ文字に変換する
「商品・その他」を要素として XElement 変数に保存しようとした時にエラーが出た。
「・」がいけないようである。無効文字とみなされる。
なので、以下のようにエスケープ文字列に変換します。
string text = XmlConvert.EncodeName(ParentNode.Text);
すると以下のように、“_x30FB_” に変換されます。
<商品_x30FB_その他>
<member>1540 純金ETF</member>
<member>1541 純プラチナETF</member>
</商品_x30FB_その他>
また、XMLファイルを読み込む時も、デコードする必要があるので、以下のようにしてエスケープ文字列を除去します。
node.Text = XmlConvert.DecodeName(node.Text);
以下、フォームの全プログラムコードです。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
using System.Xml;
using PanActiveMarket;
namespace CodeTreeView
{
public partial class Form1 : Form
{
private AllCodeNames allCodeNames = new AllCodeNames();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ListViewItem lstview = new ListViewItem();
}
private bool validCheck(string str)
{
if (System.Text.RegularExpressions.Regex.IsMatch(
textBox1.Text, @"\A\d\d\d\d\z",
System.Text.RegularExpressions.RegexOptions.ECMAScript))
{
// 4桁の10進数値です。
return (true);
}
return (false);
}
private void btnAddParent_Click(object sender, EventArgs e)
{
if (textBox1.Text != "")
{
TreeNode node = new TreeNode(textBox1.Text);
node.Name = node.Text;
if (treeView1.Nodes.ContainsKey(node.Name) == false)
{
treeView1.Nodes.Add(node);
textBox1.Text = "";
}
else
{
MessageBox.Show("同じカテゴリーが登録されています", "エラー");
}
}
}
private void btnAddChiled_Click(object sender, EventArgs e)
{
TreeNode ParentNode = treeView1.SelectedNode;
if (ParentNode != null && validCheck(textBox1.Text))
{
if (ParentNode.Level == 0)
{
TreeNode node = new TreeNode(textBox1.Text);
// 銘柄名の検索
if (allCodeNames.dict.ContainsKey(textBox1.Text))
{
// コード名の設定
node.Name = textBox1.Text; // Keyとして登録
// 表示テキストの設定
node.Text = node.Name + " " + allCodeNames.dict[textBox1.Text];
if (ParentNode.Nodes.ContainsKey(node.Name) == false)
{
// 同じコードがなければ、ノードに追加する
ParentNode.Nodes.Add(node);
ParentNode.Expand(); // 展開する
treeView1.Focus(); //追加した項目にフォーカスを当てたい
textBox1.Text = "";
}
}
else
{
MessageBox.Show("存在しないコードです", "エラー");
}
}
else
{
MessageBox.Show("カテゴリーを選択して下さい", "エラー");
}
}
}
private void btnCategory_Click(object sender, EventArgs e)
{
inputCategory dlg = new inputCategory();
TreeNode ParentNode = treeView1.SelectedNode;
if (ParentNode != null)
{
if (ParentNode.Parent == null)
{
dlg.inputText = ParentNode.Name;
dlg.titleText = "カテゴリー名編集";
if (dlg.ShowDialog() == DialogResult.OK)
{
//OKがクリックされた場合
ParentNode.Text = dlg.inputText;
ParentNode.Name = dlg.inputText;
}
}
else
{
MessageBox.Show("カテゴリーを選択して下さい。");
}
}
else
{
MessageBox.Show("カテゴリーを選択して下さい。");
}
}
private void btnUpNode_Click(object sender, EventArgs e)
{
TreeNode treeNode = treeView1.SelectedNode;
if (treeNode != null)
{
if (treeNode.Parent == null)
{
int index = treeNode.Index;
int count = treeView1.Nodes.Count;
// ノードを一つ↑に移動する(親ノードのみ)
if (index > 0)
{
treeView1.Nodes.RemoveAt(index);
treeView1.Nodes.Insert(index - 1, treeNode);
treeView1.SelectedNode = treeNode;
}
}
else
{
TreeNode ParentNode = treeView1.SelectedNode.Parent;
int index = treeNode.Index;
// ノードを一つ↑に移動する(子ノードのみ)
if (index > 0)
{
ParentNode.Nodes.RemoveAt(index);
ParentNode.Nodes.Insert(index - 1, treeNode);
treeView1.SelectedNode = treeNode;
}
}
treeView1.Focus(); // フォーカスをあてる
}
}
private void btnDownNode_Click(object sender, EventArgs e)
{
TreeNode treeNode = treeView1.SelectedNode;
if (treeNode != null)
{
if (treeNode.Parent == null)
{
int index = treeNode.Index;
int count = treeView1.Nodes.Count;
// ノードを一つ↓に移動する(親ノードのみ)
if (index < (count - 1))
{
treeView1.Nodes.RemoveAt(index);
treeView1.Nodes.Insert(index + 1, treeNode);
treeView1.SelectedNode = treeNode;
}
}
else
{
TreeNode ParentNode = treeView1.SelectedNode.Parent;
int index = treeNode.Index;
// ノードを一つ↓に移動する(子ノードのみ)
if (index < (ParentNode.Nodes.Count - 1))
{
ParentNode.Nodes.RemoveAt(index);
ParentNode.Nodes.Insert(index + 1, treeNode);
treeView1.SelectedNode = treeNode;
}
}
treeView1.Focus(); // フォーカスをあてる
}
}
private void btnSave_Click(object sender, EventArgs e)
{
string filename = @"Stock_v.xml";
XElement xml = new XElement("Root");
TreeNode ParentNode = treeView1.Nodes[0]; // 一番上のノード
while (ParentNode != null)
{
// XMLで無効な文字をエスケープ文字に変換する
string text = XmlConvert.EncodeName(ParentNode.Text);
XElement category = new XElement(text);
xml.Add(category);
foreach (TreeNode ChildNode in ParentNode.Nodes)
{
string strCodeName = ChildNode.Text;
XElement member = new XElement("member", strCodeName);
category.Add(member);
}
ParentNode = ParentNode.NextNode;
}
try
{
xml.Save(filename);
MessageBox.Show("作成完了", "成功");
}
catch
{
MessageBox.Show("ファイルの保存に失敗しました。", "実行エラー");
}
}
private void btnLoad_Click(object sender, EventArgs e)
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
try
{
doc.Load(@"Stock_v.xml");
XmlElement root = doc.DocumentElement;
XmlElement name = (XmlElement)root.FirstChild;
treeView1.Nodes.Clear();
while (name != null)
{
TreeNode node = new TreeNode(name.Name);
node.Text = XmlConvert.DecodeName(node.Text);
node.Name = node.Text;
// categoryの追加
treeView1.Nodes.Add(node);
XmlElement member = (XmlElement)name.FirstChild;
while (member != null)
{
string strCodeName = member.FirstChild.Value;
string[] str = strCodeName.Split(' ');
// str[0] : code
// str[1] : name
TreeNode memberNode = new TreeNode(strCodeName);
memberNode.Name = str[0];
// member(code & name)の追加
node.Nodes.Add(memberNode);
member = (XmlElement)member.NextSibling;
}
name = (XmlElement)name.NextSibling;
}
}
catch
{
MessageBox.Show("ファイルの読み込みに失敗しました。", "実行エラー");
}
}
private void btnDelNode_Click(object sender, EventArgs e)
{
string caption = "リスト削除";
MessageBoxButtons buttons = MessageBoxButtons.YesNo;
DialogResult result;
if (treeView1.SelectedNode != null)
{
string message = treeView1.SelectedNode.Text + "\r";
result = MessageBox.Show(message + "削除してもよろしいですか?", caption, buttons);
if (result == System.Windows.Forms.DialogResult.Yes)
{
treeView1.SelectedNode.Remove();
}
}
}
}
}
実行結果
メッセージボックスがはみ出してるけど、細かいことは気にしないで。。。