概要

分割ウインドウとは、ウインドウが2つ以上に分割されているウィンドウの事である。
Visual C++ではそれぞれの分割された個々のウインドウを「ペイン」と呼ぶ。


左右2分割の分割ウインドウの作成

MFCでは、分割ウインドウを作成するAPIが標準で用意されている。それが、CSplitterWnd::CreateStatic関数である。
下図のような1行2列の2ペインを持つ分割ウインドウのプログラムを記述する。


まず、各ペインを管理するViewクラスを作成する。
Viewクラスは、ペインの数だけ必要になるので、上図のように左右2つに分割すると、Viewクラスは全部で2つ必要になる。
ペインA(左ペイン)にはプロジェクト作成時で用意されているViewクラスを利用し、ペインB(右ペイン)のViewクラスは新規で作成する。

プロジェクトに新規Viewクラスを追加する。
流れは以下の通りである。
[挿入(I)]->[クラスの新規作成(N)]
  [クラスの種類(T)]   MFCクラスを選択
  [クラス名(N)]     好きなクラス名(ここではCPaneBView)
  [基本クラス(B)]    CViewの派生クラス(ここではCFormViewクラスを使用する)

まず、CMainFrm.hファイルを開いて、以下のコードを入力する。
※MDIアプリケーションの場合は、CChildFrm.hファイルのCChildFrameクラスに同様のコードを追加する。

 CMainFrame.h
 
 class CMainFrame : public CFrameWndEx
 {
    // ...
    public:
       CSplitterWndEx  m_SplitMain;  // スプリッタウインドウ
    // ...
 }


次に、CMainFrm.cppファイルのCMainFrameクラスにOnCreateClientメンバ関数をオーバーライドする。
手順は以下の通りである。
[表示(V)]->[ClassWizard(W)]
  [プロジェクト(P)]  PaneSample
  [クラス名(N)]    CMainFrame
  [オブジェクトID(I)] CPaneBView
  [メッセージ(G)]   OnCreateClient

