タブ(TabPage)毎に異なるコンテキストメニューを開く

ここには、タブをマウスの右ボタンで押し、そのボタンを離したときにタブに応じたコンテキストメニューを開くC#ソースコードを載せてある。このコンテキストメニューの開き方はVisual Studioでのそれに近い。

TabPageに直接コンテキストメニューを設定すると、上のタブ部分での右クリックに反応してくれない。

だからTabControlにコンテキストメニューを設定しないといけない。しかし、タブ毎に異なるコンテキストメニューを設定するメンバは無いし、クリックされたタブを簡単に取得できるメンバも無い。
ウィンドウメッセージなら、クリックされたタブが分かるらしいけど、ドトネで作ってるんだから、ドトネの中でやりたい。

そしたら以下のようなプログラムになった。短いし、要所要所でコメント入れてるのですぐ読めると思う。おおざっぱな流れは

  1. tabControl.MouseDownでクリックしたタブを取得
  2. tabControl.MouseUpでマウス右ボタンだったなら明示的にコンテキストメニューを開くよう指示
  3. contextMenuStrip.Openingで、タブがクリックされていたなら、タブにあった項目を用意してコンテキストメニューを開く

以下ソースコード

using System;
using System.Windows.Forms;
using System.ComponentModel;

public class Form1 : Form
{
    TabControl tabControl = new TabControl();
    TabPage    tabPage1   = new TabPage();
    TabPage    tabPage2   = new TabPage();

    ContextMenuStrip contextMenuStrip    = new ContextMenuStrip();
    ToolStripMenuItem toolStripMenuItem1 = new ToolStripMenuItem();
    ToolStripMenuItem toolStripMenuItem2 = new ToolStripMenuItem();

    // クリックしたタブページ。タブがクリックされた瞬間だけタブページへの参照を入れる。それ以外の時はnull
    TabPage clickedTabPage = null;

    public Form1()
    {
	// コンテキストメニューの項目
	toolStripMenuItem1.Text = "toolStripMenuItem1";
	toolStripMenuItem2.Text = "toolStripMenuItem2";
	toolStripMenuItem1.Click += delegate (object sender, EventArgs e)
	{
	    MessageBox.Show(sender.ToString());
	};
	toolStripMenuItem2.Click += delegate (object sender, EventArgs e)
	{
	    MessageBox.Show(sender.ToString());
	};

	// マウスクリックしたタブをthis.clickedTabPageに取得
	tabControl.MouseDown += delegate (object sender, MouseEventArgs e)
	{
	    this.clickedTabPage = null;
	    for (int i = 0; i < tabControl.TabCount; i++)
	    {
		if (tabControl.GetTabRect(i).Contains(e.X, e.Y))
		{
		    this.clickedTabPage = (TabPage)tabControl.GetControl(i);
		    tabControl.SelectedTab = this.clickedTabPage;
		}
	    }
	};

	// マウスの右ボタンが離された時にコンテキストメニューを開くよう指示
	tabControl.MouseUp += delegate (object sender, MouseEventArgs e)
	{
	    if (this.clickedTabPage != null)
	    {
		if (e.Button == MouseButtons.Right)
		{
		    contextMenuStrip.Show((TabControl)sender, e.Location);
		}
	    }
	};

	// コンテキストメニューを開く直前にコンテキストメニューの項目を組み立てる
	contextMenuStrip.Opening += delegate (object sender, CancelEventArgs e)
	{
	    ContextMenuStrip menu = (ContextMenuStrip)sender;

	    if (this.clickedTabPage != null)
	    { // タブがクリックされていたならコンテキストメニューを開く
		toolStripMenuItem1.Text = this.clickedTabPage.Text + " : " + "toolStripMenuItem1";
		toolStripMenuItem2.Text = this.clickedTabPage.Text + " : " + "toolStripMenuItem2";

		menu.Items.Clear();
		menu.Items.Add(toolStripMenuItem1);
		menu.Items.Add(toolStripMenuItem2);

		this.clickedTabPage = null;
	    }
	    else
	    { // タブがクリックされてないならコンテキストメニューを開かない
		e.Cancel = true;
	    }
	};

	// 最初に項目が追加しておかないと、初回だけコンテキストメニューが出ない。何で?
	contextMenuStrip.Items.Add(new ToolStripMenuItem());

	// タブの表示文字列
	tabPage1.Text = "tabPage1";
	tabPage2.Text = "tabPage2";

	// tabControlにタブとコンテキストメニューを設定
	tabControl.TabPages.Add(tabPage1);
	tabControl.TabPages.Add(tabPage2);
	tabControl.ContextMenuStrip = contextMenuStrip;

	this.Controls.Add(tabControl);
    }

    public static void Main()
    {
	Application.Run(new Form1());
    }
}

フィールドclickedTabPageは、TabControlのサブクラスに含めた方が適切かな