はじめに
前回は、ダイアログおよびコントロールを作成しました。今回は、フレームウィンドウとビューウィンドウについて説明します。
対象読者
ATL/WTLによるWindowsプログラミングに興味があり、C++やWin32APIによるWindowsプログラミングの基本的な知識がある方。
必要な環境
サンプルはVisual C++ 6.0で作成し、Windows2000で動作確認しています。
フレームウィンドウ
第1回では、CWindowImpl
の派生クラスからウィンドウを作成しましたが、WTLには基本的なウィンドウの機能を持つCFrameWindowImpl
が用意されています。
CFrameWindowImpl
(または、その基底クラスであるCFrameWindowImplBase
)クラスには、WM_DESTROY
メッセージ用のデフォルトのハンドラが用意されている他、ウィンドウサイズが変更されたときに自動的にレイアウトを調整するWM_SIZE
メッセージハンドラ、ツールバーやステータスバーを作成する関数、キーボードアクセラレータ用のコードやツールチップ用のメッセージハンドラなどが備えられています。また、メニューバーなどのリソースを簡単に設定するマクロをサポートしています。
今回はまず、第1回で作成したプログラムの基底クラスを、CWindowImpl
からCFrameWindowImpl
へ変更して書き換えてみます。また、メニューバーやツールバー、ステータスバーなども追加します。なお、このプログラムはATL/WTL AppWizardを使用せずWin32Applicationプロジェクトでビルドします。
#include <atlbase.h> #include <atlapp.h> extern CAppModule _Module; #include <atlwin.h> #include <atlcrack.h> #include <atlmisc.h> #include <atlframe.h> // CFrameWindowImplクラスを使用するため
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CMessageFilter, public CIdleHandler { public: // ウィンドウクラス名、共通リソースID、スタイル、背景色を登録 DECLARE_FRAME_WND_CLASS_EX(_T("Project03"), IDR_MAINFRAME, CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, COLOR_WINDOW) private: // メッセージフィルタ処理 virtual BOOL PreTranslateMessage(MSG* pMsg){ // 基底クラスのPreTranslateMessageを呼び出す return CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg); } // アイドル処理 virtual BOOL OnIdle(){ return FALSE; } // メッセージマップ BEGIN_MSG_MAP_EX(CMainFrame) MSG_WM_PAINT(OnPaint) MSG_WM_CREATE(OnCreate) COMMAND_ID_HANDLER_EX(ID_MENUITEM_EXIT, OnMenuExit) // CFrameWindowImplクラスへチェーン CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) 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); } LRESULT OnCreate(LPCREATESTRUCT lpcs){ // ツールバーを作成 CreateSimpleToolBar(); // ステータスバーを作成 CreateSimpleStatusBar(); // メッセージループにメッセージフィルタとアイドルハンドラを追加 CMessageLoop* pLoop = _Module.GetMessageLoop(); pLoop->AddMessageFilter(this); pLoop->AddIdleHandler(this); return 0; } void OnMenuExit(UINT uNotifyCode, int nID, HWND hWndCtl){ PostMessage(WM_CLOSE); } };
#include "stdafx.h" #include "resource.h" #include "mainfrm.h" CAppModule _Module; int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow) { _Module.Init(NULL, hInstance); CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop); // 独自ウィンドウを作成 CMainFrame wnd; wnd.CreateEx(); wnd.ShowWindow(nCmdShow); wnd.UpdateWindow(); int nRet = theLoop.Run(); _Module.RemoveMessageLoop(); _Module.Term(); return nRet; }
主な変更点は以下のとおりです。
- 「atlframe.h」をインクルード
- 基底クラスを
CWindowImpl
からCFrameWindowImpl
へ変更 DECLARE_FRAME_WND_CLASS_EX
マクロでウィンドウクラスを登録- メッセージフィルタで
CFrameWindowImpl<>::PreTranslateMessage()
呼び出し - メッセージマップで
CFrameWindowImpl
クラスへチェーン OnCreate()
でツールバーとステータスバーを作成
CFrameWindowImpl
CFrameWindowImpl
を使用するためには、WTLの「atlframe.h」ヘッダをインクルードする必要があります。
CFrameWindowImpl
は、CWindowImpl
と同様に3つのテンプレート引数を持ちます。今回の例では省略可能な第2、3テンプレート引数を省略し、第1テンプレート引数に派生クラスの名前を渡します。なお、デフォルトで第2テンプレート引数にはCWindow
、第3テンプレート引数にはウィンドウ特性としてCFrameWinTraits
が渡されます。 CFrameWinTraits
はATLの「atlwin.h」ヘッダで次のように定義されています。
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE>
CFrameWinTraits;
ウィンドウクラス情報
WTLには、フレームウィンドウ用のウィンドウクラス名登録マクロが用意されています。
DECLARE_FRAME_WND_CLASS(ウィンドウクラス名, 共通リソースID)
DECLARE_FRAME_WND_CLASS_EX(ウィンドウクラス名, 共通リソースID, スタイル, 背景色)
CFrameWndClassInfo
構造体
CFrameWndClassInfo
構造体は、ウィンドウクラス名マクロよりもウィンドウの属性を細かく指定できます。CWndClassInfo
を使用して新しい属性を指定するためには、CWindowImpl::GetWndClassInfo()
をオーバーライドして、カスタマイズした静的なCWndClassInfo
インスタンスの参照を返します。
class CMainFrame : public CFrameWindowImpl<CMainFrame> { public: static WTL::CFrameWndClassInfo& GetWndClassInfo() { static WTL::CFrameWndClassInfo wc = { {sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, StartWindowProc, 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, "proj03", NULL}, // WNDCLASSEX構造体 NULL, // 既存のウィンドウクラス名 NULL, // 既存のウィンドウプロシージャ MAKEINTRESOURCE(IDC_CURSOR1), // カーソルリソース名 FALSE, // システムカーソルならばTRUE、それ以外はFALSE 0, // 登録済みウィンドウクラスの識別子 _T("") // ATLが自動生成したウィンドウクラス名 IDR_MAINFRAME // 共通リソースID }; return wc; } ... };
これらのマクロや構造体で指定する共通リソースIDとは、フレームウィンドウで使用するリソースに共通で付けるIDです。CFrameWindowImpl
は、このIDのリソースを自動的にウィンドウに設定します。
共通リソースIDを設定できるのは以下のリソースです。
- メニューバー
- ツールバー
- ウィンドウアイコン
- ウィンドウタイトル文字列
- キーボードアクセラレータ
今回の例では以下のリソースを追加し、共通リソースIDとしてIDR_MAINFRAME
というIDを設定します。
リソース名 | リソースID | 説明 |
メニュー | IDR_MAINFRAME | メニューバー |
ツールバー | IDR_MAINFRAME | ツールバー |
アイコン | IDR_MAINFRAME | ウィンドウアイコン |
文字列 | IDR_MAINFRAME | ウィンドウタイトル文字列 |
メニューリソースには[ファイル]-[アプリケーションの終了]メニューアイテムを追加し、IDをID_MENUITEM_EXIT
に設定します。同じくツールバーにもボタンを一つ追加し、IDをID_MENUITEM_EXIT
に設定します。
メッセージ
メッセージフィルタでCFrameWindowImpl
のメンバ関数であるPreTranslateMessage()
を呼び出します。この関数ではキーボードアクセラレータ用に::TranslateAccelerator()
を呼び出していますが、今回はキーボードアクセラレータを使用しないのであまり関係ありません。
BOOL PreTranslateMessage(MSG* pMsg) { if(m_hAccel != NULL && ::TranslateAccelerator(m_hWnd, m_hAccel, pMsg)) return TRUE; return FALSE; }
次に、メッセージマップに基底クラスであるCFrameWindowImpl
へチェーンを追加します。
チェーンとは、あるメッセージマップを別のメッセージマップに繋ぐ仕組みです。今回の例ではCHAIN_MSG_MAP()
マクロで基底クラスのメッセージマップに繋いでいます。こうすることにより、派生クラスでメッセージハンドラが見つからない場合は、基底クラスのメッセージマップを検索するようになります。
CFrameWindowImpl
はWM_DESTROY
メッセージハンドラを用意しており、最終的に::PostQuitMessage()
を呼び出すようになっています。チェーンを利用することによって、CMainFrame
クラスでWM_DESTROY
メッセージを処理しなくとも、CFrameWindowImpl
のWM_DESTROY
メッセージハンドラに処理を任せることができます。
ツールバーとステータスバー
CFrameWindowImpl
は、ツールバーやステータスバーを簡単に作成するメンバ関数を用意しています。
ツールバー
フレームウィンドウにツールバーを追加するにはCreateSimpleToolBar()
を呼び出します。CreateSimpleToolBar()
には3つの引数を渡すことができます。第1引数にはツールバーリソースIDを指定します。デフォルト引数は0
で、この場合は共通リソースID(今回の例ではIDR_MAINFRAME
)が使用されます。第2引数にはツールバーのスタイルを指定します。デフォルト引数はATL_SIMPLE_TOOLBAR_STYLE
で、これは「atlframe.h」ヘッダで次のように定義されています。
#define ATL_SIMPLE_TOOLBAR_STYLE \
(WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS | TBSTYLE_TOOLTIPS)
第3引数には識別子を指定します。デフォルト引数はATL_IDW_TOOLBAR
です。
CreateSimpleToolBar()
は、内部でCreateSimpleToolBarCtrl()
というメンバ関数を呼び出しており、その呼び出しによって得られたツールバーのハンドルを、CFrameWindowImpl
の基底クラスであるCFrameWindowImplBase
クラスのm_hWndToolBar
というHWND
型のメンバ変数に代入します。
ステータスバー
フレームウィンドウにステータスバーを追加するにはCreateSimpleStatusBar()
を呼び出します。CreateSimpleStatusBar()
には2つバージョンがあります。両方とも3つの引数を指定できますが、一方は、第1引数にアイドル時に表示する文字列を指定するもので、他方は、第1引数にアイドル時に表示する文字列リソースのIDを指定するものです。後者はすべての引数を省略可能です。今回はすべての引数を省略したので後者が使用されます。この場合、第1引数にはデフォルト引数としてATL_IDS_IDLEMESSAGE
が指定されます。文字列リソースでATL_IDS_IDLEMESSAGE
に文字列を割り当てると、アイドル時にその文字列がステータスバーに表示されます。両バージョンとも第2引数はステータスバーのスタイルです。デフォルト引数は次のスタイルです。
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | SBARS_SIZEGRIP
両バージョンとも第3引数は識別子です。デフォルト引数はATL_IDW_STATUS_BAR
です。
どちらのバージョンのCreateSimpleStatusBar()
も、結果的に内部で::CreateStatusWindow()
を呼び出しており、その呼び出しによって得られたステータスバーのハンドルを、CFrameWindowImpl
の基底クラスであるCFrameWindowImplBase
クラスのm_hWndStatusBar
というHWND
型のメンバ変数に代入します。
ビュー
ビューウィンドウとは、フレームウィンドウのクライアント領域を覆うようにして表示される子ウィンドウのことです。CFrameWindowImpl
はビューウィンドウをサポートする仕組みを備えています。
次に示すのは、リストビューコントロールを元にビューウィンドウを作成する例です。システムで利用可能なフォント名の一覧をビューウィンドウに表示します。また、指定したフォントファイルを一時的に利用可能にする機能も持ちます。
#include <atlbase.h> #include <atlapp.h> extern CAppModule _Module; #include <atlwin.h> #include <atlcrack.h> #include <atlmisc.h> #include <atlctrls.h> // リストビューコントロールを使用するため #include <atldlgs.h> // ファイル選択ダイアログを使用するため #include <atlframe.h>
class CFontView : public CWindowImpl<CFontView, CListViewCtrl> { public: DECLARE_WND_SUPERCLASS(NULL, CListViewCtrl::GetWndClassName()) BOOL PreTranslateMessage(MSG* pMsg){ return FALSE; } // メッセージマップ BEGIN_MSG_MAP_EX(CFontView) MSG_WM_CREATE(OnCreate) END_MSG_MAP() LRESULT OnCreate(LPCREATESTRUCT lpcs){ LRESULT lRet = DefWindowProc(); SetFont(AtlGetDefaultGuiFont()); SetExtendedListViewStyle(LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT); // リストビューにカラム挿入 CRect rcList; ::GetClientRect(GetParent(), rcList); int nScrollWidth = GetSystemMetrics(SM_CXVSCROLL); int n3DEdge = GetSystemMetrics(SM_CXEDGE); InsertColumn(0, _T("フォント名"), LVCFMT_LEFT, 190, -1); InsertColumn(1, _T("種類"), LVCFMT_LEFT, rcList.Width() - 190 - nScrollWidth - n3DEdge * 2, -1); FillList(); return lRet; } void FillList(){ DeleteAllItems(); CClientDC dc(m_hWnd); EnumFontFamilies(dc, NULL, (FONTENUMPROC)FontProc, (LPARAM)this); } static int CALLBACK FontProc(ENUMLOGFONT *lpelf, NEWTEXTMETRIC *lpntm, int nFontType, LPARAM lParam) { CFontView* pList = (CFontView*)lParam; // フォント名追加 int nIndex = pList->InsertItem( pList->GetItemCount(), lpelf->elfLogFont.lfFaceName); if(nFontType & RASTER_FONTTYPE){ pList->SetItemText(nIndex, 1, _T("ラスタフォント")); }else if(nFontType & TRUETYPE_FONTTYPE){ pList->SetItemText(nIndex, 1, _T("TrueTypeフォント")); }else{ pList->SetItemText(nIndex, 1, _T("ベクタフォント")); } return 1; } };
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CMessageFilter, public CIdleHandler { public: DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) CFontView m_view; // メッセージフィルタ処理 virtual BOOL PreTranslateMessage(MSG* pMsg){ if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg)) return TRUE; return m_view.PreTranslateMessage(pMsg); } // アイドル処理 virtual BOOL OnIdle(){ return FALSE; } // メッセージマップ BEGIN_MSG_MAP_EX(CMainFrame) MSG_WM_FONTCHANGE(OnFontChange) MSG_WM_CREATE(OnCreate) COMMAND_ID_HANDLER_EX(ID_BUTTON_ADDFONT, OnButtonAddfont) COMMAND_ID_HANDLER_EX(ID_MENUITEM_EXIT, OnMenuExit) // CFrameWindowImplクラスへチェーン CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) END_MSG_MAP() void OnFontChange(){ // フォント名リスト更新 m_view.FillList(); } LRESULT OnCreate(LPCREATESTRUCT lpcs){ // ツールバーを作成 CreateSimpleToolBar(); // ステータスバーを作成 CreateSimpleStatusBar(); // ビューを作成 m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | LVS_REPORT | LVS_SHOWSELALWAYS, WS_EX_CLIENTEDGE); // メッセージループにメッセージフィルタとアイドルハンドラを追加 CMessageLoop* pLoop = _Module.GetMessageLoop(); pLoop->AddMessageFilter(this); pLoop->AddIdleHandler(this); return 0; } void OnButtonAddfont(UINT uNotifyCode, int nID, HWND hWndCtl){ CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_CREATEPROMPT, _T("TrueTypeフォントファイル (*.ttf)\0*.ttf\0すべてのファイル (*.*)\0*.*\0\0")); if(dlg.DoModal() == IDOK){ // システムに一時的にフォント追加 AddFontResource(dlg.m_szFileName); ::SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); } } void OnMenuExit(UINT uNotifyCode, int nID, HWND hWndCtl){ PostMessage(WM_CLOSE); } };
#include "stdafx.h" #include "resource.h" #include "view.h" #include "mainfrm.h" CAppModule _Module; int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpCmdLine, int nCmdShow) { // コモンコントロール初期化 AtlInitCommonControls(ICC_WIN95_CLASSES | ICC_COOL_CLASSES); _Module.Init(NULL, hInstance); CMessageLoop theLoop; _Module.AddMessageLoop(&theLoop); // 独自ウィンドウを作成 CMainFrame wnd; wnd.CreateEx(); wnd.ShowWindow(nCmdShow); wnd.UpdateWindow(); int nRet = theLoop.Run(); _Module.RemoveMessageLoop(); _Module.Term(); return nRet; }
ビューウィンドウ
まずはビューウィンドウのためのクラスを定義します。上の例ではプロジェクトに「view.h」というヘッダファイルを追加し、そこにCWindowImpl
クラスから派生したCFontView
というクラスを定義しています。CWindowImpl
クラスの第2引数には、ベースとなるCListViewCtrl
クラスを指定します。これにより、CFontView
はCListViewCtrl
の派生クラスとなります。
CFontView
クラス内では、まずDECLARE_WND_SUPERCLASS()
マクロを使ってリストビューコントロールをスーパークラス化します。スーパークラス化とは、定義済みのウィンドウクラス("BUTTON"など)を拡張する新しいウィンドウクラスを定義することです。DECLARE_WND_SUPERCLASS()
の第1引数には新しいウィンドウクラス名を指定し、第2引数には元になるウィンドウクラス名を指定します。今回の例では第1引数にはNULL
を指定して名前を任意名にし、第2引数にはCListViewCtrl::GetWndClassName()
を指定しています。これにより、CFontView
はリストビューコントロールに基づくウィンドウクラスになります。
CFontView
クラスでは、次にWM_CREATE
メッセージハンドラを追加します。WM_CREATE
メッセージハンドラでは始めにDefWindowProc()
を呼び出し、デフォルトのGUIフォントを設定してからカラムを2つ挿入してシステムで利用可能なフォント名を列挙します。
フレームウィンドウ
フレームウィンドウであるCMainFrame
クラス内では、ビューウィンドウであるCFontView
クラスのインスタンスをメンバ変数として宣言します。OnCreate()
ではこのビューウィンドウを作成し、CFrameWindowImpl
の基底クラスであるCFrameWindowImplBase
クラスのm_hWndClient
というHWND
型のメンバ変数に代入します。
CFrameWindowImpl
にはWM_SIZE
メッセージハンドラが用意されています。このため、メインウィンドウのサイズを変更したときのWM_SIZE
メッセージがチェーン先のCFrameWindowImpl
クラスに送られると、CFrameWindowImpl
クラスのWM_SIZE
メッセージハンドラが呼び出されます。このハンドラではUpdateLayout()
という関数を呼び出しており、この関数はm_hWndToolBar
で識別されるツールバーやm_hWndStatusBar
で識別されるステータスバー、m_hWndClient
で識別されるビューウィンドウを自動的にフレームウィンドウのサイズに合わせます。
また、CMainFrame
クラスのメッセージフィルタでは、CFontView
クラスの独自メッセージフィルタ関数を呼び出しています。これにより、ビューウィンドウにもメッセージフィルタリングの機会を与えています。
フォント追加
今回のプログラムは、システムにインストールされていないフォントを一時的に利用可能にする機能を持っています。この機能を実現するためには、まずツールバーにボタンを一つ追加し、そのボタンのハンドラ関数(OnButtonAddfont()
)でファイル選択ダイアログを開き、選択されたフォントファイルをAddFontResource()
でシステムに追加します。なお、WTLのファイル選択ダイアログクラスCFileDialog
を使用するために「atldlgs.h」をインクルードします。「atldlgs.h」にはコモンダイアログをサポートするクラスが定義されています。
次に、hwnd
にHWND_BROADCAST
をセットしてWM_FONTCHANGE
メッセージを送信します。これにより他のウィンドウや自ウィンドウに利用可能なフォントが変更されたことを示すWM_FONTCHANGE
メッセージが送られます。CMainFrame
クラスではWM_FONTCHANGE
メッセージハンドラ関数(OnFontChange()
)を用意し、ビューウィンドウのFillList()
を呼び出してフォント名リストを更新します。
最後に
本稿では、ATL/WTLでフレームウィンドウとビューウィンドウを作成しました。これまで3回に渡り、ATL/WTLのウィンドウやダイアログ、コントロール、フレームウィンドウ、ビューウィンドウなど、WindowsのGUIプログラムを構成する基本的な部品について、大雑把ではありますが説明してきました。次回からはWTLのより高度なGUIクラス(ダイアログリサイズなど)を扱いたいと思います。