このチュートリアルではアイコンをシステムトレイに格納し、 どうやってポップアップメニュを作成、使用するかを学ぶ。
ソース | 実行結果 |
|
システムトレイはタスクバーにある矩形領域で、そこにはアイコンがいくつかあるはずだ。 普通はそこに時計アイコンがあるだろう(私はないけど=p)。 そこには自分のプログラムでも格納することができる。 やり方は以下のような手順を踏めばよい。
- 以下に説明するNOTIFYICONDATA構造体をセットする。
- cbSize
この構造体のサイズ。- hwnd
ウィンドウハンドル。マウスイベントがトレイアイコン上で起こった時に、このウィンドウに通知が来る。- uID
アイコンを識別する定数。この値は自由に決められる。複数のアイコンを使用したければ、どのトレイアイコンがマウスの通知を受けたかをチェックすればよい。- uFlags
この構造体のどのメンバを有効にするかのフラグを指定する。
- NIF_ICON:hIconを有効にする
- NIF_MESSAGE:uCallbackMessageを有効にする
- NIF_TIP:szTipを有効にする
- uCallbackMessage
トレイアイコンにマウスイベントが発生したときに、hwndで指定されるウィンドウへWindowsに送信してもらうメッセージ。このメッセージは自分で指定する。- hIcon
システムトレイに格納したいアイコンのハンドル- szTip
マウスカーソルがトレイアイコン上にあるときにツールチップ文字列として使用される最大64バイトの文字列(システムDLLが5.0か、それ以降なら最大128バイトまで)- shell32.incで定義されているShell_NotifyIcon関数をCALLする。
Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD
- dwMessageはシェルへ送信するメッセージのタイプを指定する
- NIM_ADD:ステータスエリアにアイコンを追加する
- NIM_DELETE:ステータスエリアからアイコンを削除する
- NIM_MODIFY:ステータスエリアのアイコンを変更する
- pnid
適当な値をセットしたNOTIFYICONDATA構造体へのポインタこれが全てだ。しかしたいていは、アイコンを格納するだけで満足できない。 トレイアイコンに発生するマウスイベントに対しての処理も必要である。 なので、NOTIFYICONDATA構造体のメンバであるuCallbackMessageに記述されるメッセージに対する処理を行うことになる。 このメッセージでは wParam と lParam に以下のような値がセットされる。
- wParamはアイコンのIDとなっている。これはNOTIFYICONDATA構造体のuIDメンバにセットした値と同じである。
- lParamの下位ワードはマウスメッセージとなっている。例えば、ユーザがアイコン上で右クリックをしたら、lParamはWM_RBUTTONDOWNとなる。
しかしながらこれだけではなく、ほとんどのトレイアイコンはユーザが右クリックしたらメニューがポップアップされるようになっている。 ポップアップメニューを作成することによりこの機能を実装し、TrackPopupMenu関数をCALLしてそのメニューを表示させる。手順は以下だ。
- CreatePopupMenu関数をCALLしてポップアップメニューを作成する。この関数は空のメニューを作成し、成功すればメニューハンドルを返す。
- AppendMenu関数、もしくはInsertMenu関数かInsertMenuItem関数を使用してメニューアイテムを追加する。
- マウスカーソルがあるときにポップアップメニューを表示したければ、GetCursorPos関数をCALLしてマウスカーソルのスクリーン座標値を取得し、GetCursorPos関数をCALLしてメニューを表示させる。
ユーザがそのポップアップメニューからメニューアイテムを選択すれば、Windowsは通常のメニューセクションと同じようにWM_COMMANDメッセージをウィンドウプロシージャに送信する。
※ トレイアイコンにポップアップメニューを使用するときは、2つのやっかいな問題に気をつけなければならない
- ポップアップメニューが表示されたとき、メニュー以外のどこかの場所をクリックしてもすぐにはそのポップアップメニューは消えない。これは、ポップアップメニューから通知を受け取るウィンドウはフォアグラウンドでなければならないからだ。なのでこれを回避するために、SetForegroundWindow関数をCALLする。
- SetForegroundWindow関数をCALLした後、ポップアップメニューが表示される一番最初の動作は大丈夫だが、次回、ポップアップメニューが表示されたときは、すぐに閉じてしまう。これは「意図的」な動作であるとMSDNには記述されており、そのうち必要となるトレイアイコンのオーナーであるプログラムへのタスクスイッチが必要である。
このタスクスイッチを強制的に発生させたければ、ウィンドウへなんらかのメッセージを送信すればよい。その際、PostMessage関数を使用しなければならない。SendMessage関数ではないことに気をつけること。
|
|
|
プログラムはウィンドウを表示し、最小化ボタンを押したとき、ウィンドウを隠しシステムトレイにアイコンを格納する。 そのアイコンをダブルクリックしたとき、プログラムはウィンドウを復元させシステムトレイからアイコンを削除する。 右クリックしたとき、ポップアップメニューを表示し、プログラムを終了するかウィンドウを復元するかを選択できる。
.if uMsg==WM_CREATE invoke CreatePopupMenu mov hPopupMenu,eax invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitStringメインウィンドウが作成されたとき、ポップアップメニューを作成し2つのメニューアイテムを追加する。 AppendMenu関数はこのようになっている。
AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
- hMenu
アイテムを追加したいメニューのハンドル- uFlags
Windowsに追加するアイテムがビットマップか文字列かもしくは、有効、無効、グレー表示かを指定するためのフラグで、詳細はWin32APIリファレンスに書かれている。
今回の例では、MF_STRINGを使用しており、メニューアイテムが文字列であることを指定している。- uIDNewItem
メニューアイテムのID。メニューアイテムを表すために使用されるユーザが定義できる値。- lpNewItem
uFlagsに何をセットするかによるが、メニューアイテムの内容を指定する。今回の例ではMF_STRINGを指定しているので、lpNewItemはポップアップメニューに表示される文字列へのポインタでなければならない。ポップアップメニューが作成し終えたら、メインウィンドウはユーザが最小化ボタンを押すまで待ち続ける。
ウィンドウが最小化されれば、wParamがSIZE_MINIMIZEDにセットされ、WM_SIZEメッセージを受け取る。
.elseif uMsg==WM_SIZE .if wParam==SIZE_MINIMIZED mov note.cbSize,sizeof NOTIFYICONDATA push hWnd pop note.hwnd mov note.uID,IDI_TRAY mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP mov note.uCallbackMessage,WM_SHELLNOTIFY invoke LoadIcon,NULL,IDI_WINLOGO mov note.hIcon,eax invoke lstrcpy,addr note.szTip,addr AppName invoke ShowWindow,hWnd,SW_HIDE invoke Shell_NotifyIcon,NIM_ADD,addr note .endifこのメッセージを受け取ったときにNOTIFYICONDATA構造体をセットする。 IDI_TRAYはソースコードの最初に定義されている、単なる定数である。この値は何でも好きな値をセットできる。 今回の例ではトレイアイコンが1つしかないので重要ではないのだが、 システムトレイに複数のアイコンを格納する場合はそれぞれのアイコンにユニークな値を割り振らなければならない。
そして、アイコン、メッセージ、ツールチップテキストを使用するので、uFlagsに全てのフラグをセットする。 WM_SHELLNOTIFYは WM_USER+5 と定義された単なるメッセージである。 値自体はユニークでありさえすれば重要ではない。 ここではトレイアイコンとしてwinlogoアイコンを使用するが、どんなアイコンでも使用できる。 LoadIcon関数でリソースからアイコンをロードし、戻り値をhIconにセットする。 最後に、マウスがアイコンに重なったときに表示させるszTipに文字列をセットする。
「最小化することによりトレイアイコンが表示される」というイリュージョンを完成させるためにメインウィンドウを画する。 次に、システムトレイにアイコンを追加するためにNIM_ADDメッセージ付きでShell_NotifyIcon関数をCALLする。
これにより、メインウィンドウは隠れて、アイコンがシステムトレイに格納される。 もしマウスカーソルをアイコンに重ねれば、szTipにセットした文字列を表示したツールチップが現れるだろう。 次に、ダブルクリックすれば、メインウィンドウは再び表示され、トレイアイコンは消え去る。
.elseif uMsg==WM_SHELLNOTIFY .if wParam==IDI_TRAY .if lParam==WM_RBUTTONDOWN invoke GetCursorPos,addr pt invoke SetForegroundWindow,hWnd invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL invoke PostMessage,hWnd,WM_NULL,0,0 .elseif lParam==WM_LBUTTONDBLCLK invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0 .endif .endifマウスイベントがトレイアイコン上で発生すれば、 作成したウィンドウにuCallbackMessageにセットしたWM_SHELLNOTIFYメッセージが送信される。 このメッセージを受け取ったら、wParamにトレイアイコンのID、lParamは実際のマウスメッセージになるということを思い出そう。 上のコードでは、まずこのメッセージがトレイアイコンから送信されたものかどうかをチェックする。 もしそうなら、マウスメッセージをチェックし、今回の例ではマウスの右クリックとダブルクリックしか必要ないので、 WM_RBUTTONDOWNとWM_LBUTTONDBLCLKメッセージの場合だけに対して処理を行う。
マウスメッセージがWM_RBUTTONDOWNだったら、GetCursorPos関数をCALLしてマウスカーソルのスクリーン座標値を取得する。 関数からの戻り値は、マウスカーソルのスクリーン座標値がセットされたPOINT構造体となっている。 スクリーン座標値とは、ウィンドウは関係なく、完全に画面の座標値である。 例えば、解像度が640*480だったら、一番右下の座標値は (639,479) となる。 このスクリーン座標値を各々のウィンドウの座標値に変換するには、ScreenToClient関数を使用する。
しかしながら、今回の目的は、TrackPopupMenu関数をCALLしてマウスカーソルの場所にポップアップメニューを表示することなので、 別にウィンドウ座標値に変換する必要はなく、直接GetCursorPos関数でセットされたスクリーン座標値を使用すればよいのである。
TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
- hMenu
表示させるポップアップメニューのハンドル- uFlags
関数のオプションを指定する。このフラグによりマウスが押されたときの座標値により、ポップアップメニューをどのような位置関係で表示するかを指定する。
この例では、TPM_RIGHTALIGNを使用して、ポップアップメニューの出現位置をクリックしたx座標にポップアップメニューの左端がくるようにしている。(これ、ちょっと違うような気がするんだが・・・)- x and y
メニューの場所をスクリーン座標値により指定する- nReserved
NULLにしなければならない- hWnd
メニューからメッセージを受け取るウィンドウのハンドル- prcRect
メニューを削除せずにクリックすることを可能にするスクリーン上の矩形領域。通常ここはNULLとして、ユーザがポップアップメニュー以外の場所をクリックしたときにメニューは削除される。ユーザがトレイアイコン上でダブルクリックした場合、 ポップアップメニューの「Restore」メニューアイテムをユーザがセレクトしたのと同じ動作を行うために、 IDM_RESTOREで指定されたウィンドウにWM_COMMANDメッセージを送信する。 その後、メインウィンドウが復元され、システムトレイからアイコンが削除される。 ダブルクリックメッセージを受け取るために、 メインウィンドウはCS_DBLCLKSスタイルになっていなければならない。
invoke Shell_NotifyIcon,NIM_DELETE,addr note mov eax,wParam .if ax==IDM_RESTORE invoke ShowWindow,hWnd,SW_RESTORE .else invoke DestroyWindow,hWnd .endifユーザがRestoreメニューアイテムを選択したら、 再度、Shell_NotifyIcon関数をCALLしてトレイアイコンを削除するのだが、 今度はNIM_DELETEをセットする。 次に、メインウィンドウをもともとの状態で復元しする。 ユーザがExitメニューアイテムを選択したら、トレイアイコンはもちろん削除するが、 DestroyWindow関数をCALLしてメインウィンドウも削除する。