NotifyIconから呼び出されるContextMenuのMenuItemでPopupイベントが発生しない問題を回避する。


Description

NotifyIconから呼び出されたContextMenuの中のMenuItemPopupイベントを発生しないという不具合があります。

BUG: The Popup Event Does Not Raise for the Menu Item of the Context Menu Control That Is Associated with the Notify Icon

MicorosoftのKBにはwork aroundとして「Formに関連付けて使う」、というのと「MenuItemのイベントの代わりにContextMenuPopupイベントを使う」という2つの手段が記載されています。 ただし、1つ目の方法であるとFormを非表示にした際にコンテキストメニューを表示できなくなります。 2つ目の方法はサブメニューが表示される直前に処理したい場合には使いづらいものです。

Workaround

ダミーのFormを作成し、ContextMenuプロパティに対象のメニューを設定し、Win32の TrackPopupMenu APIを利用し、表示させることでPopupイベントを発生する状態にできます。

これを実現するにはNotifyIconを継承してコンテキストメニューを表示させる部分をオーバーライドするか、MouseDownイベントでメニューを表示する方法があります。次のコードはイベントで処理する場合です。

Sample Code

このコードを利用した場合、ContextMenuPopupイベントが発生しなくなることに注意してください。必要であればMouseDownイベント等で処理を行ってください。

private ContextMenu _contextMenu = new ContextMenu();
private NotifyIcon _notifyIcon = new NotifyIcon();
...
_notifyIcon.MouseDown += new MouseEventHandler(NotifyIcon_MouseDown);
...
// NotifyIcon の MouseDown イベントを処理する。
private void NotifyIcon_MouseDown(object sender, MouseEventArgs e)
{
	// ContextMenu を持つダミーフォームを作る。
	Form dummyForm = new Form();
	//dummyForm.Visible = false;
	dummyForm.ContextMenu = _contextMenu;

	// できればメニューが消えなくなる問題の対策として Win32 API の SetForegroundWindow をやっておいたほうが良い。
	// SetForegroundWindow(dummyForm.Handle);

	// メニュー表示。
	TrackPopupMenu(_contextMenu.Handle, 0, Cursor.Position.X, Cursor.Position.Y, 0, dummyForm.Handle, IntPtr.Zero);

	dummyForm.Close();
}

[DllImport("user32.Dll")]
private static extern Int32 TrackPopupMenu(
	IntPtr hMenu,
	UInt32 uFlags,
	Int32  x,
	Int32  y,
	Int32  nReserved,
	IntPtr hWnd,
	IntPtr prcRect
);