ホーム ATL/WTL
フレームウィンドウ
ドキュメント種別 ATL/WTL に関する文書
最終更新日 2004/10/02
PR
 たいていのメインウィンドウにはメニューバーがあり、WM_DESTROYメッセージハンドラでは 単にPostQuitMessage()を呼び出しています。 ところが、これまでの例のようにCWindowImplクラスをメインウィンドウの基底クラスとして使用すると、 毎回自分でCWndClassInfo構造体をカスタマイズしてメニューリソースを設定したり、 メッセージマップにWM_DESTROYメッセージ用のエントリを追加して、 PostQuitMessage()を呼び出すためだけにWM_DESTROYメッセージハンドラを書かなければなりません。

 WTLはこのような基本的な機能を持つウィンドウを簡単に作るために、CFrameWindowImplというクラスを 用意しています。CFrameWindowImpl(または、その基底クラスであるCFrameWindowImplBase)クラスには、 WM_DESTROYメッセージ用のデフォルトのハンドラが用意されている他、 ウィンドウサイズが変更されたときに自動的にレイアウトを調整するWM_SIZEメッセージハンドラ、 ツールバーやステータスバーを作成する関数、キーボードアクセラレータ用のコードやツールチップ用の メッセージハンドラなどが備えられています。また、メニューバーなどのリソースを簡単に設定するマクロをサポートしています。

 ここでは、前回のポップアップメニューを追加した「Hello, ATL/WTL」プログラムの基底クラスを CWindowImplからCFrameWindowImplへ変更して書き換えます。


// stdafx.h内
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlframe.h> // CFrameWindowImplクラスを使用するため
			

// MainWindow.h内
// メインウィンドウの基底クラスをCFrameWindowImplに変更
class CMyWindow : public CFrameWindowImpl<CMyWindow>,
    public CMessageFilter, public CIdleHandler
{
public:
    // ウィンドウクラス名、共通リソースID、スタイル、背景色を登録
    DECLARE_FRAME_WND_CLASS_EX(_T("Hello"), IDR_MAINFRAME,
        CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, COLOR_WINDOW)

private:
    // メッセージフィルタ処理
    virtual BOOL PreTranslateMessage(MSG* pMsg){
        // 基底クラスのPreTranslateMessageを呼び出す
        return CFrameWindowImpl<CMyWindow>::PreTranslateMessage(pMsg);
    }

    // アイドル処理
    virtual BOOL OnIdle(){
        return FALSE;
    }

    // メッセージマップ
    BEGIN_MSG_MAP_EX(CMyWindow)
        MSG_WM_PAINT(OnPaint)
        MSG_WM_CONTEXTMENU(OnContextMenu)
        MSG_WM_CREATE(OnCreate)
        COMMAND_ID_HANDLER_EX(ID_MENUITEM_HELLO, OnMenuHello)
        COMMAND_ID_HANDLER_EX(ID_MENUITEM_EXIT, OnMenuExit)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)  // CFrameWindowImplクラスへチェーン
    END_MSG_MAP()

    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);
    }

    void OnContextMenu(HWND hWnd, CPoint pt){
        // [Shift]+[F10]キーが押された場合は座標をクライアント領域の左上に設定
        if(pt.x == -1 && pt.y == -1){
            pt.SetPoint(0, 0);
            ClientToScreen(&pt);
        }

        // 座標がクライアント領域内の場合のみポップアップメニューを表示
        CRect rc;
        GetClientRect(&rc);
        ClientToScreen(&rc);
        if(rc.PtInRect(pt)){
            CMenu menuPopup;
            menuPopup.LoadMenu(IDR_MENU_POPUP);
            menuPopup.GetSubMenu(0).TrackPopupMenu(
                TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, pt.x, pt.y, m_hWnd);
        }else{
            SetMsgHandled(false);
        }
    }

    LRESULT OnCreate(LPCREATESTRUCT lpcs){
        // メッセージループにメッセージフィルタとアイドルハンドラを追加
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->AddMessageFilter(this);
        pLoop->AddIdleHandler(this);
        return 0;
    }

    void OnMenuHello(UINT uNotifyCode, int nID, HWND hWndCtl){
        MessageBox(_T("Hello, ATL/WTL"));
    }

    void OnMenuExit(UINT uNotifyCode, int nID, HWND hWndCtl){
        PostMessage(WM_CLOSE);
    }
};
			

// hello.cpp内
#include "stdafx.h"

#include "resource.h"