そして、CMainFrm.cppファイルに、以下のコードを追加する。

 CMainFrm.cpp
 
 #include  "CPaneDoc.h"   // CPaneAView.hよりも先にインクルード
 #include  "CPaneAView.h"  // CPaneAViewクラスのヘッダをインクルード
 #include  "CPaneBView.h"  // CPaneBViewクラスのヘッダをインクルード
 
 // ...
 
 BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) 
 {
    // ...
    if(FLASE == m_SplitMain.CreateStatic(  // スプリッタウインドウの作成
                   this,                      // スプリッタウインドウの親ウィンドウを指定
                                              // ここではメインフレームを分割するのでthisを指定
                   1,                         // 行の分割数を指定。ここでは縦分割は無いので1を指定
                   2)                         // 列の分割数を指定。ここでは左右分割なので2を指定
    {
       return FALSE;
    }
 
    if(FALSE == m_SplitMain.CreateView(        // ペインA(ペイン場所1行1列目)を作成
                   0,                          // スプリッタの行番号を指定
                   0,                          // スプリッタの列番号を指定
                   RUNTIME_CLASS(CPaneAView),  // 指定ペインを管理するViewクラスCPaneAViewを指定
                   CSize(20, 0),               // ペインのサイズ
                   pContext)
    {
       return FALSE;
    }
 
    if(FALSE == m_SplitMain.CreateView(        // ペインB(ペイン場所1行2列目)を作成。
                   0,                          // スプリッタの行番号を指定
                   1,                          // スプリッタの列番号を指定
                   RUNTIME_CLASS(CPaneBView),  // paneBを管理するViewクラスCPaneBViewを指定
                   CSize(0, 0),                // ペインのサイズ
                   pContext)
    {
       return FALSE;
    }
    // ...
    return TRUE;
 }


ここで注意するのが、CSplitterWndEx::CreateView関数の第3引数である。
第3引数において、指定ペインを管理するViewクラスを指定する。
ペインAを管理するのはCPaneAViewなのでこれを指定する。同様に、ペインBを管理するのはCPaneBViewクラスなのでこれを指定する。
※MDIアプリケーションの場合も同様に、CChildFrameクラスにOnCreateClient関数をオーバーライドして、
 OnCreateClient関数内に同様のコードを追加する。


変則分割ペインを持つ分割ウインドウの作成

下図のように、左側に1つ、右側に2つのウインドウを持つ分割ウインドウを作成する。


CSplitterWnd::CreateStatic関数は、n行m列に分割するしか出来ないので、以下の方法を用いる。
左右に2分割した後、分割した右側のペインを更に上下に2分割する。(このように2つのスプリッタが交錯するような構造をネストと呼ぶ)

まず、基本分割ウインドウと同様に、それぞれのペインを管理するViewクラスを作成する。
ペインAの管理にはCPaneAViewクラス、ペインBはCPaneBView、ペインCはCPaneCViewとしてクラスを追加する。
流れは以下の通りである。
[挿入(I)]->[クラスの新規作成(N)]
  [クラスの種類(T)]   MFCクラスを選択
  [クラス名(N)]     CPaneBViewとCPaneCView
  [基本クラス(B)]    CViewの派生クラス(ここではCFormViewクラスを使用する)

次に、CMainFrm.hファイルに下記のソースコードを追加する。

 CMainFrm.h
 
 class CMainFrame : public CFrameWndEx
 {
    // ...
    public:
       CSplitterWnd  m_SplitMain;  // メインフレームを左右分割する為に使用する分割ウインドウ(メインスプリッタと命名)
       CSplitterWnd  m_SplitSub;   // 左右分割されたウインドウの右ペインを上下分割するために使用する分割ウインドウ(サブスプリッタと命名)
    // ...
 }


次に、CMainFrm.cppファイルにCMainFrame::OnCreateClient関数をオーバーライドして、下記のソースコードを追加する。
手順は以下の通りである。
[表示(V)]->[ClassWizard(W)]
  [プロジェクト(P)]  PaneTest
  [クラス名(N)]    CMainFrame
  [オブジェクトID(I)] CPaneBViewとCPaneCView
  [メッセージ(G)]   OnCreateClient

※MDIアプリケーションの場合は、CChildFrameクラスに追加すること。

 CMainFrm.cpp
 
 #include  "CPaneDoc.h"
 #include  "CPaneAView.h"  // CPaneAViewクラスのヘッダをインクルード
 #include  "CPaneBView.h"  // CPaneBViewクラスのヘッダをインクルード
 #include  "CPaneCView.h"  // CPaneCViewクラスのヘッダをインクルード
 
 // ...
 
 BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) 
 {
    // ...
    if(FALSE == m_SplitMain.CreateStatic(     //  メインスプリッタを作成
                   this,                      //  メインスプリッタの親ウインドウを指定。ここではメインフレームを分割するのでthisを指定
                   1,                         //  メインスプリッタの行分割数を指定
                   2))                        //  メインスプリッタの列分割数を指定
    {
       return FALSE;
    }
 
    if(FALSE == m_SplitSub.CreateStatic(      // サブスプリッタを作成
                   &m_SplitMain,              //   サブスプリッタの親ウィンドウはm_SplitMain
                   2,                         //   サブスプリッタの行分割を指定
                   1,                         //   サブスプリッタの列分割を指定
                   WS_CHILD | WS_VISIBLE,            //   サブスプリッタのスタイルを指定
                   m_SplitMain.IdFromRowCol(0, 1)))  //   子ウィンドウIDを指定。m_SplitMainの行0(=1行目)列1(=2列目)のIDを取得
    {
       return FALSE;
    }
 
    m_SplitMain.CreateView(        //  ペインAを作成
       0,                          //  メインスプリッタの行番号を指定
       0,                          //  メインスプリッタの列番号を指定
       RUNTIME_CLASS(CPaneAView),  //  ペインAを管理するViewクラスCPaneAViewを指定。
       CSize(320, 240),               //  ペインのサイズ
       pContext))
    {
       return FALSE;
    }
 
    m_SplitSub(                    //  ペインBを作成
       0,                          //  サブスプリッタの行番号を指定
       0,                          //  サブンスプリッタの列番号を指定
       RUNTIME_CLASS(CPaneBView),  //  ペインBを管理するViewクラスCPaneBViewを指定。
       CSize(320, 120),            //  ペインのサイズ
       pContext))
    {
       return FALSE;
    }
 
    m_SplitSub(                     //  ペインCを作成。
       1,                           //  サブスプリッタの行番号を指定
       0,                           //  サブスプリッタの列番号を指定
       RUNTIME_CLASS(CPaneCView),   //  ペインCを管理するViewクラスCPaneCViewを指定。
       CSize(320, 120),             //  ペインのサイズ
       pContext))
    {
       return FALSE;
    }
    // ...
    return TRUE;
 }



管理外Viewクラスからの各ペインへのアクセス方法(SDIの場合)

ここまで、分割ウインドウを作成して様々なペインを作成したが、各ペインをリンクさせながら動作させる方法を説明する。
(ペインAで左クリックされたらペインBにテキストを出力する等)
ここでは、上記で作成した変則分割ペインを使用する。

各ペインに描画するには、各ペインを管理しているViewクラスの描画関数(OnDraw関数やOnPaint関数)を用いる。

CPaneAView.hファイルとCPaneAView.cppファイルに以下のソースコードを追加する。

 CPaneAView.h
 
 #include  "CPaneBView.h"
 #include  "CPaneCView.h"
 #include  "CMainFrm.h"

 class CPaneAView : public CFormView
 {
    // ...
    public:
       CPaneBView *pPaneB;  // CPaneBViewクラスへのポインタ
       CPaneCView *pPaneC;  // CPaneCViewクラスへのポインタ
    // ...
 }
 
 CPaneAView.cpp
 
 #include "CMainFrm.h"
 
 // ...
 
 CPaneAView::OnDraw(CDC *pDC)
 {
    // ...
    pPaneB = (CPaneBView*)((CMainFrame*)AfxGetMainWnd())->m_SplitSub.GetPane(0, 0);
    pPaneC = (CPaneCView*)((CMainFrame*)AfxGetMainWnd())->m_SplitSub.GetPane(1, 0);
    // ...
 }

AfxGetMainWnd関数はメインフレームのオブジェクトを返すので、CPaneAtViewクラスの任意の場所でペインBやペインCにアクセス出来る。
また、各ペインのドキュメントには、CWnd::GetDC関数でアクセスできる。
例えば、ペインBに"This region is PaneB."と出力する場合は、下記のように記述する。

 CPaneAView.cpp
 
 pPaneB->GetDC()->TextOut(0, 0, _T("This region is PaneB."));