class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler
{
}
上記が、そのクラスの宣言部分ですが、このクラスはいくつかのクラスを多重継承しています。WTL(ATL)ではこの多重継承がよく使われていますので、多重継承がわからないという方はこの機に多重継承を理解しておいてください。
それぞれ継承されているクラスを説明すると、CFrameWindowImpl<>
というテンプレートクラスは atlframe.h
で定義されているクラスで、これ自体もテンプレートクラスを継承した・・・それこそ継承やテンプレートを理解していないと、何だコリャ?クラスになっていますが、ツールバー、ステータスバー、クライアントウィンドウ、アクセラレータを管理できるフレームワークのように理解していいと思います(メンバ変数は
atlframe.h の386行目あたりに定義されています:WTL7.1)。
この辺の定義がきちんとソースで読めるので理解さえできれば、自分好みに手を加えることも可能なのが、WTL
の大きな魅力のひとつでしょう。逆に言えば、WTL で Wizard を使って SDI
アプリケーションを作ると、自然にこれらをサポートするフレームワークが提供されることになります。
CUpdateUI
はメニュー、ツールバー、ステータスバーなどのボタンの状態やチェックマークなどの状態を更新するためのクラスです。MainFrm.h には
BEGIN_UPDATE_UI_MAP というマクロが記述されていて、ここに
UPDATE_ELEMENT(シンボルID, そのシンボルを更新する種類)
という形で登録することで、後述する CUpdateUIBase::UIUpdate
系の(CUpdateUIBase は CUpdateUI
の基本クラスで多重継承しています)メソッドを実行したときに関連するものも一緒に更新されるようになっています。
CMessageFilter
はメッセージのディスパッチを行います。きちんと理解していないで書くのもアレなんですが、このフレーム内でビューやダイアログを使うときに、その中でキーボードアクセラレータなどを使いたいときに
PreTranslateMessage
を実行してあげる必要があるわけですが、それを実装するために必要な抽象インタフェースクラスになっています。
意味がおかしかったら突っ込みお願いします・・・
CIdelHandler
はアイドル時に行う処理を記述するときに使う、OnIdle()(後述)というメソッドを実装するための抽象インターフェースクラスです。これらは作成したアプリケーションを使うユーザーが何もしていないときに呼び出され、ステータスバーの状態やツールバーの状態を更新したりするときに使います。
この辺は変な説明を聞くよりも MSDN で 「ATL 3.0
ウィンドウクラス:入門」というトピックを読んだり、ソースを眺めたりしてもらったほうがわかるかもしれません。
また、WTL
ではテンプレートクラスを継承する際に、継承先のクラス(つまり派生クラス)をテンプレートのメンバとして指定するという方法がよく使われています。最初なんだこりゃ?って感じですごく理解に苦しんだのですが、現在はこう解釈しています。
間違っているようでしたらご指摘ください。
またこの仕組みに名前がついていたら教えてください
ここで例として
class CMainFrame
: public CFrameWindowImpl<CMainFrame>
という部分を考えて見ます。CFrameWindowImpl<>
というテンプレートクラスは、ユーザーがフレームウィンドウ(枠部分)を作成するときに使うテンプレートなわけです(何を当たり前のことを・・・)。で、当然のことながら
CFrameWindowImpl<>
はユーザーが定義するフレームウィンドウクラスに継承されることを前提として作られています。また、WTL には 〜 Impl とか 〜
Base というクラスが出てくるのですが、〜 Impl というクラスはメッセージマップを持っていて、さまざまな Window
メッセージに対応することができるようになっています。
WTL Wizard によって作成された SDI
アプリケーションの雛形はこちらで何も定義していないのにもかかわらず、メニューがあって、ツールバー( Rebar )
があって、サイズを変更したときにも正しくツールバーが描画されています。SDK などでは自分で WM_SIZE
メッセージを拾って、クライアントサイズを取得して、ツールバーのサイズを調整して・・・といったことを行う必要があるのですが、この辺の共通する部分はすべてフレームワークつまりこの場合
CFrameWindowImpl<> の中で定義されています(atlframe.h 1249 行目あたり)。
ところがテンプレート側ではユーザーが定義したウィンドウにどんな子ウィンドウが定義されているか?WM_SIZE
を呼ばれたときにどんなことをしてほしいのかわかりません。また、元の OnSize
は仮想関数(virtual)として定義されているわけではないので、オーバーライドすることができません。
では、どのようにして呼んでいるのかというと、自分のポインタをこのテンプレートクラス(CFrameWindowImpl<>)に渡したクラスのポインタにキャストして
UpdateLayout() 呼び出しています。このメソッドは実際には自分で定義するのではなく、CFrameWindowImplBase<>(
CFrameWindowImpl<> の継承元) に定義されていて、ここで先ほど説明した一連の操作が行われています。
LRESULT OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if(wParam != SIZE_MINIMIZED)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
bHandled = FALSE;
return 1;
}
なぜ WTL
ではこのような複雑なことをしているのでしょうか?
これはおそらく MFC
のような CObject からすべてが継承されるクラスモデルの場合には、仮想関数を定義することによって起こる VTABLE
のオーバーヘッドが大きかったためだろうと思われます。WTL の仕組みでは仮想関数を使わないので、VTABLE
を経由することなく仮想関数的な動作を行うことができるから・・・だと思っています。
前置きが長くなりました、しかも解説に自信なし
CS2H_ConvView m_cFolderView;
CS2H_ConvView m_cSourceView;
CS2H_ConvView m_cHtmlView;
CSplitterWindow の左側に配置する View は現在のフォルダの一覧を表示し、右上に配置する View
はユーザーが選択したソースファイルの内容を表示し、右下に配置する View はそれを HTML
に変換した内容を表示するということにします。
また、上記の絵にはありませんが、Explorer
のようにフォルダツリーの上にペインと呼ばれるテキストを表示する領域を追加してみましょう。これは CPaneContainer
と呼ばれています(atlctrlx.h)。
これらを利用するためにヘッダファイルに以下の宣言を追加しておきます。
CSplitterWindow m_cSplitterWindow;
CHorSplitterWindow m_cHorSplitterWindow;
CPaneContainer m_cPaneContainer;
またこれらを使うので、stdafx.h に atlctrlx.h と atlsplit.h を追加しておいてください。
(略)
extern CAppModule _Module;
#include <atlwin.h>
#include <atlframe.h>
#include <atlctrls.h>
#include <atldlgs.h>
#include <atlctrlw.h>
#include <atlctrlx.h>
#include <atlsplit.h>
上記をすべて追加した状態のウィンドウの親子関係は以下のようになります(上が親)
大体感じをつかめたでしょうか?
それでは、MainFrm.cpp の OnCreate を変更していきます。最初に二つの SplitterWindow と Pane
を作ってしまいましょう。m_cSplitterWindow の親には CMainFrame の m_hwnd
を、m_cHorSplitterWindow の親には m_cSplitterWindow の m_hWnd
を指定してることに注意してください。PaneContaineer のタイトルはフォルダとしました。
次に、デフォルトの
OnCreate では CMainFrame の子ウィンドウは m_view つまり CS2H_ConvView だったのですが、これを
CSplitterWindow に変更します。
LRESULT CMainFrame::OnCreate
(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
(略)
CreateSimpleStatusBar();
/// SplitterWindow の作成
m_cSplitterWindow.Create(m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
m_cHorSplitterWindow.Create(m_cSplitterWindow.m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
/// Pane Windows の作成
m_cPaneContainer.Create(m_cSplitterWindow, _T("フォルダ"));
/// 基礎となる Window の設定
m_hWndClient = m_cSplitterWindow.m_hWnd;
また、3つの View
も作成しておきます。パラメータは今はデフォルトの View
と同じものを使いますが、親となるウィンドウハンドルに注意してください。そして、それぞれの SplitterWindow
に子ウィンドウを割り当てて行きます。
m_cSplitterWindow(左右に分割したウィンドウ)の左側の子ウィンドウは CPaneWindow、右側は
m_cHorSplitterWindow (上下に分割したウィンドウ)になります。そして、m_cHorSplitterWindow
の上側の子ウィンドウは m_cSourceView、下側は m_cHtmlView となります。
この割り当てを行うには
SplitterWindow の SetSplitterPanes というメソッドを使います。SetSplitterPane
というメソッドを使うと個別に割り当てることもできます。
(続き)
/// 各子ウィンドウ(View)の作成
m_cFolderView.Create(m_cPaneContainer.m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
m_cSourceView.Create(m_cHorSplitterWindow.m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
m_cHtmlView.Create(m_cHorSplitterWindow.m_hWnd, rcDefault, NULL,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
/// Splitter Windows のクライアント(子ウィンドウ)の設定
m_cSplitterWindow.SetSplitterPanes ( m_cPaneContainer, m_cHorSplitterWindow );
m_cHorSplitterWindow.SetSplitterPanes ( m_cSourceView, m_cHtmlView );
次に Pane
Containeer の子ウィンドウを設定します。これには SetClient メソッドを使います。ここでは Pane の子は
CFolderView ですので、そのように指定します。また、Pane Containeer
には×ボタンが表示されますが、今は消されては困るので消してみました。このようなウィンドウを消せるようにする場合は、メニューからそれぞれの子ウィンドウ(この場合だと
CFolderView) を表示させることができるようにしないと、二度と表示できないので注意が必要です。
UpdateLayout
は各 Window の領域を更新します。
その後、Splitter
Window の幅の設定を行います。最初に上下、左右の Splitter Window のそれぞれの Window
の最小値を決定します。ここではそれぞれ 200 と 100 に設定してみました。
また、初期表示のときにどのくらいの比で分割されているかを設定するには SetSplitterPos メソッドで指定します。
(続き)
/// Pane のクライアント(子ウィンドウ)の設定
m_cPaneContainer.SetClient(m_cFolderView);
m_cPaneContainer.SetPaneContainerExtendedStyle ( PANECNT_NOCLOSEBUTTON );
UpdateLayout();
/// Splitter の最低サイズの設定
m_cSplitterWindow.m_cxyMin = 200;
m_cHorSplitterWindow.m_cxyMin = 100;
/// Splitter の初期の分割比の設定
m_cSplitterWindow.SetSplitterPos(200);
m_cHorSplitterWindow.SetSplitterPos(400);
UIAddToolBar(hWndToolBar);
(略)
}
以上で、Splitter
と Pane を使ったウィンドウが作成できました。