9.분할윈도우,다중 뷰,다양한 윈도우 스타일

본장에서는 다양한 형태의 윈도우를 출력하는 방법에 대해서 설명합니다. 프레임 윈도우 내부가 분할되고 여러 View가 화면에 출력되는 분할윈도우, 한개의 도큐먼트 안에 여러개의 View가 존재하고, 이View들이 서로 교차되면서 출력된느 다중View 등을 만드는 방법을 알아봅니다. 이방법들을 이용하여 좀더 발전된 형태로 멀티미디어 타이틀 형태의 윈도우와 방패 윈도우를 만드는 방법에 대해서 설명합니다. MDI형태에서는 여러개의 스타일을 다향하게 결합할수 있습니다. 본장에서는 지금까지 설명한 윈도우 출력의 여러 방법을 이용하여 MDI형태에서 다중 템플레이트에서 분할윈도우설정, 차일드 프레임에 툴바와 상태바 붙이기 등을 설명하며 팁으로는 메인프레임 배경화면에 비트맵을 출력하는 방법에 대해서 설명합니다.

대상자: 초급,중급,고급

Chapter 등급: 1-2 필수, 3-4 선택

공부방법:

1,2 항목은 필수적으로 알아두는 것이 좋습니다.

나머지 항목은 □이런 방법도 있구나□ 하는 정도로 알고 계셨다가 후에 프로그래밍 할때 필요한 부분이 있으면 그때 읽어보고 예제의 내용을 응용하여 프로그래밍 하면됩니다.

1.분할윈도우

분할윈도우를 컨트롤하는 윈도우 클래스는 CSplitterWnd입니다. 이클래스가 프레임윈도우에 설정되고 이 윈도우위에 여러개의 View윈도우들이 설정될수 있는것입니다. 분할윈도우를 설정하고자 할 때는 AppWizar Step4에서 Advance항목을 선택하고 분할윈도우 스타일을 설정해 주어야 합니다. 그림 1-9-1은 분할윈도우를 설정하는 방법을 도시한 것입니다.

(그림 파일)

(그림 1-9-1)분할윈도우 설정

AppWizard Step4에서 그림 1-9-1의 ①항목 버튼을 클릭하게 되면 ⓑ항목 과 같은 대화상자가 출력됩니다. 이 대화상자에서 ② 항목을 선택하고 ③항목인 체크박스를 체크해주면 분할윈도우가 설정됩니다. 이렇게 분할윈도우를 설정하고 프로젝트를 만든다음 컴파일하고 실행하면 그림 1-9-2와 같이 실행됩니다.

(그림 파일)

(그림 1-9-2)분할윈도우를 설정한 예제 ExSpliterWnd

ExSpliterWnd는 그림 1-9-1과 같이 분할윈도우를 선택하고 아무런 코딩이 없이 프로그램을 컴파일한것입니다. 그림 1-9-2는 ExSpliterWnd 프로그램을 실행하였을 때 의 형태입니다. 그림 1-9-1의 ①과 같이 메뉴에서 □나누기□ 항목을 선택하고 특정 영역을 설정하면 해당 위치로 분할된 윈도우가 설정됩니다. 매우 간단하게 분할윈도우를 만들 수 있으나 ExSpliterWnd는 하나의 View를 가지고 여러 평면으로 설정된 형태이기 때문에 별로 큰 의미는 없습니다. 그러나 ExSpliterWnd를 분석하면 분할윈도우는 어떤 형태로 설정되는가를 알수 있게 됩니다.

ExSpliterWnd의 CMainFrame 클래스에 보면 다음과 같은 맴버 변수가 설정되어 있을것입니다.

CSplitterWnd m_wndSplitter;

CSplitterWnd 형인 m_wndSplitter 변수가 분할윈도우를 컨트롤 하는 클래스입니다. m_wndSplitter 변수는  CMainFrame의 OnCreateClient 함수에서  Create함수에 의해 생성됩니다. 다음은 CMainFrame 의 OnCreateClient함수입니다.

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

return m_wndSplitter.Create(this,

2, 2,            // TODO: adjust the number of rows, columns

CSize(10, 10),      // TODO: adjust the minimum pane size

pContext);

}

OnCreateClient함수는 메인프레임안에 존재하는 클라이언트 윈도우가 만들어졌을대 수행되는 함수입니다. 이때 m_wndSplitter윈도우를 생성하는 것입니다. CSplitterWnd의 맴버 함수인 Create함수는 동적으로 분할윈도우를 만드는 함수입니다. 동적으로 분할윈도우를 만든다는 의미는 □나누기□ 항목을 선택하였을때 해당 영역으로 분할윈도우를 가변적으로 설정해 준다는 의미입니다. Create함수를 이용하였을경우에는 pContext라는 CCreateContext 클래스이 변수를 마지막 인자로 갖게 됩니다. 이렇게 pContext가 필요한 이유는 분할윈도우 영역에 설정할 View윈도우를 알고자 함이며 이 View윈도우 와 도큐먼트의 연결 설정을 해주어야 하기 때문입니다. ExSpliterWnd 예제를 수행하여 4개의 분할윈도우를 만들면 한개의 도큐먼트 안에는 4개의 View윈도우가 설정되게 됩니다. 그냥 화면에 보이는 View가 단순하게 복사되는 것이 아니라 도큐먼트에 새로운 View윈도우 여러개를 설정한 형태가 되는 것입니다. 따라서 분할윈도우를 만들고자 할때는 분할윈도우에 설정할 View와 도큐먼트의 정보를 함께 부여 해주어야 합니다. pContext는 바로 View와 도큐먼트 정보를 함께 가지고 있기 때문에 분할윈도우를 만들때 이 인자를 설정하는 것입니다.

<1>정적 분할윈도우

정적으로 분할윈도우를 만들고자 할경우에는 CSplitterWnd 클래스의 맴버함수인 CreateStatic 함수를 이용합니다. CreateStatic 함수의 형태는 다음과 같습니다.

CSplitterWnd::CreateStatic

BOOL CreateStatic(

CWnd* pParentWnd, //부모윈도우 핸들

int nRows, //새로 칸

int nCols, //가로 칸

DWORD dwStyle = WS_CHILD | WS_VISIBLE, //윈도우 스타일

UINT nID = AFX_IDW_PANE_FIRST //분할윈도우 ID

);

CreateStatic는 정적으로 분할되는 형태입니다. 이렇게 분할되면 이 형태가 고정되게 됩니다. 고정된 형태에서 각 윈도우의 크기는 변화될수 있습니다. 만일 가로로 두개의 분할윈도우를 만들었다면 이두개의 좌우측의 크기가 변화될수 있게 됩니다.

CReateStatic를 이용하여 가로로 두개의 분할 윈도우를 만들고자 한다면 다음과 같이 할수 있습니다.

m_wndSplitter.CreateStatic(this,1,2);

위와 같이 하면 정적으로 분할된상태만 될뿐입니다. 이분할윈도우에 특정 윈도우를 설정하기 위해서는 CreateView 함수를 사용해야 합니다. CreateView함수 형태는 다음과 같습니다.

CSplitterWnd::CreateView

virtual BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext );

row와 col는 설정할 세로와 가로의 번호이며 pViewClass는 View클래스 포인터입니다. 이 View클래스를 바로 설정하는 것이 아니라 CRuntimeClass형태로 설정해 주어야 합니다. CRuntimeClass로 설정하기 위해서는 RUNTIME_CLASS 라는 매크로 함수를 사용합니다. sizeInit는 윈도우의 가로와 세로의 크기입니다. pContext 는 현재 View와 연결하고자 하는 도큐먼트 정보를 가지고 있는 컨텍스트입니다. 이부분이 있어야 View와 도큐먼트가 연결되는 것입니다.

만일 CExStaticSplitView 클래스를 현재 분할된 윈도우 좌측에 설정하고자 한다면 다음과 같이 할수 있습니다.

m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CExStaticSplitView),

CSize(100,100),pContext);

정적으로 분할되는 윈도우 예제를 만들어 보겟습니다. 본 예제는 ExStaticSplit입니다. ExStaticSplit 전체 구성도는 그림 1-9-3와 같습니다.

(그림 파일)

(그림 1-9-3)ExStaticSplit 전체 구성도

ExStaticSplit 프로그램을 제작하는 방법은 무척 간단합니다. 분할윈도우 형태의 SDI프로젝트를 만든다음 그림 1-9-3의 ④ 항목이 가르키는 윈도우와 같은 대화상자 폼을 만든다음 이폼과 연결할 클래스를 CFormView로 설정합니다. 이렇게 설정한 클래스가 CAnotherView입니다. 이렇게 설정한후에 CMainFrame에서 OnCreateClient 함수에서 정적인 형태의 윈도우를 만드는것입니다. 그림 1-9-3의 ②,③,④ 부분이 바로 이내용입니다. 먼저 m_wndSplitter 윈도우를 정적으로 가로로 두개로 나누어지게 만듭니다. 이렇게 만든후에 CreateView함수를 이용하여 기본적으로 만들어진 CView를 우측에 설정하고 새로만든 FormView를 좌측에 설정한것입니다. 그림 1-9-3의  ②항목은 정적으로 분할윈도우를 만드는 예이며 ③항목은  우측에 기본적으로 설정된 CView를 좌측에는 새로만든 CFormView를 설정한것입니다.  이렇게 하면 정적으로 분할된 분할윈도우가 만들어지는것입니다.

