たいていのメインウィンドウにはメニューバーがありますが、
これまでの例のようにCWindowImplクラステンプレートをメインウィンドウの基底クラスとして使用すると、
毎回メニューリソースをロードしてSHCreateMenuBar()を呼び出したり、
メニューバーの高さを考慮してウィンドウサイズを調整しなければなりません。
WTLは基本的な機能を持つウィンドウを作成するために、CFrameWindowImplというクラステンプレートを
用意しています。CFrameWindowImpl(または、その基底クラスであるCFrameWindowImplBase)クラステンプレートには、
::PostQuitMessage()を呼び出すWM_DESTROYメッセージハンドラが用意されている他、
メニューバーやステータスバーを作成する関数、
キーボードアクセラレータ用のコードやメニューバーの高さを考慮してウィンドウサイズを調整するコードなどが備えられています。
また、キーボードアクセラレータなどのリソースを簡単に設定するマクロをサポートしています。
以下に示すのは、CFrameWindowImplクラステンプレートを使用してフレームウィンドウを作成する例です。
// stdafx.h
#pragma once
#define WINVER _WIN32_WCE
#define _SECURE_ATL 1
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>
#include <aygshell.h>
#pragma comment(lib, "aygshell.lib")
#include <atlcrack.h>
#include <atlmisc.h>
#include <atlframe.h> // CFrameWindowImplを使用するため
|
// MainWindow.h
#pragma once
class CMainWindow : public CFrameWindowImpl<CMainWindow>,
public CMessageFilter, public CIdleHandler
{
public:
// ウィンドウクラス名、共通リソースID、スタイル、背景色を登録
DECLARE_FRAME_WND_CLASS_EX(_T("SampleProject"), IDR_MAINFRAME,
CS_HREDRAW | CS_VREDRAW, 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_RMENU_VERSION, OnRMenuVersion)
COMMAND_ID_HANDLER_EX(ID_LMENU_CLOSE, OnLMenuClose)
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){
// メニューバー作成
CreateSimpleCEMenuBar(IDR_MAINFRAME, SHCMBF_HMENU);
// メッセージループにメッセージフィルタとアイドルハンドラを追加
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 OnRMenuVersion(UINT uNotifyCode, int nID, CWindow wndCtl){
MessageBox(_T("SampleProject"));
}
void OnLMenuClose(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);
TCHAR szAppName[] = _T("SampleProject");
HWND hWnd = FindWindow(szAppName, szAppName);
if(hWnd){
SetForegroundWindow((HWND)((ULONG) hWnd | 0x00000001));
return 0;
}
_Module.Init(NULL, hInstance);
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
CMainWindow wnd;
wnd.CreateEx();
wnd.ShowWindow(nCmdShow);
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>
CFrameWinTraits;
|
CMainWindowクラスではまず、DECLARE_FRAME_WND_CLASS_EXマクロを使用して、
ウィンドウクラス名、リソースID、スタイル、背景色を登録します。
この時、以下のリソースで共通のリソースIDを設定すると、
CFrameWindowImplはそれらのリソースを自動的にウィンドウに設定します。
- ウィンドウタイトル文字列
- キーボードアクセラレータ
- ツールバー
メインウィンドウの基底クラスにCWindowImplクラステンプレートを使用していた場合は、
ウィンドウクラス名や背景色など属性を指定するためにDECLARE_WND_CLASSなどのマクロを使用しましたが、
メインウィンドウの基底クラスにCFrameWindowImplクラステンプレートを使用するとDECLARE_FRAME_WND_CLASS_EXマクロを含め次のようなマクロが使用できます。
これらのマクロはpublic宣言で使用しなければなりません。
これらのマクロを使ってウィンドウクラス名を明示的に登録しない、
またはウィンドウクラス名としてNULLを指定した場合、
WTLは "ATL:00406060" のような名前を自動的に登録します。
今回の例では、共通リソースIDとしてIDR_MAINFRAMEを使用します。
そこで、プロジェクトにリソースを追加し、それぞれにIDR_MAINFRAMEというIDを付けます。
| リソース名 |
ID |
Caption |
| 文字列 |
IDR_MAINFRAME |
SampleProject |
| メニュー |
IDR_MAINFRAME |
- |
| メニューアイテム(トップレベル) |
ID_LMENU_CLOSE |
閉じる |
| メニューアイテム(トップレベル) |
- |
メニュー |
| メニューアイテム |
ID_RMENU_VERSION |
バージョン情報 |

なお、今回の例ではツールバーやキーボードアクセラレータは使用しないので、そのためのリソースは追加しません。
次に、メッセージフィルタでは、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;
}
|
次に、WM_CREATEメッセージハンドラでCreateSimpleCEMenuBar()を呼び出してメニューバーを作成します。
CreateSimpleCEMenuBar()の第1引数にはメニューリソースIDを指定し、
第2引数以降にはSHMENUBARINFO構造体のdwFlags、nBmpId、cBmpImagesの値を指定可能です。
作成されたメニューバーのハンドルは、CFrameWindowImplクラスの基底クラスであるCFrameWindowImplBaseのm_hWndCECommandBarというHWND型のメンバ変数に代入されます。
フレームウィンドウはメニューバーに対する操作を、このm_hWndCECommandBarで識別されるハンドルに対して行います。
最後に、_tWinMain()でウィンドウを作成するためにCreateEx()を使用します。
CFrameWindowImplはCreate()も用意していますが、
共通リソースIDによって自動的にリソースを設定するにはCreateEx()を使用する必要があります。
|