タスクトレイにアイコンを表示する方法

 これは、インサイドウィンドウズ1996年9月号の「WindowsProgramingTips」第2回――タスクバーを有意義に使おう!――の、タスクトレイにアイコンを表示し、そのアイコンに対するメッセージを受け取るアプリケーションを、MFCを使って実装する方法です。


 最初に、タスクトレイにアイコンを表示してみましょう。
 タスクトレイの表示は、アプリケーションの初期化時に行うのがいいと思います。どのクラスも初期化を行いますが、メッセージを受け取ることを考えると、CFrameWndから継承されたクラス(たいがいはCMainFrame)の中がいいようです。ここでは、CMainFrameOnCreateメンバ関数(WM_CREATEのハンドラです)中に書き込みます。

 まず、CMainFrameNOTIFYICONDATA型のメンバ変数m_stNtfyIconを作成してください。
 次に、CMainFrame::OnCreate()の中に次のようなコードを書き込みます。


int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

	//////////////////////////////////
	// アイコンをタスクトレイに表示します。
	m_stNtfyIcon.cbSize = sizeof( NOTIFYICONDATA );	//構造体のサイズです。
	m_stNtfyIcon.uID = 0;	//アイコンの識別ナンバーです。
	m_stNtfyIcon.hWnd = m_hWnd;	//メッセージを送らせるウィンドウのハンドルです。
	m_stNtfyIcon.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;	//各種設定です。
	m_stNtfyIcon.hIcon = AfxGetApp()->LoadIcon( IDR_MAINFRAME );	//アプリケーションのアイコンです。
	m_stNtfyIcon.uCallbackMessage = WM_USER_NTFYICON;	//送ってもらうメッセージです。
	lstrcpy( m_stNtfyIcon.szTip, _T( "テスト" ) );	//チップの文字列です。
	::Shell_NotifyIcon( NIM_ADD, &m_stNtfyIcon );	//タスクトレイに表示します。

	return 0;
}
	

 m_stNtfyIconに関連づけるウィンドウハンドラは、このCMainFrameのメンバ変数m_hWndを使います。
 アイコンの取得はAfxGetApp()を使って、このアプリケーションのCWinAppオブジェクトtheAppへのポインタを取得してから、アプリケーションのリソースからアイコンを取得します。
 lstrcpyはC言語の文字列をコピーする関数です。_TはUnicodeに対応するためのマクロです。
 タスクトレイにアイコンを表示する関数Shell_NotifyIcon()はAPIです。


 さて次に、アプリケーション終了時にこのアイコンを削除するコードを加えます。
 これも同じくCMainFrame::OnDestroy()メンバ関数(WM_DESTROYのハンドラです)中にコードを加えます。


void CMainFrame::OnDestroy() 
{
	CFrameWnd::OnDestroy();
	
	::Shell_NotifyIcon( NIM_DELETE, &m_stNtfyIcon );	//タスクトレイのアイコンを削除します。
	
}
	

 これは、記事とほとんど同じコードです。


 さて、ここからが本題です。アイコンから送られてくるメッセージを処理するためのコードを加えていきます。
 アイコンをタスクトレイに登録したときに、CMainFrameのウィンドウハンドルを渡しました。ということは、タスクトレイから送られてくるメッセージはCMainFrameで処理しなければならないということです。そこで、このメッセージに対するメッセージハンドラを自作してみましょう。

 まず、MainFrm.cppIMPLEMENT_DYNCREATEの前に次のコードを書き込みます。


#define WM_USER_NTFYICON	(WM_USER+100)	//ウィンドウメッセージの登録。
	

 メッセージはすべてUINT型なので、このように定義しておきます。100という数字に特に意味はありません。ちなみに、カッコでくくるのにはわけがあります。例えば、


	iTemp = WM_USER_NTFYICON * 100;	//こんなことフツーないが。
	

 というコードがあった場合、カッコがあるときとないときでは結果が変わってしまいます。これは、#defineマクロというものが単に文字列を置き換えるだけだからです。メッセージにかけ算をする何てことはフツーありませんが、念のためということで。

 次に、メッセージマップにメッセージをハンドラと関連づけるためのコードを加えます。


IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	//{{AFX_MSG_MAP(CMainFrame)
	ON_MESSAGE( WM_USER_NTFYICON, OnNotifyIconIvents )		//メッセージをハンドラと関連づけます。
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
	

 ON_MESSAGEの行を加えてください。
 ON_MESSAGEはユーザー定義メッセージを関数へと関連づけるためのマクロです。

 さらに、MainFrm.hの中に、次のコードを書き込みます。


// 生成されたメッセージ マップ関数
protected:
	//{{AFX_MSG(CMainFrame)
	afx_msg LRESULT OnNotifyIconIvents( WPARAM wParam, LPARAM lParam );
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
	

 afx_msgの行を加えてください。

 以上で、やっとアイコンからのメッセージを処理する関数を宣言することができました。つまり、アイコンでクリックしたりしたとき、そのメッセージはOnNotifyIconIvents関数ですべて処理されるようにしたということです。
 これらのようなメッセージに対するハンドラの定義、宣言は、通常クラスウィザードが行っていますが、ユーザー定義メッセージを扱う場合には、このように自分ですべて書かなければいけません。

 さて、最後の締めです。OnNotifyIconIvents関数を定義しましょう。
 MainFrm.cppの最後に、次の関数を加えてください。


