つくってみよう−タスクトレイアプリケーションの作成

通常のアプリケーションは、メインウィンドウが開かれるとタスクバーに表示されます。 これをタスクバーではなく、タスクバーの右端についているタスクトレイの中へ表示させるにはどうすればいいのでしょうか?
ここでは、タスクトレイに格納されるアプリケーションを、順を追って作成していきたいと思います。



その1 タスクバーに表示されないようにする
第一段階として、タスクバーにアプリケーションが表示されないようにしてみましょう。 タスクバーに何も表示されないようにするには、メインウィンドウを非表示(つまり見えなくする)にする必要があります。 しかし、メインウィンドウが見えなくてはまともに作業もできず、困ってしまいますね。
そこで、メインウィンドウの中で子ウィンドウを作成し(これは見えるようにする)、こちらをメインウィンドウの代わりに使います。

では、実際にどうすればタスクバーに表示されないようになるか、やってみましょう。ここではダイアログベースにします。

まず最初に、VC++でダイアログベースのアプリケーション(MFC AppWizard(.exe))を新規に作成して下さい。 ここでは仮に「UKTaskTray」というプロジェクト名にします。 ディレクトリ(位置)は適当な所を指定し、その他はすべてデフォルトでかまいません。
アプリケーションを新規に作成できたら、 「ResourceView」からIDD_UKTASKTRAY_DIALOGというIDを持つダイアログリソースを開いてください。 ダイアログリソースを開いたら、「表示」−「プロパティ」メニューでプロパティを開きます。 この中にある「拡張スタイル」タブを選択し、「ツールウィンドウ」の項目をチェックします。 これで、ダイアログには拡張スタイルであるWS_EX_TOOLWINDOWスタイルが設定されたことになります。

さて、もう1つやることがあります。実はこのダイアログにはデフォルトでWS_EX_APPWINDOWというスタイルが設定されています。 このスタイルを削除してやらないと、タスクバーから表示を消すことができません。
ではどうやって削除するか?方法は2つです。1つは、リソースファイル(UKTaskTray.rc)をテキストエディタで開き、 WS_EX_APPWINDOWの記述を削除する方法です。VC++の「ファイル」−「開く」メニューでファイル選択ダイアログを開き、 リソースファイルを指定します。「OK」ボタンを押す前に、ダイアログの下部にある「用途」の欄を「テキスト」にしてください。 これで、テキスト形式でリソースファイルを開くことができます。
ファイル内に、以下のような部分がありますよね?この部分がダイアログIDD_UKTASKTRAY_DIALOGを現しています。

	IDD_UKTASKTRAY_DIALOG DIALOGEX 0, 0, 319, 201
	STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
	EXSTYLE WS_EX_TOOLWINDOW | WS_EX_APPWINDOW
	CAPTION "UKTaskTray"
	FONT 9, "MS Pゴシック"
	BEGIN
	    DEFPUSHBUTTON   "OK",IDOK,260,7,50,14
	    PUSHBUTTON      "キャンセル",IDCANCEL,260,23,50,14
	    LTEXT           "TODO: ダイアログのコントロールをここに配置",IDC_STATIC,50,90,200,8
	END
3行目の赤い部分に注目してください。先ほど指定したWS_EX_TOOLWINDOWスタイルの指定がありますね。その後に、 WS_EX_APPWINDOWスタイルが指定されています。これを削除すればよいのです。「 | WS_EX_APPWINDOW」のところを削除すればOKです。
もう1つの方法は、ダイアログの初期化処理でスタイルを変更するというものです。 「FileView」でUKTaskTrayDlg.cppファイルを開き、OnInitDialog関数に次の1行を追加します。
BOOL CUKTaskTrayDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	(中略)
	
	// TODO: 特別な初期化を行う時はこの場所に追加してください。
	ModifyStyleEx(WS_EX_APPWINDOW, 0);    // この1行を追加する!
	
	return TRUE;  // TRUE を返すとコントロールに設定したフォーカスは失われません。
}
さて、これでビルドして実行すると、ダイアログが表示されてもタスクバーには何も表示されませんよね。 結構、簡単にできてしまうものです。
・・・えっ、だったらこのダイアログをメインウィンドウにして使えば、 何も非表示にして子ウィンドウを作る必要なんかないじゃないかって?
実はそれだと1つ問題があるんです。何かというと、WS_EX_TOOLWINDOWスタイルを設定したウィンドウには、 最大化ボタンと最小化ボタンをつけることができないようになっているのです。 もちろん、「最大化ボタンも最小化ボタンもいらんわい!」という場合はこれでOKなんですが、 これらが必要な場合はこのダイアログを非表示にして、その子ウィンドウをメインウィンドウの代わりにするという方法が必要です。