분할 윈도우를 설정하고 프로젝트를 만들면 ID가 ID_WINDOW_SPLIT 인 "나누기□ 라는 항목이 설정됩니다. 이항목을 선택하면 윈도우 분할영역을 조정할수 있습니다. 그러나 재미있게도 이 항목을 선택하였을때 수행되는 부분이 전혀 없습니다. 그렇다면 이부분은 어느곳에서 수행이 될까요? 이부분은 CMainFrame내부에서 수행됩니다. 외부에서 분할윈도우를 만들었는데 어떻게 CMainFrame에서 외부에 있는 m_wndSplitter 를 인식하고 컨트롤 할수 있는것일까? 정답은 m_wndSplitter의 ID에 있습니다. 정적으로 분할되거나 동적으로 분할되는 m_wndSplitter의 ID는 AFX_IDW_PANE_FIRST 이됩니다. 이 ID를 이용하여 CMainFrame 내부에서 분할윈도우 영역을 설정하는 것입니다.

<2>분할윈도우의 각 View 윈도우 클래스 접근

정적으로 분할하든 동적으로 분할하든 분할되면 다중 View 형태가 됩니다. 다중View란 한개의 도큐먼트에 여러개의 View가 설정된 형태를 의미합니다. 전항목에서 CDocument의 GetFirstViewPosition 함수를 이용하여 View윈도우 리스트 의 포지션을 얻은다음 GetNextView 함수를 이용하여 도큐먼트에서 View를 얻는것을 보았을것입니다. □일반적으로 도큐먼트와 View는 한쌍으로 연결될텐데 왜 이렇게 두단계를 거쳐서 얻을가 그냥 GetView라는 함수를 이용하여 한번에 얻게 하면 안될까? □ 라는 의아심을 가질수도 있을것입니다. View에서 도큐먼트를 얻을때는 GetDocument함수 한개로 가능하면서 도큐먼트에서 View를 얻고자 할때는 복잡하게 GetFirstViewPosition함수후에 GetNextView에 의해서 View를 얻으니까요? 이렇게 한 이유는 한개의 도큐먼트에 여러개의 View를 설정할수 있기 때문입니다. ExStaticSplit 예제는 한개의 도큐먼트에 두개의 View를 가지고 있는 형태입니다. 이렇기 때문에 도큐먼트에서 View를 얻고자 할때 먼저 View리스트의 포지션을 얻은다음 GetNextView를 이용하여 얻게 되는 것입니다. ExStaticSplit 에서 CFormView나 CView 윈도우를 얻고자 할때 내용이 그림 1-9-4와 같습니다.

(그림 파일)

(그림 1-9-4)View윈도우 클래스 얻기

다중 View (그림 1-9-4와 같이 View가 여러개읜 형태)에서 특정 View를 얻고자 한다면 제일먼저 Document를 얻는것이 우선입니다. 그림 1-9-4의 ①항목은 현재 활성화 되어 있는 도큐먼트를 얻는 내용입니다. 이렇게 도큐먼트 클래스 포인터를 얻은다음 CDocument의 GetFristViewPosition 함수를 이용하여 View 윈도우 리스트의 포지션을 얻습니다. 그림 1-9-4의 ②항목이 이내용입니다. 이렇게 한후에 GetNextView를 수행하면 첫번째 View윈도우 클래스가 넘겨지고 GetNextView를 또다시 실행하면 두번째 View윈도우 클래스가 넘겨지게 됩니다. 그림 1-9-4의 ③항목이 첫번째 View 즉 분할윈도우에 우측에 있는 CView형태의 윈도우 클래스를 얻는 예이고 ④항목이 FormView 형태의 윈도우 클래스를 얻는 예입니다.

분할윈도우를 만들고 각 View를 다른 곳에서 얻고자 할때 그림 1-9-4와 같은 방법은 매우 많이 사용됩니다. 그림 1-9-4를 유념해서 보아주셨으면 합니다.

<3>View 윈도우 클래스 접근 예제 ExSplitCont

ExSplitCont는 4개의 분할윈도우를 설정하고 각 View 윈도우 클래스 포인터를 얻는 예제 입니다. ExSplitCont의 전체 구성도는 그림 1-9-5와 같습니다.

(그림 파일)

(그림 1-9-5)ExSplitCont 전체 구성도

ExSplitCont 프로그램은 4개의 View를 정적으로 만든다음 0.5 초마다 각 윈도우에 돌아가면서 화면에 윈도우 번호를 출력하는 예제 입니다. 이렇게 만들기 위해서 먼저 4개의 정적윈도우를 구성시켜야 합니다. 4개의 정적윈도우는 그림 1-9-5의 ①,②,③,④ 입니다. ③ 과 ④는 한개의 View를 이용하였습니다. 즉 기본적으로 설정되는 CExSplitContView 와 CAnotherView1,CAnotherView2 두개를 만들어 CAnotherView2를 ③과 ④의 영역에 설정한 형태입니다. 그림 1-9-5의 ⓐ항목 처럼 4개의 View에 0.5초마다 돌아가면서 화면에 윈도우 번호를 출력하기 위해서는 4개의 View를 얻어야 합니다. 이 4개의 View를 얻는 항목이 ⓑ항목입니다.

ⓑ 항목은  CMainFrame 의 OnTimer함수입니다. 이함수에서 View를 얻어야 하는 이유는 0.5초마다 해당 View를 얻고 그 View에 문자를 출력하기 위해서입니다. CMainFrame의 OnCrate함수에 다음과 같이 설정하면 0.5초 마다 OnTimer함수가 실행되고 이렇게 실행되면 0.5초마다 4개의 윈도우를 돌아가면서 해당 View에 문자를 출력하게 됩니다.

SetTimer(1,500,NULL);

위와 같이 설정한후 OnTimer함수가 실행될때 oldView가 NULL인가를 확인합니다.  최초로 OnTimer함수가 실행되면 oldView가 NULL이 됩니다. 그리고 OnTimer 함수가 실행이 종료 되는 부분 (그림 1-9-5 의 ⑨ 항목)에서 현재 얻은 View를 oldView가 받게 됩니다. 다시 OnTimer함수가 실행되면 oldView가 NULL이 아니므로 화면을 재출력하도록 합니다. 이렇게 하는 이유는 처음에서는 첫번째 View에 □View Num1" 이라는 문자를 ①윈도우에 출력하고 두번째 OnTimer함수가 실행되면 ①윈도우에 출력하였던 □View Num1" 이라는 문자를 지우고 다시 두번째 윈도우에 □View Num2" 라는 문자를 출력하기 위해서 입니다. GetDC를 이용하여 문자를 출력하고 난후에 Invalidate함수를 이용하면 GetDC를 이용하여 출력한 문자열은 삭제되는  방법을 이용하기 위해서 입니다. ⑤항목에서 oldView가 NULL 이 아니면 이전 즉 전에 출력한 View윈도우가 있다는 것이고 이윈도우에 출력된 문자를 제거하기 위해서 oldView의 Invalidate함수를 호출하여 화면을 재출력한것입니다.  이렇게 한후 현재 활성화되어 있는 도큐먼트를 얻은다음 이도큐먼트에서 View 리스트의 포진션을 받고 이포지션에서 m_nNum 에 해당하는 윈도우를 얻는것이 ⑦ 항목입니다. m_nNum은 ①~④항목에 설정되어 있을것입니다. m_nNum만큼 GetNextView함수를 반복하여 View윈도우를 얻는것입니다. 예를 들어서, 2번째 View윈도우를 얻고자 한다면(m_nNum 이 2일경우) GetNextView함수를 두번반복하면 됩니다. 이렇게 한후에 View의 DC를 받고 이 DC에 문자열을 출력하는 형태가 그림 1-9-5의 ⑧ 항목입니다. 본예제에서 중요한 부분은 ⑥과 ⑦ 항목입니다. 이두개의 항목에서 View를 얻기 때문입니다.

본예제에서 3개의 View를 만들고 각 View의 OnDraw에 다음과 같이 문자열을 출력합니다.

//①번 분할 윈도우에 출력할 View의  OnDraw

void CExSplitContView::OnDraw(CDC* pDC)

{

CExSplitContDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CString data="main";

pDC->TextOut(0,0,data);

}

