Tree Menu を実装

TreeView を操作する Tree Menu を実装します。

前田稔の超初心者のプログラム入門

Tree Menu の説明

  1. TreeView を操作するツリー(T)メニューを実装します。
    コピーやペーストなどではクリップボードを使うので、現在のファイルだけでなく複数の Memo ファイル間で相互に利用することが出来ます。
    メソッド(イベントハンドラ)は Menu を登録 で作成済です。
    親メニュー 子メニュー メソッド 説明
    ツリー(&T)
    ☆兄弟項目を追加(&S) Add_Sibling 空の兄弟項目を追加します
    ・子項目を追加(&I) Add_Child 空の子項目を追加します
    削除/ごみ箱(&D) Del_Node 項目を削除します(ごみ箱へ移動)
    コピー(&C) Copy_Node 項目を記録します
    記憶して切り取り(&T) Cut_Node 項目を記録して切り取ります
    ☆兄弟で貼り付け(&P) Ins_Sibling 記録した項目を兄弟で貼り付けます
    ・子で貼り付け(&Q) Ins_Child 記録した項目を子で貼り付けます
  2. TreeView に直接ノードを追加・削除したのでは TreeView と ArrayList が一致しなくなります。
    Memo2 ではデータを ArrayList で管理しているので、それでは困ります。
    そこで ArrayList を修正した後で、ReTree_Node() 関数から Set_TVFunc() を呼んで TreeView の登録をやり直します。
    ArrayList に挿入する関数 array.Insert(idx, "☆新規項目") は idx の直前に挿入されます。
    直前より直後の方が操作性に優れている (^_^;) ので、このことを考慮して関数を作成します。
  3. 指定されたノードを文字列(m_str)に退避する(コピーする) Get_TVFunc() 関数です。
    ノードに子ノードが含まれるときは、これも再帰でコピーします。
    子ノードに兄弟ノードが含まれるとき兄弟ノードもコピーします。
    m_str に退避したデータは、memo2.mem に書かれているデータ形式に準じます。
        string  m_str;          // Node 選択文字列
    
        // node のデータを再帰で m_str に取得 
        private void Get_TVFunc(TreeNode node, int lev)
        {
            string  str;
            int     i;
            if (node==null) return;
            for(i=0; i<t_node.Count && (TreeNode)t_node[i]!=node; i++);
            if (i>=t_node.Count)    return;
            str = new string('@', (int)t_lev[i]);
            str += t_ttl[i] + ":" + t_ymd[i] + "\n" + t_txt[i];
            m_str += str;
            Get_TVFunc(node.FirstNode, lev);
            if ((int)t_lev[i] > lev) Get_TVFunc(node.NextNode, lev);
        }
    
  4. 指定されたノードを ArrayList から削除する Del_Array() 関数です。
    ノードに子 Node が含まれるときは、これも再帰で削除します。
    子に兄弟ノードが含まれるときは、これも対象になります。
    ArrayList から削除すると Index の位置もずれてくるので注意して下さい。
        // ArrayList の idx から連続する lev 以上(子)のデータを削除する
        private void Del_Array(int idx, int lev)
        {
            t_node.RemoveAt(idx);
            t_lev.RemoveAt(idx);
            t_ttl.RemoveAt(idx);
            t_ymd.RemoveAt(idx);
            t_txt.RemoveAt(idx);
            while(idx<t_lev.Count && (int)t_lev[idx]>lev)
            {   t_node.RemoveAt(idx);
                t_lev.RemoveAt(idx);
                t_ttl.RemoveAt(idx);
                t_ymd.RemoveAt(idx);
                t_txt.RemoveAt(idx);
            }
        }
    
  5. 指定された Node を ArrayList に挿入(追加)する Ins_Array() 関数です。
    Insert(idx, "☆新規項目") では idx の直前に挿入されます。
        // ArrayList の idx の直後に m_str を挿入する
        // lev:最初に追加するレベル  Insert(ix,文字列)はixの直前に追加
        private void Ins_Array(int idx, int lev)
        {   int     ix, lv, pt, wk;
            pt = m_str.IndexOf("@@");
            if (pt<0)
            {   MessageBox.Show("memo data ではありません","Error");
                return;
            }
            for(wk=pt; m_str[pt]=='@'; pt++);
            lv = lev-(pt-wk);
            for(pt=0, ix=idx+1; pt<m_str.Length; ix++)
            {   pt = m_str.IndexOf("@@", pt);
                if (pt<0)   break;
                for(wk=pt; m_str[pt]=='@'; pt++);
                t_lev.Insert(ix, (pt-wk)+lv);
                for(wk=pt; m_str[pt]!=':'; pt++);
                t_ttl.Insert(ix, m_str.Substring(wk,pt-wk));
                pt++;
                for(wk=pt; m_str[pt]!='\n'; pt++);
                t_ymd.Insert(ix, m_str.Substring(wk,pt-wk));
                pt++;
                wk = pt;
                pt = m_str.IndexOf("@@", wk);
                if (pt<0)   pt = m_str.Length;
                t_txt.Insert(ix, m_str.Substring(wk,pt-wk));
            }
        }
    
  6. TreeView をクリアして ArrayList のデータを登録する ReTree_Node() 関数です。
    データの登録が終わると idx のノードを表示して m_UP = true に設定します。
    treeView1.ExpandAll() で全ての TreeView を開くことも出来るのですが、データが少ないときに限ります。
        private void ReTree_Node(int idx)
        {
            t_node.Clear();
            treeView1.Nodes.Clear();
            m_Idx = 0;
            Set_TVFunc(treeView1, 2);       // TreeView を再登録
            m_Idx = 0;
            if (idx < t_node.Count) m_Idx = idx;
            //treeView1.ExpandAll();
            treeView1.SelectedNode = (TreeNode)t_node[m_Idx];
            richTextBox1.Text = (string)t_txt[m_Idx];
            richTextBox1.Modified = false;
            m_UP = true;
        }
    