その2 メインウィンドウを非表示にし、代わりのウィンドウを表示する
さて、次に非表示にするメインウィンドウの代わりになるウィンドウを作成しましょう。ここではダイアログとします。 「ResourceView」の「ダイアログ」フォルダ上で右クリックすると、ポップアップメニューが表示されるので、 「ダイアログの挿入」を選択してください。新しいダイアログリソースが作成されます。 おそらく、ダイアログのIDはIDD_DIALOG1になっているかと思います。
ダイアログリソースを開いたら、「表示」−「プロパティ」メニューでプロパティを開きます。 そして「スタイル」タブを選択し、「最小化ボタン」と「最大化ボタン」をチェックし、ボタンをつけてやりましょう。
プロパティを閉じたら、「表示」−「ClassWizard」メニューを選択してください。ClassWizardダイアログの上に、 「クラスの追加」ダイアログが表示ます。「クラスの新規作成」が選択されていると思うので、 そのままOKボタンを押してクラスを作成に移りましょう。 「クラスの新規作成」ダイアログで、クラス名を「CChildDlg」とし、OKボタンを押します。これでCChildDlgクラスが作成されます。

さて、クラスを作成したら、メインダイアログからこのダイアログを呼び出すようにしましょう。 「FileView」でUKTaskTrayDlg.cppファイルを開き、OnInitDialog関数を次のようにします。

// ヘッダのインクルードも忘れずに!
#include "ChildDlg.h"

BOOL CUKTaskTrayDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	(中略)
	
	// TODO: 特別な初期化を行う時はこの場所に追加してください。
	ModifyStyleEx(WS_EX_APPWINDOW, 0);
	// 以下の2行を追加する
	CChildDlg dlg;
	int iID = dlg.DoModal();
	
	return TRUE;  // TRUE を返すとコントロールに設定したフォーカスは失われません。
}
さあ、これでビルドをして実行してみましょう。最初にCChildDlgクラスのダイアログが表示され、 これを閉じるとメインのダイアログが表示されますね。あとは、このメインのダイアログが表示されないようにすればよいのです。 それにはどうすればよいのでしょうか?
よく考えてみれば、CChildDlgクラスをメインの代わりにするのですから、 CChildDlgクラスのダイアログを閉じるということは、即ちアプリケーションを終了させるということですよね。 だったらメインダイアログを閉じてアプリケーションを終了させてしまいましょう。
	EndDialog(iID);
この1行を先ほど追加した2行の直後(DoModalの後ろ)に入れてください。 これでCUKTaskTrayDlgクラスのダイアログを閉じることができます。 ちなみにEndDialog関数の引数には、IDOKまたはIDCANCELのいずれかが入ります。 OKボタンを押した場合とキャンセルボタンを押した場合などでその後の処理が異なる場合には、 適時この引数を変更する必要があります。ここではCChildDlgクラスのダイアログの戻り値をそのまま使用しましょう。

このようにOnInitDialog関数の中でダイアログを終了させてしまえば、ダイアログが表示される前に終了するので、 結果としてダイアログは非表示になります。 ちなみにOnInitDialog関数終了後もメインダイアログを閉じずに非表示のまま生かしておくには、 EndDialog関数の代わりに次の1行を記述します。 (ただし今回はこの方法を使うと非表示のメインダイアログを閉じる手段がありませんので注意!)

	SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_HIDEWINDOW);