//②번 분할 윈도우에 출력할 View의  OnDraw

void CAnotherView1::OnDraw(CDC* pDC)

{

CDocument* pDoc = GetDocument();

// TODO: add draw code here

CString data="View1";

pDC->TextOut(0,0,data);

}

//③번,④번 분할 윈도우에 출력할 View의  OnDraw

void CAnotherView2::OnDraw(CDC* pDC)

{

CDocument* pDoc = GetDocument();

CString data="View2";

pDC->TextOut(0,0,data);

}

위와 같이 각View의 OnDraw에서 문자열을 출력하면 그림 1-9-5의 ⓐ와 같이 화면 상단에 현재 문장열이 출력됩니다.

본예제의 소스중에서 중요한 부분은 CMainFrame입니다. CMainFrame의 소스는 다음과 같습니다. 나머지 부분은 제공되는 CD-ROM을 참조하시기 바랍니다.

(프로그램 소스)

// MainFrm.h : interface of the CMainFrame class

//

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

#if !defined(AFX_MAINFRM_H__64D47A2B_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

#define AFX_MAINFRM_H__64D47A2B_3A68_11D2_8B92_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

protected:

CSplitterWnd m_wndSplitter;

public:

int m_nNum;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

public:

virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext);

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnTimer(UINT nIDEvent);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__64D47A2B_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include "stdafx.h"

#include "ExSplitCont.h"

#include "MainFrm.h"

#include "ExSplitContDoc.h"

#include "ExSplitContView.h"

#include "AnotherView1.h"

#include "AnotherView2.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_WM_TIMER()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

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

// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE

| CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0("Failed to create toolbar\n");

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0("Failed to create status bar\n");

return -1;      // fail to create

}

// TODO: Delete these three lines if you don't want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

m_nNum=1;

SetTimer(1,500,NULL);

return 0;

}

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

m_wndSplitter.CreateStatic(this,2,2);

m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CExSplitContView),

CSize(200,200),pContext);

m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CAnotherView1),

CSize(200,200),pContext);

m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CAnotherView2),

CSize(200,200),pContext);

m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CAnotherView2),

CSize(200,200),pContext);

return TRUE;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

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

// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

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

// CMainFrame message handlers

void CMainFrame::OnTimer(UINT nIDEvent)

{

static CView *oldView=NULL;

if(oldView)

oldView->Invalidate();

CDocument *pDoc;

CView *pView;

CString data;

pDoc=GetActiveDocument();

POSITION pd;

//첫번째 View윈도우 포지션을 얻는다.

pd=pDoc->GetFirstViewPosition();

//View 윈도우를 얻는다.

for(int i=0;i<m_nNum;i++)

pView=pDoc->GetNextView(pd);

//View 윈도우에 View Num %d 문자를 출력한다.

data.Format("View Num %d",m_nNum);

CDC *pDC=pView->GetDC();

pDC->TextOut(0,20,data);

pView->ReleaseDC(pDC);

//oldView에 현재 View를 설정한다.

oldView=pView;

m_nNum++;

if(m_nNum==5)

m_nNum=1;

CFrameWnd::OnTimer(nIDEvent);

}

(프로그램 소스끝)

2.다중 View

지금까지 설명한 모든 분할윈도우는 다중 View입니다. 다중 View란 도큐먼트에 View가 여러개 설정된 형태를 의미합니다. 꼭 분할 윈도우이어야만 다중 View인가? 그렇지 않습니다. 이외에 View자체가 바뀌면서 다중 View형태의 프로그램을 제작할수 있습니다. 예를 들어서 CFormView와 CView가 있을경우 어떨때에는 CFormView를 또는 어떨때는 CView를 화면에 보여주는 형태를 만들었을경우 이때에서 다중View 형태를 이용합니다.

<1>다중 View만들기

다중 View를 만드는 기본적인 방법은 그림 1-9-6과 같습니다.

(그림 파일)

(그림 1-9-6)다중 View 만들기

다중 View를 하기 위해서는 3개의 파트로 나누어 집니다. 첫번째 파트는 새로 설정할 View를 만드는 파트 (그림 1-9-6의 ①항목)이며,두번재 파트는 기본적으로 설정된 View를 화면에 출력하는 파트(②항목) 이며, 세번재 파트는 새로 설정할 View 화면을 출력하는 파트(③ 항목) 입니다. ① 항목은 단 한번 수행으로 더이상 수행하지 않습니다. 이유는 윈도우를 만드는것은 단 한번으로 끝나기 때문입니다. ② 와 ③ 항목은 여러 번 수행할수 있습니다. 현재 ⓐ와 같은 상태에서 ⓑ와 같은 상태로 바꾸고자 한다면 ③ 항목의 내용을 이용하고,ⓑ와 같은 상태에서 ⓐ와 같은 상태로 바꾸조가 한다면 ② 항목을 이용하면 됩니다. ①,②,③ 항목에서 가장 중요한 항목은 ①일것입니다. 새로  View를 만들고 이것을 어떻게 도큐먼트와 설정하는 가 하는 것이 본 항목의 주요 요점 이기 때문입니다. 다른 View를 만들깅 위해서는 먼저 다른 View에 해당하는 클래스의 변수를 메모리에 할당합니다. 그림 1-9-6의 ④가 바로 이항목입니다. 이렇게 한후에 View클래스의 Create함수를 이용하여 윈도우를 만듭니다. 이때 Create함수의 마지막 인자 형인 CCreateContext 가 매우 중요합니다. View를 만들때 그냥 만드는 것이 아니라 현재의 Document와 결합해서 View를 만들어야 하기 때문입니다. 이때에 CCreateContext는 현재의 Document와 View의 정보를 함께 가지고 있는 클래스 이기 때문에,이 클래스에 도큐먼트 클래스의 포인터를 넘겨주고,이 인자를 View의 Create함수가 수행될때 넘겨주는 것입니다. 그림 1-9-6의 ⑤ 항목은 현재의 Document 를 받는 내용입니다. 이렇게 현재의 Document를 받은다음 이것을 CCreateContext 형의 변수인 context.m_pCurrentDoc 예 연결을 하는 것입니다. 이렇게 도큐먼트 클래스이 포인터를 CCreateContext의 변수인 context의 m_pCurrentDoc에 설정하고, View윈도우를 만들때 마지막 인자로 이값을 넘겨주는 것입니다. 이렇게 되면 새로 만든 View는 현재 도큐먼트의 두번째 View로 등록되는 것입니다. 이렇게 하고 새로만든 View를 OnInitialUpdate함수를 이용하여 새롭게 업데이트하면 윈도우가 만들어집니다.

이렇게 새로 View를 만든다음 이 View를 화면에 출력하기 위해서는 ③항목과 같이  하면 됩니다. 프레임 윈도우에 맴버 함수중에 SetActiveView함수가 있습니다. 이함수를 이용하여 특정 View를 활성화 되어 있는 View로 설정할수 있습니다. 이렇게 활성화 되어 있는 View로 설정한다음 ShowWindow함수를 이용하여 화면에 출력합니다.  이렇게 되면 현재 View가 화면에 나타납니다. 이다음 내용이 그림 1-9-6의 ⑥에 해당 되는 내용인데  쉽게 보면 현재 View를 화면 처음으로 설정한다는 의미입니다. ⑥ 내용을 설정한 이유는 WM_COMMAND 메시지를 받기 위해서 입니다. WM_COMMAND메시지를 받기 위해서라는 의미는 메뉴에서 항목을 클릭했을경우나 또는 툴바에서 항목을 선택하였을때 현재 View에서 이내용을 수행하고자 하는 함수가 있다면 이함수를 호출할수 있게 하기 위해서입니다.  이후에 프레임 윈도우의 RecalcLayout 함수를 이용하여 프레임을 다시 출력하면 새로설정된 View가 화면에 출력되게 됩니다. 새로설정된 View상태에서 다시 그림 1-9-6의 ⓐ와 같은 기본적으로 설정된 View로 전환할려면 ②와 같이 하면 됩니다. 이방법은 ③과 비슷합니다. 차이는 새로운 View클래스 포인터가 아닌 기존에 설정된 View클래스 포인터를 이용한것이 차입니다.

그림 1-9-6과 같은 방법을 이용하면 쉽게 다중 View를 설정할수 있습니다.

<2>다중 View 예제 ExMultiView

ExMultiView는 그림 1-9-6의 방법으로 CFormView와 CView 를 교차하는 예제입니다. 본내용은 전항목에서 설명한 방법을 그대로 사용한것입니다. 그림 1-9-7은 ExMultiView의 전체 구성입니다.

(그림 파일)

(그림 1-9-7)ExMultiView의 전체 구성

ExMultiView는 SDI형태로 만들고 기본적인 View는 CFormView로 설정한 형태입니다. 이형태에서 그림 1-9-7의 ⓐ와  ⓑ 같이 □CView","CFormView" 항목을 만들고 해당 항목을 클릭하면 선택된 View로 화면에 출력하는 형태입니다. 이때 로직은 그림 1-9-7의 ①과 ② 에 기록한 내용대로 코딩 하면 됩니다. 그림 1-9-7 의②항목을 선택하였을때 구동되는 내용은 다음과 같습니다.

void CMainFrame::OnAnotherview()

{

//현재 활성화 되어있는 View가 메인 View이므로

//m_pMainView에 설정한다.

m_pMainView =(CExMultiViewView *) GetActiveView();

//OtherView가 만들어 지지 않았다면

if (m_pOtherView == NULL)

{

//OtherView를 만들고

m_pOtherView = new CAnotherView;

CCreateContext context;

//현재View의 토큐먼트를 받는다.

context.m_pCurrentDoc = m_pMainView->GetDocument();

//View를 만든다.

m_pOtherView->Create(NULL, NULL, 0L, CFrameWnd::rectDefault,

this, 2, &context);

//화면에 업데이트한다.

m_pOtherView->OnInitialUpdate();

}

//OtherView를 활성화 시킨다.

SetActiveView(m_pOtherView);

//OtherView를 보여준다.

m_pOtherView->ShowWindow(SW_SHOW);

//메인 View를 감춘다.

m_pMainView->ShowWindow(SW_HIDE);

//OhterView를 첫번째 PANE로 설정한다.

m_pOtherView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

RecalcLayout();

}

위와 같이 함수에 내용을 코딩하기 전에 헤더에는 다음과 같이 두개의 변수를 설정합니다.

CExMultiViewView *m_pMainView;

CAnotherView *m_pOtherView;

m_pMainView는 기본적으로 설정된 View의 클래스 포인터를 가지고 있는 변수이며 m_pOtherView는 새로만든 View의 클래스 포인터를 가지고 있는 변수입니다. CMainFrame의 OnCrate함수에서 m_pOtherView는 NULL로 다음과 같이 설정해주어야 합니다.

m_pOtherView=NULL;

위와 같이 설정하는 이유는 다른 View가 NULL이면 현재 만들어지지 않았으므로 새로 설정하고, 새로 설정되면 m_pOhterView는 NULL이 아니기 때문에 이때는 새로 만들지 않고 그냥 화면에 출력하기 위해서 입니다. 그림 1-9-6의 ①항목과 ② 항목을 함께 사용한 형태가 되는 것입니다.

그림 1-9-7의 ①항목을 선택하였을때의 함수는 다음과 같습니다.

void CMainFrame::OnMainview()

{

//메인뷰를 활성화 한다.

SetActiveView(m_pMainView);

//OtherView를 감춘다.

m_pOtherView->ShowWindow(SW_HIDE);

//메인 View를 보여준다.

m_pMainView->ShowWindow(SW_SHOW);

//메인 View를 첫번째 윈도우로 설정한다.

m_pMainView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

RecalcLayout();

}

위에서 설명한 두개의 함수는 그림 1-9-6의 내용을 이용하여 만든것입니다. 그림 1-9-6 과 비교하여 소스를 보시면 이해하실수 있을것입니다.

ExMultiView의 내용은 메인 프레임에서만 코딩하였습니다. 다음은 ExMultiView의 CMainFrame의 전체 소스입니다.

(프로그램 소스)

// MainFrm.h : interface of the CMainFrame class

//

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

#if !defined(AFX_MAINFRM_H__64D47A59_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

#define AFX_MAINFRM_H__64D47A59_3A68_11D2_8B92_006097AD9DDC__INCLUDED_

#include "ExMultiViewDoc.h"

#include "ExMultiViewView.h"

#include "AnotherView.h"

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

CExMultiViewView *m_pMainView;

CAnotherView *m_pOtherView;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnAnotherview();

afx_msg void OnMainview();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__64D47A59_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include "stdafx.h"

#include "ExMultiView.h"

#include "MainFrm.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_COMMAND(ID_ANOTHERVIEW, OnAnotherview)

ON_COMMAND(ID_MAINVIEW, OnMainview)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

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

// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE

| CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0("Failed to create toolbar\n");

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0("Failed to create status bar\n");

return -1;      // fail to create

}

// TODO: Delete these three lines if you don't want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

m_pOtherView=NULL;

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

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

// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

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

// CMainFrame message handlers

void CMainFrame::OnAnotherview()

{

//현재 활성화 되어있는 View가 메인 View이므로

//m_pMainView에 설정한다.

m_pMainView =(CExMultiViewView *) GetActiveView();

//OtherView가 만들어 지지 않았다면

if (m_pOtherView == NULL)

{

//OtherView를 만들고

m_pOtherView = new CAnotherView;

CCreateContext context;

//현재View의 토큐먼트를 받는다.

context.m_pCurrentDoc = m_pMainView->GetDocument();

//View를 만든다.

m_pOtherView->Create(NULL, NULL, 0L, CFrameWnd::rectDefault,

this, 2, &context);

//화면에 업데이트한다.

m_pOtherView->OnInitialUpdate();

}

//OtherView를 활성화 시킨다.

SetActiveView(m_pOtherView);

//OtherView를 보여준다.

m_pOtherView->ShowWindow(SW_SHOW);

//메인 View를 감춘다.

m_pMainView->ShowWindow(SW_HIDE);

//OhterView를 첫번째 PANE로 설정한다.

m_pOtherView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

RecalcLayout();

}

void CMainFrame::OnMainview()

{

//메인뷰를 활성화 한다.

SetActiveView(m_pMainView);

//OtherView를 감춘다.

m_pOtherView->ShowWindow(SW_HIDE);

//메인 View를 보여준다.

m_pMainView->ShowWindow(SW_SHOW);

//메인 View를 첫번째 윈도우로 설정한다.

m_pMainView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

RecalcLayout();

}

(프로그램 소스끝)

3.다양한 윈도우 스타일

본항목 에서는 다양한 윈도우 스타일을 출력하는 방법에 대해서 설명합니다. 지금까지 설명한 내용으로 MFC를 이용하여 화면에 여러 윈도우를 출력하는 전반적인 방법을 다설명하였습니다. 지금까지 설명한 방법을 응용하면 여러 다양한 윈도우를 출력할수 있습니다. 본항목에서는 지금까지 설명한 윈도우 컨트롤 방법을 응용하여 실제 어플리케이션에 적용 시키는 몇가지 팁을 소개하는 것입니다. 간단하게 박스만 설정되어있는 멀티미디어 타이틀 프로그램에 사용하는 윈도우와,MDI를 이용하여 제작하는 분할윈도우와 툴바와 상태바 다이얼로그바가 결합된 다중 프레임 윈도우, 방패 형태의 또는 원형형태의 윈도우를 만드는 방법들이 본항목에서 소개됩니다.

<1>멀티미디어 타이틀 프로그램 윈도우 만들기

보통 멀티미디어 타이틀 프로그램은 단순하게 박스 형태로 되어 있으며 화면이 움직이지 않는 형태로 설정됩니다. 이런 타이틀 윈도우를 만들고자 할때는 중요한 3가지 팁을 사용합니다.

첫번째 팁은 윈도우 스타일을 단순하게 박스 형태로 만드는 것입니다. 이렇게 만들때 사용하는 윈도우 스타일은 다음과 같습니다.

WS_VISIBLE | WS_POPUP

위 두개의 스타일을 윈도우에 설정하게 되면 단순한 박스 형태의 윈도우가 만들어집니다. 보통 멀티미디어 타이틀은 SDI형태로 제작합니다. 이때 SDI형태의 메인프레임 (CMainFrame)의 PreCreateWindow 함수에서 위의 스타일을 설정해주면 됩니다. CMainFrame 의 PreCreateWindow함수는 다음과 같이 설정되어 있습니다.

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

//이곳에 코딩

return TRUE;

}

PreCreateWindow 함수는 윈도우가 만들어지기 이전에 수행되는 함수입니다. 이함수에서 CREATESTRUCT 구조체 형으로 넘겨주는 인자 cs의 맴버들에 변경할 값을 설정하게 되면 해당 윈도우가 cs 에 설정된 값에 의해서 윈도우가 만들어 집니다. cs의 맴버중에 style 변수는 윈도우 스타일을 설정하는 변수입니다. 이변수에 위에서 설명한 스타일로 설정하면 박스 형태의 윈도우가 생성됩니다.

이 내용은 다음과 같습니다.

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

cs.style = WS_VISIBLE | WS_POPUP ;

return TRUE;

}

