Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

ATL/WTLプログラミング 3:フレームウィンドウとビュー

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

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/11/29 12:00

Windowsアプリケーションを作成するためのC++クラスライブラリといえば、Microsoftが提供するMFCが有名ですが、同社が提供するライブラリATLを利用して作成することもできます。本稿では、ATL/WTLにおけるフレームウィンドウとビューの利用方法について解説します。

はじめに

 前回は、ダイアログおよびコントロールを作成しました。今回は、フレームウィンドウとビューウィンドウについて説明します。

対象読者

 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プロジェクトでビルドします。

CFrameWindowImpl版「Hello, ATL/WTL」プログラム
CFrameWindowImpl版「Hello, ATL/WTL」プログラム
stdafx.h
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlframe.h> // CFrameWindowImplクラスを使用するため
mainfrm.h
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);
    }
};
proj03.cpp
#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」ヘッダで次のように定義されています。

CFrameWinTraitsの定義
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インスタンスの参照を返します。

CFrameWndClassInfo構造体の使用例
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()を呼び出していますが、今回はキーボードアクセラレータを使用しないのであまり関係ありません。

CFrameWindowImplのPreTranslateMessage()定義
BOOL PreTranslateMessage(MSG* pMsg)
{
    if(m_hAccel != NULL &&
        ::TranslateAccelerator(m_hWnd, m_hAccel, pMsg))
        return TRUE;
    return FALSE;
}

 次に、メッセージマップに基底クラスであるCFrameWindowImplへチェーンを追加します。

 チェーンとは、あるメッセージマップを別のメッセージマップに繋ぐ仕組みです。今回の例ではCHAIN_MSG_MAP()マクロで基底クラスのメッセージマップに繋いでいます。こうすることにより、派生クラスでメッセージハンドラが見つからない場合は、基底クラスのメッセージマップを検索するようになります。

 CFrameWindowImplWM_DESTROYメッセージハンドラを用意しており、最終的に::PostQuitMessage()を呼び出すようになっています。チェーンを利用することによって、CMainFrameクラスでWM_DESTROYメッセージを処理しなくとも、CFrameWindowImplWM_DESTROYメッセージハンドラに処理を任せることができます。

ツールバーとステータスバー

 CFrameWindowImplは、ツールバーやステータスバーを簡単に作成するメンバ関数を用意しています。

ツールバー

 フレームウィンドウにツールバーを追加するにはCreateSimpleToolBar()を呼び出します。CreateSimpleToolBar()には3つの引数を渡すことができます。第1引数にはツールバーリソースIDを指定します。デフォルト引数は0で、この場合は共通リソースID(今回の例ではIDR_MAINFRAME)が使用されます。第2引数にはツールバーのスタイルを指定します。デフォルト引数はATL_SIMPLE_TOOLBAR_STYLEで、これは「atlframe.h」ヘッダで次のように定義されています。

ATL_SIMPLE_TOOLBAR_STYLE定義
#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はビューウィンドウをサポートする仕組みを備えています。

 次に示すのは、リストビューコントロールを元にビューウィンドウを作成する例です。システムで利用可能なフォント名の一覧をビューウィンドウに表示します。また、指定したフォントファイルを一時的に利用可能にする機能も持ちます。

フォント名一覧プログラム
フォント名一覧プログラム
stdafx.h
#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>
view.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;
    }
};
mainfrm.h
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);
    }
};
proj03.cpp
#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クラスを指定します。これにより、CFontViewCListViewCtrlの派生クラスとなります。

 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」にはコモンダイアログをサポートするクラスが定義されています。

 次に、hwndHWND_BROADCASTをセットしてWM_FONTCHANGEメッセージを送信します。これにより他のウィンドウや自ウィンドウに利用可能なフォントが変更されたことを示すWM_FONTCHANGEメッセージが送られます。CMainFrameクラスではWM_FONTCHANGEメッセージハンドラ関数(OnFontChange())を用意し、ビューウィンドウのFillList()を呼び出してフォント名リストを更新します。

最後に

 本稿では、ATL/WTLでフレームウィンドウとビューウィンドウを作成しました。これまで3回に渡り、ATL/WTLのウィンドウやダイアログ、コントロール、フレームウィンドウ、ビューウィンドウなど、WindowsのGUIプログラムを構成する基本的な部品について、大雑把ではありますが説明してきました。次回からはWTLのより高度なGUIクラス(ダイアログリサイズなど)を扱いたいと思います。

参考資料

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


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

バックナンバー

連載:ATL/WTLを利用したVisual C++のWindowsプログラミング
All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5