その3 タスクトレイにアイコンを表示する
さて、タスクバーに表示されないようにする処理が片付いたので、いよいよタスクトレイにアイコンを表示させてみましょう。
まずClassWizardを使って、CChildDlgクラスにOnInitDialog関数とOnDestroy関数を追加してください。 ClassWizardダイアログで、クラス名とオブジェクトIDでそれぞれ「CChildDlg」を選択し、 「メッセージ」欄でWM_INITDIALOGとWM_DESTROYをダブルクリックすればOKです。
関数の追加に成功したら、次のようにCChildDlgクラスのヘッダとソースを変更します。
	// ヘッダファイルでの記述 --------------------
	class CChildDlg : public CDialog
	{
	(中略)
	private:
		NOTIFYICONDATA m_stNotifyIcon;
	};

	// ソースファイルでの記述 --------------------
	BOOL CChildDlg::OnInitDialog() 
	{
		CDialog::OnInitDialog();

		// TODO: この位置に初期化の補足処理を追加してください
		m_stNotifyIcon.cbSize = sizeof(NOTIFYICONDATA);
		m_stNotifyIcon.uID = 0;
		m_stNotifyIcon.hWnd = m_hWnd;
		m_stNotifyIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
		m_stNotifyIcon.hIcon = AfxGetApp()->LoadIcon( IDR_MAINFRAME );
		m_stNotifyIcon.uCallbackMessage = 0;
		lstrcpy( m_stNotifyIcon.szTip, "タスクトレイアプリのテスト" );
		::Shell_NotifyIcon( NIM_ADD, &m_stNotifyIcon );

		// この際なのでダイアログを常に最前面に表示するようにします
		// (この処理はタスクトレイとは何の関係もありません)
		SetWindowPos(&wndTopMost, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);

		return TRUE;  // コントロールにフォーカスを設定しないとき、戻り値は TRUE となります
		              // 例外: OCX プロパティ ページの戻り値は FALSE となります
	}

	void CChildDlg::OnDestroy() 
	{
		CDialog::OnDestroy();

		// TODO: この位置にメッセージ ハンドラ用のコードを追加してください
		::Shell_NotifyIcon( NIM_DELETE, &m_stNotifyIcon );

	}
これだけでOKです。やってることは、大してむずかしいことではありません。 NOTIFYICONDATA構造体を使用してタスクトレイアイコンのデータを設定し、 API関数Shell_NotifyIconでタスクトレイへの追加や削除を行っています。



その4 タスクトレイアイコンからのメニュー表示
タスクトレイにアイコンを表示するタイプのアプリケーションの多くは、 アイコン上で右クリックをするとポップアップメニューが表示されるようになっています。 この機能は是非欲しいところですね。早速、作ってみることにしましょう。

まずポップアップメニュー用のメニューリソースを作成します。作成方法については、 「ポップアップメニューを表示する」 を参照してみてください。メニューの項目は何でもかまいません。リソースのIDは、IDR_MENU1としておきましょう。

次に、タスクトレイアイコンで右クリックされたことを知らせるためのメッセージを定義しなければなりません。 これはユーザーが定義する必要があります。まず、アプリケーションクラスのヘッダーファイルUKTaskTray.hを開き、 以下の1行を挿入します。

	#include "resource.h"		// メイン シンボル
	#define WM_USER_POPUP	WM_USER+100  // この1行を追加!
これで「WM_USER_POPUP」というメッセージを定義することができました。では、タスクトレイアイコンからこのメッセージを発行させるにはどうすればよいのでしょうか?
「その3」で、OnInitDialog関数に処理を追加しましたが、その中に次の1行がありますよね。
	m_stNotifyIcon.uCallbackMessage = 0;
これを、次のように書きかえればよいのです。
	m_stNotifyIcon.uCallbackMessage = WM_USER_POPUP;
今度は、このメッセージを受け取る処理が必要です。このメッセージはCWnd::WindowProc関数で受け取ることにしましょう。 「ClassWizard」を使って、WindowProc関数を定義します。
	LRESULT CChildDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
	{
		// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください

		if( message == WM_USER_POPUP ) {

			// カーソルの現在位置を取得
			POINT point;
			::GetCursorPos(&point);

			switch(lParam) {
			case WM_RBUTTONDOWN:
				SetForegroundWindow();	// この処理を忘れずに!
				CMenu menu;
				VERIFY(menu.LoadMenu(IDR_MENU1));
				CMenu* pPopup = menu.GetSubMenu(0);
				ASSERT(pPopup != NULL);
				pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
				PostMessage(WM_NULL);	// この処理も忘れずに!
				break;
			}
		}
	
		return CDialog::WindowProc(message, wParam, lParam);
	}
WM_USER_POPUPメッセージは、タスクトレイアイコン上でカーソルの移動、左クリック、 右クリックなどの処理が行われると発行されます。どの処理が行われたかは、 CWnd::WindowProc関数の引数lParamを参照すればわかるようになっています。尚、wParamは常に0になっているようです。

目次へ