위와 같이 하면 윈도우가 박스형태로 되지만 크기는 임의적으로 만들어집니다. 멀티미디어 타이틀 프로그램 같은 경우에는 보통 크기를 고정하고 화면 중앙에 그크기대로 설정합니다. 이렇게 하는 이유는 멀티미디어 프로그램에 출력되는 데이터들이 보통 그림이나 동영상 음성등이기 때문입니다. 크기가 변경된다면 그림 데이터를은 그 크기에 맞게 끔 리사이징이 되어야 하는데 그림의 크기가 변경되면 다소 왜곡 되는등 화면이 일그러지는 현상도 발생되어 멀티미디어 타이틀의 분위기를 망칠수 있으며 크기에 따라 모든 그림을 따로 그려 놓을수도 없는 문제이기 때문입니다.

일반적으로 800*600 크기나 640*480 크기의 윈도우를 설정하고 이크기에 맞게 여러 그림이나 동영상 등을 제작합니다.  이렇게 크기를 설정할경우 이크기를 PreCreateWindow 에서 설정할수가 있습니다. 다음은 PreCreateWindow 에서 화면중앙에 640*480 형태의 박스 윈도우를 설정한 예입니다.

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

cs.style = WS_VISIBLE | WS_POPUP ;

int x=GetSystemMetrics(SM_CXMAXTRACK);

int y=GetSystemMetrics(SM_CYMAXTRACK);

x=(x-640)/2;

y=(y-480)/2;

cs.x = x;

cs.y = y;

cs.cx = 640;

cs.cy = 480;

return TRUE;

}

cs의 맴버 x,y 는 윈도우가 출력될 스크린의 위치이며,cx,cy는 윈도우의 가로와 세로 크기입니다. x,y의 위치는 화면 중앙이 되어야 함으로 윈도우의 가로크기와 세로크기를 뺀값을 2로 나눈값이 됩니다. 그리고 cx,cy는 윈도우의 가로 크기와 세로크기를 설정한 것입니다. 위와 같이하면 화면 중앙에 640x480 크기의 박스 형태의 윈도우가 나타나게 될것입니다.

위와 같이 했을때는 화면 중앙에 박스 형태의 윈도우가 나타나면서 메뉴는 그대로 가지고 있습니다. 이메뉴를 삭제해야 합니다. 이때 메뉴를 삭제하는 방법은 간단합니다. 현재 설정된 메뉴를 받고 이메뉴를 삭제하면 됩니다. 이내용은 다음과 같습니다.

//메뉴를 만들고

CMenu* pMenu = new CMenu;

//이메뉴에 현재 설정되어잇는 메뉴를 연결시킨다.

pMenu->Attach( cs.hMenu );

//현재 메뉴늘 지운다.

pMenu->DestroyMenu();

//메뉴를 NULL로 설정

cs.hMenu = NULL;

//메뉴를 삭제한다.

delete pMenu;

먼저 새로운 매뉴를 만들고 이메뉴를 현재 설정된 cs의 hMenu와 연결한다음 pMenu를 삭제하게 되면 메뉴가 삭제됩니다. 이렇게 한후에 cs.hMenu를 NULL로 설정하고 pMenu를 메모리에서 해제하면 메뉴가 삭제됩니다. 위와 같이 하지 않고 단순하게 cs.hMenu를 NULL로 설정하여도 사라집니다. cs.hMenu를 NULL로 설정하는 것이 간단한 방법이기도 합니다. 여러분은 둘중에 편한 방법을 이용하시기 바랍니다.

메뉴를 삭제하고 화면 중앙에 640x480형태의 박스 윈도우를 만드는 방법을 설정한 PreCreateWindow 함수는 다음과 같습니다.

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

cs.style = WS_VISIBLE | WS_POPUP ;

int x=GetSystemMetrics(SM_CXMAXTRACK);

int y=GetSystemMetrics(SM_CYMAXTRACK);

x=(x-640)/2;

y=(y-480)/2;

cs.x = x;

cs.y = y;

cs.cx = 640;

cs.cy = 480;

cs.hMenu=NULL;

return TRUE;

}

위와 같이 하면 단순하게 박스 형태의 윈도우가 출력됩니다.

단순하게 박스형태로 출력하는 것은 간단합니다만 실제로 타이틀 프로그램을 제작할때 화면 전환을 하는 부분에서는 갈등이 많이 있게 됩니다. 화면이 전환되는 부분을 View클래스이 OnDraw함수 한개에서만 처리하고자 할때는 그내용이 너무 복잡하고 그렇다고 새로운 윈도우를 출력하고자 할때는 SDI형태이기 때문에 문제가조금 어렵고 그리고 MDI형태는 타이틀 프로그램을 만드는 형태와는 다르고 하는등등 일것입니다. 이때 주로 사용하는 방법이 다중 View입니다. 즉 도큐먼트는 한개이며 이 도큐먼트에 화면이 바뀔때 마다 여러개의 View를 바꾸어 출력하는 것입니다. 다중 View를 이용하는 방법은 전 항목에서 설명한 방법을 그대로 이용하면 됩니다. 이렇게 하면 멀티미디어 타이틀을 쉽게 제작할수 있을것입니다.

<2>멀티미디어 타이틀 예제 ExMultiViewTitle

하나의 예제를 만들면서 멀티미디어 타이틀을 어떻게 만드는가에 대해서 알아보겠습니다. ExMultiViewTitle 예제는 단순하게 두개의 프레임이 있으며 이 두개의 프레임을 이동하고 종료 버튼을 클릭하면 종료되는 예제 입니다. 본항목에서는 트루칼라 비트맵을 자원으로 등록하여 사용하는 방법도 팁으로 설명합니다. 그림 1-9-8은 ExMultiViewTitle의 전체 구조입니다.

(그림 파일)

(그림 1-9-8)ExMultiViewTitle 전체 구조

그림 1-9-8의 ⓐ가 ExMultiViewTitle프로그램이 출력될때의 처음 형태입니다. 이형태에서 □다음□ 버튼을 클릭하면 화면이 ⓑ항목으로 바뀌게 됩니다. 이와 같이 바뀌는 방법은 OnDraw에서 두개의 bmp를 교차하고 차일드 윈도우의 버튼 이름을 바꾸는 방법도 있습니다. 본 예제에서는 이방법을 사용하지 않고 다중 View를 사용하였습니다. ⓐ와 같은 화면이 출력되었을때 □다음□ 버튼을 클릭하면 새로운 View를 만들고 이View를 화면에 출력시켜줍니다. 이때 사용하는 방법은 그림 1-9-6의 ①과 ③항목입니다. 이렇게 하여 새로운 View를 화면에 보여주는 것입니다. 메인 프레임에서 □다음□ 버튼을 클릭했을때 수행하는 함수를 다음과 같이 만들었습니다.

//첫번째 윈도우에서 "다음" 버튼을 클릭했을때

void CMainFrame::CreateAnotherView()

{

//현재 활성화 되어있는 View가 메인 View이므로

//m_pMainView에 설정한다.

m_pMainView =(CExMultiViewTitleView *) GetActiveView();

//OtherView가 만들어 지지 않았다면

if (m_pAnotherView == NULL)

{

//OtherView를 만들고

m_pAnotherView = new CAnotherView;

CCreateContext context;

//현재View의 토큐먼트를 받는다.

context.m_pCurrentDoc = m_pMainView->GetDocument();

//View를 만든다.

m_pAnotherView->Create(NULL, NULL, 0L, CFrameWnd::rectDefault,

this, 2, &context);

//화면에 업데이트한다.

m_pAnotherView->OnInitialUpdate();

}

//OtherView를 활성화 시킨다.

SetActiveView(m_pAnotherView);

//OtherView를 보여준다.

m_pAnotherView->ShowWindow(SW_SHOW);

//메인 View를 감춘다.

m_pMainView->ShowWindow(SW_HIDE);

//OhterView를 첫번째 PANE로 설정한다.

m_pAnotherView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

RecalcLayout();

}

지금까지 설명한 내용을 이해하시면 위의 내용이 어떤 뜻인지 알수 있을것입니다.

CMainFrame에 설정한 CreateAnotherView() 는 □다음□ 이라는 버튼이 클릭되었을때 함수를 만들고 이함수에서 CMainFrame 의 CreateAnotherView함수를 호출해야 합니다. 이 이유는 CreateAnotherView는 CMainFrame에 있고 □다음□ 이라는 버튼은 View에 있기 때문입니다. View안에 차일드 윈도우로 있는 □다음□ 버튼이 크릭되었을때의 함수를 만들고 이함수에서 메인프레임의 CreateAnotherView()를 호출하는 것입니다. 이내용과 □exit" 버튼을 클릭했을때 수행되는 함수는 그림 1-9-9와 같습니다.

(그림 파일)

(그림 1-9-9)첫번째 프레임에서 버튼 클릭시 구동되는 루틴

