TreeView上で動的にコンテキストメニューを表示したい(Windows Forms)

Sponsored Link

ソース内にコンテキストメニューの設定を記述する

表示する内容及び表示位置が完全に固定であるならば、デザイナ上でコントロールをセットする形でもよいかと思います。

ただ・・・

マウスクリック位置により

「メニューの表示内容を変更したい」

「表示位置を変更したい」

という場合にはデザイナで対応するよりは、C#のソース側でセットするほうが楽です。

Sponsored Link

ContextMenuStripを使用する

まず、フォームのTreeViewロードのタイミングでContextMenuStripを作成します。

// ContextMenuStripの宣言
ContextMenuStrip sampleMenu = new ContextMenuStrip();

// コンテキストメニュー一番目
sampleMenu.Items.Add("一番目のメニュー", makeImage(1), (s, e) => {
    // このコンテキストメニュー実行時の処理を記述
    MessageBox.Show("一番目");
});

// コンテキストメニュー二番目
sampleMenu.Items.Add("二番目のメニュー", makeImage(2), (s, e) => {
    // このコンテキストメニュー実行時の処理を記述
    MessageBox.Show("二番目");
});

宣言したContextMenuStripオブジェクトに対して、Items.Addでメニューを追加していきます。

Items.Addの書き方はいくつかありますが、今回は3つの引数をセットする形で対応します。

一番目の引数は「メニューの表示文字列」

二番目の引数は「そのメニュー左側に表示する画像」

三番目の引数は「そのメニューをクリックした際に実行する処理」

となります。

上のソース例では「一番目のメニュー」という文字列のメニューが表示され、そのメニューをクリックした際に実行されるのは「一番目」というメッセージボックスが表示される、という単純な処理です。

実際には別画面を表示させたり、より複雑な処理を記述させることになるかと思います。

二番目の引数の「画像」ですが、実際にはResourcesに画像を事前に登録し、それを参照するのが一番楽かと思います。今回は、後述の画像オブジェクトを生成してセットする形とします。

Sponsored Link

Imageオブジェクト生成

コンテキストメニューの左側にセットする画像、Imageオブジェクトをセットしますが、本来いちいちソース内で生成する必要はありません。

が、今回はImageオブジェクトをソースで生成し、セットします。

private Image makeImage(int pattern)
{
    Bitmap canvas = new Bitmap(16, 16);

    // Graphicsオブジェクトを生成
    using (Graphics g = Graphics.FromImage(canvas))
    {
        // 文字から画像にします。Arial 11ポイントのフォント。
        Font fnt = new Font("Arial", 11);
        switch (pattern)
        {
            case 1:
                // 引数に「1」が渡された場合 ⇒ ★
                g.DrawString("★", fnt, Brushes.Blue, 0, 0);
                break;
            case 2:
                // 引数に「2」が渡された場合 ⇒ ▼
                g.DrawString("▼", fnt, Brushes.Red, 0, 0);
                break;
        }
        
        // リソース解放
        fnt.Dispose();
        g.Dispose();
    }

    return canvas;
}

この関数は、文字列を画像にして返しています。引数の値により表示画像文字列を変える形です。

もちろんこれ以外の形でImageオブジェクトを生成して返してもよいかと思います。

Sponsored Link

クリックした場所により、表示位置・メニュー内容を変えたい

まず、表示位置。

これは単純に対象のContextMenuStripのShowメソッドの引数に「Point」を渡すだけです。

マウス位置の取得はTreeViewオブジェクトの「MouseDown」イベントで取得可能です。