Tree Menu の実装

  1. ツリー(&T)の Add_Sibling() メソッドで仮の兄弟ノードを追加します。
    兄弟ノードの挿入位置は m_Idx で参照されているノードが終わった位置(子ノードを含む)になります。
    レベルは m_Idx で参照されるノードと同じです。
    t_ymd は C# に合わせた形式で作成日付と更新日付(YYYY/MM/DD:YYYY/MM/DD)を設定します。
    ファイルが更新された事を示す m_UP = true; を設定して下さい。
        // 兄弟項目を追加
        private void Add_Sibling(object sender, EventArgs e)
        {
            string str = DateTime.Now.ToString();
            str = str.Substring(0, 10) + ":" + str.Substring(0, 10);
            int lev = (int)t_lev[m_Idx];
            for (m_Idx++; m_Idx < t_lev.Count && (int)t_lev[m_Idx] > lev; m_Idx++) ;
            t_lev.Insert(m_Idx, lev);
            t_ttl.Insert(m_Idx, "☆新規項目");
            t_ymd.Insert(m_Idx, str);
            t_txt.Insert(m_Idx, "新規項目\n");
            ReTree_Node(m_Idx);
        }
    
  2. ツリー(&T)の Add_Child() メソッドで仮の子ノードを追加します。
    挿入位置は m_Idx+1 で、レベルは t_lev[m_Idx]+1 になります。
        // 子項目を追加
        private void Add_Child(object sender, EventArgs e)
        {
            string str = DateTime.Now.ToString();
            str = str.Substring(0, 10) + ":" + str.Substring(0, 10);
            t_lev.Insert(m_Idx + 1, (int)t_lev[m_Idx] + 1);
            t_ttl.Insert(m_Idx+1, "☆新規項目");
            t_ymd.Insert(m_Idx+1, str);
            t_txt.Insert(m_Idx+1, "新規項目\n");
            ReTree_Node(m_Idx+1);
        }
    
  3. ツリー(&T)の Del_Node() では、ノードを削除すると「ごみ箱」に移動します。
    ごみ箱に移動する手順です。
    1. ノードを Get_TVFunc() で m_str に退避(コピー)します。
    2. Ins_Array() で m_str からゴミ箱に移動します。
    3. Del_Array() でノードを削除します。
    「ごみ箱」から削除するときは Del_Array() を使います。
    ごみ箱から削除すると、完全に情報が無くなります。
        // 項目を削除します(ごみ箱へ移動)
        private void Del_Node(object sender, EventArgs e)
        {
            int dust;       //ごみ箱のインデックス
            for(dust=0; dust<t_ttl.Count && (string)t_ttl[dust]!="ごみ箱"; dust++);
            TreeNode node;
            for(node=(TreeNode)t_node[m_Idx]; node.Parent!=null; node=node.Parent);
            if (dust<t_ttl.Count && t_node[dust]==node)
            {   //MessageBox.Show("ごみ箱から削除", "Message");
                Del_Array(m_Idx, (int)t_lev[m_Idx]);
            }
            else
            {   //MessageBox.Show("ごみ箱に移動", "Message");
                m_str = "";
                Get_TVFunc((TreeNode)t_node[m_Idx], (int)t_lev[m_Idx]);
                Ins_Array(dust, (int)t_lev[dust]+1);
                Del_Array(m_Idx, (int)t_lev[m_Idx]);
            }
            ReTree_Node(m_Idx);
        }
    
  4. ツリー(&T)の Copy_Node() メソッドでノードをコピーします。
    同時に開いた Memo2 同士でコピー/ペーストが出来るように Clipboard を利用します。
    コピーするだけなので ReTree_Node(m_Idx) は不要です。
        // 項目をコピーします
        private void Copy_Node(object sender, EventArgs e)
        {
            m_str = "";
            Get_TVFunc((TreeNode)t_node[m_Idx], (int)t_lev[m_Idx]);
            Clipboard.SetDataObject(m_str, true);
        }
    
  5. ツリー(&T)の Cut_Node() メソッドでノードを削除します。
    TreeView から実際にノードを削除するのは簡単です。
        TreeNode node = (TreeNode)t_node[m_Idx];
        node.Remove();
    
    TreeView からノードを削除したのでは TreeView と ArrayList が一致しなくなります。
    また、削除する場合は事前にノードの情報を退避しておいて「兄弟で貼り付け(&P)」「子で貼り付け(&Q)」などで利用します。
    Get_TVFunc() 関数で削除対象のノード情報を退避してから Del_Array() 関数で削除します。
    同時に開いた Memo2 同士で切り取り/ペーストが出来るように Clipboard を利用します。
        // 項目を記録して切り取ります
        private void Cut_Node(object sender, EventArgs e)
        {
            m_str = "";
            Get_TVFunc((TreeNode)t_node[m_Idx], (int)t_lev[m_Idx]);
            Clipboard.SetDataObject(m_str, true);
            Del_Array(m_Idx, (int)t_lev[m_Idx]);
            ReTree_Node(m_Idx);
        }
    
  6. ツリー(&T)の Ins_Sibling() メソッドで兄弟ノードを挿入します。
    Clipboard にノードが格納されているきは、Clipboard から取得します。
        // m_Idx の兄弟 NODE として m_str を挿入する
        private void Ins_Sibling(object sender, EventArgs e)
        {
            IDataObject data = Clipboard.GetDataObject();
            if (data != null && data.GetDataPresent(DataFormats.Text) == true)
                m_str = Clipboard.GetText();
            int lev = (int)t_lev[m_Idx];
            for(m_Idx++; m_Idx<t_lev.Count && (int)t_lev[m_Idx]>lev; m_Idx++);
            Ins_Array(m_Idx-1, lev);
            ReTree_Node(m_Idx);
        }
    
  7. ツリー(&T)の Ins_Child メソッドで子ノードを挿入します。
    Clipboard にノードが格納されているきは、Clipboard から取得します。
        // m_Idx の子 NODE として m_str を挿入する
        private void Ins_Child(object sender, EventArgs e)
        {
            IDataObject data = Clipboard.GetDataObject();
            if (data != null && data.GetDataPresent(DataFormats.Text) == true)
                m_str = Clipboard.GetText();
            Ins_Array(m_Idx, (int)t_lev[m_Idx]+1);
            m_Idx++;
            ReTree_Node(m_Idx);
        }
    
  8. TreeView のラベルを修正する機能を追加して、修正されたとき ArrayList も更新します。
    ラベル修正を許可して、修正されたとき treeView1_AfterLabelEdit() メソッドを呼び出します。
    TreeView のラベルをゆっくり2回クリックすると編集できます。
        this.treeView1.LabelEdit = true;
        this.treeView1.AfterLabelEdit += new NodeLabelEditEventHandler(treeView1_AfterLabelEdit);
    
    treeView1_AfterLabelEdit() メソッドでは、t_ttl[m_Idx] を更新して m_UP を true に設定して、データが修正されたことを記録します。
        // TreeView のラベルが変更されたとき
        private void treeView1_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
        {   if (e.Label != null)
            {   //MessageBox.Show("ラベルが変更されたとき", "Message");
                if (m_Idx < t_ttl.Count)
                {
                    t_ttl[m_Idx] = e.Label;
                    Title.Text = (string)t_ttl[m_Idx] + "(" + (string)t_ymd[m_Idx] + ")";
                }
                m_UP = true;
            }
        }
    

