세 방향 분할 윈도우

1) 정적 분할 윈도우

 하나의 정적 분할 창을 다른 정적 분할 창의 구획들 중의 하나 안에 끼워 넣어서 세 방향 분할 창을 만들 수 있다. 다음의 OnCreateClient() 함수는 두 개의 구획이 수직으로 나누어져 있고, 그 왼쪽 열이 다시 수평으로 두 개의 구획으로 나누어진 세 방향 분할 창을 만든다. MainFrm.h에서는 굵은 글씨 부분이 고친 부분이고, MainFrm.cpp 의 OnCreateClient()함수는 전체를 그대로 타이핑해야 한다. 그리고 여기서 쓰일 View들은 미리 만들어 놓아야 한다.

다음은 OnCreateClient() 함수안에서 어떤 일이 발생하는가에 대한 개요이다.

 1. CreateStatic을 이용하여 첫 번째 분할 창(m_wndSplitter)을 프레임 창에 만든다.
   m_wndSplitter는 하나의 행과 두 개의 열을 가진다.

 2. IdFromRowCol() 함수의 리턴값으로 왼쪽 pane의 ID를 받아 m_IDpane에 저장한다.

 3. 두 번째 분할창 m_wndSplitter2를 m_wndSplitter의 왼쪽 구획에 만든다.
    m_wndSplitter2는 프레임 창의 자식 윈도우가 아니라 m_wndSplitter의 자식 윈도우이므로    CreateStatic() 함수의 첫 번째 인자로 m_WndSplitter의 주소값을 받았다.

 4. 각 pane에 쓰일 뷰를 만든다. CreateView의 첫 번째와 두 번째 인자는 뷰의 위치, 세번째 인자는 미리 만들어 두었던 뷰를 각각의 pane에 등록하는 역할, 네 번째 인자는 pane의 크기를 지정한다. 여기서는 크기가 바뀌기 때문에 네 번째 인자를 어떤 숫자로 하든 상관이 없다.  

 5. SetColumnInfo()와 SetRowInfo()를 써서 각 pane의 크기를 지정한다.

 6. 활성창을 지정한다. 이 부분을 없어도 상관없다.

// MainFrm.h의 일부분
class CMainFrame : public CFrameWnd
{
protected: // create from serialization only
        CMainFrame();
        DECLARE_DYNCREATE(CMainFrame)

// Attributes
protected:
        
public:
    CSplitterWnd m_wndSplitter, m_wndSplitter2;

// Operations
private :
        int m_IDpane;

....................

==========================================================

// MainFrm.cpp 의 한 부분
BOOL CMainFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/, CCreateContext* pContext)
{   
        // 수직선을 나눈다.
        if(!m_wndSplitter.CreateStatic(this, 1, 2)) {
                TRACE0("Fail to left vertical splitter...");
                return FALSE;
        }
        
        m_IDpane = m_wndSplitter.IdFromRowCol(0, 0);
        
        // 수평선을 나눈다.
        if(!m_wndSplitter2.CreateStatic(&m_wndSplitter, 2, 1, WS_CHILD|WS_VISIBLE, m_IDpane)) {
                TRACE0("Fail to left horizontal splitter...");
                return FALSE;
        } // 위의 코드를 통해 3등분 했음

        // 오른쪽 입력창의 뷰를 만듬 (formview)
        if(!m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CInput), CSize(0, 0), pContext)) {
                TRACE0("Fail to create Input Form View...");
                return FALSE;
        }
        
        // Main View (link 출력 뷰) 를 만듬(왼쪽 상단)
        if(!m_wndSplitter2.CreateView(0, 0, RUNTIME_CLASS(CPistonView), CSize(800, 200), pContext)) {
                TRACE0("Fail to create MainView...");
                return FALSE;
        }
        
        // Graph 출력 뷰를 만듬(왼쪽 하단)
        if(!m_wndSplitter2.CreateView(1, 0, RUNTIME_CLASS(CGraphView),
                   CSize(800, 10), pContext)) {
                TRACE0("Fail to create GraphView...");
                return FALSE;
        }
        
        // 수직 분할 윈도우의 크기
        m_wndSplitter.SetColumnInfo( 0, 550, 20 );
        // 왼쪽 수평 분할 윈도우의 크기
        m_wndSplitter2.SetRowInfo( 0, 150, 10 );
        
        // 활성창 지정
        SetActiveView((CView *)m_wndSplitter2.GetPane(0, 0));
        return TRUE; // 절대로 parent의 코드를 실행하지 말 것
}

 

