WTLのメッセージマップ
ここまではATLのみを使用してきましたが、ここから徐々にWTLを導入していこうと思います。
まずはメッセージマップをWTLのものに置き換えます。WTLもメッセージマップを用意しており、WTLの「atlcrack.h」ヘッダで定義されています。WTLのメッセージマップを利用するにはこの「atlcrack.h」をインクルードし、メッセージマップのBEGIN_MSG_MAPをBEGIN_MSG_MAP_EXに変更する必要があります。
class CMyWindow : public CWindowImpl<CMyWindow> { // WTLのメッセージマップ BEGIN_MSG_MAP_EX(CMyWindow) MSG_WM_PAINT(OnPaint) MSG_WM_DESTROY(OnDestroy) END_MSG_MAP() void OnPaint(HDC /*hDC*/){ // WM_PAINTメッセージへの応答処理 } void OnDestroy(){ // WM_DESTROYメッセージへの応答処理 } };
このようにWTLには各メッセージ専用のメッセージマクロがあり、メッセージハンドラ関数には不要な引数がありません。
なお、WTLにはATLが用意する汎用メッセージハンドラマクロ、コマンドメッセージハンドラマクロ、通知メッセージハンドラマクロの拡張版が用意されています。
拡張版汎用メッセージハンドラマクロ
すべてのメッセージを対象とします。拡張版汎用メッセージハンドラマクロには次の2つがあります。
MESSAGE_HANDLER_EX(メッセージ名, ハンドラ名)MESSAGE_RANGE_HANDLER_EX(開始位置のメッセージ名, 終了位置のメッセージ名, ハンドラ名)
// ハンドラ関数名は任意
LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam);
uMsgはメッセージを識別し、wParamとlParamはメッセージパラメータです。メッセージパラメータの内容はメッセージの種類によって変わります。
拡張版コマンドメッセージハンドラマクロ
コマンドメッセージ(WM_COMMAND)を対象とします。拡張版コマンドメッセージハンドラマクロには次の5つがあります。
COMMAND_HANDLER_EX(コントロールID, 通知コード, ハンドラ名)COMMAND_ID_HANDLER_EX(コントロールID, ハンドラ名)COMMAND_CODE_HANDLER_EX(通知コード, ハンドラ名)COMMAND_RANGE_HANDLER_EX(開始位置のメッセージ名, 終了位置のメッセージ名, ハンドラ名)COMMAND_RANGE_CODE_HANDLER_EX(開始位置のメッセージ名, 終了位置のメッセージ名, 通知コード, ハンドラ名)
// ハンドラ関数名は任意 void CommandHandler(UINT uNotifyCode, int nID, HWND hWndCtl);
uNotifyCodeは通知コード、nIDはコマンドを送信しているコントロールの識別子、hWndCtlはコマンドを送信しているコントロールのハンドルです。
拡張版通知メッセージハンドラマクロ
拡張版通知メッセージ(WM_NOTIFY)を対象とします。拡張版通知メッセージハンドラマクロには次の5つがあります。
NOTIFY_HANDLER_EX(コントロールID, 通知コード, ハンドラ名)NOTIFY_ID_HANDLER_EX(コントロールID, ハンドラ名)NOTIFY_CODE_HANDLER_EX(通知コード, ハンドラ名)NOTIFY_RANGE_HANDLER_EX(開始位置のメッセージ名, 終了位置のメッセージ名, ハンドラ名)NOTIFY_RANGE_CODE_HANDLER_EX(開始位置のメッセージ名, 終了位置のメッセージ名, 通知コード, ハンドラ名)
// ハンドラ関数名は任意
LRESULT NotifyHandler(LPNMHDR pnmh);
pnmhはNMHDR構造体へのポインタです。
メッセージ処理の終了通知
ATLのメッセージマクロを使用してる場合は、ハンドラ関数のプロトタイプに処理を終えたかどうかを通知するフラグがありました。次に示すのはATL版メッセージハンドラ関数のプロトタイプです。
// bHandledが処理を終えたかどうかを通知するフラグ
LRESULT MessageHandler(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled);
しかし、WTLのメッセージハンドラ関数にはそのようなフラグが用意されていません。
そこで、WTLでは、メッセージハンドラ関数内でSetMsgHandled()という関数を呼び出すことによって、メッセージの処理を終えたかどうかを通知します。SetMsgHandled()の引数にtrueを渡した場合はメッセージの処理を終えたことを意味し、falseを渡した場合はメッセージマップ内でさらにこのメッセージ用のハンドラがないかどうか検索することを意味します。WTLでは基本的にメッセージハンドラ関数の呼び出し前にtrueが設定されているため、メッセージマップを検索させたい場合だけ明示的にfalseを設定する必要があります。
WTLのメッセージループ
「Hello, ATL/WTL」プログラムではメッセージループをSDKスタイルで書きました。
MSG msg; while(GetMessage(&msg, NULL, 0, 0) > 0){ TranslateMessage(&msg); DispatchMessage(&msg); }
WTLには、メッセージループをカプセル化するためにCMessageLoopというクラスが用意されています。このクラスを利用するためにはWTLの「atlapp.h」ヘッダをインクルードする必要があります。
また、CMessageLoopクラスはCAppModuleクラスによって管理されるため、これまで記述してきたCComModuleクラスをCAppModuleクラスに置き換えます。CAppModuleクラスも「atlapp.h」ヘッダに宣言されているため、CAppModuleクラスを使用する前に「atlapp.h」ヘッダをインクルードします。
#include <atlbase.h> #include <atlapp.h> extern CAppModule _Module; // CComModuleからCAppModuleに置き換える #include <atlwin.h> #include <atlcrack.h> // WTLメッセージクラッカーを使うため
次に、_tWinMain()内でCMessageLoopクラスのインスタンスを作成し、それをCAppModule::AddMessageLoop()でCAppModule内部のマップに追加します。メッセージループはCMessageLoop::Run()によって実行され、必要なくなったメッセージループはCAppModule::RemoveMessageLoop()によって CAppModule内部のマップから削除します。
#include "stdafx.h" #include "MainWindow.h" CAppModule _Module; // CComModuleからCAppModuleに置き換える int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow) { _Module.Init(NULL, hInstance); CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop); // 独自ウィンドウを作成 CMyWindow wnd; wnd.Create(NULL, CWindow::rcDefault, _T("Hello, ATL/WTL"), WS_OVERLAPPEDWINDOW | WS_VISIBLE); int nRet = theLoop.Run(); _Module.RemoveMessageLoop(); _Module.Term(); return nRet; }
メッセージフィルタ
メッセージループにCMessageLoopクラスを使用すると、CMessageFilterクラスを使ってメッセージフィルタを追加することができます。メッセージフィルタを追加すると、CMessageLoopクラス内のメッセージループで::TranslateMessage()が呼び出される前に何か処理をさせることができます。
CMessageFilterクラスを利用するためには、メインウィンドウであるCMyWindowクラスを次のように書き換える必要があります。
// 基底クラスにCMessageFilterを追加 class CMyWindow : public CWindowImpl<CMyWindow>, public CMessageFilter { public: // ウィンドウクラス名を登録 DECLARE_WND_CLASS(_T("Hello")); private: // メッセージフィルタ処理 virtual BOOL PreTranslateMessage(MSG* pMsg){ return FALSE; } // メッセージマップ BEGIN_MSG_MAP_EX(CMyWindow) MSG_WM_PAINT(OnPaint) MSG_WM_CREATE(OnCreate) MSG_WM_DESTROY(OnDestroy) END_MSG_MAP() void OnPaint(HDC /*hDC*/){ PAINTSTRUCT ps; HDC hDC = BeginPaint(&ps); RECT rect; GetClientRect(&rect); DrawText(hDC, _T("Hello, ATL/WTL"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(&ps); } LRESULT OnCreate(LPCREATESTRUCT lpcs){ // メッセージループにメッセージフィルタを追加 CMessageLoop* pLoop = _Module.GetMessageLoop(); pLoop->AddMessageFilter(this); return 0; } void OnDestroy(){ PostQuitMessage(0); } };
まずはCMyWindowクラスの基底クラスにCMessageFilterクラスを追加します。CMessageFilterクラスにはPreTranslateMessage()という純粋仮想関数が一つあるだけです。
class CMessageFilter { public: virtual BOOL PreTranslateMessage(MSG* pMsg) = 0; };
CMyWindowクラスではこのPreTranslateMessage()を実装します。今回の例ではFALSEを返しているだけですが、これは何もせずに通常の処理を続けるということを意味します。
最後にWM_CREATEメッセージハンドラを追加し、CMessageLoopのメンバ関数であるAddMessageFilter()を呼び出してメッセージループにメッセージフィルタを追加します。
これにより、CMessageLoopクラス内のメッセージループで::TranslateMessage()が呼び出される前にCMyWindowクラスで実装したPreTranslateMessage()が呼び出されることになります。
アイドルハンドラ
メッセージループにCMessageLoopクラスを使用すると、CIdleHandlerクラスを使ってアイドルハンドラを追加することができます。アイドルハンドラを追加すると、CMessageLoopクラス内のメッセージループで処理するメッセージが無い時に、アイドル処理をさせることができます。
CIdleHandlerクラスを利用するためには、メインウィンドウであるCMyWindowクラスを次のように書き換える必要があります。
// 基底クラスにCIdleHandlerを追加 class CMyWindow : public CWindowImpl<CMyWindow>, public CMessageFilter, public CIdleHandler { public: // ウィンドウクラス名を登録 DECLARE_WND_CLASS(_T("Hello")); private: // メッセージフィルタ処理 virtual BOOL PreTranslateMessage(MSG* pMsg){ return FALSE; } // アイドル処理 virtual BOOL OnIdle(){ return FALSE; } // メッセージマップ BEGIN_MSG_MAP_EX(CMyWindow) MSG_WM_PAINT(OnPaint) MSG_WM_CREATE(OnCreate) MSG_WM_DESTROY(OnDestroy) END_MSG_MAP() void OnPaint(HDC /*hDC*/){ PAINTSTRUCT ps; HDC hDC = BeginPaint(&ps); RECT rect; GetClientRect(&rect); DrawText(hDC, _T("Hello, ATL/WTL"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(&ps); } LRESULT OnCreate(LPCREATESTRUCT lpcs){ //メッセージループにメッセージフィルタとアイドルハンドラを追加 CMessageLoop* pLoop = _Module.GetMessageLoop(); pLoop->AddMessageFilter(this); pLoop->AddIdleHandler(this); return 0; } void OnDestroy(){ PostQuitMessage(0); } };
まずはCMyWindowクラスの基底クラスにCIdleHandlerクラスを追加します。CIdleHandlerクラスにはOnIdle()という純粋仮想関数が一つあるだけです。
class CIdleHandler { public: virtual BOOL OnIdle() = 0; };
CMyWindowクラスではこのOnIdle()を実装します。今回の例ではFALSEを返していますが、この値はチェックされていないようです。
最後にWM_CREATEメッセージハンドラで、CMessageLoopのメンバ関数であるAddIdleHandler()を呼び出してメッセージループにアイドルハンドラを追加します。
これにより、アイドル時になるたびにCMyWindowクラスで実装したOnIdle()が呼び出されることになります。
GDI
ここまではWM_PAINTメッセージハンドラの処理をSDKスタイルで書いてきました。
void OnPaint(HDC /*hDC*/){ PAINTSTRUCT ps; HDC hDC = BeginPaint(&ps); RECT rect; GetClientRect(&rect); DrawText(hDC, _T("Hello, ATL/WTL"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(&ps); }
WTLは、GDIをサポートするクラスを用意しています。次に示すのは、WTLのGDIサポートクラスを使用して書き換えた例です。
void OnPaint(HDC /*hDC*/){ CPaintDC dc(m_hWnd); CRect rect; GetClientRect(rect); dc.DrawText(_T("Hello, ATL/WTL"), -1, rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); }
なお、この例ではWTLのCRectクラスを使用するために「atlmisc.h」ヘッダをインクルードする必要があります。
WTLにはGDIサポートクラスとしてCPen/CBrush/CFont/CPalette/CBitmap/CRgnなどのクラスが用意されています。
最後に
本稿では、ATL/WTLで簡単なウィンドウを作成し、ATL/WTLの基本的な使い方を示しました。今後はダイアログやコントロールの他、WTL特有のGUIクラスであるダイアログリサイズやペインコンテナなどを扱っていきたいと思います。
