SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ATL/WTLを利用したVisual C++のWindowsプログラミング

ATL/WTLプログラミング 1:ATL/WTLの概要と基本

ATL/WTLを利用したVisual C++のWindowsプログラミング


WTLのメッセージマップ

 ここまではATLのみを使用してきましたが、ここから徐々にWTLを導入していこうと思います。

 まずはメッセージマップをWTLのものに置き換えます。WTLもメッセージマップを用意しており、WTLの「atlcrack.h」ヘッダで定義されています。WTLのメッセージマップを利用するにはこの「atlcrack.h」をインクルードし、メッセージマップのBEGIN_MSG_MAPBEGIN_MSG_MAP_EXに変更する必要があります。

WTLのメッセージマップとメッセージハンドラ関数
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はメッセージを識別し、wParamlParamはメッセージパラメータです。メッセージパラメータの内容はメッセージの種類によって変わります。

拡張版コマンドメッセージハンドラマクロ

 コマンドメッセージ(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);

 pnmhNMHDR構造体へのポインタです。

メッセージ処理の終了通知

 ATLのメッセージマクロを使用してる場合は、ハンドラ関数のプロトタイプに処理を終えたかどうかを通知するフラグがありました。次に示すのは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スタイルで書きました。

ATL版メッセージループ
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」ヘッダをインクルードします。

「stdafx.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内部のマップから削除します。

「hello.cpp」
#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クラスを次のように書き換える必要があります。

MainWindow.h
// 基底クラスに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()という純粋仮想関数が一つあるだけです。

「atlapp.h」のCMessageFilterクラス定義
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クラスを次のように書き換える必要があります。

MainWindow.h
// 基底クラスに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()という純粋仮想関数が一つあるだけです。

atlapp.hのCIdleHandlerクラス定義
class CIdleHandler
{
public:
    virtual BOOL OnIdle() = 0;
};

 CMyWindowクラスではこのOnIdle()を実装します。今回の例ではFALSEを返していますが、この値はチェックされていないようです。

 最後にWM_CREATEメッセージハンドラで、CMessageLoopのメンバ関数であるAddIdleHandler()を呼び出してメッセージループにアイドルハンドラを追加します。

 これにより、アイドル時になるたびにCMyWindowクラスで実装したOnIdle()が呼び出されることになります。

GDI

 ここまではWM_PAINTメッセージハンドラの処理をSDKスタイルで書いてきました。

WM_PAINTメッセージハンドラ
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サポートクラスを使用して書き換えた例です。

GDIクラスを使用したWM_PAINTメッセージハンドラ
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クラスであるダイアログリサイズやペインコンテナなどを扱っていきたいと思います。

参考資料

  1. Windows Template Library (WTL) 7.1
  2. Windows Template Library (WTL)
  3. WTL support list
  4. The Code Project

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
ATL/WTLを利用したVisual C++のWindowsプログラミング連載記事一覧

もっと読む

この記事の著者

So()

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/178 2006/05/25 10:13

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー