SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

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

ATL/WTLプログラミング 5:スプリッタウィンドウ

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


  • X ポスト
  • このエントリーをはてなブックマークに追加

メインウィンドウ

 以下に、前述の二つのビューウィンドウを使用するプログラム全体のソースコードを示します。

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

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlctrls.h>
#include <atlctrlw.h>  // コマンドバーを使用するため
#include <atlframe.h>
#include <atlsplit.h>  // スプリッタウィンドウを使用するため
mainfrm.h
class CMainFrame : public CFrameWindowImpl<CMainFrame>,
    public CUpdateUI<CMainFrame>,
    public CMessageFilter, public CIdleHandler
{
public:
    DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)

    CSplitterWindow  m_wndSplitter;      // スプリッタウィンドウ
    CFontListView m_viewFontList;        // 左ペイン
    CFontPreviewView m_viewFontPreview;  // 右ペイン

    CCommandBarCtrl m_CmdBar;            // コマンドバー

    virtual BOOL PreTranslateMessage(MSG* pMsg){
        if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
            return TRUE;

        // 左ペインのPreTranslateMessageを呼び出す
        if(m_viewFontList.PreTranslateMessage(pMsg))
            return TRUE;

        // 右ペインのPreTranslateMessageを呼び出す
        return m_viewFontPreview.PreTranslateMessage(pMsg);
    }

    virtual BOOL OnIdle(){
        UIUpdateToolBar();
        UIUpdateStatusBar();

        CString strCount;
        strCount.Format(_T("フォント数:%d"),
        m_viewFontList.GetCount());
        CStatusBarCtrl bar = m_hWndStatusBar;
        bar.SetText(1, strCount);

        return FALSE;
    }

    BEGIN_UPDATE_UI_MAP(CMainFrame)
        UPDATE_ELEMENT(ID_MENUITEM_CHANGEVIEW,
            UPDUI_MENUPOPUP | UPDUI_TOOLBAR)

    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP_EX(CMainFrame)
        MSG_WM_CREATE(OnCreate)
        MSG_WM_SIZE(OnSize)
        COMMAND_CODE_HANDLER_EX(LBN_SELCHANGE, OnListSelChange)
        COMMAND_ID_HANDLER_EX(ID_MENUITEM_CHANGEVIEW,
                              OnMenuChangeView)
        COMMAND_ID_HANDLER_EX(ID_APP_EXIT, OnFileExit)
        CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
        CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
    END_MSG_MAP()

    LRESULT OnCreate(LPCREATESTRUCT lpcs){
        // コマンドバーを作成
        HWND hWndCmdBar = m_CmdBar.Create(m_hWnd, rcDefault, NULL,
            ATL_SIMPLE_CMDBAR_PANE_STYLE);

        // コマンドバーに現在のメニューバーのアイテムと画像をセット
        m_CmdBar.AttachMenu(GetMenu());
        m_CmdBar.LoadImages(IDR_MAINFRAME);

        // メニューバーを削除
        SetMenu(NULL);

        // ツールバーを作成
        HWND hWndToolBar = CreateSimpleToolBarCtrl(m_hWnd,
            IDR_MAINFRAME, FALSE, ATL_SIMPLE_TOOLBAR_PANE_STYLE);

        // リバーを作成
        CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);

        // リバーのバンドにコマンドバーとツールバーを追加
        AddSimpleReBarBand(hWndCmdBar);
        AddSimpleReBarBand(hWndToolBar, NULL, TRUE);

        // ステータスバーを作成
        CreateSimpleStatusBar();

        UIAddToolBar(hWndToolBar);
        UIAddStatusBar(m_hWndStatusBar);

        UISetCheck(ID_MENUITEM_CHANGEVIEW, 1);

        // スプリッタウィンドウを作成
        m_wndSplitter.Create(m_hWnd, rcDefault, NULL,
            WS_CHILD | WS_VISIBLE | 
            WS_CLIPCHILDREN | WS_CLIPSIBLINGS);

        // スプリッタウィンドウ拡張スタイルを設定
        m_wndSplitter.SetSplitterExtendedStyle(0);

        // 左ペインのビューウィンドウを作成
        m_viewFontList.Create(m_wndSplitter, rcDefault, NULL,
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
            WS_CLIPCHILDREN | WS_VSCROLL | LBS_NOINTEGRALHEIGHT |
            LBS_NOTIFY | LBS_WANTKEYBOARDINPUT | LBS_SORT,
            WS_EX_CLIENTEDGE);

        m_wndSplitter.SetSplitterPane(SPLIT_PANE_LEFT,
                                      m_viewFontList);

        // 右ペインのビューウィンドウを作成
        m_viewFontPreview.Create(m_wndSplitter, rcDefault, NULL,
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            WS_EX_CLIENTEDGE);

        m_wndSplitter.SetSplitterPane(SPLIT_PANE_RIGHT,
                                      m_viewFontPreview);

        m_hWndClient = m_wndSplitter;
        UpdateLayout();

        // 分割バーの位置を設定
        m_wndSplitter.SetSplitterPos(120);

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

        return 0;
    }

    void OnSize(UINT uType, CSize size){
        // ステータスバーにペインを設定
        if(m_hWndStatusBar != NULL){
            CStatusBarCtrl bar = m_hWndStatusBar;

            // フォント数を表示するペインの幅
            int cxPosPane = 80;

            // デフォルトペインの幅
            CRect rcClient;
            GetClientRect(rcClient);
            int cxDefaultPane = rcClient.right - cxPosPane
                - ::GetSystemMetrics(SM_CXVSCROLL)
                - ::GetSystemMetrics(SM_CXEDGE);

            int nPanes[] = {cxDefaultPane, cxDefaultPane + cxPosPane};
            bar.SetParts(sizeof(nPanes)/sizeof(nPanes[0]), nPanes);
        }

        // 基底クラスのWM_SIZEメッセージハンドラも呼び出すため
        SetMsgHandled(false);
    }

    void OnListSelChange(UINT uNotifyCode, int nID, HWND hWndCtl){
        // 現在選択されているアイテムを取得
        int nIndex = m_viewFontList.GetCurSel();
        if(nIndex != LB_ERR){
            CString strText;
            m_viewFontList.GetText(nIndex, strText);

            // 取得した文字列を右ペインに設定
            m_viewFontPreview.SetFontName(strText);
        }
    }

    void OnMenuChangeView(UINT uNotifyCode, int nID, HWND hWndCtl){
        if(m_wndSplitter.GetSinglePaneMode() == SPLIT_PANE_RIGHT){
            // 両ペイン表示
            m_wndSplitter.SetSinglePaneMode(SPLIT_PANE_NONE);
            UISetCheck(ID_MENUITEM_CHANGEVIEW, 1);
        }else{
            // 右ペインのみ表示
            m_wndSplitter.SetSinglePaneMode(SPLIT_PANE_RIGHT);
            UISetCheck(ID_MENUITEM_CHANGEVIEW, 0);
        }
    }

    void OnFileExit(UINT uNotifyCode, int nID, HWND hWndCtl){
        PostMessage(WM_CLOSE);
    }
};
proj05.cpp
#include "stdafx.h"

#include "resource.h"

#include "FontListView.h"
#include "FontPreviewView.h"
#include "MainFrm.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE,
                     LPTSTR lpCmdLine, int nCmdShow)
{
    HRESULT hRes = ::CoInitialize(NULL);
    ATLASSERT(SUCCEEDED(hRes));

    ::DefWindowProc(NULL, 0, 0, 0L);

    AtlInitCommonControls(ICC_COOL_CLASSES | ICC_WIN95_CLASSES);

    hRes = _Module.Init(NULL, hInstance);
    ATLASSERT(SUCCEEDED(hRes));

    int nRet = 0;
    // BLOCK: アプリケーション実行
    {
        CMessageLoop theLoop;
        _Module.AddMessageLoop(&theLoop);

        CMainFrame wnd;
        wnd.CreateEx();
        wnd.ShowWindow(nCmdShow);
        wnd.UpdateWindow();

        nRet = theLoop.Run();

        _Module.RemoveMessageLoop();
    }

    _Module.Term();
    :: CoUninitialize();

    return nRet;
}

 まず、リソースを用意します。今回のプログラムでは使用しないアクセラレータリソースとダイアログリソースをプロジェクトから削除します。

 メニューリソース(リソースID:IDR_MAINFRAME)は次のように設定します。

メニューリソース
トップレベルアイテム名リソースID
ファイル(&F)アプリケーションの終了(&X)ID_APP_EXIT
表示(&V)ビュー切り換え(&C)ID_MENUITEM_CHANGEVIEW
メニューリソース
メニューリソース

 ツールバーリソース(リソースID:IDR_MAINFRAME)にはアイテムを1つ追加し、リソースIDにID_MENUITEM_CHANGEVIEWを設定します。

ツールバーリソース
ツールバーリソース

 次に、「stdafx.h」で「atlctrlw.h」と「atlframe.h」、「atlsplit.h」をインクルードします。これらはコマンドバーやUI更新ハンドラ、スプリッタウィンドウを使用するために必要です。

 次にCMainFrameクラスを定義します。本稿では以下の部分に注目して説明します。

コマンドバー

 まず、CMainFrameクラスのメンバ変数としてCCommandBarCtrlのインスタンスであるm_CmdBarを宣言します。

 CMainFrameクラスのWM_CREATEメッセージハンドラでは、m_CmdBarに対してCreate()を呼び出すことでコマンドバーを作成します。Create()の第4引数にはコマンドバーのスタイルを指定します。ATL_SIMPLE_CMDBAR_PANE_STYLEはコマンドバーの標準的なスタイルを意味し、「atlctrlw.h」ヘッダ内で次のように定義されています。

ATL_SIMPLE_CMDBAR_PANE_STYLEの定義
// Window Styles:
#define CBRWS_TOP            CCS_TOP
#define CBRWS_BOTTOM         CCS_BOTTOM
#define CBRWS_NORESIZE       CCS_NORESIZE
#define CBRWS_NOPARENTALIGN  CCS_NOPARENTALIGN
#define CBRWS_NODIVIDER      CCS_NODIVIDER

// standard command bar styles
#define ATL_SIMPLE_CMDBAR_PANE_STYLE \
    (WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | \
     CBRWS_NODIVIDER | CBRWS_NORESIZE | CBRWS_NOPARENTALIGN)

 コマンドバーを作成した後は、コマンドバーにメニューアイテムを追加します。今回のプログラムはAttachMenu()を呼び出すことによってフレームウィンドウのメニューバーからメニューアイテムを読み込んでいますが、LoadMenu()を呼び出してメニューリソースから直接アイテムをロードすることもできます。

 次に、LoadImages()を呼び出すことによってツールバーの画像をロードしています。これにより、IDが等しいメニューアイテムとツールバーの画像が関連付けられ、メニューアイテムの横に画像が表示されるようになります。なお、メニューアイテムの横に画像を表示するためには、AddBitmap()AddIcon()を使うこともできます。

 最後にツールバーとリバーを作成し、バンドにコマンドバーとツールバーを追加します。

ステータスバー

 今回のプログラムは、ステータスバーの右端にフォント数を表示する領域(ペイン)を持ちます。この領域はフレームウィンドウのサイズが変わるたびに設定する必要があるため、CMainFrameクラスのWM_SIZEメッセージハンドラで設定します。

 WM_SIZEメッセージハンドラでは、ステータスバーに2つのペインを追加します。1番目はフレームウィンドウの大きさに合わせて幅を調整するデフォルトペイン、2番目はフォント数を表示するペインです。ペインを設定するにはSetParts()を呼び出します。第1引数にはペインの数、第2引数にはペインの右端の座標を要素とする配列へのポインタを指定します。なお、WM_SIZEメッセージは基底クラスであるCFrameWindowImplクラス内でもマッピングされているため、最後にSetMsgHandled(false)を呼び出す必要があります。

 フォント数はOnIdle()内で取得してステータスバーに表示します。

スプリッタウィンドウ

 まず、CMainFrameクラスのメンバ変数としてスプリッタウィンドウクラスであるCSplitterWindowのインスタンスであるm_wndSplitterを宣言します。さらに、各ペインのクラスであるCFontListViewCFontPreviewViewのインスタンスもメンバ変数として宣言します。

 CMainFrameクラスのWM_CREATEメッセージハンドラでは、m_wndSplitterに対してCreate()を呼び出すことでスプリッタウィンドウを作成します。

 次に、SetSplitterExtendedStyle()を呼び出してスプリッタウィンドウの拡張スタイルを設定します。今回のプログラムでは0を設定していますが、これは、スプリッタウィンドウのサイズが変更された場合に、左ペインの幅(水平分割スプリッタウィンドウの場合は上ペインの高さ)を固定することを意味します。スプリッタウィンドウ拡張スタイルには次のスタイルが指定できます。

スプリッタウィンドウの拡張スタイル
スタイル説明
SPLIT_PROPORTIONAL
(デフォルト)
スプリッタウィンドウのサイズが変更された場合に両ペインのサイズも変更
SPLIT_RIGHTALIGNEDスプリッタウィンドウのサイズが変更された場合に右ペインの幅を固定
SPLIT_BOTTOMALIGNEDスプリッタウィンドウのサイズが変更された場合に下ペインの幅を固定
SPLIT_NONINTERACTIVEスプリッタウィンドウの分割バーを移動不可

 スプリッタウィンドウを作成した後は、スプリッタウィンドウを親として各ペインのウィンドウを作成します。各ペインはSetSplitterPane()を呼び出すことによってスプリッタウィンドウにセットされます。SetSplitterPane()の第1引数にはペインを識別するための次のようなフラグを指定します。

ペインの識別フラグ
識別フラグ説明
SPLIT_PANE_LEFT左ペインを識別
SPLIT_PANE_RIGHT右ペインを識別
SPLIT_PANE_TOP上ペインを識別
SPLIT_PANE_BOTTOM下ペインを識別

 なお、両方のペインを一度に設定する場合はSetSplitterPanes()が使えます。これは第1引数に左(または上)ペインのウィンドウハンドル、第2引数には右(または下)ペインのウィンドウハンドルを設定します。

 WM_CREATEメッセージハンドラでは、最後にスプリッタウィンドウのハンドルをm_hWndClientに代入し、UpdateLayout()を呼び出してレイアウトを更新してからSetSplitterPos()で分割バーの位置を設定しています。

 次に、スプリッタウィンドウに関連する2つのコマンドメッセージハンドラを追加します。1つはフォントリストのアイテムを選択した時に実行されるOnListSelChange()、もう1つはメニューの[ビュー切り換え]を選択した時に実行されるOnMenuChangeView()です。

 OnListSelChange()は、LBN_SELCHANGE通知コードを持つコマンドメッセージのハンドラ関数です。通常、このメッセージはフォントリストペインの親であるスプリッタウィンドウへ送られますが、CSplitterWindowクラスの基底クラスであるCSplitterWindowImplクラス内のメッセージマップにはFORWARD_NOTIFICATIONS()がエントリされているため、コマンドメッセージはさらにその親であるフレームウィンドウへ送られます(同様に、ペインで通知メッセージWM_NOTIFYが発生した場合もフレームウィンドウへ送られます)。今回のプログラムではこのハンドラ関数で、フォントリストの選択されているアイテム(フォント名)を取得して右ペインに設定します。

 OnMenuChangeView()では、GetSinglePaneMode()を呼び出して現在のペインの状態を取得し、SetSinglePaneMode()を呼び出してペインをセットします。

UI更新ハンドラ

 まずはCMainFrameの基底クラスにCUpdateUIを追加します。CMainFrameクラスにはUI更新ハンドラマップを用意し、更新するメニューアイテムのリソースIDとUIのタイプを登録します。

UI更新ハンドラマップ
BEGIN_UPDATE_UI_MAP(CMainFrame)
    UPDATE_ELEMENT(ID_MENUITEM_CHANGEVIEW,
    UPDUI_MENUPOPUP | UPDUI_TOOLBAR)

END_UPDATE_UI_MAP()

 今回のプログラムでは、ID_MENUITEM_CHANGEVIEWをメニューとツールバーに使用しているため、UIのタイプにUPDUI_MENUPOPUPとUPDUI_TOOLBARを設定しています。UIのタイプには次のようなものが用意されています。

UIのタイプ
UIタイプ説明
UPDUI_MENUPOPUPポップアップメニュー(メニューバー下のメニューアイテム含む)
UPDUI_MENUBARメニューバー(トップレベルのメニューアイテム)
UPDUI_CHILDWINDOW子ウィンドウ
UPDUI_TOOLBARツールバー
UPDUI_STATUSBARステータスバー

 UI更新ハンドラでは、ポップアップメニューとそれ以外のUIの更新方法が少し異なります。

ポップアップメニュー

 メッセージマップに、基底クラスであるCUpdateUIクラスへチェーンを追加します。CUpdateUIクラスへチェーンすることによって、メインウィンドウにWM_INITMENUPOPUPメッセージが送られてくるたびにCUpdateUIへメッセージが送られます。CUpdateUIの基底クラスであるCUpdateUIBaseにはWM_INITMENUPOPUPメッセージハンドラが用意されており、そこでポップアップメニューの状態が更新されます。

 このため、UISetCheck()でポップアップメニューの状態を変更すると、WM_INITMENUPOPUPメッセージが送られてくるタイミングでポップアップメニューの状態が更新されます。

ポップアップメニュー以外

 今回のプログラムではツールバーを例にしますが、まず、CMainFrameクラスのWM_CREATEメッセージハンドラでUIAddToolBar()を呼び出します。UIAddToolBar()の引数には、更新するツールバーのハンドルを指定します。UI更新ハンドラは、ここで指定されたハンドルで識別されるツールバーに対して更新処理を行います。CUpdateUIは次のようなハンドル追加用メンバ関数を用意しています。

ハンドル追加用メンバ関数
メンバ関数説明
UIAddMenuBarメニューバー(トップレベルのメニューアイテム)のハンドルを追加
UIAddChildWindowContainer子ウィンドウのハンドルを追加
UIAddToolBarツールバーのハンドルを追加
UIAddStatusBarステータスバーのハンドルを追加

 次に、OnIdle()UIUpdateToolBar()を呼び出します。UIUpdateToolBar()は、UI更新ハンドラマップに登録されたツールバーアイテムを更新します。CUpdateUIは次のような更新用メンバ関数を用意しています。

更新用メンバ関数
メンバ関数説明
UIUpdateMenuBarメニューバー(トップレベルのメニューアイテム)を更新
UIUpdateChildWindows子ウィンドウを更新
UIUpdateToolBarツールバーを更新
UIUpdateStatusBarステータスバーを更新

 このため、UISetCheck()でツールバーの状態を変更すると、アイドル時にツールバーの状態が更新されます。

最後に

 本稿では、スプリッタウィンドウ、コマンドバー、UI更新ハンドラを使用しました。次回は、ペインコンテナやマルチペインステータスバーなどを扱いたいと思います。

参考資料

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

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
ATL/WTLを利用したVisual C++のWindowsプログラミング連載記事一覧

もっと読む

この記事の著者

So()

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/346 2006/05/25 10:14

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング