たいていのメインウィンドウにはメニューバーがあり、
WM_DESTROYメッセージハンドラでは::PostQuitMessage()を呼び出します。
ところが、これまでの例のようにCWindowImplクラステンプレートをメインウィンドウの基底クラスとして使用すると、
毎回CWndClassInfo構造体をカスタマイズしてメニューリソースを設定したり、
WM_DESTROYメッセージハンドラで::PostQuitMessage()を呼び出さなければなりません。
WTLは基本的な機能を持つウィンドウを作成するために、CFrameWindowImplというクラステンプレートを
用意しています。CFrameWindowImpl(または、その基底クラスであるCFrameWindowImplBase)クラステンプレートには、
::PostQuitMessage()を呼び出すWM_DESTROYメッセージハンドラが用意されている他、
ウィンドウサイズが変更されたときに自動的にレイアウトを調整するWM_SIZEメッセージハンドラ、
ツールバーやステータスバーを作成する関数、キーボードアクセラレータ用のコードやツールチップ用の
メッセージハンドラなどが備えられています。また、メニューバーなどのリソースを簡単に設定するマクロをサポートしています。
以下に示すのは、CFrameWindowImplクラステンプレートを使用してフレームウィンドウを作成する例です。
// stdafx.h
#pragma once
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>
#include <atlcrack.h>
#include <atlmisc.h>
#include <atlframe.h> // CFrameWindowImplを使用するため
|
// MainWindow.h
#pragma once
// メインウィンドウの基底クラスをCFrameWindowImplに変更
class CMainWindow : public CFrameWindowImpl<CMainWindow>,
public CMessageFilter, public CIdleHandler
{
public:
// ウィンドウクラス名、共通リソースID、スタイル、背景色を登録
DECLARE_FRAME_WND_CLASS_EX(_T("Hello"), IDR_MAINFRAME,
CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, COLOR_WINDOW)
virtual BOOL PreTranslateMessage(MSG* pMsg){
// 基底クラスのPreTranslateMessageを呼び出す
return CFrameWindowImpl<CMainWindow>::PreTranslateMessage(pMsg);
}
virtual BOOL OnIdle(){
return FALSE;
}
BEGIN_MSG_MAP(CMainWindow)
MSG_WM_PAINT(OnPaint)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
COMMAND_ID_HANDLER_EX(ID_APP_EXIT, OnFileExit)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainWindow>) // CFrameWindowImplへチェイン
END_MSG_MAP()
void OnPaint(CDCHandle /*dc*/){
CPaintDC dc(m_hWnd);
CRect rect;
GetClientRect(rect);
dc.DrawText(_T("Hello, ATL/WTL"), -1,
rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
}
int OnCreate(LPCREATESTRUCT lpCreateStruct){
// メッセージループにメッセージフィルタとアイドルハンドラを追加
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
return 0;
}
void OnDestroy(){
// メッセージループからメッセージフィルタとアイドルハンドラを削除
CMessageLoop* pLoop = _Module.GetMessageLoop();
pLoop->RemoveMessageFilter(this);
pLoop->RemoveIdleHandler(this);
SetMsgHandled(false);
}
void OnFileExit(UINT uNotifyCode, int nID, CWindow wndCtl){
PostMessage(WM_CLOSE);
}
};
|
// SampleProject.cpp
#include "stdafx.h"
#include "resource.h"
#include "MainWindow.h"
CAppModule _Module;
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
_Module.Init(NULL, hInstance);
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
CMainWindow wnd;
wnd.CreateEx();
wnd.ShowWindow(nCmdShow);
wnd.UpdateWindow();
int nRet = theLoop.Run();
_Module.RemoveMessageLoop();
_Module.Term();
return nRet;
}
|
まず、stdafx.hヘッダではCFrameWindowImplクラステンプレートを使用するためにatlframe.hヘッダをインクルードします。
次に、メインウィンドウであるCMainWindowクラスの基底クラスを、
これまでのCWindowImplからCFrameWindowImplに変更します。
CFrameWindowImplクラステンプレートもCWindowImplクラステンプレートと同様に、
3つのテンプレート引数を持ちます。ここでは第1テンプレート引数に派生クラスの名前を渡し、
省略可能な第2、第3テンプレート引数を省略します。
なお、デフォルトで第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;
|
CMainWindowクラスではまず、フレームウィンドウ用のウィンドウクラス名登録マクロを使って、
ウィンドウクラス名、リソースID、スタイル、背景色を登録します。
この時、リソースIDは以下のリソースで共通とします。
- ウィンドウアイコン
- メニューバー
- ウィンドウタイトル文字列
- キーボードアクセラレータ
- ツールバー
これらのリソースに共通のリソースIDを付け、そのリソースIDをDECLARE_FRAME_WND_CLASS_EXマクロによって
登録すると、CFrameWindowImplはそれらのリソースを自動的にウィンドウに設定します。
今回の例では、共通リソースIDとしてIDR_MAINFRAMEを使用します。
そこで、プロジェクトにメニューやアイコン、ウィンドウタイトルのための文字列リソースを追加し、それぞれにIDR_MAINFRAMEというIDを付けます。
| リソース名 |
ID |
Caption |
Prompt |
| アイコン |
IDR_MAINFRAME |
- |
- |
| 文字列 |
IDR_MAINFRAME |
Hello, ATL/WTL |
- |
| メニュー |
IDR_MAINFRAME |
- |
- |
| メニューアイテム(トップレベル) |
- |
ファイル(&F) |
- |
| メニューアイテム |
ID_APP_EXIT |
終了(&X) |
アプリケーションの終了\n終了 |

なお、今回の例ではツールバーやキーボードアクセラレータは使用しないので、そのためのリソースは追加しません。
次に、メッセージフィルタでは、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メッセージハンドラでSetMsgHandled(false)を呼び出します。
これによって、まず、メインウィンドウにWM_DESTROYメッセージが送られてくると、
CMainWindowクラスのメッセージマップのMSG_WM_DESTROY()エントリによって
CMainWindow::OnDestroy()が実行されます。
CMainWindow::OnDestroy()ではメッセージループからメッセージフィルタとアイドルハンドラを削除してから
SetMsgHandled(false)を呼び出します。
この呼び出しによってさらにメッセージマップ内が検索され、
WM_DESTROYメッセージは、
チェインによって基底クラスであるCFrameWindowImplへ送られます。
CFrameWindowImplではさらにチェインによって基底クラスであるCFrameWindowImplBaseへ送られ、
最終的にCFrameWindowImplBaseのWM_DESTROYメッセージハンドラで
::PostQuitMessage()が呼び出されてアプリケーションは終了します。
以下に示すのはCFrameWindowImplBaseのWM_DESTROYメッセージハンドラのソースです。
// atlframe.h
LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& bHandled)
{
if((GetStyle() & (WS_CHILD | WS_POPUP)) == 0)
::PostQuitMessage(1);
bHandled = FALSE;
return 1;
}
|
_tWinMain()では、ウィンドウを作成するためにCreateEx()を使用します。
CFrameWindowImplはCreate()も用意していますが、
共通リソースIDによって自動的にリソースを設定するにはCreateEx()を使用する必要があります。
|