MFCの基礎 - 常駐ソフトウェア
概要
タスクトレイに常駐するソフトウェアを開発する手順を記載する。
常駐ソフトウェアを開発するには、ソフトウェアのメインウィンドウとして登録するウィンドウクラスに、以下の処理を記述する。
- タスクバーに非表示のウィンドウを作成する。
- ウィンドウを破棄する時、ウィンドウクラスのオブジェクトが自身のオブジェクトを削除する。
- タスクトレイに対して、アイコン、ツールチップ文字列、コールバックを登録・変更・削除する。
- タスクトレイのアイコンに対するイベントの応答。
- ウィンドウを作成する時、タスクトレイに対してアイコンを登録する処理を呼び出す。
- ウィンドウを破棄する時、タスクトレイに対してアイコンを削除する処理を呼び出す。
常駐ソフトウェア
タスクバーに非表示のウィンドウを作成する
CDialog
クラスまたはCWnd
クラスを継承したCTrayWndクラスに、ウインドウを作成するメソッドを記述する。
タスクバーに非表示のウィンドウを作成するため、Createメソッドを使用して、拡張ウィンドウスタイルとしてWS_EX_TOOLWINDOW
を指定する。
ウィンドウを非表示にするため、ウィンドウの幅・高さを0にする。
BOOL CTrayWnd::Create()
{
return CreateEx(WS_EX_TOOLWINDOW, AfxRegisterWndClass(0), _T("TrayWnd"), WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL);
}
CTrayWndクラスに、Createメソッドの宣言を追加する。
#pragma once
class CTrayWnd : public CWnd
{
public:
BOOL Create();
DECLARE_DYNAMIC(CTrayWnd)
// ...略
}
ウィンドウを破棄する時にウィンドウクラスが自身のオブジェクトを削除する
CTrayWndクラスに、PostNcDestroyメソッドを追加して、自身のオブジェクトを削除する処理を記述する。
void CTrayWnd::PostNcDestroy()
{
// TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
CWnd::PostNcDestroy();
delete this;
}
タスクトレイにアイコン・ツールチップ・コールバックを登録・変更・削除する
上記で作成したCTrayWndクラスに、タスクトレイにアイコンを登録・変更・削除するメソッドを記述する。
このメソッドは、最終的には、Win32APIのShell_NotifyIcon
関数が行う。
Shell_NotifyIcon
関数の第1引数で、アイコンの登録・編集・削除を指定する。
Shell_NotifyIcon
関数の第2引数のNOTIFYICONDATA
構造体において、アイコン、ツールチップ文字列、コールバックを指定する。
BOOL CTrayWnd::NotifyIcon( DWORD dwMessage, HICON hIcon, LPCTSTR pszTip /*= NULL*/ )
{
ASSERT(NIM_ADD == dwMessage || NIM_DELETE == dwMessage || NIM_MODIFY == dwMessage);
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(NOTIFYICONDATA);
nid.hWnd = GetSafeHwnd();
nid.uID = ID_ICON; // アイコンを1つだけサポートする
// タスクトレイのアイコンID
// 1つのプロセスで複数のアイコンをタスクトレイに表示する場合は、それぞれ異なるIDを指定する
// このIDはプロセスごとに独立しているので、他のプロセスが同じIDを指定していたとしても、それらが衝突することはない
nid.uCallbackMessage = WM_USER_TRAYNOTIFYICON;
nid.uFlags = NIF_MESSAGE;
if(NULL != hIcon && m_hIcon_current != hIcon)
{ // アイコン変更
nid.uFlags |= NIF_ICON;
nid.hIcon = hIcon;
m_hIcon_current = hIcon;
}
if(NULL != pszTip)
{ // ツールチップ表示
nid.uFlags |= NIF_TIP;
_tcsncpy_s(nid.szTip, sizeof(nid.szTip), pszTip, _TRUNCATE);
}
return Shell_NotifyIcon(dwMessage, &nid);
}
BOOL CTrayWnd::NotifyIcon( DWORD dwMessage, HICON hIcon, UINT nStringResource )
{
// 表示メッセージを、ストリングリソースのリソースIDで指定する関数
CString msg = _T("");
VERIFY(msg.LoadString(nStringResource));
return NotifyIcon( dwMessage, hIcon, msg );
}
TrayWnd.cppファイルのヘッダ部に、ID_ICONおよびWM_USER_TRAYNOTIFYICONの定義を記述する。
CTrayWndクラスのコンストラクタに、メンバ変数m_hIcon_currentの初期化処理を記述する。
// TrayWnd.cpp
#include "stdafx.h"
#include "MyApplication.h"
#include "TrayWnd.h"
#define ID_ICON 100
#define WM_USER_TRAYNOTIFYICON WM_APP + 100
IMPLEMENT_DYNAMIC(CTrayWnd, CWnd)
CTrayWnd::CTrayWnd() : m_hIcon_current(NULL)
{
}
// ...略
TrayWnd.hファイルに、メンバ変数m_hIcon_currentおよびNotifyIconメソッドの宣言を記述する。
#pragma once
class CTrayWnd : public CWnd
{
private:
HICON m_hIcon_current;
public:
BOOL Create();
BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, LPCTSTR pszTip = NULL);
BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, UINT nStringResource);
DECLARE_DYNAMIC(CTrayWnd)
// ...略
}
タスクトレイのアイコンにおけるイベント(常駐ソフトウェアの共通処理)
CTrayWndクラスに、タスクトレイのアイコンにおけるイベントの応答処理を記述する。
イベントの応答処理は、早く処理を完了させる必要があるため、イベントに応じて、ウィンドウメッセージをポストする。
// TrayWnd.cpp
LRESULT CTrayWnd::OnTrayNotify(UINT wParam, LONG lParam)
{
UINT uiIconID = (UINT)wParam;
UINT uiMouseMsg = (UINT)lParam;
if(ID_ICON != uiIconID)
{
return 0;
}
// ここで模造メッセージをポストする理由 :
// この通知関数からは、できるだけ早く抜ける必要がある
// 例えば、OnLButtonDownでOLEコントロールを作成する場合、エラーコード"RPC_E_CANTCALLOUT_ININPUTSYNCCALL"が送出される
// このエラーは、WinError.hによると、"ソフトウェアが入力同期呼び出しをディスパッチしているため、発信呼び出しはできない"
switch(uiMouseMsg)
{
case WM_LBUTTONDOWN:
m_bFireDoubleClick = FALSE;
SetTimer( ID_CLICKTIMER, GetDoubleClickTime(), NULL );
break;
case WM_LBUTTONUP:
if(m_bFireDoubleClick)
{
PostMessage(WM_LBUTTONDBLCLK);
}
break;
case WM_LBUTTONDBLCLK:
m_bFireDoubleClick = TRUE;
KillTimer(ID_CLICKTIMER);
break;
case WM_RBUTTONUP:
PostMessage(WM_RBUTTONUP);
break;
}
return 0;
}
TrayWnd.cppファイルに、ID_CLICKTIMERの定義を記述する。
CTrayWndクラスのコンストラクタに、メンバ変数m_bFireDoubleClickの初期化処理を記述する。
また、CTrayWndクラスに、WM_TIMER
、WM_LBUTTONUP
、WM_RBUTTONUP
、WM_LBUTTONDBLCLK
メッセージに対応するイベントハンドラを記述する。
// TrayWnd.cpp
#include "stdafx.h"
#include "MyApplication.h"
#include "TrayWnd.h"
#define ID_ICON 100
#define WM_USER_TRAYNOTIFYICON WM_APP + 100
#define ID_CLICKTIMER 4
IMPLEMENT_DYNAMIC(CTrayWnd, CWnd)
ON_MESSAGE(WM_USER_TRAYNOTIFYICON, OnTrayNotify)
// ... 略
BEGIN_MESSAGE_MAP(CTrayWnd, CWnd)
ON_MESSAGE(WM_USER_TRAYNOTIFYICON, OnTrayNotify)
END_MESSAGE_MAP()
// ...略
CTrayWnd::CTrayWnd() : m_hIcon_current(NULL), m_bFireDoubleClick(false)
{
}
// ...略
void CTrayWnd::OnTimer(UINT_PTR nIDEvent)
{
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
if(ID_CLICKTIMER == nIDEvent)
{
KillTimer(nIDEvent);
::PostMessage(WM_LBUTTONUP);
}
// CWnd::OnTimer(nIDEvent);
}
void CTrayWnd::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
LButtonClick();
// CWnd::OnLButtonUp(nFlags, point);
}
void CTrayWnd::OnRButtonUp(UINT nFlags, CPoint point)
{
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
RButtonClick();
// CWnd::OnRButtonUp(nFlags, point);
}
void CTrayWnd::OnLButtonDblClk(UINT nFlags, CPoint point)
{
// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
LButtonDoubleClick();
// CWnd::OnLButtonDblClk(nFlags, point);
}
void CTrayWnd::LButtonClick()
{
POINT pt;
GetCursorPos( &pt );
CMenu menu;
menu.LoadMenu(IDM_TRAY_L);
CMenu *pPopup = menu.GetSubMenu(0);
// SetForgroundWindowとPostMessageが必要な理由は、Knowledge Base (Q135788)参照のこと
SetForegroundWindow();
pPopup->TrackPopupMenu( TPM_RIGHTBUTTON, pt.x, pt.y, this );
::PostMessage(WM_NULL);
}
void CTrayWnd::RButtonClick()
{
POINT pt = {0};
GetCursorPos(&pt);
CMenu menu;
menu.LoadMenu(IDM_TRAY_R);
CMenu *pPopup = menu.GetSubMenu(0);
// SetForgroundWindowとPostMessageが必要な理由は、Knowledge Base (Q135788)参照のこと
SetForegroundWindow();
pPopup->TrackPopupMenu(TPM_RIGHTBUTTON, pt.x, pt.y, this);
::PostMessage(WM_NULL);
}
void CTrayWnd::LButtonDoubleClick()
{
::SendMessage(WM_COMMAND, ID_APP_ABOUT);
// または
CAboutDlg dlg;
dlg.DoModal();
}
TrayWnd.hファイルに、メンバ変数m_bFireDoubleClickおよびOnTrayNotifyメソッドの宣言を記述する。
// TrayWnd.h
#pragma once
class CTrayWnd : public CWnd
{
private:
HICON m_hIcon_current;
BOOL m_bFireDoubleClick;
protected:
virtual void LButtonClick();
virtual void RButtonClick();
virtual void LButtonDoubleClick();
public:
BOOL Create();
BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, LPCTSTR pszTip = NULL);
BOOL NotifyIcon(DWORD dwMessage, HICON hIcon, UINT nStringResource);
DECLARE_DYNAMIC(CTrayWnd)
public:
CTrayWnd();
virtual ~CTrayWnd();
protected:
DECLARE_MESSAGE_MAP()
virtual void PostNcDestroy();
afx_msg LRESULT OnTrayNotify(UINT wParam, LONG lParam);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
};
ウィンドウの作成および破棄におけるアイコンの登録・削除
CTrayWndクラスのコンストラクタに、メンバ変数m_hIcon_mainの初期化処理を記述する。
CTrayWndクラスに、WM_CREATE
、WM_DESTROY
メッセージに対応するイベントハンドラを記述する。
// TrayWnd.cpp
#include "stdafx.h"
#include "MyApplication.h"
#include "TrayWnd.h"
#include "AboutDlg.h"
IMPLEMENT_DYNAMIC(CMyApplicationTrayWnd, CTrayWnd)
CTrayWnd::CTrayWnd() : m_hIcon_main(NULL)
{
// ...略
}
// ...略
int CTrayWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CWnd::OnCreate(lpCreateStruct) == -1)
{
return -1;
}
// TODO: ここに特定な作成コードを追加してください。
m_hIcon_main = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
NotifyIcon(NIM_ADD, m_hIcon_main, _T("MyApplication"));
return 0;
}
void CTrayWnd::OnDestroy()
{
CWnd::OnDestroy();
// TODO: ここにメッセージ ハンドラ コードを追加します。
NotifyIcon( NIM_DELETE, NULL );
}
TrayWnd.hファイルに、メンバ変数m_hIcon_mainの宣言を記述する。
// TrayWnd.h
#pragma once
class CTrayWnd : public CWnd
{
private:
HICON m_hIcon_main;
protected:
void LButtonClick();
void RButtonClick();
void LButtonDoubleClick();
DECLARE_DYNAMIC(CTrayWnd)
// ...略
}