Context Menu-2

  1. Context Menu-2 に Tree Menu で定義したメニューを設定します。
    CSForm.cs のデザインを表示して二個目の ContextMenuStrip を貼り付けます。
    ContextMenuStrip の [ここへ入力] に、先に示した表のメニューをタイプします。
  2. ContextMenuStrip のメニューを選択してイベント(稲妻のアイコン)をクリックします。
    Click にメソッドをタイプするのですが、既にメソッドは Tree Menu としてコーディングされています。
  3. Context Menu はマウスの右クリックで呼び出されます。
    InitializeComponent() の treeView1 に contextMenuStrip2 を設定します。
    これで TreeView 上で右クリックすると ContextMenu が表示されます。
    表示されたメニューから実行するメソッドを選択して動作を確認して下さい。
        private void InitializeComponent()
        {
            ・・・
            // treeView1
            this.treeView1.ContextMenuStrip = this.contextMenuStrip2;
            ・・・
            this.treeView1.AfterLabelEdit +=
                new System.Windows.Forms.NodeLabelEditEventHandler(this.treeView1_AfterLabelEdit);
            this.treeView1.AfterSelect +=
                new System.Windows.Forms.TreeViewEventHandler(this.treeView1_AfterSelect);
    

[Next Chapter ↓] File Menu
[Previous Chapter ↑] Edit Menu

超初心者のプログラム入門(C# Frame Work)