ExMultiViewTitle는 SDI형태에 View가 CView인 형태로 만들었습니다. 이렇게 만든 상태에서 두개의 버튼을 도시하고자 할경우에는 동적으로 설정해야 합니다. 이렇게 하기 위해서는 그림 1-9-9의 ①항목처럼 CButton 클래스의 변수로 다음 버튼에 해당하는 버튼 윈도우를 m_nextBut, 이전 버튼에 해당하는 버튼 윈도우를 m_exitBut로 설정합니다.  이렇게 버튼을 클래스를 설정한후에 ③항목처럼 Create함수를 이용하여 버튼을 만듭니다. 이렇게 버튼을 만들었을경우에는 ON_BN_ 계열의 메시지 매크로를 이용해야 합니다. ②항목처럼 버튼이 눌려졋을때의 메시지 매크로를 설정하고 이매크로에 의해서 수행되는 함수를 ①항목처럼 헤더에 선언하고,④와 ⑤항목처럼 함수를 만듭니다. 이렇게 함수를 만든다음 □다음□ 버튼을 클릭했을때 함수에서는 메인프레임의 CreateAnotherView 함수를 호출하고,"exit" 버튼을 클릭했을때는 메인프레임에 WM_CLOSE메시지를 보내서 프로그램을 종료 시키는 것입니다. 그림 1-9-9는 본책 처음 부터 지금까지의 내용중에 일부분을 종합하여 만든것입니다. 지금까지의 내용을 이해하시면 그림 1-9-9의 내용을 이해하실수 있을것입니다. 특별한 팁으로 프로그램을 종료할때 어떻게 하는 부분이 그림 1-9-9의 ⑤항목일것입니다. 단순하게 메인프레임에 WM_CLOSE메시지를 보내기만 하면 됩니다. 이렇게 지킴이 웹에서 답변을 하였더니 □에러가 납니다□ 라고 말씀하신 분이 있더군요. 어째서 에러가 날까? 하고 궁금했는데 이분은 우측 버튼을 클릭했을때 프로그램이 종료 되는 것을 하였습니다. 다음과 같이 말입니다.

void CExMultiViewTitleView::OnRButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

AfxGetMainWnd()->SendMessage(WM_CLOSE,0,0);

CView::OnRButtonDown(nFlags, point);

}

위와 같이 하면 당연하게 에러가 납니다. 이이유는 간단합니다. WM_CLOSE메시지를 보내면 윈도우가 모두 닫혀지고 프로그램이 종료 되는 상태가 됩니다. 이상태에서 CView::OnRButtonDown 함수가 수행되게 됩니다. 현재 윈도우가 완전히 소멸된 상태에서 OnRButtonDown함수를 수행될수가 없습니다. 이렇기 때문에 에러가 나는 것입니다. 만일 OnRButtonDown함수에서 프로그램을 종료하고자 한다면 다음과 같이 CView::OnRButtonDown함수를 제거해주어야 합니다.

void CExMultiViewTitleView::OnRButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

AfxGetMainWnd()->SendMessage(WM_CLOSE,0,0);

}

지금까지 버튼이 눌리면 두번째 화면이 전환되고 exit버튼을 누르면 프로그램이 종료 되는 방법을 설명하였습니다. 이제 다음화면에서 □이전□ 버튼을 클릭하면 이전 윈도우로 돌아가는 방법에 대해서 설명하겠습니다.

먼저 메인프레임에 이전윈도우로 돌려주는 ShowMainView 함수를 만듭니다. 이함수는 그림 1-9-6의 ②항목과 같은 루틴입니다. 이렇게 되면 다시 메인 화면으로 돌아오게 됩니다.  새로만든 View에서 □이전□버튼을 클릭하면 메인 프레임의 ShowManiView를 호출하는것과 □exit" 버튼을 클릭하면 프로그램이 종료 하는 것은 그림 1-9-9의  내용을 응용하시면 됩니다.

이제 남은 부분은 비트맵을 화면에 출력하는 부분입니다. 이것은 GDI편에서 설명한 비트맵 로드 방법대로 하면됩니다. 먼저 헤더에 CBitmap 형의 변수를 선언합니다.

CBitmap m_pBitmap;

위와 같이 선언하고 OnCrate함수에에서 m_pBitmap에 자원에 있는 비트맵을 로드합니다.

//비트맵을 로드한다.

m_pBitmap.LoadBitmap(IDB_FIRST);

위와 같이 로드한후에 OnDraw함수에서 화면에 출력하면 됩니다.

void CExMultiViewTitleView::OnDraw(CDC* pDC)

{

CExMultiViewTitleDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CDC cdc;

//현재 DC와 호환성이 있는 DC로 설정

cdc.CreateCompatibleDC(pDC);

//이 DC에 비트맵을 설정한다.

cdc.SelectObject(&m_pBitmap);

//cdc를 화면 DC pDC로 출력

pDC->BitBlt(0,0,640,480,&cdc,0,0,SRCCOPY);

}

본예제에서 비트맵은 트루칼라 비트맵입니다. 보통 일반적으로 트루칼라 비트맵은 자원에등록 되지 않는것으로 생각하는 분들이 있습니다. 그러나 트루칼라 비트맵도 자원으로 등록할수 있습니다. 이방법은 의외로 간단합니다. RES 디렉토리에 트루칼라 비트맵 파일을 복사해 두고 난후에,ResourceView항목에서 우측 마우스 버튼을 클릭하여 팝업 메뉴를 실행시킨다음 import 항목을 선택하고 RES에 복사해둔 트루칼라 비트맵 파일을 설정하면 됩니다. 이때 경고 메시지가 발생되는데 이경고는 Resource의 비트맵 에디터에서는 트루칼라를 수정할수 없다는 경고이지 출력을 할수 없다는 경고가 아닙니다.  이렇게 하여 트루칼라 비트맵을 설정하면 화면에 트루칼라 비트맵이 출력됩니다.

<3>예제 ExMultiViewTitle 소스

ExMultiViewTitle 의 소스는 3개의 클래스를 사용하였습니다. 다음은 이 3개의 클래스의 소스입니다.

(1)CMainFrame 소스

(프로그램 소스)

// MainFrm.h : interface of the CMainFrame class

//

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

#if !defined(AFX_MAINFRM_H__64D47A6E_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

#define AFX_MAINFRM_H__64D47A6E_3A68_11D2_8B92_006097AD9DDC__INCLUDED_

#include "ExMultiViewTitleDoc.h"

#include "ExMultiViewTitleView.h"

#include "AnotherView.h"

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

CAnotherView *m_pAnotherView;

CExMultiViewTitleView *m_pMainView;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

void ShowMainView();

void CreateAnotherView();

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__64D47A6E_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include "stdafx.h"

#include "ExMultiViewTitle.h"

#include "MainFrm.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

cs.style = WS_VISIBLE | WS_POPUP ;

int x=GetSystemMetrics(SM_CXMAXTRACK);

int y=GetSystemMetrics(SM_CYMAXTRACK);

x=(x-640)/2;

y=(y-480)/2;

cs.x = x;

cs.y = y;

cs.cx = 640;

cs.cy = 480;

cs.hMenu=NULL;

return TRUE;

}

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

// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

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

// CMainFrame message handlers

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

m_pAnotherView=NULL;

return 0;

}

//첫번째 윈도우에서 "다음" 버튼을 클릭했을때

void CMainFrame::CreateAnotherView()

{

//현재 활성화 되어있는 View가 메인 View이므로

//m_pMainView에 설정한다.

m_pMainView =(CExMultiViewTitleView *) GetActiveView();

//OtherView가 만들어 지지 않았다면

if (m_pAnotherView == NULL)

{

//OtherView를 만들고

m_pAnotherView = new CAnotherView;

CCreateContext context;

//현재View의 토큐먼트를 받는다.

context.m_pCurrentDoc = m_pMainView->GetDocument();

//View를 만든다.

m_pAnotherView->Create(NULL, NULL, 0L, CFrameWnd::rectDefault,

this, 2, &context);

//화면에 업데이트한다.

m_pAnotherView->OnInitialUpdate();

}

//OtherView를 활성화 시킨다.

SetActiveView(m_pAnotherView);

//OtherView를 보여준다.

m_pAnotherView->ShowWindow(SW_SHOW);

//메인 View를 감춘다.

m_pMainView->ShowWindow(SW_HIDE);

//OhterView를 첫번째 PANE로 설정한다.

m_pAnotherView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

RecalcLayout();

}

void CMainFrame::ShowMainView()

{

//메인뷰를 활성화 한다.

SetActiveView(m_pMainView);

//OtherView를 감춘다.

m_pAnotherView->ShowWindow(SW_HIDE);

//메인 View를 보여준다.

m_pMainView->ShowWindow(SW_SHOW);

//메인 View를 첫번째 윈도우로 설정한다.

m_pMainView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

RecalcLayout();

}

(프로그램 소스끝)

(2)CExMultiViewTitleView 소스

(프로그램 소스)

// ExMultiViewTitleView.h : interface of the CExMultiViewTitleView class

//

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

#if !defined(AFX_EXMULTIVIEWTITLEVIEW_H__64D47A72_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