#include "MainWindow.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow)
{
    _Module.Init(NULL, hInstance);

    CMessageLoop theLoop;
    _Module.AddMessageLoop(&theLoop);

    // 独自ウィンドウを作成
    CMyWindow wnd;
    wnd.CreateEx();
    wnd.ShowWindow(nCmdShow);
    wnd.UpdateWindow();

    int nRet = theLoop.Run();

    _Module.RemoveMessageLoop();

    _Module.Term();

    return nRet;
}
			

 まず、stdafx.h内ではCFrameWindowImplクラスを使用するためにatlframe.hヘッダをインクルードします。

 次に、メインウィンドウであるCMyWindowクラスの基底クラスを、 CWindowImplクラスからCFrameWindowImplクラスへ変更します。 CFrameWindowImplクラスもCWindowImplクラスの時と同様に、 3つのテンプレート引数を持ちます。ここでは省略可能な第2、3テンプレート引数を省略し、 第1テンプレート引数に派生クラスの名前を渡します。なお、デフォルトで第2テンプレート引数にはCWindow、 第3テンプレート引数にはウィンドウ特性としてCFrameWinTraitsが渡されます。 CFrameWinTraitsはATLのatlwin.hヘッダで次のように定義されています。

// atlwin.h内
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
    WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>    CFrameWinTraits;
			

 CMyWindowクラスではまず、フレームウィンドウ用のウィンドウクラス名登録マクロを使って、 ウィンドウクラス名、リソースID、スタイル、背景色を登録しています。 この時、リソースIDは以下のリソースで共通です。

  • ウィンドウアイコン
  • メニューバー
  • ウィンドウタイトル文字列
  • キーボードアクセラレータ
  • ツールバー

つまり、これらのリソースに共通のリソースIDを付け、そのIDをDECLARE_FRAME_WND_CLASS_EXマクロによって 登録すると、CFrameWindowImplクラスはそれらのリソースを自動的にウィンドウに設定するのです。 今回の例では、共通リソースIDとしてIDR_MAINFRAMEを使用することにしました。 そこで、アイコンリソースやウィンドウタイトルのための文字列リソースを追加し、それぞれにIDR_MAINFRAMEというIDを付けます。 また、メニューバーのためのメニューリソースIDもIDR_MAINFRAMEに変更します。



なお、今回の例ではツールバーやキーボードアクセラレータは使用しないので、そのためのリソースは追加していません。

 次に、メッセージフィルタでは、基底クラスであるCFrameWindowImplクラスの PreTranslateMessage()を呼び出します。CFrameWindowImpl::PreTranslateMessage()は、 キーボードアクセラレータ用に::TranslateAccelerator()を呼び出しています。以下はそのソースです。

// atlframe.h内
BOOL PreTranslateMessage(MSG* pMsg)
{
    if(m_hAccel != NULL && ::TranslateAccelerator(m_hWnd, m_hAccel, pMsg))
        return TRUE;
    return FALSE;
}
			

ただし、今回はキーボードアクセラレータを使用しないのであまり関係ありません。

 次に、メッセージマップに、基底クラスであるCFrameWindowImplクラスへチェーンを追加します。 これによって、WM_DESTROYメッセージに対する処理は、CMyWindowクラスではなく、 CFrameWindowImplクラスに任せることができるので、上記のソースコードからは WM_DESTROYメッセージハンドラ用のコードを削除しています。

 もちろん、自分でWM_DESTROYメッセージハンドラを用意して、 そこに自分のアプリケーション特有の処理を加えることができます。その場合、基底クラスである CFrameWindowImplクラスへメッセージを送ることを忘れてはいけません。 そうしないと、最終的に::PostQuitMessage()が呼び出されず、 プログラムが終了しないからです。 以下は自分でWM_DESTROYメッセージハンドラを用意した場合のコードです。

// MainWindow.h内
class CMyWindow : public CFrameWindowImpl<CMyWindow>,
    public CMessageFilter, public CIdleHandler, public CUpdateUI<CMyWindow>
{
    ...
    
    // メッセージマップ
    BEGIN_MSG_MAP_EX(CMyWindow)
        MSG_WM_DESTROY(OnDestroy)
        ...
        ...
        CHAIN_MSG_MAP(CFrameWindowImpl<CMyWindow>)  // CFrameWindowImplクラスへチェーン
    END_MSG_MAP()

    ...
    ...
    
    void OnDestroy(){
        // アプリケーション特有の処理
        ...
        
        // この関数呼び出しによりメッセージはチェーン先のCFrameWindowImplへ送られる
        SetMsgHandled(false);
    }
}
			

 この例ではまず、WM_DESTROYメッセージが送られてくると、 CMyWindowクラスのメッセージマップのMSG_WM_DESTROY()エントリによって CMyWindow::OnDestroy()が実行されます。 CMyWindow::OnDestroy()ではアプリケーション特有の処理をしてから SetMsgHandled(false)を呼び出します。 この呼び出しによってさらにメッセージマップ内が検索され、 WM_DESTROYメッセージは、 チェーンによって基底クラスであるCFrameWindowImplクラスへ送られます。 CFrameWindowImplクラス内ではさらにチェーンによって基底クラスであるCFrameWindowImplBaseクラスへ送られ、 最終的にCFrameWindowImplBaseクラスのWM_DESTROYメッセージハンドラで ::PostQuitMessage()が呼び出されてアプリケーションは終了します。

 _tWinMain()では、ウィンドウを作成するためにCreateEx()を使用しています。 CFrameWindowImplクラスはCreate()も用意していますが、 共通リソースIDによって自動的にリソースを設定するにはCreateEx()を使用する必要があります。