// アイコンをクリックしたときの動作。
LRESULT CMainFrame::OnNotifyIconIvents( WPARAM wParam, LPARAM lParam )
{
	switch( lParam )
	{
	case WM_LBUTTONDOWN:
		//ここに、左クリックしたときの処理を書き込みます。
		break;
	case WM_RBUTTONDOWN:
		//ここに、右クリックしたときの処理を書き込みます。
		break;
	}

	return 0;
}
	

 この関数は「関数の追加」を使わずに書き込んでください。前もってメッセージと関連づけてあるので、もし「関数の追加」を使って加えると、二重にメッセージと関連づけることになってしまいます。
 WM_LBUTTONDOWNはウィンドウズ95の「マウスの左ボタンをクリック」という意味のメッセージです。このパラメーターを調べることで、タスクトレイ上のアイコンがどのようなことをされたのか分かるでしょう。

 以上で、メッセージのフレームウィンドウへの対応は完了しました。

Update Room
 ここからは、クラスウィザードに干渉せず、さらに簡単に実装する方法を紹介します。
 ウィンドウメッセージの登録までは同じです。つまり、


#define WM_USER_NTFYICON	(WM_USER+100)	//ウィンドウメッセージの登録。
	

 までは同じようにコーディングしてください。ちなみに、すべてのウィンドウメッセージはこのように整数です。Winuser.hに基本的なメッセージが書かれています。していることは同じです。

 さて、まず「仮想関数の追加」を選択し、その中からWndProcを選びます。この関数は、すべてのメッセージを処理できる関数です。
 Shell_NotifyIcon()でアイコンをタスクトレイに追加したときの構造体を思い出してください。そのアイコン上でのすべてのイベント(「マウスが動いた」ということでさえ)はm_stNtfyIcon.hWndの中に格納したウィンドウハンドルへと送られます。送られてくるのはすべてm_stNtfyIcon.uCallbackMessageに格納したメッセージです。つまり、


LRESULT CMainFrame::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
	switch( message )
	{
	case WM_USER_NTFYICON:	//アイコンからのメッセージ。
		if( lParam == WM_LBUTTONDOWN )
			;	//左クリックされました。
		else if( lParam == WM_RBUTTONDOWN )
			;	//右クリックされました。
		return 0;
	}	

	return CFrameWnd::WindowProc(message, wParam, lParam);
}
	

 というような形でWM_USER_NTFYICONを受け取り処理すればいいということです。このメッセージを受け取ったとき、lParamには、そのアイコンが実際に受け取ったイベントのメッセージが格納されています。これを処理すればいいわけです。
 ちなみにwParamには常に0が入っています。また、どうやらそのイベントのlParamwParamなどの情報はまったく得られないようです。「コントロールキーが押されてるかどうか」などをチェックしたい場合にはWin32APIの::GetKeyState( VK_CONTROL )を使うといいでしょう。

 これで、一応コーディングは終了です。この方法だと非常に簡単に済みます。
 最後に、ここからポップアップメニューを開く場合。ポップアップメニューを開く場合にはTrackPopupMenu()という関数を使います(Win32API、もしくはCMenuのメンバ関数として)が、ただこのまま使うといくつか不具合が生じます。表示されたまま消えなくなったり、2度目に表示したときに何もせずすぐに消えてしまったりします。
 この問題を解決するには次のようにします。


	SetForegroundWindow();	//ウィンドウをフォアグラウンドに持ってきます。
	SetFocus();	//これをしないと、メニューが消えなくなります。

	cIconMenu.TrackPopupMenu( TPM_CENTERALIGN | TPM_LEFTBUTTON, 0, 0, m_hWnd );	//ポップアップメニューを表示します。

	PostMessage( WM_NULL );	//これをしないと、2度目のメニューがすぐ消えちゃいます。
	

 この情報はKnowledgeBaseの"PRB: Menus for Notification Icons Don't Work Correctly -- Article ID: Q135788"に書かれていました。これ解決するのに無茶苦茶時間掛かったのに、こんなところに書かれてたなんて……。

Update Room 2
 タスクトレイに表示するアイコン、上の例では大きいアイコンがタスクトレイに合うように縮小されて表示されるので、汚くなってしまいます。
 この問題を解決するには、単純に小さいアイコンしか作製されていないアイコンをしていすればOKです。

 アプリケーションのアイコンがIDR_MAINFRAMEにあるとします。まずこのアイコンをコピーします。ワークスペース内でアイコンを表示させ、IDR_MAINFRAMEをCTRLキーを押しながらドラッグして、適当なところで離します。そうするとIDR_MAINFRAME1が作製されるはずです。
 次に、このアイコンのIDを適当に変更します。IDR_MAINFRAME1の上で右クリックし、「プロパティ」を開き、IDを変更します。ここではIDC_MINIとでもしておきましょう。
 変更したらこのアイコンを開き、「標準 (32x32)」が選択されていることを確認します。そうしたら、メニューの「イメージ」−「デバイスイメージの削除」を選択します。そうすると、あっさりと大きいアイコンが削除され、小さいアイコンのみが残ります。
 あとは、このアイコンを読み込めばOK。小さいアイコンがタスクトレイに表示されるでしょう。

(C)KAB-studio 1997 ALL RIGHTS RESERVED.