#define AFX_EXMULTIVIEWTITLEVIEW_H__64D47A72_3A68_11D2_8B92_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExMultiViewTitleView : public CView

{

protected: // create from serialization only

CExMultiViewTitleView();

DECLARE_DYNCREATE(CExMultiViewTitleView)

// Attributes

public:

CExMultiViewTitleDoc* GetDocument();

// Operations

public:

CButton m_nextBut;//"다음" 버튼

CButton m_exitBut;//"종료" 버튼

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMultiViewTitleView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

//}}AFX_VIRTUAL

// Implementation

public:

CBitmap m_pBitmap;

virtual ~CExMultiViewTitleView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExMultiViewTitleView)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

//}}AFX_MSG

afx_msg void OnButtonNext();//다음 버튼 눌려졌을때

afx_msg void OnButtonExit();//exit 버튼 눌려졌을때

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExMultiViewTitleView.cpp

inline CExMultiViewTitleDoc* CExMultiViewTitleView::GetDocument()

{ return (CExMultiViewTitleDoc*)m_pDocument; }

#endif

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

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXMULTIVIEWTITLEVIEW_H__64D47A72_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

// ExMultiViewTitleView.cpp : implementation of the CExMultiViewTitleView class

//

#include "stdafx.h"

#include "ExMultiViewTitle.h"

#include "ExMultiViewTitleDoc.h"

#include "ExMultiViewTitleView.h"

#include "MainFrm.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CExMultiViewTitleView

IMPLEMENT_DYNCREATE(CExMultiViewTitleView, CView)

BEGIN_MESSAGE_MAP(CExMultiViewTitleView, CView)

//{{AFX_MSG_MAP(CExMultiViewTitleView)

ON_WM_CREATE()

//}}AFX_MSG_MAP

ON_BN_CLICKED(IDC_NEXT,OnButtonNext) //버튼클릭

ON_BN_CLICKED(IDC_EXIT,OnButtonExit) //버튼클릭

END_MESSAGE_MAP()

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

// CExMultiViewTitleView construction/destruction

CExMultiViewTitleView::CExMultiViewTitleView()

{

// TODO: add construction code here

}

CExMultiViewTitleView::~CExMultiViewTitleView()

{

}

BOOL CExMultiViewTitleView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

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

// CExMultiViewTitleView drawing

void CExMultiViewTitleView::OnDraw(CDC* pDC)

{

CExMultiViewTitleDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CDC cdc;

//현재 DC와 호환성이 있는 DC로 설정

cdc.CreateCompatibleDC(pDC);

//이 DC에 비트맵을 설정한다.

cdc.SelectObject(&m_pBitmap);

//cdc를 화면 DC pDC로 출력

pDC->BitBlt(0,0,640,480,&cdc,0,0,SRCCOPY);

}

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

// CExMultiViewTitleView diagnostics

#ifdef _DEBUG

void CExMultiViewTitleView::AssertValid() const

{

CView::AssertValid();

}

void CExMultiViewTitleView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExMultiViewTitleDoc* CExMultiViewTitleView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExMultiViewTitleDoc)));

return (CExMultiViewTitleDoc*)m_pDocument;

}

#endif //_DEBUG

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

// CExMultiViewTitleView message handlers

int CExMultiViewTitleView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

// TODO: Add your specialized creation code here

//비트맵을 로드한다.

m_pBitmap.LoadBitmap(IDB_FIRST);

//버튼 만들기

CRect rect=CRect(600,10,635,35);

m_nextBut.Create("다음",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,

rect,this,IDC_NEXT);

rect=CRect(600,40,635,65);

m_exitBut.Create("exit",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,

rect,this,IDC_EXIT);

return 0;

}

//다음 버튼 클릭시

void CExMultiViewTitleView::OnButtonNext()

{

((CMainFrame *)AfxGetMainWnd())->CreateAnotherView();

}

//exit 버튼 클릭시

void CExMultiViewTitleView::OnButtonExit()

{

AfxGetMainWnd()->SendMessage(WM_CLOSE,0,0);

}

(프로그램 소스끝)

(2)CAnotherView 소스

#if !defined(AFX_ANOTHERVIEW_H__64D47A79_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

#define AFX_ANOTHERVIEW_H__64D47A79_3A68_11D2_8B92_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// AnotherView.h : header file

//

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

// CAnotherView view

class CAnotherView : public CView

{

protected:

DECLARE_DYNCREATE(CAnotherView)

// Attributes

public:

CAnotherView();           // protected constructor used by dynamic creation

// Operations

public:

CBitmap m_pBitmap;

CButton m_prevBut;

CButton m_exitBut;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAnotherView)

protected:

virtual void OnDraw(CDC* pDC);      // overridden to draw this view

//}}AFX_VIRTUAL

// Implementation

protected:

virtual ~CAnotherView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

// Generated message map functions

protected:

//{{AFX_MSG(CAnotherView)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

//}}AFX_MSG

afx_msg void OnButtonPrev();

afx_msg void OnButtonExit();

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_ANOTHERVIEW_H__64D47A79_3A68_11D2_8B92_006097AD9DDC__INCLUDED_)

// AnotherView.cpp : implementation file

//

#include "stdafx.h"

#include "ExMultiViewTitle.h"

#include "AnotherView.h"

#include "MainFrm.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CAnotherView

IMPLEMENT_DYNCREATE(CAnotherView, CView)

CAnotherView::CAnotherView()

{

}

CAnotherView::~CAnotherView()

{

}

BEGIN_MESSAGE_MAP(CAnotherView, CView)

//{{AFX_MSG_MAP(CAnotherView)

ON_WM_CREATE()

//}}AFX_MSG_MAP

ON_BN_CLICKED(IDC_PREV,OnButtonPrev) //버튼클릭

ON_BN_CLICKED(IDC_EXIT,OnButtonExit) //버튼클릭

END_MESSAGE_MAP()

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

// CAnotherView drawing

void CAnotherView::OnDraw(CDC* pDC)

{

CDocument* pDoc = GetDocument();

CDC cdc;

//현재 DC와 호환성이 있는 DC로 설정

cdc.CreateCompatibleDC(pDC);

//이 DC에 비트맵을 설정한다.

cdc.SelectObject(&m_pBitmap);

//cdc를 화면 DC pDC로 출력

pDC->BitBlt(0,0,640,480,&cdc,0,0,SRCCOPY);

}

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

// CAnotherView diagnostics

#ifdef _DEBUG

void CAnotherView::AssertValid() const

{

CView::AssertValid();

}

void CAnotherView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

#endif //_DEBUG

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

// CAnotherView message handlers

int CAnotherView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

m_pBitmap.LoadBitmap(IDB_SECOND);

//버튼 만들기

CRect rect=CRect(600,10,635,35);

m_prevBut.Create("이전",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON

,rect,this,IDC_PREV);

rect=CRect(600,40,635,65);

m_exitBut.Create("exit",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON

,rect,this,IDC_EXIT);

return 0;

}

void CAnotherView::OnButtonPrev()

{

((CMainFrame *)AfxGetMainWnd())->ShowMainView();

}

void CAnotherView::OnButtonExit()

{

AfxGetMainWnd()->SendMessage(WM_CLOSE,0,0);

}

(프로그램 소스끝)

<4>방패 윈도우

방패 윈도우란 원형 형태를 가진 윈도우로  그림 1-9-10과 같은 형태의 윈도우를

의미합니다. 보통 그림 1-9-10과 같은 윈도우를 만들명 □와! 어떻게 만들었지?□ 하고 매우 신기하게 생각할는지 몰라도 매우 간단합니다.

(그림 1-9-10) 방패 윈도우 예제 ExRoundWnd

방패 윈도우를 만드는 것은 먼저 멀티미디어 타이틀 윈도우를 만들듯이 만든다음 메인프레임에서 SetWindowRgn  함수를 사용하면 됩니다. SetWindowRgn함수의 형태는 다음과 같습니다.

int SetWindowRgn( HRGN hRgn, BOOL bRedraw );

SetWindowRgn함수는 CWnd 의 맴버 함수로써 HRGN형태의 영역을 설정해주면 해당 영역형태로 윈도우가 만들어집니다. HRGN형을 만드는방법은 여러 방법이 있습니다. 이중에서 원형 형태를 만들고자 할때는 Round 박스 형태의 HRGN을 만들어주는 CreateRoundRectRgn 함수를 이용합니다.

만일 640x480의 박스 윈도우를 완전히 원형에 가까운 윈도우로 만들고자 한다면 CreateRoundRectRgn을 이용하여 HRGN형의 변수에 영역을 설정하고 이값을 SetWindowRgn에 설정해 주면 됩니다. 이내용은 다음과 같습니다.

HRGN rgn=CreateRoundRectRgn(0,0,640,480,640,480);