treeView1.MouseDown += (s, e) => {
    // マウス右クリック時のみ
    if (e.Button == MouseButtons.Right)
    {
        // マウスカーソル位置
        Point p = Cursor.Position;

        // ContextMenuStripのShowメソッドにPointを渡して表示
        sampleMenu.Show(p);

コンテキストメニューを表示させるのは「右クリック」時が多いかと思います。そのため、条件として「MouseButtons.Right(右クリック)」の場合のみとし、それ以外は処理は行わないものとします。

Cursor.Positionでその時点のPointを取得し、Showメソッドに渡してコンテキストメニュー表示です。

そして、メニュー内容。

クリックした位置によってメニュー内容を変えたい、などあるかと思います。なので、TreeViewのどの部分をクリックしているかを判定します。

treeView1.MouseDown += (s, e) => {
    // マウス右クリック時のみ
    if (e.Button == MouseButtons.Right)
    {
        // 右クリックをした箇所により処理を変える
        switch (treeView1.HitTest(e.Location).Location)
        {
            case TreeViewHitTestLocations.Label:
                // ラベル=テキスト部分の位置
                TreeNode node = treeView1.GetNodeAt(e.X, e.Y);
                switch (node.Level)
                {
                    case 0:
                        // 親(トップ)ノード
                        break;
                    case 1:
                        // 子ノード
                        break;
                    default:
                        // もっと階層がある場合はそれに合わせて記述
                        break;
                }
                break;

            case TreeViewHitTestLocations.PlusMinus:
                // ノード折り畳みのプラスマイナスの位置
                break;

            case TreeViewHitTestLocations.RightOfLabel:
                // TreeViewのテキスト領域の右側の位置
                break;

            case TreeViewHitTestLocations.None:
                // ノードやノードの一部では無い位置
                break;

            case TreeViewHitTestLocations.BelowClientArea:
                // TreeViewの下側の位置
                break;
        }

HitTest.Locationを使用して、どの部分がクリックされているかを確認します。

TreeViewHitTestLocationsのオプションはいくつかありますが、そのうち主に使われるであろうオプション項目のみ記述します。

  • Label → TreeViewのラベル(テキスト)自体
  • PlusMinus → TreeViewの左側「+」マーク部分
  • RightOfLabel → TreeViewの右側余白部分
  • BelowClientArea → TreeViewの下部余白部分

Labelをクリックした際には、「親ノード」「子ノード」など、どのレベルのノードがクリックされたかを確認したい場合があるかと思います。

その場合には「GetNodeAt」でTreeNodeオブジェクトを取得し、「Level」を参照することでどのレベルノードがクリックされたか判定することができます。

上から「0」「1」「2」・・・となります。

Sponsored Link

まとめ

treeView1.MouseDown += (s, e) => {
    // マウス右クリック時のみ
    if (e.Button == MouseButtons.Right)
    {
        Point p = Cursor.Position;

        ContextMenuStrip sampleMenu = new ContextMenuStrip();
        sampleMenu.Items.Add("一番目のメニュー", makeImage(1), (ss, ee) => {
            // このコンテキストメニュー実行時の処理を記述
            MessageBox.Show("一番目");
        });
        sampleMenu.Items.Add("二番目のメニュー", makeImage(2), (ss, ee) => {
            // このコンテキストメニュー実行時の処理を記述
            MessageBox.Show("二番目");
        });

        // 右クリックをした箇所により処理を変える
        switch (treeView1.HitTest(e.Location).Location)
        {
            case TreeViewHitTestLocations.Label:
                // ラベル=テキスト部分の位置
                TreeNode node = treeView1.GetNodeAt(e.X, e.Y);
                switch (node.Level)
                {
                    case 0:
                        // 親(トップ)ノード
                        sampleMenu.Items.Add("親用メニュー", null, (ss, ee) => {
                            // このコンテキストメニュー実行時の処理を記述
                            MessageBox.Show("親メニュー");
                        });
                        break;
                    case 1:
                        // 子ノード
                        break;
                    default:
                        // もっと階層がある場合はそれに合わせて記述
                        break;
                }
                break;

            case TreeViewHitTestLocations.PlusMinus:
                // ノード折り畳みのプラスマイナスの位置
                break;

            case TreeViewHitTestLocations.RightOfLabel:
                // TreeViewのテキスト領域の右側の位置
                break;

            case TreeViewHitTestLocations.None:
                // ノードやノードの一部では無い位置
                break;

            case TreeViewHitTestLocations.BelowClientArea:
                // TreeViewの下側の位置
                break;
        }

        // コンテキストメニュー表示
        sampleMenu.Show(p);
    }
};
private Image makeImage(int pattern)
{
    Bitmap canvas = new Bitmap(16, 16);

    using (Graphics g = Graphics.FromImage(canvas))
    {
        Font fnt = new Font("Arial", 11);
        switch (pattern)
        {
            case 1:
                g.DrawString("★", fnt, Brushes.Blue, 0, 0);
                break;
            case 2:
                g.DrawString("▼", fnt, Brushes.Red, 0, 0);
                break;
        }

        fnt.Dispose();
        g.Dispose();
    }

    return canvas;
}

この例では「MouseDown」のタイミングでContextMenuStripを生成していますが、コンテキストメニューのパターンがそんなに多くないのであれば、事前に生成しておき、クリック箇所によってContextMenuStripオブジェクトを単純にShowする形のほうがすっきりするかと思います。

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