2) 동적 분할 윈도우

- 동적분할의 문제점

 위의 정적분할의 예에서 두 번째 CreateStatic() 대신에 Create() 함수를 사용하면 정적분할 창 내에서 동적분할 창을 나타낼 수 있다.
 그런데, 동적 분할이 일어날 때 View가 처음 자신의 Document에 액세스하려고 하면 액세스 오류가 발생할 수가 있다.
 그 이유는 프레임 워크에 있다. 동적분할을 나눌 때 CSplitterWnd에 있는 코드는 새로운 구획에 대해 하나의 View를 만들기위해 NULL pContext 포인터를 가지고 CreateView를 호출한다. pContext가 NULL이기 때문에 CreateView는 활성 View(입력 포커스를 가진 View)에 대한 포인터를 위해 프레임창을 검색하고, 새로운 View를 위한 모델로 그 View를 사용한다. 그런데 분할이 일어날 때 프레임워크는 모델이 되는 View가 동적분할의 자식창이 아니라는 것을 알게 되고, Document에 연결되지 않은 "빈" View를 만들게 된다. View가 처음으로 Document에 액세스하려고 할 때 액세스 오류가 발생하게 될 것이다.

- 해결책
  
 CSplitterWnd 대신에 여기에서 파생된 CNestedSplitterWnd를 사용한다. 두 클래스 사이의 유일한 차이는 새로운 구획을 만들기위해 수평분할 표시줄을 끌어당길 때 뒤의 클래스는 프레임워크에 위해 호출되는 가상 SplitRow함수를 재정의 한다. SplitRow를 재정의하는 버전은 분할이 일어나기 전에 동적분할 창의 위쪽 구획에 있는 View를 활성 View로 만든다. 그래서 활성 View가 정적분할창의 자식창이 될 때 나타나는 동적보기창의 생성문제를 교묘하게 피하게 된다. 다음은 CNestedSplitterWnd.h와 cpp 파일이다.

// CNestedSplitterWnd.h
//////////////////////////////////////////
#if !defined(AFX_NESTEDSPLITTERWND_H__7E87511F_3C94_11D3_A2B1_CBBA7F687FCA__INCLUDED_)
#define AFX_NESTEDSPLITTERWND_H__7E87511F_3C94_11D3_A2B1_CBBA7F687FCA__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
// NestedSplitterWnd.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CNestedSplitterWnd window

class CNestedSplitterWnd : public CSplitterWnd
{
// Construction
public:
        CNestedSplitterWnd();

// Attributes
public:

// Operations
public:

// Overrides
        // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CNestedSplitterWnd)
        //}}AFX_VIRTUAL

// Implementation
public:
        virtual ~CNestedSplitterWnd();

        // Generated message map functions
protected:
        virtual BOOL SplitRow(int cyBefore);
        //{{AFX_MSG(CNestedSplitterWnd)
                // NOTE - the ClassWizard will add and remove member functions here.
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_NESTEDSPLITTERWND_H__7E87511F_3C94_11D3_A2B1_CBBA7F687FCA__INCLUDED_)

================================================================================================

// NestedSplitterWnd.cpp : implementation file
//////////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Split.h"
#include "NestedSplitterWnd.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CNestedSplitterWnd

CNestedSplitterWnd::CNestedSplitterWnd()
{
}

CNestedSplitterWnd::~CNestedSplitterWnd()
{
}


BEGIN_MESSAGE_MAP(CNestedSplitterWnd, CSplitterWnd)
        //{{AFX_MSG_MAP(CNestedSplitterWnd)
                // NOTE - the ClassWizard will add and remove mapping macros here.
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CNestedSplitterWnd message handlers

BOOL CNestedSplitterWnd::SplitRow(int cyBefore)
{
        GetParentFrame()->SetActiveView((CView *)GetPane(0,0));
        return CSplitterWnd::SplitRow(cyBefore);
}