【C#】TreeViewコントロールを使ってみる

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

機能としては、TreeViewノードの追加、削除、編集、データの保存と読み込みです。

動作確認環境

  • Windows10
  • Visual studio Community 2017
  • ターゲットフレームワーク .NET Framework 4.6.1

Windows フォームの追加とプロパティ

親ノード(カテゴリー名)編集用として下記のようなWindowsフォームを作成します。ファイル名をinputCategory.csとしてWindowsフォームを追加します。テキストボックスとボタンを以下にように配置します。カテゴリー名編集ボタンをクリックした時に、親フォームから呼び出されるように実装します。

コントロールプロパティ
Form1FormBorderStyleFixedDialog
Form1MaximizeBoxFalse
Form1MinimizeBoxFalse
Form1StartPositionCenterParent

PanActiveDBAc.csの追加

Pan Active Market Databaseにアクセスするためのクラスを追加します。「ローソク足チャートを描いてみる」で作成したPanActiveDBAc.csを使用します。

コントロールの配置とプロパティ

メインフォームのコントロールの配置とプロパティを以下のように設定します。

コントロールプロパティ
TreeViewAnchorTop, Bottom, Left

参照設定

Pan Active Marketにチェックを入れます。

ここまででソリューションエクスプローラは以下のようになります。

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();
                }
            }
        }
    }
}

実行結果

メッセージボックスがはみ出してるけど、細かいことは気にしないで。。。

タイトルとURLをコピーしました