SetWindowRgn(rgn,TRUE);

위와 같이 하면 원형의 방패 윈도우가 자동적으로 생성됩니다. CreateRoundRectRgn의 5,6 번째 인자는 박스를 라운드 형태로 만들때 모서리에 그려주는 원의  가로와 세로의  반지름입니다.

방패 윈도우예제는 제공되는 CD-ROM의 ExRoundWnd 에 있습니다.  OnCreate 에서 원형 윈도우를 만들어주는 것을 제외하고는 ExMultiViewTitle 를 만들때 설명한 방법과 같은 루틴을 이용하였습니다. 소스는 제공되는 CD-ROM을 참조하시기바랍니다.

4.MDI 다양한 윈도우 스타일

본항목에서 작성하고자 하는 MDI 다양한 형태의 윈도우는 그림 1-9-11과 같은 형태입니다.

(그림 파일)

(그림 1-9-11)MDI  다양한 형태의 윈도우

그림 1-9-11 과 같이 MDI에서 우리가 만들고자 하는것은 두개의 다중 템플레이트에서 한개의 템플레이트에는 분할윈도우를,또다른 템플레이트에는 툴바와 상태바를 설정한 템플레이트로 제작합니다. 그리고 메인프레임 윈도우에 배경화면에 bmp파일을 출력하는 예입니다. 그림 1-9-11에서 ①과 ② 항목은 지금까지 설명한 내용을 그대로 응용하면 됩니다. SDI형태에서 분할윈도우를 만들었던 방법으로 MDI형태에서도 AppWizard Step4에서 같은 방법으로 분할윈도우를 설정해주고 OnCreateClient에서 정적 분할윈도우를 설정해 주면됩니다. 단 SDI의 차이점은 SDI에서는 CMainFrame에서 설정하였던 모든것들을 MDI에서는 템플레이트의 프레임 윈도우, 즉 ChildFrame 윈도우와 새로만든 템플레이트에 설정한 차일드 프레임 윈도우에서 설정한다는 것입니다.

<1>MDI 형태의 분할 윈도우

MDI 형태의 분할윈도우를 만드는 예제는 ExMdiSplit 입니다. 이예제에서는 AppWizard에서 프로젝트를 만들때 MDI형태로 설정하고 그림 1-9-1 과 같이 AppWizard Step4에서 Advanced 버튼을 클릭하고 분할윈도우를 설정하면 분할 윈도우 형태로 MDI 프로그램을 제작할수 있습니다. 여기에서 분할윈도우가 설정되는 부분이 CMainFrame이 아니라 CChildFrame 입니다. CChildFrame의 헤더부분과 소스부분에 보면 다음 과 같이 되어 있을것입니다.

//헤더 부분

CSplitterWnd m_wndSplitter;

//소스 부분

BOOL CChildFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

return m_wndSplitter.Create( this,

2, 2,

CSize( 10, 10 ),

pContext );

}

SDI형태에서 CMainFrame에 있던 부분이 그대로 CChildFrame 에 설정된 것입니다. 따라서 모든 내용을 CMainFrame에 설정하였던 것을 그대로 CChildFrame에 설정하면 됩니다. SDI 분할 윈도우에서 사용하였던 방법을 같이 새로운 대화상자 폼을 그림 1-9-11의 ① 항목의 우측에 설정된 윈도우와 같게 만들고 이것을 CAnotherView (base class CFormView) 로 연결합니다. 이렇게 분할윈도우에 설정할 View윈도우를 만든다음 OnCreateClient를 다음과 같이 수정하면 분할윈도우를 설정할수 있습니다.

BOOL CChildFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

m_wndSplitter.CreateStatic(this,1,2);

m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CExMdiSplitView),

CSize(100,100),pContext);

m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CAnotherView),

CSize(160,100),pContext);

return TRUE;

}

위와 같이 하면 간단하게 그림 1-9-11의 ①항목과 같은 윈도우를 만들수 있습니다.

<2>차일드 윈도우에 툴바와 상태바 붙이기

그림 1-9-11의 ② 항목과 같이 또다른 템플레이트를 만들고 이템플레이트에는 툴바와 메뉴바 상태바를 붙였습니다. 이것또한 CMainFrame에 설정하였던 방법과 같습니다. 그림 1-9-11에서 ①항목과 ②항목을 함께 사용한 예제는 제공되는 CD-ROM에 ExMdiSplit2에 있습니다. ExMdiSplit 프로젝트에서 새롭게 템플레이트를 추가한것입니다. 새로운 템플레이틀를 추가할때 프레임 윈도우를 CAnotherFrame로 설정하였습니다. 이 클래스의 base class는 CMDIChildWnd 입니다. 여기에서 우리가 생각하고 넘어가야 할것이 CMDiChildWnd 클래스입니다. CMidChildWnd는  CFrameWnd 를 상속받은 윈도우 클래스 입니다. CFrameWnd에 툴바와 상태바를 붙일수 있다면 CMdiChildWnd 또한 툴바와 상태바를 붙일수 있습니다. ExMdiSplit2에서는 CMainFrame에 있는 상태바와 툴바를 붙이는 내용을 그대로 CAnotherFrame에 설정한것입니다. 다음은 CAnotherFrame의 헤더와 소스부분에서 툴바와 상태바를 붙인 내용입니다.

//헤더부

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

//소스부

int CAnotherFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

if (CMDIChildWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |

WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0("Failed to create toolbar\n");

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0("Failed to create status bar\n");

return -1;      // fail to create

}

// TODO: Delete these three lines if you don't want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

return 0;

}

위와 같이하면 그림 1-9-11의  ②와 같은 항목이 설정됩니다.

<3>메인프레임 배경에 그림 그리기

지금까지 설명하지 않는 부분이 메인프레임에 배경을 그리는 부분입니다. 메인프레임은 보기에 단한개의 윈도우 같으나 실제로는 클라이언트 윈도우를 차일드 윈도우로 가지고 있는 두개의 윈도우 입니다. 즉 메인프레임이 있고 이 메인 프레임 위에 클라이언트 영역을 설정하기 위한 또하나의 윈도우가 있으며 이 윈도우 위에 여러 차일드 윈도우들이 존재합니다. 이렇게 설정되어 있기 때문에 CMainFrame에서 OnPaint 함수를 이용하여 비트맵을 출력하거나 또는 문자를 CMainFrame에 출력하고자 하여도 출력이 되지 않습니다. 출력이 되지 않는 다는 의미가 아니라 메인프레임에 문자와 그림이 출력되었다 하여도 순간적으로 클라이언트 윈도우가 나타나서 그위를 덮어 버리기 때문에 문자와 그림이 출력되지 않는 것처럼 보이는 것입니다.

메인프레임에 그림을 출력하기 위해서는 메인프레임의 차일드 윈도우인 클라이언트 윈도우를 받아야 합니다. 그리고 이 윈도우 위에 그림을 그려야 합니다. 그림 1-9-12는 메인 프레임 배경에 그림을 출력하는 방법입니다.

(그림 파일)

(그림 1-9-12)메인프레임 배경에 그림 출력하기

중요 키포인트라고 본다면 그림 1-9-12의 ③항목입니다. 메인 프레임의 OnCreateClient함수에서 SubclassWindow를 이용하여 새로만든 CWnd형의 윈도우를 CMainFrame 의 m_hWndMDIClient 서브 윈도우로 만드는것입니다.

m_hWndMDIClient 는 클라이언트 윈도우의 핸들입니다. SubclassWindow 함수는 특정 윈도우의 서브 윈도우로 설정되는 것입니다. 쉽게 보변 메인프레임 위에 클라이언트 윈도우가 있고 이 윈도우 위에 CWnd클래스를 상속받은 CMyClient 윈도우가 설정되는 것입니다. 이렇게 설정되면 CMyClient의 OnPaint함수에서 그림을 출력할수가 있습니다. ③항목에서 SubclassWindow를 호출한후에 CMyClient의 LoadBackBitmap 함수를 호출하게 되면 ④ 항목처럼 자원에서 비트맵을 로드하게 됩니다. 이렇게 비트맵을 로드한후에, CMyClient의 OnPaint함수에 출력합니다. 이렇게 되면 클라이언트 윈도우의 서브 윈도우 형태에서 비트맵을 출력하기 때문에 메인프레임 배경에 비트맵을 출력하는 것과 같은 형태가 되는 것입니다.

배경에 비트맵을 출력하여 그림 1-9-11과 같은 형태로 만든 예제는 ExMdiSplit3입니다. 제공되는 CD-ROM에 ExMdiSplit3 에 본 예제가 있습니다. 본예제의 프로젝트명은 ExMdiSplit 입니다. 전항목에서 만든 예제에 새롭게 기능을 추가한것입니다. ExMdiSplit3의 전체 소스는 본책에서 생략합니다. 제공되는 CD를 참조하시기 바랍니다.