8.SDI,MDI 템플레이트

본장에서는 템플레이트에 대한 전반적인 내용에 대해서 설명합니다. 프로그램 내부어느곳에서든지 App클래스,View클래스,Document클래스,차일드 프레임 클래스,메인프레임 클래스를 얻는 방법을 설명하고, 다중 템플레이트를 이용한 방법을 SDI와 MDI 두개의 경우를 통해서 설명합니다.

본장을 통해서 MFC로 작성된 SDI와 MDI의 전체구조를 이해하실수 있을것입니다.

MDI 에서는 여러개의 차일드 윈도우가 출력됩니다. 이여러개의 차일드 윈도우에서 특정 윈도우 클래스 인스턴스를 얻는 방법또한 템플레이트 구조를 알았을때 가능합니다. 본장에서는 이런 방법에 대해서도 알아보며 다중 템플레이트 에서 여러 윈도우를 출력했을때 특정 윈도우를 얻는 방법을 예제로 작성합니다. 본장의 내용은 매우 중요합니다. 본장을 이해하면 MFC로 작성하는 윈도우 프로그램의 전반적인 내용을 알수 있기 때문에 꼭 알아두어야 할내용입니다.

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

Chapter 등급: 필수

공부방법: 예제를 직접 만들어보고 꼭 이해하시기를 바랍니다.

1.SDI 템플레이트

SDI에서는 한개의 템플레이트를 사용합니다. 메인프레임과 View클래스 그리고 도큐먼트가 하나의 템플레이트가 되는 것입니다. 때로는 메인 프레임에서 현재 View 윈도우 클래스 포인터를 알고자 할때가 있으며,도큐먼트 클래스에서 메인프레임 클래스 포인터를 얻고자 할때가 있습니다. 다시 말씀드려서 SDI프로그램에서 어느곳이던지 상관없이 현재의 View와, 메인프레임,도큐먼트를 알고자 할때가 있다는 것입니다. 어플리케이션 프로그램을 작성할때는 특히 이런경우가 매우 많습니다. 메인프레임에서 현재 도큐먼트를 얻고자 할경우가 있으며 대화상자를 실행하였을때 대화상자에서 현재의 View와 Document클래스 포인터를 얻어야 할필요성도 있기 때문입니다. SDI에서 어느곳이던지 상관없이 템플레이트안에 있는 3개의 클래스와 어플리케이션 클래스를 얻는 방법은 그림 1-8-1과 같습니다.

(그림 파일)

(그림 1-8-1)SDI 에서 각 클래스 접근

<1>SDI의 각 클래스 포인터 얻기

Windows Programming Bible에서도 언급한 내용이지만 템플레이트에 설정되어 잇는 각클래스의 접근 방법에 대한 설명을 하기전에 쓸데없는 이야기를 하나 하겠습니다. 마음에 드는 여성에게 환심을 사기위해서는 먼저 그여성을 자주 만나야 합니다.(자꾸 여자 이야기를 많이하는것은 이렇게 함으로써 이해가 되기를 원하는 것이고 이책을 보는 분들중 많은 분이 미혼일거라는 그리고 남자가 많을것이라는 생각 때문이기 때문입니다. 혹시나 필자가 조금 엉큼한 사람이라는 생각을 한다면 맞습니다.) 그 여성을 만나지 않고서는 아무런 일도 일어나지 않습니다. 그리고 나서 얼굴에 나타나는 표정을 보아야 합니다. 예를 들어 □ 우리 순대나 먹으로 갈까요?□ 라고 했을대 환히 웃으면 □아하! 순대를 좋아하는거나□ 라고 생각하고 그리고  계속적으로 순대를 사주면 되는 것입니다. 즉 얼굴의 표정을 보고 그녀가 좋아하는것과 싫어하는 것을 느낄수 있기 때문입니다. 이 이유는 얼굴에 나타나는 표정이 바로 그녀의 생각을 많이 대변하고 있기 때문입니다. 즉 여성을 상대하기 위해서는 먼저 여성을 만나고 만나고 나서야 바로 얼굴을 볼수 있으며 얼굴을 통해서 마음도 알수 있는 것입니다. MFC에서도 같은 형태입니다. 만일 현재 프로그램의 View즉 화면을 (여성의 얼굴에 해당한다고 볼수 있습니다) 얻고자 한다면 먼저 해야할일은 그 프로그램의 메인프레임 (여성의 외형)에 접근하고 그리고 난후에 View에 접근을 해야 합니다. 또한 View는 현재 도큐먼트의 데이터를 출력하는것이 기본이니까 View와 도큐먼트는 쌍으로 볼수가 있는 것입니다.(여성의 얼굴에 나타나는 표정과 마음을 쌍으로 보듯이) 결국 현재 프로그램에서 View와 도큐먼트를 접근하기 위해서는 우선적으로 접근해야 할곳이 바로 프레임입니다. 이프레임을 접근한후에 그리고 바로 뷰나 도큐먼트에 접근을 하는 것입니다. 예를 들어 프로그램에서 View클래스에서 대화상자를 실행시키고 이대화상자에서 □라인□ 이란 버튼을 클릭하면 View에 라인이 그려진다고 가정을 합시다. 일반적인 생각이라면 View위에 대화상자가 실행되었으니까 그냥 바로 View를 어떻게 접근하면 되겠지 할수 있겠으나 (물론 안된다는 것은 아닙니다) View위에서 실행을 하였다 하더라도 현재 View로 접근을 하고자 한다면 먼저 메인 프레임에 접근을 하고 그리고 그 메인프레임에서 View로 접근을 하는 것이 좋습니다.. 이것이 MFC에서 View나 도큐먼트에 접근하는 기본 방식입니다.

다시 말씀드리면 View로 접근하고자 한다면 먼저 프레임에 접근하고 그다음 그프레임에서 뷰로 접근해야 합니다. 프레임은 WinApp안에 소속되어 있습니다. 즉 프로젝트를 만들때 윈도우의 클래스를 등록하는 클래스안에 들어 있다는 의미입니다. 그렇기 때문에 현재 View에 접근하고자 한다면 먼저 WinApp에 접근을 하고 그리고 그안에 프레임에 접근을 한다음 그프레임에서 View로 접근을 하는것이 기본적인 방법입니다. 그림1은 바로 MFC에서 이런 방법으로 각 클래스에 접근하는 것을 도시한것입니다.

예를 들어서 프로젝트명이 ExTempSdk라고 가정을 하고 프로그램 내부의 전국 어느곳에서든지 이프로젝트의 클래스 CExTempSdkApp(CWinApp 를 상속받은 클래스)를 얻고자 한다면 AfxGetApp()함수를 호출하면 됩니다. 이함수는 현재 프로젝트이 CWinApp의 포인터를 리턴합니다. 현재 프로젝트가 CExTempSdkApp라면 다음과 같이 형전환이 필요합니다.

CExTempSdkApp *pApp=(CExTempSdkApp *)AfxGetApp();

현재 프로젝트에서 사용하는 CWinApp계열 클래스는 CWinApp 를 상속받은 ExTempSdkApp이기 때문에 이형태로 형전환을 시킨것입니다.

그림 1-8-1의 ①항목이 이렇게 어플리케이션 클래스를 얻는 형태입니다.

메뉴와 툴바,상태바 등은 메인프레임에 있습니다. 때로는 대화상자에 의해서 또는 뷰의 특정 액션에 의해서 메뉴나 툴바나 상태바를 바꾸어야 할때가 있습니다. 이럴경우 메인프레임을 받아야 합니다. 메인프레임을 받을때 사용하는 함수가 AfxGetMainWnd입니다. 예를 들어 ExTempSdk 프로젝트일 경우 메인프레임을 받고자 한다면 다음과 같이 하면 됩니다.

CMainFrame *pFrame =(CMainFrame *)AfxGetMainWnd();

위와 같이하면 pFrame 에 현재프로젝트의 메인프레임을 리턴하게 됩니다. AfxGetMainWnd는 CFrameWnd를 리턴하기 때문에 이것또한 현재 프로젝트에 맞게 바꾸어 주어야 합니다.

그림 1-8-1의 ②항목이 바로 메인프레임 클래스를 얻는 예입니다.

대화상자에서 특정 항목을 클릭하면 현재 View에서 데이터가 바뀌거나 출력이 바뀌어야 합니다. 이렇게  View를 핸들링하기 위해서는 View클래스를 포인터로 받아야 하는데 이때 사용하는 함수가 GetActiveView()입니다. 헌데 이함수는 CFrameWnd(프로젝트에서는 CMainFrame)의 맴버함수입니다. 따라서 GetActiveView를 호출하기 전에 먼저 CMainFrame를 호출하여 그 포인터를 얻은다음 이 클래스이 맴버 함수 GetActiveView() 를 호출하는 것입니다. ExTempSdk가 프로젝트일때 현재의 View를 받고자 한다면 다음과 같을 것입니다.

CMainFrame *pFrame =(CMainFrame *)AfxGetMainWnd();

CExTempSdkView *pView=(CExTempSdkView *)pFrame->GetActiveView();

AfxGetMainWnd()함수에 의해서 CMainFrame클래스를 pFrame로 받습니다.

그리고 pFrame 의 맴버함수인 GetActiveView()를 실행시켜 현재의 View클래스를 받는 것입니다. 위와 같은 내용을 한줄로 표현한다면 다음과 같습니다.

CExTempSdkView *pView

=(CExTempSdkView *)

((CMainFrame *)AfxGetMainWnd())->GetActiveView()

그림 1-8-1의 ③항목이 바로 View클래슬 얻는 예입니다.

도큐먼트를 받고자 할경우 View와 같습니다. MExSdist가 프로젝트일때 도큐먼트를 받고자 하는 소스는 다음과 같습니다.

CMainFrame *pFrame=(CMainFrame *)AfxGetMainWnd();

CExTempSdkDoc *pDoc=(CExTempSdkDoc *)pFrame->GetActiveDocument();

위의 소스를 한줄로 줄인다면 다음과 같이 할수 있을겁니다.

CExTempSdkDoc *pDoc

=(CExTempSdkDoc*)

((CMainFrame *)AfxGetMainWnd())->GetActiveDocument()

매우 짧은 내용이나 SDI에서 위의 4개의 함수를 이용하는 것은 꼭 알고 넘어가야 할부분입니다. 프로그램을 작성하다 보면 View에서 메인프레임을 핸들링 할때가 있는 가하면 도큐먼트에서  View를 받아서 이용할때가 있습니다. 또한 대화상자에서 현재의 데이터를 메인프레임에 또는 View에 또는 도큐먼트에 넣을때가 매우 많습니다. 이럴경우 위의 4개를 함수를 기억하고 있다면 그렇게 큰 어려움이 없습니다. 부디 이부분은 꼭 기억하시기바랍니다.

<2>템플레이트

템플레이트의 개념은 본 파트 1장과 2장에서 설명된 내용입니다. 본장에서 이템플레이트에 대한 개념을 좀더 설명을 하겠습니다.

SDI형태로 프로젝트를 만들명 CWinApp안에는 CFramWnd형의 m_pMainWnd라는 맴버 변수가 있습니다. 예를 들어 프로젝트가 ExTempSdk 라고 할경우 CExTempSdkApp안에는 m_pMainWnd라는 맴버변수가 있다는 의미입니다. 이것은 바로 CFrameWnd 클래스의 형으로 존재합니다. 이렇게 본다면 App클래스안에는 메인 프레임이 m_pMainWnd로 가지고 있다는 것입니다.그렇기 때문에 AfxGetMainWnd는 CWinApp클래스이 맴버 함수로 선언되어 있는 것입니다.

이 메인 프레임에는 차일드 윈도우로 View를 가지고 있습니다.(엄밀히 말하면 메인 프레임안에는 클라이언트 영역을 설정하는 윈도우가 있고 이 윈도우에 View가 설정됩니다) 이 View와 도큐먼트는 쌍으로 상호관계를 가지고 있기 때문에 프레임에서 View와 도큐먼트를 동시에 갖게 되는 형태입니다. 그렇기 때문에 GetActiveView(),GetActiveDocument() 함수가 CFrameWnd의 맴버 함수로 설정되어 있는 것입니다.

이렇게 생각한다면 CWinApp는 단순하게 메인 윈도우 핸들만 가지고 있고 나머지는 프레임과 도규먼트 뷰가 함께 연결되어 움직인다는 결론을 내릴수 있습니다.

즉 프레임이 뷰를 포함하고 있으며 뷰는 도큐먼트와 쌍으로 연결되어 있고 이도큐먼트또한 프레임에서 관리 하기 때문입니다. 실제로 프레임과 View와 도큐먼트가 쌍으로 움직인다는 내용이 CWinApp 를 상속받은 클래스의 InitInstance() 함수에 보면 나타납니다. 예를들어 프로젝트가 ExTempSdk라고 할경우 CExtempSdkApp의 InitInstance()함수에 다음과 같은 내용이 있을겁니다.

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CExTempSdkDoc),

RUNTIME_CLASS(CMainFrame),       // main SDI frame window

RUNTIME_CLASS(CExTempSdkView));

AddDocTemplate(pDocTemplate);

위의 소스에서 CSingleDocTemplate라는 클래스의 생성자에서 RUNTIME_CLASS로 현재 프로젝트이 View와 도큐먼트와 프레임을 설정하였습니다. 즉 이3개의 클래스를 인자로 넣고 한개의 CSingleDocTemplate 클래스를 만든다는 것입니다.

CSingleDocTemplate 간단를 보통 템플레이트 클래스라고 하며 앞으로는 템플레이트라고 이야기 할것입니다. 템플레이트는 현재 화면에 보이는 View를 감싸고 있는 프레임과 이 View와 쌍으로 연결되는 도큐먼트 그리고 이템플레이트에 연결될 메뉴,아이콘,String Table의 데이터 등의 자원 아이디를 포함하는 클래스입니다.

MFC프로그램을 제작할경우 이템플레이트를 여러개 만들수 있습니다. 그것의 의미는 View와 도큐먼트와 이것을 감싸고 잇는 프레임과 또한 메뉴들이 모두 성격이 다른 또하나의 템플레이트가 필요하다면 얼마든지 로드할수 있다는 의미입니다.

예를 들어서 하나는 단지그림을 그리는 CView형태이고 또하나는 자원을 로드하는 폼뷰형태인 두개의 View와 이두개의 View가 설정하는 데이터가 모두 다르다면 단연히 도큐먼트 가 달라질것이고 이렇게 되면 메뉴또한 달라지게 됩니다. 메뉴가 다르면 또한 도구바까지 다르게 될것이고 결국 View와 도큐먼트 메뉴 프레임이 모두 다르게 되는 것입니다. 그렇기때문에 또하나의 템플레이트를 설정할수 있다는 것입니다. 여러개의 템플레이트를 설정된 프로그램을 다중 템플레이트프로그램이라고 합니다.

위의 소스를 다시 설명한다면 pDocTemplate 는 CSingleDocTemplate 클래스의 변수이며 이것을 생성시켜야 할때는 템플레이트에서 필요한 (프레임,View,도큐먼트,그리고 메뉴와 리소스,아이콘 문자열 테이블)4개의 인자를 설정하면서 생성자를 로드하는 것입니다.이와 같이하여 pDocTemplate를 메모리에 할당한후에,이 변수를인자로 하는 AddDocTemplate 라는 함수를 실행시켜서 템플레이트를 현재 프로젝트에 등록합니다. 이렇게 등록하고 특정 템플레이트를 로드하는 것은 본장 뒤에서 계속 설명할것입니다. 지금까지의 내용에서 우리가 알아야 할것은 프레임,View,도큐먼트 그리고 메뉴와 리소스,아이콘 문자열를 대표하는 리소스 ID를 묶어서 저장한것을 템플레이트라고 하며 이것을 컨트롤하는 클래스가 SDI에서는 CSingleDocTemplate 라는 것입니다.

<3>각클래스 접근 예제 ExTempSdk

ExTempSdk는 SDI형태로 제작된 프로그램입니다. ExTempSdk는 대화상자를 출력하여 대화상자에서 각 클래스에 접근하여 맴버에 값을 설정하고 읽는 예제 프로그램입니다. 그림 1-8-2는 ExTempSdk의 전체 구조입니다.

(그림 파일)

(그림 1-8-2)ExTempSdk의 전체 구조

그림 1-8-2의 ⓐ와 같이 대화상자가 출력되면서 4개의  에디터 박스가 나타납니다. 이 4개의 에디터 박스는 어플리케이션 클래스,CMainFrame,View윈도우,도큐먼트 클래스의 맴버에 설정한 CString m_strData의 값을 출력하는 에디터 박스입니다. 이4개의 에디터 박스와 연결된 대화상자 클래스의 맴버와, 이맴버와 연결된 각클래스의 맴버들의 형태는 그림 1-8-2의 ①,②,③,④와 같습니다. 그림 1-8-2에서 ⑤항목을 클릭하면 4개의 클래스 맴버에 설정되어 있는 m_strData의 값이 화면에 출력되며 ⑥ 항목을 클릭하면 현재 대화상자에 설정되어 있는 값을 각클래스의 m_strData에 저장합니다. 본 예제의 목적은 템플레이트를 구성하는 각클래스의 인스턴스를 얻고 이클래스의 맴버 변수와 맴버 함수들을 어떻게 호출하는가를 설명하기 위해서입니다. 본예제에서는 m_strData라는 CString형의 데이터를 이용하였으나 각 클래스를 호출한 후에 클래스 안에 있는 여러 함수들도 호출할수 있습니다. 예를 들어서 메인프레임에 OnLoadData()라는 함수가 있고 이함수를 대화상자에서나 또는 다른 곳에서 호출하고자 한다면 다음과 같이 할수있습니다.

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

본예제를 만드는 형태는 다음과 같습니다.

(1)각 클래스에 맴버로 m_strData를 설정한다.

SDI형태로 ExTempSdk 프로젝트를 만들고 나면, 어플리케이션 클래스로 CExTempSdkApp라는 클래스가 설정되고, 메인프레임은 CMainFrame,View 윈도우은 CExTempSdkView, 도큐먼트는 CExTempSdkDoc로 설정됩니다. 이 각 클래스 헤더에 다음과 같이 설정합니다.

CString m_strData;

위와 같이 설정한 이유는 대화상자에서 m_strData에 값을 넣고 값을 읽고자 하기 때문입니다.

(2)대화상자 만들기

대화상자는 그림 1-8-2의 ⓐ와 같은 형태로 만듭니다. 각 ID는 그림 1-8-2의 ①,②,③,④를 참조하시기 바랍니다. 이 대화상자를 클래스와 연결하고자 할때,클래스명은 CClassSeeDlg 로 설정하고 Base class는 CDialog로 설정합니다. 각 에디터박스와 CClassSeeDlg의 맴버를 DoDataExchange 함수안에 DDX_Text함수를 이용하여 연결합니다. 연결할때 선언되는 변수는 그림 1-8-2의 ①,②,③,④를 참조하시기 바랍니다.

CClassSeeDlg에서  WM_INITDIALOG 메시지에 의해서 발생되는 함수 OnInitDialog 를 ClassWizard로 만든후에 다음과 같이 수정합니다.

BOOL CClassSeeDlg::OnInitDialog()

{

CDialog::OnInitDialog();

//어플리케이션 클래스의 m_strData를 m_strApp로

m_strApp =((CExTempSdkApp *)AfxGetApp())->m_strData;

//메인 프레임 클래스의 m_strData를 m_strMainframe로

m_strMainframe = ((CMainFrame *)AfxGetMainWnd())->m_strData;

//View 클래스의 m_strData 를 m_strViewClass로

m_strViewclass =((CExTempSdkView *)

((CMainFrame *)AfxGetMainWnd())->GetActiveView())->

m_strData;

//도큐먼트 클래스의 m_strData를 m_strDocument로

m_strDocument = ((CExTempSdkDoc *)

((CMainFrame *)AfxGetMainWnd())->GetActiveDocument())->

m_strData;

UpdateData(FALSE);

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

지금까지 설명한 방법으로 각 클래스에서 m_strData의 값을 현재 에디터박스와 연결되어 있는 변수에 저장하고 UpdateData함수를 이용하여 대화상자로 전송한 것입니다. 그림 1-8-2의 ⑤항목을 클릭하였을때도 위와 같은 루틴을 그대로 사용하여 각클래스의 m_strData의 값을 읽어 옵니다.

//불러 오기 버튼 클릭시시

void CClassSeeDlg::OnOpen()

{

m_strApp =((CExTempSdkApp *)AfxGetApp())->m_strData;

m_strMainframe = ((CMainFrame *)AfxGetMainWnd())->m_strData;

m_strViewclass =((CExTempSdkView *)

((CMainFrame *)AfxGetMainWnd())->GetActiveView())->

m_strData;

m_strDocument = ((CExTempSdkDoc *)

((CMainFrame *)AfxGetMainWnd())->GetActiveDocument())->

m_strData;

UpdateData(FALSE);

}

그림 1-8-2의 ⑥항목을 클릭 하였을대 수행되는 함수는 OnOpen함수와 반대로 현재 대화상자에 설정되어 있는 값을 각클래스의 m_strData로 저장합니다. ClassWizard를 이용하여 ⑥항목을 클릭하였을 때 수행하는 함수를 만든다음 다음과 같이 설정하면 대화상자의 내용이 각클래스이 변수로 저장됩니다.

//저장하기 버튼 클릭시

void CClassSeeDlg::OnSave()

{

UpdateData(TRUE);

m_strApp =((CExTempSdkApp *)AfxGetApp())->m_strData;

((CMainFrame *)AfxGetMainWnd())->m_strData=m_strMainframe;

((CExTempSdkView *)

((CMainFrame *)AfxGetMainWnd())->GetActiveView())->

m_strData=m_strViewclass;

((CExTempSdkDoc *)

((CMainFrame *)AfxGetMainWnd())->GetActiveDocument())->

m_strData=m_strDocument;

}

지금까지 설명한 방법을 이해하시면 ExTempSdk프로그램을 제작하실수 있을것입니다. ExTempSdk의 소스 부분은 생략합니다. 제공되는 CD-ROM를 참조하시기 바랍니다.

ExTempSdk의 중요 요점은 각클래스를 얻는 부분이므로 이부분은 꼭 이해하시기를 바랍니다.

2.SDI 다중 템플레이트

일반적으로 SDI형태의 템플레이트는 1개를 사용합니다. 특별한 어플리케이션에서는 SDI형태의 다중 템플레이트를 쓰는 경우가 있습니다. 워드 프로세서 한글은 MFC로 제작된 형태는 아니지만 SDI형태의 다중 템플레이트와 비슷합니다. 한개의 파일을 오픈하면 해당 파일를 로드하는 또하나의 SDI형태의 프레임이 뜨고 이 프레임에는 메뉴와 각종 툴바가 설정되어 있습니다. 이런 형태를 SDI 로 제작하였을경우 이것을 SDI 다중 템플레이트라고 합니다. 그림 1-8-3은 SDI형태의 다중 템플레이트의 예입니다.

(그림 파일)

(그림 1-8-3)SDI 다중 템플레이트 형태

그림 1-8-3 에서 보는것처럼 한개의 프로그램이 두개의 SDI형태의 템플레이트를 가지고 있습니다.  ①항목은 CView형태의 윈도우를 가지고 있으며 ②항목은 CFormView형태의 윈도우를 가지고 있습니다. 이런 두개의 템플레이트를 가지고 있는 프로그램을 제작하는것은 의외로 간단합니다. SDI형태의 다중 템플레이트는 제작방법은 다음과 같습니다.

<1>템플레이트 등록

새로운 템플레이트로 등록하기 위해서는 새로운 클래스를 만들어야 합니다. 즉 템플레이트 구성에 필요한 프레임윈도우,View윈도우,도큐먼트 3개의 클래스를 만들어야 한다는 것입니다. 이렇게 3개의 클래스를 만들고 또한 템플레이트에 필요한 메뉴,아이콘,문자열 테이블도 만들어야 합니다. 그림 1-8-4는 새로운 템플레이트를 등록하는 방법을 예를 이용하여 도시한것입니다.

(그림 파일)

(그림 1-8-4)다중 템플레이트 등록

그림 1-8-4는 View윈도우가 CFormView인 또하나의 템플레이트를 설정하는 예입니다. 새로운 템플레이트를 만들고 이템플레이트에 View클래스로 CFormView로 사용하고자 한다면 먼저 대화상자 폼을 만든후에 이폼과 연결되는 CFormView를 만들면 됩니다. 그림 1-8-4의 ⓐ,ⓑ가 바로 이항목입니다.

새로운 템플레이트를 구성하기 위해서는 ⓐ, ⓑ 와 같이 View를 만들고,ClassWizared를 이용하여 새로운 클래스를 2개 더만들어야 합니다. 첫번째는 프레임 윈도우로 등록할 CFrameWnd를 상속받은 프레임 윈도우 클래스이며 두번재는 도큐먼트 클래스로 등록할 CDocument를 상속받은 도큐먼트 클래스입니다. 그림 1-8-4에서는 이 두개의 클래스를 만드는 방법을 생략하였습니다. 템플레이트를 구성하기 위해서는 클래스 3개외에 3개의 자원이 필요합니다. 새로운 템플레이트가 로드할 문자열 테이블,아이콘, 그리고 메뉴입니다. 이 3개의 자원은 같은 ID로 구성되어 있어야 합니다. 그림 1-8-4의 ⓒ,ⓓ,ⓔ 는 이3개의 자원을 설정하는 예입니다. 이렇게 3개의 자원을 설정하고 3개의 클래스를 설정하면 템플레이트에 등록할 준비가 끝나게 된것입니다. 이렇게 한후에 그림 1-8-4의  ⓕ 처럼 App클래스이 Initinstance의 AddDocTemplate 함수가 설정된 다음 라인에 새로운 템플레이트를 설정합니다. AddDocTemplate에 첫번째 인자는 3개의 자원 ID이며 그다음부터 도큐먼트 프레임,View윈도우를 차례로 등록합니다. 이렇게 하면 템플레이트가 등록됩니다.

<2>템플레이트 등록 예제 ExSdiMulTemp

ExSdiMulTemp 예제는 전항목에서 설명한 방법으로 다중 템플레이트를 설정하는 예입니다. ExSdiMulTemp에서는 그림 1-8-4와 같이 CFormView를 상속받은 클래스를 만들고 ExSdiMulTemp 제작시에 설정된 CMainFrame와 CExSdiMulTempDoc 을 이용하여 새로운 템플레이트를 만듭니다. 이렇게 템플레이트를 설정한 CExSdiMulTempApp 의 InitInstance함수는 다음과 같습니다.

BOOL CExSdiMulTempApp::InitInstance()

{

AfxEnableControlContainer();

#ifdef _AFXDLL

Enable3dControls();

#else

Enable3dControlsStatic();	// Call this when linking to MFC statically

#endif

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CExSdiMulTempDoc),

RUNTIME_CLASS(CMainFrame),

RUNTIME_CLASS(CExSdiMulTempView));

AddDocTemplate(pDocTemplate);

①	//새로운 템플레이트 등록

AddDocTemplate(new CSingleDocTemplate(

IDR_ANOTHERFRAME,

RUNTIME_CLASS(CExSdiMulTempDoc),

RUNTIME_CLASS(CMainFrame),

RUNTIME_CLASS(CAnotherView)));

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

②	if (!ProcessShellCommand(cmdInfo))

return FALSE;

m_pMainWnd->ShowWindow(SW_SHOW);

m_pMainWnd->UpdateWindow();

return TRUE;

}

위와 같이 설정할때 새로만든 CAnotherView의 클래스 헤더를 ExSdiMulTemp.cpp에 다음과 같이 설정해야 합니다.

#include "AnotherView.h"

위와 같이 한후에 컴파일하고 실행하면 그림 1-8-5와 같은 화면이 출력됩니다.

(그림 1-8-5)ExSdiMulTemp실행시 첫 화면

그림 1-8-5와 같이 대화상자가 설정되는 이유는 현재 프로젝트에 두개의 템플레이트가 있기 때문에 이 두개중 어떤 템플레이트를 설정할것인가를 결정해야 하기 때문입니다. 그림 1-8-5와 같은 대화상자가 출력되었을대 ExSdiM을 선택하면 기본적인 템플레이트가 로드되고 Anoth를 설정하면 FormView로 설정된 새로운 템플레이트가 로드됩니다. 그림 1-8-6은 ExSdiMulTemp에서 ExSdiM을 선택하여 기본적으로 설정된 템플레이트를 로드한 화면입니다.

(그림 1-8-6)ExSdiM 을 선택하여 기본 템플레이트가 로드된 화면

그림 1-8-6과 같은 화면이 출력된 상태에서 New항목을 선택하면 그림 1-8-5와 같은 화면이 다시 출력되고 이때 Anoth를 선택하면 그림 1-8-7과 같은 또다른 윈도우가 나타납니다.

(그림 1-8-7)Anoth 템플레이트가 로드되었을때 화면

위와 같이 파일 메뉴에서 새파일을 선택하게 되면 새로운 대화상자가 나타나는 이유는 InitInstance함수에서 다음과 같은 부분이 있기 때문입니다.

if (!ProcessShellCommand(cmdInfo))

return FALSE;

ProcessShellCommand함수는 현재 어플리케이션 클래스에 설정되어 있는 템플레이트를 조사하여 필요한 템플레이트를 로드합니다. 또는 프로그램과 함께 아규먼트로 설정된 파일명을 이용하여 이파일명을 그대로 템플레이트에 로드할수 있도록 합니다. 이렇기 때문에 ProcessShellCommand함수가 실행되면 현재 로드할 템플레이트를 결정하기 위해서 대화상자를 출력하고 로드할 템플레이트를 선택하게 하는 것입니다.

<3>다중 템플레이트 포인터 얻기

본항목에서는 ProcessShellCommand 함수에 의해서 템플레이트를 선택하는 방식을 이용하지 않고 특정 템플레이트를 로드하는 방법에 대해서 설명하겠습니다. 템플레이트를 관할하는 클래스는 CDocTemplate 입니다. 이클래스의 변수를 선언하면 특정 템플레이트를 얻을수 있습니다. 전항목에서 제작한 ExSdiMulTemp를 이용하여 계속하여 프로그램을 제작해 보겠습니다. 전항목까지의 프로그램 내용은 제공되는 CD-ROM의 ExSdiMulTemp 디렉토리에 있으며 본 항목에서 다시 업그레이드 하는 프로그램은 ExSdiMulTemp2에 있습니다. 먼저 App클래스의 ProcessShellCommand를 삭제하여 템플레이터를 선택하는 대화상자를 구동하기 않도록 합니다. SDI에서는 ProcessShellCommand함수를 제거하거 컴파일한후 실행시키면 에러가 납니다. 이이유는 SDI형태에서는 기본적으로 하나의 템플레이트를 로드하기 때문에 로드되는 템플레이트가 없으면 에러가 나기 때문입니다.

따라서 등록된 템플레이트에서 필요한 부분을 선택하여 로드해야 합니다. 현재 App클래스에 설정되어 있는 템플레이트의 포인터를 얻는 방법은 그림 1-8-8과 같습니다.

(그림 파일)

(그림 1-8-8)템플레이트 포인터 얻기

그림 1-8-8의 ①항목은 ExSdiMulTemp의 템플레이트 리스트에 설정된 두개의 템플레이트 명입니다. App 클래스에서 CWinApp클래스의 맴버 함수인 GetFirstDocTemplatePosition(); 함수를 실행하면 POSITION형태의 포지션 위치를 리턴합니다. 이때 포지션 위치는 템플레이트 리스트의 첫 위치에 설정됩니다. 이위치를 받은  pos의 값을 인자로 하여 GetNextDocTemplate 함수를 그림 1-8-8의 ③항목과 같이 실행하면, ExSdM 템플레이트 포인터를 CDocTemplate 클래스 형으로 리턴하게 됩니다. 실제로 템플레이트 포인터를 받는 부분이 GetNextDocTemplate를 실행한 상태입니다. 만일 두번재 포인터 즉 그림 1-8-8에서 Anoth 템플레이트를 받고자 한다면 그림 1-8-8의 ④항목과 같이 GetNextDocTemplate를 두번 실행하면 됩니다. 한번실행하였을경우에는 첫번재 템플레이트 포인터를 받고 이상태에서 다시 GetNextDocTemplate를 실행하게 되면 두번째 템플레이트를 받게 되는 것입니다. 이렇게 하여 템플레이트를 받은다음 이템플레이의 클래스의 맴버함수인 OpenDocumentFile 함수를 이용하여 새로운 템플레이트를 로드합니다. OpenDocumentFile 함수의 인자에 파일명을 설정하면 해당 파일을 오픈하는형태로 템플레이트가 로드되며,NULL를 설정하면 새파일형태로 템플레이트가 로드되는 것입니다.

방금 설명한 내용을 이용하여 ExSdiMulTemp 예제에서 처음에는 기본적인 템플레이트를 로드하는 형태를 설정하고 File 메뉴에서 New  항목을 선택하면 그림 1-8-8의 ①항목의 첫번째 템플레이트를 로드하고 □Another 템플레이트□ 항목을 선택하면 두번재 템플레이트를 로드하는 예를 만들어 보겠습니다.

먼저 그림 1-8-9와 같이 메뉴을 수정하여 □Another 템플레이틀□ 항목을 두개의 메뉴에(IDR_MAINFRAME,IDR_ANOTHERFRAME) 설정합니다.

(그림 1-8-9)메뉴 항목 추가

그림 1-8-9와 같이 설정한후에 App클래스에서 New항목을 선택하였을때와 Another템플레이트 항목을 선택하였을때 실행하는 함수를 만든다음 다음과 같이 수정합니다.

//File에서 New 항목을 선택하였을때

void CExSdiMulTempApp::OnFileNew()

{

//템플레이트 위치를 얻고

POSITION pos=GetFirstDocTemplatePosition();

CDocTemplate * pTemplate;

//첫번째 템플레이트를 받는다.

pTemplate=GetNextDocTemplate(pos);

//새파일 형태로 템플레이트를 로드한다.

pTemplate->OpenDocumentFile(NULL);

}

//Another 템플레이트 메뉴를 선택하였을때

void CExSdiMulTempApp::OnFileAnonew()

{

//템플레이트 위치를 얻고

POSITION pos=GetFirstDocTemplatePosition();

CDocTemplate * pTemplate;

//첫번째 템플레이트를 받는다.

pTemplate=GetNextDocTemplate(pos);

//두번째 템플레이트 위치를 받는다.

pTemplate=GetNextDocTemplate(pos);

//새파일 형태로 템플레이트를 로드한다.

pTemplate->OpenDocumentFile(NULL);

}

위와 같이 하면 메뉴에서 New항목을 선택하면 기본적으로 설정된 템플레이트를 Another 템플레이트를 선택하면 두번째 템플레이트를 로드하게 됩니다.

App 클래스의 InitInstance에서 ProcessShellCommand 를 주석으로 막고 위에서 제작한 OnFileNew함수를 호출하여 프로그램이 처음 실행되면 기본적으로 설정된 템플레이트를 로드하게 합니다. 다음은 InitInstance를 수정한 내용입니다.

BOOL CExSdiMulTempApp::InitInstance()

{

AfxEnableControlContainer();

#ifdef _AFXDLL

Enable3dControls();

#else

Enable3dControlsStatic();	// Call this when linking to MFC statically

#endif

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CExSdiMulTempDoc),

RUNTIME_CLASS(CMainFrame),

RUNTIME_CLASS(CExSdiMulTempView));

AddDocTemplate(pDocTemplate);

AddDocTemplate(new CSingleDocTemplate(

IDR_ANOTHERFRAME,

RUNTIME_CLASS(CExSdiMulTempDoc),

RUNTIME_CLASS(CMainFrame),

RUNTIME_CLASS(CAnotherView)));

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line

/*	if (!ProcessShellCommand(cmdInfo))

return FALSE;*/

OnFileNew();

m_pMainWnd->ShowWindow(SW_SHOW);

m_pMainWnd->UpdateWindow();

return TRUE;

}

위와 같이하면 특정 템플레이트를 로드하는 ExSdiMulTemp예제가 완성됩니다.

결론을 말씀드리면 다중 템플레이트를 로드할경우에는 그림 1-8-8의 ② 항목처럼 템플레이트 위치를 얻은다음 GetNextDocTemplate함수를 이용하여 템플레이트를 받을수 있다는 것입니다. 이것은 SDI뿐만 아니라 MDI에서도 같은 방법으로 통용이 됩니다.

본예제까지의 소스부분은 제공되는 CD-ROM  를 참조하시기 바랍니다.

<4>동적 템플레이트 생성

지금까지는 템플레이트를 App클래스에 등록을 한후에 이템플레이트를 로드하는 형태를 설명하였습니다. 템플레이트를 등록하지 않고 필요시에 동적으로 로드하는 방법이 있습니다. 이방법 CDocTemplate클래스의 CreateNewFrame 함수를 이용한 방법입니다. 그림 1-8-9은 동적으로 템플레이트를 생성하는 방법을 도시한 그림입니다.

(그림 파일)

(그림 1-8-9)동적으로 템플레이트 구성하기

그림 1-8-9의 ①항목처럼 CSingleDocTemplate 클래스이 변수이 m_pAnotherTemplate 변수를 설정하고 이템플레이트를 3개의 자원과 3개의 클래스를 인자를 설정하여 생성합니다. 이렇게 설정된 m_pAnotherTemplate의 CreateNewFrame 함수를 이용하여 새로운 프레임을 만듭니다. CreateNewFrame함수는 두개의 인자를 설정해주어야 합니다. 첫번째 인자는 CDocument클래스 형인 변수이며 두번째 인자는 CFrameWnd형의 변수입니다. CDocument클래스의 형이라고 하여 꼭 CDocument클래스라는 의미가 아닙니다. m_pAnotherTemplate 생성시에 선언한(그림 1-8-9의 ①항목) CDocument 클래스를 상속받은 클래스 형의 변수형이어도 됩니다. CFrameWnd도 이와 같은 형으로 설정합니다. 이렇게 하여 CreateNewFrame함수를 실행하면 새로운 템플레이트가 로드됩니다. CreateNewFrame함수를 실행하면 로드된 프레임 포인터를 리턴합니다. 이렇게 로드된 프레임을 그림 1-8-9의 ②항목 처럼 pFrame로 받은후에 InitialUpdateFrame 함수의 첫번째 인자로 이값을 넣고, 도큐먼트 클래스 포인터를 두번째 인자로 넣고 설정하면 새로운 템플레이트가 화면에 나타나게 됩니다.

지금까지 설명한 것처럼 CSingleDocTemplate 의 맴버함수에서 CreateNewFrame와 InitialUpdateFrame두개를 이용하여 새로운 템플레이트를 로드할수 있습니다. 이방법 자주 사용되지는 않으나 CSingleDocTemplate 기능을 이해할수가 있는 중요한 예입니다. 즉 CSingleDocTemplate 라는 클래스의 변수를 하나 설정하고 이클래스의 생성자를 이용하여 3개의 자원과 3개의 클래스를 등록한후 CreateNewFrame으로 프레임을 만들고 InitialUpdateFrame를 이용하여 이 템플레이트를 화면에 보여줄수가 있는 것입니다.

동적으로 템플레이트를 생성하는 방법을 이용할때는 주로 같은 도큐먼트를 사용하고 View와 프레임이 다르게 할경우에 많이 사용합니다. ExSdiMulTemp3는 현재 도큐먼트와 프레임을 그대로 사용하고 View만 바꾸는 예입니다.

ExSdiMulTemp3는 ExSdiMulTemp예제를 변형한것입니다. 본예제는 제공되는 CD-ROM에 ExSdiMulTemp3에 있습니다. 그림 1-8-10은 ExSdiMulTemp3의 전체 구조입니다.

(그림 파일)

(그림 1-8-10)ExSdiMulTemp3 전체 구조

그림 1-8-10의 ⓐ화면이 ExSdiMulTemp3 실행한 결과 입니다. ExSdiMulTemp3는 먼저 New항목을 선택하면 기본 템플레이트가 로드되고 "Another 템플레이트□ 항목을 클릭하면 FormView로 설정된 새로운 템플레이트가 로드됩니다. 새로운 템플레이트는 동적으로 로드하는 방법을 이용하였습니다. 새로운 템플레이트를 AppClass의 AddDocTemplate함수를 이용하여 등록한것이 아니라 CSingleDocTemplate 클래스의 변수 형태로 가지고 있습니다. 이렇게 하기 위해서 그림 1-8-10의 ① 항목 처럼 m_pAnotherTemplate라는 변수를 선언한것입니다. 이렇게 선언한후에 그림 1-8-10의 ③ 항목처럼 CSingleDocTemplate 생성자 함수를 이용하여 새로운 템플레이트를 생성합니다. 이렇게 생성한후에 □Another 템플레이트□ 를 선언하면 ⑥ 항목의 함수가 실행되고 새로운 템플레이트가 로드됩니다. 그림 1-8-10의 ⑥ 항목에서는 먼저 템플레이트를 로드하고 로드된 템플레이트의 프레임 윈도우 포인터를 m_pAnotherFrame에 설정합니다. 이렇게 설정하기 위해서 그림 1-8-10의 ① 항목처럼 헤더부에 CMainFrame형의 m_pAnotherFrame를 선언한것입니다. 이렇게 선언하고 m_pAnotherFrame를 생성자에서 NULL로 선언합니다. 이렇게 선언이 되면 ⑥항목에서 ⓑ 항목이 수행되지 않고 그다음으로 넘어가게 됩니다.  다음으로 넘어가면 그림 1-8-9의 로직 형태로  새로운 템플레이트를 동적으로 로드하게 됩니다. 이렇게 로드한후에 ⓒ항목에서 로드된 템플레이트의 프레임윈도우를 m_pAnotherFrame에 설정하게 됩니다. 이렇게 되면 다시 ⑥항목이 수행될경우 m_pAnotherFrame가 NULL이 아니기 때문에 ActivateFrame함수 만실행되고 다시 템플레이트를 만들지 않게 되는 것입니다. ActivateFrame함수는 현재 프레임 윈도우를 활성화 시킨다는 의미입니다. 활성화 시킨다는 것은 현재 프레임이 화면 상단에 위치하면서 포커스가 설정된다는 의미입니다. 지금까지 만들었던 ExSdiMulTemp와 ExSdiMulTemp2는 "Another 템플레이트□ 를 선택하면 한번만 활성화 되고 다시 메뉴를 선택하여도 활성화 되지 않았습니다. 그러나 ExSdiMulTemp3는 다시 메뉴를 선택하면 다시 활성화 됩니다. 이이유는 ⑥ 항목에서 ActivateFrame가 수행되기 때문입니다.

본예제에서도 ProcessShellCommand 함수를 사용하지 않았습니다. 따라서 수동으로 템플레이트를 수행시켜야 합니다. 그림 1-8-9의 ④ 항목을 설정한 이유가 템플레이트를 로드하기 위해서입니다. 프로그램이 수행되면 첫번째 템플레이트를 로드하는 형태를 취했습니다.

그림 1-8-10을 분석하시면 쉽게 동적으로 템플레이트를 로드할수 있을것입니다.

<5>ExSdiMulTemp3 소스

ExSdiMulTemp3는 App 클래스의 헤더부와 소스부만 수정하였습니다. 이부분의 소스는 다음과 같습니다.

(프로그램 소스)

// ExSdiMulTemp.h : main header file for the EXSDIMULTEMP application

//

#if !defined(AFX_EXSDIMULTEMP_H__A7F387D0_2AE8_11D2_8C8B_006097ADB77E__INCLUDED_)

#define AFX_EXSDIMULTEMP_H__A7F387D0_2AE8_11D2_8C8B_006097ADB77E__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

#include "MainFrm.h"

#ifndef __AFXWIN_H__

#error include 'stdafx.h' before including this file for PCH

#endif

#include "resource.h"       // main symbols

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

// CExSdiMulTempApp:

// See ExSdiMulTemp.cpp for the implementation of this class

//

class CExSdiMulTempApp : public CWinApp

{

public:

CExSdiMulTempApp();

CSingleDocTemplate* m_pAnotherTemplate;

CMainFrame *m_pAnotherFrame;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExSdiMulTempApp)

public:

virtual BOOL InitInstance();

//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CExSdiMulTempApp)

afx_msg void OnAppAbout();

afx_msg void OnFileNew();

afx_msg void OnFileAnonew();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

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

#endif // !defined(AFX_EXSDIMULTEMP_H__A7F387D0_2AE8_11D2_8C8B_006097ADB77E__INCLUDED_)

// ExSdiMulTemp.cpp : Defines the class behaviors for the application.

//

#include "stdafx.h"

#include "ExSdiMulTemp.h"

#include "MainFrm.h"

#include "ExSdiMulTempDoc.h"

#include "ExSdiMulTempView.h"

#include "AnotherView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CExSdiMulTempApp

BEGIN_MESSAGE_MAP(CExSdiMulTempApp, CWinApp)

//{{AFX_MSG_MAP(CExSdiMulTempApp)

ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

ON_COMMAND(ID_FILE_NEW, OnFileNew)

ON_COMMAND(ID_FILE_ANONEW, OnFileAnonew)

//}}AFX_MSG_MAP

// Standard file based document commands

ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

// Standard print setup command

ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

END_MESSAGE_MAP()

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

// CExSdiMulTempApp construction

CExSdiMulTempApp::CExSdiMulTempApp()

{

// TODO: add construction code here,

// Place all significant initialization in InitInstance

m_pAnotherFrame=NULL;

}

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

// The one and only CExSdiMulTempApp object

CExSdiMulTempApp theApp;

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

// CExSdiMulTempApp initialization

BOOL CExSdiMulTempApp::InitInstance()

{

AfxEnableControlContainer();

// Standard initialization

// If you are not using these features and wish to reduce the size

//  of your final executable, you should remove from the following

//  the specific initialization routines you do not need.

#ifdef _AFXDLL

Enable3dControls();	// Call this when using MFC in a shared DLL

#else

Enable3dControlsStatic();	// Call this when linking to MFC statically

#endif

// Change the registry key under which our settings are stored.

// You should modify this string to be something appropriate

// such as the name of your company or organization.

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();  // Load standard INI file options (including MRU)

// Register the application's document templates.  Document templates

//  serve as the connection between documents, frame windows and views.

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CExSdiMulTempDoc),

RUNTIME_CLASS(CMainFrame),       // main SDI frame window

RUNTIME_CLASS(CExSdiMulTempView));

AddDocTemplate(pDocTemplate);

m_pAnotherTemplate =new CSingleDocTemplate(

IDR_ANOTHERFRAME,

RUNTIME_CLASS(CExSdiMulTempDoc),

RUNTIME_CLASS(CMainFrame),

RUNTIME_CLASS(CAnotherView));

// Parse command line for standard shell commands, DDE, file open

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line

//	if (!ProcessShellCommand(cmdInfo))

//	return FALSE;

POSITION pos=GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

pTemplate=(CMultiDocTemplate *)GetNextDocTemplate(pos);

pTemplate->OpenDocumentFile(NULL);

m_pMainWnd->ShowWindow(SW_SHOW);

m_pMainWnd->UpdateWindow();

return TRUE;

}

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

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog

{

public:

CAboutDlg();

// Dialog Data

//{{AFX_DATA(CAboutDlg)

enum { IDD = IDD_ABOUTBOX };

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAboutDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

//{{AFX_MSG(CAboutDlg)

// No message handlers

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)

{

//{{AFX_DATA_INIT(CAboutDlg)

//}}AFX_DATA_INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

//{{AFX_MSG_MAP(CAboutDlg)

// No message handlers

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

// App command to run the dialog

void CExSdiMulTempApp::OnAppAbout()

{

CAboutDlg aboutDlg;

aboutDlg.DoModal();

}

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

// CExSdiMulTempApp commands

void CExSdiMulTempApp::OnFileNew()

{

((CMainFrame *)AfxGetMainWnd())->ActivateFrame(-1);

}

void CExSdiMulTempApp::OnFileAnonew()

{

if(m_pAnotherFrame!=NULL)

{

((CMainFrame *)m_pAnotherFrame)->ActivateFrame(-1);

return;

}

CFrameWnd *pActiveFrame = ((CMainFrame *)m_pMainWnd)->GetActiveFrame();

CDocument* pDocument;

pDocument= ((CMainFrame *)m_pMainWnd)->GetActiveDocument();

CFrameWnd* pFrame=m_pAnotherTemplate->CreateNewFrame(pDocument,pActiveFrame);

m_pAnotherTemplate->InitialUpdateFrame(pFrame,pDocument);

m_pAnotherFrame=(CMainFrame *)pFrame;

}

(프로그램 소스끝)

3.MDI 템플레이트

MDI 형태는 SDI형태와 다르게 메인 프레임이 있으며 차일드 프레임과 View클래스 도큐먼트를 3개를 하나의 템플레이트로 설정하고 한개의 템플레이트를 이용하여 여러개의 윈도우를 출력할수 있는 형태입니다. 1장에서도 설명하였듯이 MDI에서는 메인프레임과 View사이에 차일드 프레임이라는 것이 있는 형태가 됩니다.

MDI 에서는 여러개의 템플레이트를 선언할수 있으며 좀더 다양한 형태의 윈도우를 구사하면 컨트롤 할수 있습니다. 본항목에서는 MDI 프로그램에서 템플레이트를 원할하게 컨트롤 하는 방법들을 분석해 보겠습니다.

<1>MDI의 각 클래스 포인터 얻기

MDI 각클래스의 포인터를 얻는 방법은 그림 1-8-11과 같습니다.

(그림 파일)

(그림 1-8-11)MDI에서 각클래스 포인터 얻기

그림 1-8-11의 ⓐ항목이 MDI형태의 프로그램입니다. 이프로그램을 ExMdiCont라고 하였을경우 각 클래스에 접근하는 방법을 예로 도시한것이 그림 1-8-11입니다. ExMdiCont로 프로젝트를 만들면 어플리케이션 클래스는 CExMdiContApp로 ,메인프레임 클래스는 CMainFrame,차일드 프레임클래스는 CChildFrame,View는 CExMdiContView, 도큐먼트는 CExMdiContDoc로 설정됩니다. 이렇게 설정되었을때,App클래스를 받는 방법이 그림 1-8-11의 ①항목입니다. AfxGetApp()함수는 프로그램 내부 어느곳에서든지 어플리케이션 클래스의 포인터를 받고자 할때 사용하는 함수입니다. ① 항목에서 (CExMdiContApp *) 로 형전환을 시킨이유는 AfxGetApp함수를 호출하였을경우에는 CWinApp 클래스의 포인터를 리턴하기 때문에 CWinApp클래스를 상속받은 현재 프로젝트의 App클래스 형으로 전환시키기 위해서 입니다. 나머지 ②,③,④,⑤ 모두 같은 방법으로 현재 프로젝트에 설정되어 있는 클래스로 형전환을 하였습니다. 메인프레임을 받는것은 SDI형태와 같이 AfxGetMainWnd() 함수를 호출하면 됩니다. 그림 1-8-11의  ②항목이 바로 메인프레임을 받는 방법입니다. SDI형태와 다른것이 바로 차일드 프레임을 받는 것입니다. 차일드 프레임을 받을경우 메인프레임 클래스의 멤버 함수인 GetActiveFrame함수를 이용하면 됩니다. ③ 항목에서 GetActiveFrame함수를 호출할때 ②항목에서 메인프레임을 받았던 클래스 포인터 pFrame를 사용한 이유가 바로 이것 때문입니다. 이렇게 하면 차일드 프레임을 받을수 있습니다. 도큐먼트와 View는 SDI와 같은 방법으로 GetActiveDocument와 GetActiveView함수를 이용하면 됩니다. 그런데 여기서 이함수는 차일드 프레임 클래스의 맴버 함수를 호출해야 합니다. 이이유는 차일드 프레임이 바로 View와 Document를 감싸고 있기 때문입니다. 다시 설명드리면 템플레이트에 등록된 프레임 클래스로 부터 View와 Document를 받을수 있는 것입니다. 그림 1-8-11에서 ④와 ⑤ 항목이 각각 Document와 View를 받는 방법입니다. 이때 ③항목에서서 차일드 프레임 클래스를 받았던 pFrame를 사용하여 이멤버함수 GetActiveDocument와 GetActiveView를 이용한것입니다.

그림 1-8-11 를 유념하여 보셔야 합니다. 어플리케이션 프로그램을 작성할경우 각 클래스 포인터를 얻고자 할때가 매우 많습니다. 이럴경우에 그림 1-8-11과 같은 방법을 주로 이용합니다.

<2>MDI의 각 클래스 포인터 얻기 예제 ExMdiCont

ExMdiCont는 대화상자를 출력하고 대화상자에서 각 클래스들의 멤버에 접근하여 데이터를 읽고 저장하는 예제 입니다. 이 예제는 ExTempSdk 와 비슷한 형태이며 차일드 프레임이 설정된것이 차이 입니다. 그림 1-8-12는 ExMdiCont의 전체 구조 입니다.

(그림 파일)

(그림 1-8-12)ExMidCont 전체 구조

그림 1-8-12의 ⓐ 항목이 ExMdiCont 프로그램에서 대화상자를 출력한 형태입니다. ⓐ항목의 대화상자 폼과 연결된 클래슨는 CDialog를 상속받은 CMdiContDlg 입니다. 이대화상자의 각 자원 ID와 CMdiContDlg의 맴버는 그림 1-8-12의 ①,②,③,④,⑤ 와 같이 연결합니다. 이렇게 연결한후 CMdiContDlg의 맴버들은 각클래스의 CString m_strData와 연결되는 것입니다.  이렇게 하기 위해서 ExMdiCont프로젝트를 만든다음 각클래스의 헤더에 m_strData를 CString 형으로 선언하고 생성자에 다음과 같이 선언을 해야 합니다.

m_strData= "";

위와 같이 한상태에서 대화상자를 실행시키면 각클래스의 m_strData값을 읽어서 해당 에디터박스에 출력하게 됩니다. 대화상자가 실행되었을때 각 클래스의 데이터를 읽고 이것을 에디터 박스에 전송한 OnInitDialog함수는 다음과 같습니다.

BOOL CMdiContDlg::OnInitDialog()

{

CDialog::OnInitDialog();

//각 클래스의 m_strData를 읽고 이것을 대화상자에 전송

OnLoaddata();

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

void CMdiContDlg::OnLoaddata()

{

//어플리케이션 클래스 포인터 얻기

CExMdiContApp *pApp=(CExMdiContApp *)AfxGetApp();

//메인프레임 클래스 포인터 얻기

CMainFrame *pFrame=(CMainFrame *)AfxGetMainWnd();

//현재 활성돠 되어 잇는 차일드 프레임 클래스 얻기

CChildFrame *pChild=(CChildFrame *)pFrame->GetActiveFrame();

//도큐먼트 클래스 얻기

CExMdiContDoc *pDoc=(CExMdiContDoc *)pChild->GetActiveDocument();

//View 클래스 얻기

CExMdiContView *pView=(CExMdiContView *)pChild->GetActiveView();

//각클래스의 데이터를 대화상자와 연결된 맴버로

m_strApp=pApp->m_strData;

m_strMainFrame=pFrame->m_strData;

m_strChildFrame=pChild->m_strData;

m_strViewClass=pView->m_strData;

m_strDocument=pDoc->m_strData;

//맴버 데이터를 대화상자로 전송

UpdateData(FALSE);

}

OnLoadData 함수는 그림 1-8-12의 ⑥항목을 선택하였을때 도 실행되게 됩니다. 대화상자가 실행되었을때와 같은 기능을 하기 때문입니다. 그림 1-8-12의 ⑦항목을 선택하였을때는 OnLoadData와 대조적으로 수행됩니다. 이 함수는 다음과 같습니다.

void CMdiContDlg::OnSavedata()

{

//자원에서 맴버로

UpdateData(TRUE);

//어플리케이션 클래스 포인터 얻기

CExMdiContApp *pApp=(CExMdiContApp *)AfxGetApp();

//메인프레임 클래스 포인터 얻기

CMainFrame *pFrame=(CMainFrame *)AfxGetMainWnd();

//현재 활성돠 되어 잇는 차일드 프레임 클래스 얻기

CChildFrame *pChild=(CChildFrame *)pFrame->GetActiveFrame();

//도큐먼트 클래스 얻기

CExMdiContDoc *pDoc=(CExMdiContDoc *)pChild->GetActiveDocument();

//View 클래스 얻기

CExMdiContView *pView=(CExMdiContView *)pChild->GetActiveView();

//대화상자에 입력된 데이터를 각클래스의 맴버로

pApp->m_strData=m_strApp;

pFrame->m_strData=m_strMainFrame;

pChild->m_strData=m_strChildFrame;

pView->m_strData=m_strViewClass;

pDoc->m_strData=m_strDocument;

}

이와 같이 하면 ExMdiCont의 대화상자 부분의 내용을 만들수가 있습니다.

이렇게 만든후에 ExMidCont예제에서는 클래스라는 메뉴항목을 만들고 □대화상자□ 항목을 만든후에 이항목을 클릭하면 CMdiContDlg 대화상자가 실행하도록 하였습니다.  이내용은 View클래스에 설정하였는데 설정된 함수는 다음과 같습니다.

#include "MdiContDlg.h"

void CExMdiContView::OnDodialog()

{

CMdiContDlg *pDlg = new CMdiContDlg;

pDlg->DoModal();

delete pDlg;

}

이와 같이 하면 ExMdiCont 예제를 완성하게 됩니다. 실제 프로그램을 작성해 보고 각 클래스에 데이터를 저장 또는 로드하면서 각클래스에 접근하는 방법을 익히시기를 부탁드립니다.

<3>예제 ExMdiCont 소스

ExMdiCont는 각클래스 헤더에 CString m_strData를 설정하고 생성자에 초기화 환부분과 View클래스에서 대화상자를 실행한 부분을 제외하면 모두 CMdiContDlg 클래스에서 만 코딩이 되어 있습니다. 다른 부분들의 소스는 제공되는 CD를 참조하시기 바랍니다. 지면 관계상 중요하지 않는 소스는 최대한 생략하려고 합니다.

다음은 CMdiContDlg의 전체 소스입니다.

(프로그램 소스)

#if !defined(AFX_MDICONTDLG_H__BA8E87C0_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

#define AFX_MDICONTDLG_H__BA8E87C0_3963_11D2_8B8F_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// MdiContDlg.h : header file

//

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

// CMdiContDlg dialog

class CMdiContDlg : public CDialog

{

// Construction

public:

CMdiContDlg(CWnd* pParent = NULL);   // standard constructor

// Dialog Data

//{{AFX_DATA(CMdiContDlg)

enum { IDD = IDD_MIDCONTDLG };

CString	m_strApp;

CString	m_strMainFrame;

CString	m_strChildFrame;

CString	m_strViewClass;

CString	m_strDocument;

//}}AFX_DATA

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMdiContDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CMdiContDlg)

virtual BOOL OnInitDialog();

afx_msg void OnLoaddata();

afx_msg void OnSavedata();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

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

#endif // !defined(AFX_MDICONTDLG_H__BA8E87C0_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

// MdiContDlg.cpp : implementation file

//

#include "stdafx.h"

#include "ExMdiCont.h"

#include "MainFrm.h"

#include "ChildFrm.h"

#include "ExMdiContDoc.h"

#include "ExMdiContView.h"

#include "MdiContDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CMdiContDlg dialog

CMdiContDlg::CMdiContDlg(CWnd* pParent /*=NULL*/)

: CDialog(CMdiContDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CMdiContDlg)

m_strApp = _T("");

m_strMainFrame = _T("");

m_strChildFrame = _T("");

m_strViewClass = _T("");

m_strDocument = _T("");

//}}AFX_DATA_INIT

}

void CMdiContDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMdiContDlg)

DDX_Text(pDX, IDC_APPCLASS, m_strApp);

DDX_Text(pDX, IDC_MAINFRAME, m_strMainFrame);

DDX_Text(pDX, IDC_CHILDFRAME, m_strChildFrame);

DDX_Text(pDX, IDC_VIEWCLASS, m_strViewClass);

DDX_Text(pDX, IDC_DOCUMENTCLASS, m_strDocument);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMdiContDlg, CDialog)

//{{AFX_MSG_MAP(CMdiContDlg)

ON_BN_CLICKED(IDC_LOADDATA, OnLoaddata)

ON_BN_CLICKED(IDC_SAVEDATA, OnSavedata)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

// CMdiContDlg message handlers

BOOL CMdiContDlg::OnInitDialog()

{

CDialog::OnInitDialog();

//각 클래스의 m_strData를 읽고 이것을 대화상자에 전송

OnLoaddata();

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

void CMdiContDlg::OnLoaddata()

{

//어플리케이션 클래스 포인터 얻기

CExMdiContApp *pApp=(CExMdiContApp *)AfxGetApp();

//메인프레임 클래스 포인터 얻기

CMainFrame *pFrame=(CMainFrame *)AfxGetMainWnd();

//현재 활성돠 되어 잇는 차일드 프레임 클래스 얻기

CChildFrame *pChild=(CChildFrame *)pFrame->GetActiveFrame();

//도큐먼트 클래스 얻기

CExMdiContDoc *pDoc=(CExMdiContDoc *)pChild->GetActiveDocument();

//View 클래스 얻기

CExMdiContView *pView=(CExMdiContView *)pChild->GetActiveView();

//각클래스의 데이터를 대화상자와 연결된 맴버로

m_strApp=pApp->m_strData;

m_strMainFrame=pFrame->m_strData;

m_strChildFrame=pChild->m_strData;

m_strViewClass=pView->m_strData;

m_strDocument=pDoc->m_strData;

//맴버 데이터를 대화상자로 전송

UpdateData(FALSE);

}

void CMdiContDlg::OnSavedata()

{

//자원에서 맴버로

UpdateData(TRUE);

//어플리케이션 클래스 포인터 얻기

CExMdiContApp *pApp=(CExMdiContApp *)AfxGetApp();

//메인프레임 클래스 포인터 얻기

CMainFrame *pFrame=(CMainFrame *)AfxGetMainWnd();

//현재 활성돠 되어 잇는 차일드 프레임 클래스 얻기

CChildFrame *pChild=(CChildFrame *)pFrame->GetActiveFrame();

//도큐먼트 클래스 얻기

CExMdiContDoc *pDoc=(CExMdiContDoc *)pChild->GetActiveDocument();

//View 클래스 얻기

CExMdiContView *pView=(CExMdiContView *)pChild->GetActiveView();

//대화상자에 입력된 데이터를 각클래스의 맴버로

pApp->m_strData=m_strApp;

pFrame->m_strData=m_strMainFrame;

pChild->m_strData=m_strChildFrame;

pView->m_strData=m_strViewClass;

pDoc->m_strData=m_strDocument;

}

(프로그램 소스끝)

4.MDI 다중 템플레이트

다중 템플레이트는 SDI형태에서 보다 MDI형태에서 많이 사용됩니다. MDI형태로 프로젝트를 만들고 난후에 또다른 템플레이트를 만들고자 할때는 SDI 에서 언급하였듯이 3개의 자원과 3개의 클래스를 등록하여야 합니다. 먼저 3개의 자원은 문자열 테이블,메뉴,아이콘 입니다. 이자원을 만들때는 같은 ID를 이용해야 합니다. 문자열 테이블에서 먼저 ID를 설정한후 메뉴와 아이콘을 만드는것이 순서입니다. 메뉴와 아이콘틀 만들고 문자열테이블에서 같은 ID를 사용하고자 한다면 같은 ID가 존재한다는 에러 메시지가 발생되며 ID를 만들수가 없습니다. 이렇게 3개의 자원을 만든후에 3개의 클래스를 만들어야 합니다. 이3개의 클래스는 프레임 클래스,도큐먼트 클래스, View클래스입니다. 프레임 클래스는 base class를 CMDIChildWnd 로 설정하여야 하며 도큐먼트 클래스는 CDocument를 설정하여야 합니다. View클래스에서 FormView를 이용하고자 한다면 SDI에서 설명하였듯이 먼저 대화상자 폼을 만든후에 ClassWizard 를 이용하여 View클래스를 만들어야 합니다. 지금까지 클래스 위저드를 이용하여 새로운 클래스를 만드는 방법에 대해서 많은 설명을 했기 때문에 3개의 클래스를 어떤 형식으로 만들어야 한다고 하면 이해가 되실것입니다. 본항목에서는 MDI용 다중 템플레이트를 로드하는 방법과 각 템플레이트에서 로드된 윈도우 들을 컨트롤 하는 방법에 대해서 알아 보겠습니다.

<1>기본적인 MDI 다중 템플레이트

ExMdiTemp 예제는 기본적인 MDI템플레이트를 만드는 예입니다. 그림 1-8-13 은 ExMdiTemp의 전체 구조 입니다.

(그림 파일)

(그림 1-8-13)ExMdiTemp 전체 구조

ExMdiTemp 예제를 만들기 위해서는 먼저 3개의 자원 (문자열테이블,메뉴,아이콘)을 만들어야 합니다. 이렇게 만든 자원의 ID를 ExMdiTemp에서는 IDR_ANOTHERTYPE 으로 설정하엿습니다. 이렇게 설정된 3개의 자원이 그림 1-8-3의 ③,④,⑤에 해당됩니다. 다음으로 3개의 클래스를 만들어야 합니다. 이 3개의 클래스는 ⑥,⑦,⑧에 해당 됩니다. 이렇게 만든다음 App클래스의 InitInstance에서 이 새로운 템플레이트를 등록합니다. 다음은 ExMdiTemp의 App 클래스의 InitInstance 소스 내용입니다.

BOOL CExMdiTempApp::InitInstance()

{

AfxEnableControlContainer();

#ifdef _AFXDLL

Enable3dControls();

#else

Enable3dControlsStatic();

#endif

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(

IDR_EXMDITTYPE,

RUNTIME_CLASS(CExMdiTempDoc),

RUNTIME_CLASS(CChildFrame), // custom MDI child frame

RUNTIME_CLASS(CExMdiTempView));

AddDocTemplate(pDocTemplate);

①	AddDocTemplate(

new CMultiDocTemplate(

IDR_ANOTHERTYPE,

RUNTIME_CLASS(CAnotherDoc),

RUNTIME_CLASS(CAnotherFrame),

RUNTIME_CLASS(CAnotherView))

);

CMainFrame* pMainFrame = new CMainFrame;

if (!pMainFrame->LoadFrame(IDR_MAINFRAME))

return FALSE;

m_pMainWnd = pMainFrame;

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

if (!ProcessShellCommand(cmdInfo))

return FALSE;

pMainFrame->ShowWindow(m_nCmdShow);

pMainFrame->UpdateWindow();

return TRUE;

}

3개의 클래스와 3개의 자원을 만든다음 위의 소스에서 ①항목과 같이 설정하면 그림 1-8-13의 ⓐ와 같은 화면이 출력되고 이때 템플레이트를 선택하면 해당 템플레이트가 로드됩니다. 이부분은 SDI에서와 같을 것입니다. ExMdiTemp에서는 더이상의 프로그래밍이 없습니다. 지금까지 설명한 내용대로 프로그램을 만들어 보시기 바랍니다. 주의 할점은 ① 항목에서 설정된 클래스 3개의 헤더 파일을 포함 시켜야 합니다. 제공되는 CD에서  ExMdiTemp 에서 CExMdiTempApp의 소스부분을 보시면 다음과 같이 헤더파일을 포함한것을 보실수가 있을것입니다.

#include "AnotherFrame.h"

#include "AnotherDoc.h"

#include "AnotherView.h"

위와 같이 해야 컴파일 할때 에러가 안납니다. 당연한 이야기를 계속적으로 하는 이유는 지킴이 웹서버의 묻고 답하기에서 이부분때문에 질문이 매우 많이 오기 때문입니다. Visual C++에서 프로그램을 작성할때 자동적인 부분이 매우 많아서 클래스만 만들면 자동적으로 인식해서 포함시키는것으로 아는 분들이 많은것 같습니다.

만약 그렇게 생각하시는 분들이 아직도 있다면(지금까지 책을 읽었는데) 조금 숙고 해야할 부분이 많을 것입니다.

<2>MDI 다중 템플레이트 선택적 로드

MDI 다중 템플레이트를 선택적으로 로드하는 부분은 SDI에서 선택적으로 로드하는 부분과 비슷합니다.  본항목에서는 예제를 작성해 가면서 다중 템플레이트를 선택적으로 로드하는 부분을 설명하겠습니다. 본항목에서 작성할 예제는 ExMdiTemp2입니다. 이 예제의 프로젝트는 전항목에서 설정한 ExMdiTemp를 이용한것입니다. (제공되는 CD에는 ExMdiTemp2 디렉토리에 있습니다.)

ExMdiTemp2의 전체적은 구조는 그림  1-8-14와 같습니다.

(그림 파일)

(그림 1-8-14)ExMdiTemp2의 전체 구조

(그림 1-8-14)의 ⓐ항목과 같이 ExMdiTemp2는 3개의 메뉴항목에 따라 서로 다르게 템플레이트가 로드됩니다. 첫번째 메뉴항목은 새파일 이며 이메뉴항목을 선택하면 등록된 첫번째 템플레이트를 로드합니다. ②항목이  이 메뉴항목을 선택하였을때 수행되는 함수입니다. SDI에서 선택적으로 템플레이트를 로드하는 부분과 같습니다. 먼저 템플레이트 위치를 찾기위해 GetFirstDocTemplatePosition 함수를 실행하고 이함수에 의해서 포지션 위치를 pos에 받습니다. 이렇게 받은후에 GetNextDocTemplate(pos); 실행하면 첫번재 템플레이트를 받습니다. GetNextDocTemplate는 CDocTemplate 클래스의 맴버 함수입니다. 본예제는 MDI형태로 제작하였기 때문에 CMultiDocTemplate 사용하므로 이형으로 전환시켜 pTemplate에 템플레이트 포인터를 받은다음 이템플레이트를 OpenDocumentFile함수를 이용하여 로드한것입니다. ⓐ항목에서 두번째 메뉴 Another File를 선택하면 그림 1-8-14의 ③항목이 수행됩니다. 이함수에서는 GetNextDocTemplate를 두번사용하여 두번째 템플레이트를 받은것입니다. 이렇게 받기 이전에 InitInstance함수에서 ①항목처럼 템플레이트를 등록해야 합니다. 등록을 해주지 않으면 두번째 템플레이트가 없기 때문에 에러를 리턴할것입니다. 이렇게 하면 그림 1-8-14의  ⓑ그림처럼 두번재 템플레이트가 로드됩니다. 세번재 메뉴 즉 □동적 Another File" 를 선택하면 ④항목이 실행됩니다. 이항목에서는 등록된 템플레이트를 로드하는 것이 아니라 템플레이트 클래스를 이용하여 동적으로 템플레이트를 생성하는 것입니다. 이부분은 SDI에서설명한 내용과 비슷합니다. 새로운 프레임과 도큐먼트를 만들고 난후에 ①항목에서 설정한 pAnotherTemplate 의 맴버함수 CreateNewFrame 함수를 이용하여 새로운 프레임을 만들고 InitialUpdateFrame 함수를 이용하여 새로운 템플레이트를 로드한것입니다. 이렇게 로드되면 기존의 템플레이트와 약간의 차이가 있게 로드됩니다. 그림 ⓑ항목에서 동적으로 로드된 템플레이트를 보시면 캡션바에 문자열이 없을것입니다. 이문자열 테이블은 App클래스에 등록되었을때 템플레이트를 로드하면서 문자열 테이블을 설정하여 주는데,동적으로 템플레이트를 만들게 되면 이런 부분이 없기 때문에 문자열 테이블이 설정되지 않는 것입니다. InitialUpdateFrame 함수대신에 OpenDocumentFile 함수를 사용하게 되면 두번째 메뉴를 선택한것과 같이 문자열이 출력되면서 템플레이트가 로드됩니다.

ExMdiTemp2는 SDI에서 설명한 부분과 일치되는 내용이 많습니다. 템플레이트 컨트롤 하는 부분은 사실 SDI나 MDI나 같은 루틴입니다. 중요한 점은 MDI에서는 여러개의 템플레이트가 메인 프레임 내부에 있는 것이고 SDI에서는 또다른 어플리케이션이 실행되듯이 되는 형태가 다른것입니다.

ExMdiTemp2는 프로그램이 실행되면 메인프레임만 출력되고 아무런 템플레이트로 출력되지 않는 형태로 만들었습니다. 이렇게 만드는것은 매우 간단합니다. InitInstance 함수에서 ProcessShellCommand 를 제거하면 됩니다. 이것이 제거되었을때 SDI에서는 수동으로 하나의 템플레이트를 선언해야 하지만 MDI에서는 아무것도 선언할 필요가 없습니다. 이이유는 메인프레임이 그어떤 템플레이트에 속해있지 않고 여러 템플레이트를 보관하는 프레임 이기 때문입니다.

본예제는 CExMdiTempApp 클래스에서만 코딩하였습니다. 다음은 CExMdiTempApp의 소스 입니다.

(프로그램 소스)

// ExMdiTemp.h : main header file for the EXMDITEMP application

//

#if !defined(AFX_EXMDITEMP_H__BA8E87C5_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

#define AFX_EXMDITEMP_H__BA8E87C5_3963_11D2_8B8F_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

#ifndef __AFXWIN_H__

#error include 'stdafx.h' before including this file for PCH

#endif

#include "resource.h"       // main symbols

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

// CExMdiTempApp:

// See ExMdiTemp.cpp for the implementation of this class

//

class CExMdiTempApp : public CWinApp

{

public:

CExMdiTempApp();

CMultiDocTemplate* pAnotherTemplate;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMdiTempApp)

public:

virtual BOOL InitInstance();

//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CExMdiTempApp)

afx_msg void OnAppAbout();

afx_msg void OnAnotherfile();

afx_msg void OnFileNew();

afx_msg void OnAnother2();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

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

#endif // !defined(AFX_EXMDITEMP_H__BA8E87C5_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

// ExMdiTemp.cpp : Defines the class behaviors for the application.

//

#include "stdafx.h"

#include "ExMdiTemp.h"

#include "MainFrm.h"

#include "ChildFrm.h"

#include "ExMdiTempDoc.h"

#include "ExMdiTempView.h"

#include "AnotherFrame.h"

#include "AnotherDoc.h"

#include "AnotherView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CExMdiTempApp

BEGIN_MESSAGE_MAP(CExMdiTempApp, CWinApp)

//{{AFX_MSG_MAP(CExMdiTempApp)

ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

ON_COMMAND(ID_ANOTHERFILE, OnAnotherfile)

ON_COMMAND(ID_FILE_NEW, OnFileNew)

ON_COMMAND(ID_ANOTHER2, OnAnother2)

//}}AFX_MSG_MAP

// Standard file based document commands

ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

// Standard print setup command

ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

END_MESSAGE_MAP()

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

// CExMdiTempApp construction

CExMdiTempApp::CExMdiTempApp()

{

// TODO: add construction code here,

// Place all significant initialization in InitInstance

}

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

// The one and only CExMdiTempApp object

CExMdiTempApp theApp;

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

// CExMdiTempApp initialization

BOOL CExMdiTempApp::InitInstance()

{

AfxEnableControlContainer();

// Standard initialization

// If you are not using these features and wish to reduce the size

//  of your final executable, you should remove from the following

//  the specific initialization routines you do not need.

#ifdef _AFXDLL

Enable3dControls();	// Call this when using MFC in a shared DLL

#else

Enable3dControlsStatic();	// Call this when linking to MFC statically

#endif

// Change the registry key under which our settings are stored.

// TODO: You should modify this string to be something appropriate

// such as the name of your company or organization.

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();  // Load standard INI file options (including MRU)

// Register the application's document templates.  Document templates

//  serve as the connection between documents, frame windows and views.

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(

IDR_EXMDITTYPE,

RUNTIME_CLASS(CExMdiTempDoc),

RUNTIME_CLASS(CChildFrame), // custom MDI child frame

RUNTIME_CLASS(CExMdiTempView));

AddDocTemplate(pDocTemplate);

pAnotherTemplate= new CMultiDocTemplate(

IDR_ANOTHERTYPE,

RUNTIME_CLASS(CAnotherDoc),

RUNTIME_CLASS(CAnotherFrame),

RUNTIME_CLASS(CAnotherView));

AddDocTemplate(pAnotherTemplate);

// create main MDI Frame window

CMainFrame* pMainFrame = new CMainFrame;

if (!pMainFrame->LoadFrame(IDR_MAINFRAME))

return FALSE;

m_pMainWnd = pMainFrame;

// Parse command line for standard shell commands, DDE, file open

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line

//이부분을 제거하면 메인 프레임만 출력되는 프로그램이 된다.

//	if (!ProcessShellCommand(cmdInfo))

//	return FALSE;

// The main window has been initialized, so show and update it.

pMainFrame->ShowWindow(m_nCmdShow);

pMainFrame->UpdateWindow();

return TRUE;

}

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

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog

{

public:

CAboutDlg();

// Dialog Data

//{{AFX_DATA(CAboutDlg)

enum { IDD = IDD_ABOUTBOX };

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAboutDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

//{{AFX_MSG(CAboutDlg)

// No message handlers

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)

{

//{{AFX_DATA_INIT(CAboutDlg)

//}}AFX_DATA_INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

//{{AFX_MSG_MAP(CAboutDlg)

// No message handlers

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

// App command to run the dialog

void CExMdiTempApp::OnAppAbout()

{

CAboutDlg aboutDlg;

aboutDlg.DoModal();

}

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

// CExMdiTempApp message handlers

//첫번째 템플레이트 로드

void CExMdiTempApp::OnAnotherfile()

{

POSITION pos=GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

pTemplate=(CMultiDocTemplate *)GetNextDocTemplate(pos);

pTemplate=(CMultiDocTemplate *)GetNextDocTemplate(pos);

pTemplate->OpenDocumentFile(NULL);

}

//두번째 템플레이트 로드

void CExMdiTempApp::OnFileNew()

{

POSITION pos=GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

pTemplate=(CMultiDocTemplate *)GetNextDocTemplate(pos);

pTemplate->OpenDocumentFile(NULL);

}

//동적으로 템플레이트 로드

void CExMdiTempApp::OnAnother2()

{

CAnotherFrame *pChild = new CAnotherFrame;

CAnotherDoc *pDoc = new CAnotherDoc;

CFrameWnd *pFrame=pAnotherTemplate->CreateNewFrame(pDoc,pChild);

pAnotherTemplate->InitialUpdateFrame(pFrame,pDoc);

}

(프로그램 소스끝)

5.다중 템플레이트에서 로드된 여러 윈도우 얻기

MDI로 프로그램을 제작하면 여러개의 차일드 윈도우를 출력할수 있습니다. 다중 템플레이트일경우에는 다중 템플레이트에서 로드된 여러 차일드 윈도우가 출력됩니다. 이렇게 출력되었을때 한개의 차일드 윈도우는 포커스를 받고 있으나 나머지 윈도우들은 포커스를 설정되어 있지 않습니다. 지금까지는 GetActiveView나 GetActiveFrame,GetActiveDoc를 이용하여 View,프레임,도큐먼트등을 받았습니다.

GetActive 계열의 함수를 이용하면 포커스를 받지 않고 뒤에 있는 윈도우들의 View나 도큐먼트 또는 프레임 윈도우를 얻을수가 없습니다. 예를 들어 현재 1,2,3 차일드 프레임이 출력되었을때 1번 차일드 프레임 활성화 되어 있다면 2번 또는 3번 윈도우들은 GetActive 계열 함수를 이용하여서 받을수 없다는 것입니다. GetActive 함수는 현재 활성화 되어 있는 윈도우를 얻을수 있기 때문입니다. 본항목에서는 포커스가 설정되어 있지 않는 여러 윈도우들의 핸들과 클래스 포인터를 얻는 방법에 대해서 알아보겟습니다.

<1>템플레이트와 도큐먼트의 관계

MDI형태에 다중 템플레이트를 이용하여 프로그램을 작성하고 여러개의 차일드 윈도우를 출력했을때 현재 로드되어 있는 윈도우를 알고자 할경우에는 그림 1-8-15와 같은 방법을 이용합니다.

(그림 파일)

(그림 1-8-15)다중 템플레이트에서 로드된 여러 윈도우

그림 1-8-15는 ExMdiTemp프로그램을 실행한 화면입니다. 이화면에는 첫번재 템플레이트로 로드된 ExMdiT1~ExMdiT3 가 있으며 두번재 템플레이트에서 로드된 Anothe1 ~ Anothe3 윈도우가 있습니다. 이 여섯개의 템플레이트의 도큐먼트와 View를 얻고자 할경우에 방법이 그림 1-8-15의  ①~⑥까지 입니다. 특정 윈도우를 얻고자 할경우에는 먼저 템플레이트를 포인터를 얻어야 합니다. 예를 들어 ExMdiT2윈도우의 핸들을 얻고자 한다면 ①항목과 같이 GetFirstDocTemplatePosition 함수를 이용하여 템플레이트 포지션을 얻은다음 GetNextDocTemplate 함수를 한번 실행하여 첫번째 템플레이트를 얻습니다. 이렇게 얻은후에 ③항목과 같이 이템플레이트의 GetFirstDocPosition 함수를 이용하여 도큐먼트 리스트의 처음 포지션을 얻습니다. 이렇게 얻은 포지션의 값을 p에 저장한후에 GetNextDoc 함수에 첫번째 인자로 이 p를 설정하면 현재 템플레이트에서 첫번째 도큐먼트를 얻을수 있습니다. 이렇게 첫번재 도큐먼트를 얻은다음 비슷한 형태로 현재 도큐먼트이 View 포지션을 GetFirstViewPosition 함수를 이용하여 얻습니다. 이렇게 얻은 포지션의 값을 그림 1-8-15의 ④항목처럼 pd에 저장하고 다시 CDocument의 맴버 함수인 GetNextView 함수를 이용하여 View윈도우를 얻을수가 있습니다. 다시 설명 드려서 특정 윈도우를 얻고자 한다면 다음과 같은 순서일것입니다.

①템플레이트 포지션을 얻는다. (그림 1-8-15의 ①,②항목)

②원하는 템플레이트를 얻는다. (그림 1-8-15의 ①,③항목)

③템플레이트에서 도큐먼트 포지션을 얻는다.(그림 1-8-15의 ③ 전체 항목)

④원하는 도큐먼트를 얻는다.(그림 1-8-15 ③의 전체 항목)

⑤도큐먼트에서 View 포지션을 얻는다.(그림 1-8-15의  ③전체 항목)

⑥원하는 View를 얻는다.(그림 1-8-15의 ③전체 항목)

템플레이트를 얻고 도큐먼트 리스트에서 도큐먼트를 얻는 부분은 당연하게 생각할수 있는데 도큐먼트에서 View리스트를 얻는다는 점에서 약간 이상하게 생각할 분들이 있을수도 있습니다. 지금까지 도큐먼트에는 단한개의 View밖에 없었기 때문이지요. 다음장에서 설명하겠습니다. 도큐먼트안에 여러개의 View를 붙일수 있습니다. 그렇기 때문에 View윈도우를 얻고자 할때는 View포지션을 얻고 그다음View를 얻는 방법을 이용하는 것입니다.

View를 얻은다음 다시 View를 감싸고 있는 프레임 윈도우를 얻고자 할때는 CView의 맴버 함수인 GetParentFrame 을 이용하면 됩니다.

MDI형으로 다중 템플레이트를 사용한 복잡한 프로그램을 제작할때 프레임과 프레임간에 상호 데이터 교환및 한쪽 프레임에서 작업한 결과를 다른 프레임에 넘겨주는 다양한 방법을 이용할때 그림 1-8-15와 같은 루틴은 매우 많이 사용됩니다.

<2>윈도우 리스트 얻기 예제 ExMdiTemp3

ExMdiTemp3는 ExMdiTemp2에 윈도우 리스트를 출력하는 대화상자를 설정한 예제입니다. 이예제 프로젝트명은 ExMdiTemp이며 제공되는 CD-ROM의 ExMdiTemp3에 설정되어 있습니다. 그림 1-8-16은 ExMdiTemp3의 전체 구조 입니다.

(그림 파일)

(그림 1-8-16)ExMdiTemp3 전체 구조

그림 1-8-16의  ⓐ항목이 ExMdiTemp3를 실행시키고 윈도우 리스트를 얻는 대화상자를 출력한 형태입니다. 대화상자가 출력되면서 현재 활성화 되어 있는 템플레이트의 윈도우 리스트를 리스트박스에 출력합니다. 이렇게 출력된 상태에서 특정 윈도우를 선택하면 해당윈도우가 활성화 되어 최상단으로 나타나는 예제입니다.

본예제의 작성하는 방법은 다음과 같습니다.

(1)대화상자를 만든다.

대화상자 폼을 그림 1-8-17과 같은 형태로 만듭니다.

(그림 파일)

(그림 1-8-17) IDD_CHILDLIST 대화상자 폼

그림 1-8-17과 같이 대화상자 폼을 만든후에 이대화상자와 연결되는 클래스를 □그림 1-8-17의 ①항목과 같이 CChildListDlg 설정하고 ②항목과 같이 리스트박스는 CListBox형인 m_listBox와 연결시킵니다.

(2)템플레이트 번호 설정 생성자 만들기

본예제에서는 대화상자가 실행될때 활성화 되어 있는 템플레이트의 윈도우 리스트를 출력하기 때문에 현재 활성화 되어 있는 템플레이트 번호를 대화상자가 생성될때 설정되는 값에 의해서 알도록 하였습니다. 먼저 CChildListDlg 헤더에 다음과 같이 템플레이트 번호를 설정할 변수를 설정합니다.

int mode;//템플레이트 번호

위와 같이 설정하고 템플레이트 번호에 값을 넣는 생성자를 다음과 같이 설정합니다.

헤더부:

CChildListDlg(int m);

소스부:

CChildListDlg::CChildListDlg(int m)

: CDialog(CChildListDlg::IDD, NULL)

{

mode=m;

}

위와 같이 하고 CChildListDlg(int m)을 이용하여 대화상자를 생성시키면 m값이 1이면 첫번재 템플레이트를 2이면 두번째 템플레이트를 설정하게 되는 것입니다.

다음은 첫번째 템플레이트의 윈도우 리스트를 출력하는 예입니다.

//첫번재 템플레이트 윈도우 리스트 출력시

CChildListDlg *pDlg = new CChildListDlg(1);

pDlg->DoModal();

(3)OnInitDialog 함수 만들기

OnInitDialog함수에서는 mode에 설정된 템플레이트를 로드하고 이템플레이트 형으로 현재 구동되고 있는 윈도우 리스트를 리스트박스에 출력합니다. 이내용은 다음과 같습니다.

BOOL CChildListDlg::OnInitDialog()

{

CDialog::OnInitDialog();

//템플레이트 포지션을 얻는다.

①POSITION pos=AfxGetApp()->GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

//mode를 반복하여 GetNextDocTemplate를 하면 해당 템플레이트가

//얻어짐

②for(int i=0;i<mode;i++)

pTemplate=(CMultiDocTemplate *)AfxGetApp()->GetNextDocTemplate(pos);

CDocument *pDoc;

CView *pView;

CMDIChildWnd *pFrame;

CString data;

//템플레이트에서 첫번째 도큐먼트 포지션을 얻는다.

③POSITION p=pTemplate->GetFirstDocPosition();

for(;;)

{

//다음 도큐먼트를 얻는다.

pDoc=pTemplate->GetNextDoc(p);

POSITION pd;

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

pd=pDoc->GetFirstViewPosition();

//View 윈도우를 얻는다.

pView=pDoc->GetNextView(pd);

//View윈도우를 감싸고 있는 프레임 윈도우를 얻는다.

pFrame=(CMDIChildWnd *)pView->GetParentFrame();

//윈도우 이름을 얻는다.

pFrame->GetWindowText(data);

//리스트박스에 윈도우 이름을 기록한다.

m_listBox.AddString(data);

//GetNextDoc함수를 호출할때 p가 0이면 현재 도큐먼트가

//마지막 도큐먼트라는 의미임 이때는 탈출

if(p==0)

break;

}

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

위의 소스의 ①항목처럼 먼저 템플레이트 포지션을 얻은다음  ②항목 처럼 mode만큼 반복하여 GetNextDocTemplate 함수를 실행합니다. mode가 1이면 한번실행하게 됨으로 첫번째 템플레이트를 얻고 mode가 2이면 두번 실행하므로 두번째 템플레이트가 얻어지게 됩니다. 이렇게 템플레이트를 얻고,템플레이트의 도큐먼트 포지션을 얻습니다. 이항목이 위의 소스에서 ③항목입니다. 이렇게 도큐먼트 포지션을 얻은다음 GetNexDoc함수를 이용하여 계속적으로 현재 등록되어 있는 도큐먼트들을 얻습니다. 이렇게 도큐먼트를 얻은다음 도큐먼트에 소속되어 있는 View를 얻고 View를 감싸고 있는 프레임 윈도우를 얻은다음 프레임 윈도우의 캡션바에 설정되어 있는 텍스트를 받아서 이것을 리스트박스에 설정한것입니다. 이렇게 하면 현재 템플레이트로 구동된 윈도우 리스트를 얻게 됩니다.

(4)OK버튼을 클릭했을때 현재 선택된 윈도우를 활성화하기

OK버튼을 클릭했을때 리스트박스에서 특정 항목을 선택하였다면 해당 항목의 윈도우를 활성화 시켜야 합니다. 이때의 방법은 OnInitDialog비슷하게 현재 템플레이트의 프레임 윈도우 이름을 루프를 돌면서 얻은다음 이 윈도우 이름과 리스트박스에서 선택한 문자열이 같으면 해당 프레임을 활성화 하면 됩니다. 이내용은 다음과 같습니다.

void CChildListDlg::OnOK()

{

int num;

CString data;

CString listdata;

//리스트박스에서 현재 선택한 항목을 listdata에 저장한다.

num=m_listBox.GetCurSel();

m_listBox.GetText(num,listdata);

//템플레이트 포지션을 얻는다.

POSITION pos=AfxGetApp()->GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

//mode를 반복하여 GetNextDocTemplate를 하면 해당 템플레이트가

//얻어짐

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

pTemplate=(CMultiDocTemplate *)AfxGetApp()->GetNextDocTemplate(pos);

CDocument *pDoc;

CView *pView;

CMDIChildWnd *pFrame;

//템플레이트에서 첫번째 도큐먼트 포지션을 얻는다.

POSITION p=pTemplate->GetFirstDocPosition();

for(;;)

{

//다음 도큐먼트를 얻고

pDoc=pTemplate->GetNextDoc(p);

POSITION pd;

//도큐먼트에서 첫번째 View포지션을 얻고

pd=pDoc->GetFirstViewPosition();

//View윈도우를 얻은다음

pView=pDoc->GetNextView(pd);

//프레임 윈도우를 얻고

pFrame=(CMDIChildWnd *)pView->GetParentFrame();

//문자열을 얻은다음

pFrame->GetWindowText(data);

//리스트 박스에서 선택한 이름과 같으면 해당 프레임을

//활성화 한다.

if(data==listdata)

pFrame->ActivateFrame(-1);

//GetNextDoc함수를 호출할때 p가 0이면 현재 도큐먼트가

//마지막 도큐먼트라는 의미임 이때는 탈출

if(p==0)

break;

}

CDialog::OnOK();

}

(5)대화상자 실행

대화상자가 실행할때는 현재 템플레이트 번호를 넘겨주어야 합니다. 현재 템플레이트 번호를 넘겨줄는것은 메뉴를 이용하면 됩니다. 그림 1-8-19은 메뉴항목을 만들고 대화상자를 실행하는 구성도입니다.

(그림 파일)

(그림 1-8-19)메뉴항목설정및 대화상자 실행

그림 1-8-19의 ⓐ항목은 첫번째 템플레이트에 설정된 메뉴 (ID:IDR_EXMDITTYPE) 입니다. 이항목은 첫번째 템플레이트가 활성될때만 나타나게 됩니다. 이항목에서 □윈도우 리스트□ 메뉴를 선택하였을때의 함수를 App클래스에 만들고 생성자의 인자를 1로 하여 CChildListDlg  대화상자를 실행시키면 첫번째 템플레이트로 구동된 윈도우 리스트가 나타나게 됩니다.

ⓑ항목은 두번째 템플레이트에 설정된 메뉴 입니다. 이항목은 두번재 템플레이트가 활성될때만 나타나게 되므로 이항목에서 □윈도우 리스트□ 메뮤를 선택하였을대 함수를 App클래스에 만들고 생성자 인자를 2로 하여 대화상자를 실행시키면 두번재 템플레이트로 구동된 윈도우 리스트가 나타나게 됩니다.

지금까지 설명한 방법대로 ExMdiTemp3를 만들면 다중 템플레이트에서 윈도우 리스트를 얻고 해당 윈도우를 활성화 할수 있을것입니다.

<3>예제 ExMdiTemp3 소스

다음은 ExMdiTemp3의 App클래스 부분과 CChildListDlg소스입니다. 나머지 부분은 제공되는 CD-ROM 을 참조하시기 바랍니다.

(1)CExMdiTempApp 클래스 소스

(소스시작)

// ExMdiTemp.h : main header file for the EXMDITEMP application

//

#if !defined(AFX_EXMDITEMP_H__BA8E87C5_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

#define AFX_EXMDITEMP_H__BA8E87C5_3963_11D2_8B8F_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

#ifndef __AFXWIN_H__

#error include 'stdafx.h' before including this file for PCH

#endif

#include "resource.h"       // main symbols

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

// CExMdiTempApp:

// See ExMdiTemp.cpp for the implementation of this class

//

class CExMdiTempApp : public CWinApp

{

public:

CExMdiTempApp();

CMultiDocTemplate* pAnotherTemplate;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMdiTempApp)

public:

virtual BOOL InitInstance();

//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CExMdiTempApp)

afx_msg void OnAppAbout();

afx_msg void OnAnotherfile();

afx_msg void OnFileNew();

afx_msg void OnAnother2();

afx_msg void OnWindowlist();

afx_msg void OnWindowlist2();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

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

//{{AFX_INSERT_LOCATION}}

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

#endif // !defined(AFX_EXMDITEMP_H__BA8E87C5_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

// ExMdiTemp.cpp : Defines the class behaviors for the application.

//

#include "stdafx.h"

#include "ExMdiTemp.h"

#include "MainFrm.h"

#include "ChildFrm.h"

#include "ExMdiTempDoc.h"

#include "ExMdiTempView.h"

#include "AnotherFrame.h"

#include "AnotherDoc.h"

#include "AnotherView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CExMdiTempApp

BEGIN_MESSAGE_MAP(CExMdiTempApp, CWinApp)

//{{AFX_MSG_MAP(CExMdiTempApp)

ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

ON_COMMAND(ID_ANOTHERFILE, OnAnotherfile)

ON_COMMAND(ID_FILE_NEW, OnFileNew)

ON_COMMAND(ID_ANOTHER2, OnAnother2)

ON_COMMAND(ID_WINDOWLIST, OnWindowlist)

ON_COMMAND(ID_WINDOWLIST2, OnWindowlist2)

//}}AFX_MSG_MAP

// Standard file based document commands

ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

// Standard print setup command

ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

END_MESSAGE_MAP()

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

// CExMdiTempApp construction

CExMdiTempApp::CExMdiTempApp()

{

// TODO: add construction code here,

// Place all significant initialization in InitInstance

}

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

// The one and only CExMdiTempApp object

CExMdiTempApp theApp;

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

// CExMdiTempApp initialization

BOOL CExMdiTempApp::InitInstance()

{

AfxEnableControlContainer();

// Standard initialization

// If you are not using these features and wish to reduce the size

//  of your final executable, you should remove from the following

//  the specific initialization routines you do not need.

#ifdef _AFXDLL

Enable3dControls();	// Call this when using MFC in a shared DLL

#else

Enable3dControlsStatic();	// Call this when linking to MFC statically

#endif

// Change the registry key under which our settings are stored.

// TODO: You should modify this string to be something appropriate

// such as the name of your company or organization.

SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings();  // Load standard INI file options (including MRU)

// Register the application's document templates.  Document templates

//  serve as the connection between documents, frame windows and views.

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(

IDR_EXMDITTYPE,

RUNTIME_CLASS(CExMdiTempDoc),

RUNTIME_CLASS(CChildFrame), // custom MDI child frame

RUNTIME_CLASS(CExMdiTempView));

AddDocTemplate(pDocTemplate);

pAnotherTemplate= new CMultiDocTemplate(

IDR_ANOTHERTYPE,

RUNTIME_CLASS(CAnotherDoc),

RUNTIME_CLASS(CAnotherFrame),

RUNTIME_CLASS(CAnotherView));

AddDocTemplate(pAnotherTemplate);

// create main MDI Frame window

CMainFrame* pMainFrame = new CMainFrame;

if (!pMainFrame->LoadFrame(IDR_MAINFRAME))

return FALSE;

m_pMainWnd = pMainFrame;

// Parse command line for standard shell commands, DDE, file open

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

// Dispatch commands specified on the command line

//	if (!ProcessShellCommand(cmdInfo))

//	return FALSE;

// The main window has been initialized, so show and update it.

pMainFrame->ShowWindow(m_nCmdShow);

pMainFrame->UpdateWindow();

return TRUE;

}

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

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog

{

public:

CAboutDlg();

// Dialog Data

//{{AFX_DATA(CAboutDlg)

enum { IDD = IDD_ABOUTBOX };

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CAboutDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

//{{AFX_MSG(CAboutDlg)

// No message handlers

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)

{

//{{AFX_DATA_INIT(CAboutDlg)

//}}AFX_DATA_INIT

}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CAboutDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)

//{{AFX_MSG_MAP(CAboutDlg)

// No message handlers

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

// App command to run the dialog

void CExMdiTempApp::OnAppAbout()

{

CAboutDlg aboutDlg;

aboutDlg.DoModal();

}

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

// CExMdiTempApp message handlers

void CExMdiTempApp::OnAnotherfile()

{

POSITION pos=GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

pTemplate=(CMultiDocTemplate *)GetNextDocTemplate(pos);

pTemplate=(CMultiDocTemplate *)GetNextDocTemplate(pos);

pTemplate->OpenDocumentFile(NULL);

}

void CExMdiTempApp::OnFileNew()

{

POSITION pos=GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

pTemplate=(CMultiDocTemplate *)GetNextDocTemplate(pos);

pTemplate->OpenDocumentFile(NULL);

}

void CExMdiTempApp::OnAnother2()

{

CAnotherFrame *pChild = new CAnotherFrame;

CAnotherDoc *pDoc = new CAnotherDoc;

CFrameWnd *pFrame=pAnotherTemplate->CreateNewFrame(pDoc,pChild);

pAnotherTemplate->InitialUpdateFrame(pFrame,pDoc);

}

#include "ChildListDlg.h"

void CExMdiTempApp::OnWindowlist()

{

CChildListDlg *pDlg = new CChildListDlg(1);

pDlg->DoModal();

delete pDlg;

}

void CExMdiTempApp::OnWindowlist2()

{

CChildListDlg *pDlg = new CChildListDlg(2);

pDlg->DoModal();

delete pDlg;

}

(소스끝)

(2)CChildListDlg 클래스 소스

(소스시작)

#if !defined(AFX_CHILDLISTDLG_H__BA8E87DA_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

#define AFX_CHILDLISTDLG_H__BA8E87DA_3963_11D2_8B8F_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// ChildListDlg.h : header file

//

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

// CChildListDlg dialog

class CChildListDlg : public CDialog

{

// Construction

public:

CChildListDlg(CWnd* pParent = NULL);   // standard constructor

CChildListDlg(int m);

// Dialog Data

//{{AFX_DATA(CChildListDlg)

enum { IDD = IDD_CHILDLIST };

CListBox	m_listBox;

//}}AFX_DATA

int mode;//템플레이트 번호

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CChildListDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CChildListDlg)

virtual BOOL OnInitDialog();

virtual void OnOK();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

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

#endif // !defined(AFX_CHILDLISTDLG_H__BA8E87DA_3963_11D2_8B8F_006097AD9DDC__INCLUDED_)

// ChildListDlg.cpp : implementation file

//

#include "stdafx.h"

#include "ExMdiTemp.h"

#include "ChildListDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CChildListDlg dialog

CChildListDlg::CChildListDlg(CWnd* pParent /*=NULL*/)

: CDialog(CChildListDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CChildListDlg)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

mode=1;

}

CChildListDlg::CChildListDlg(int m)

: CDialog(CChildListDlg::IDD, NULL)

{

mode=m;

}

void CChildListDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CChildListDlg)

DDX_Control(pDX, IDC_LIST, m_listBox);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CChildListDlg, CDialog)

//{{AFX_MSG_MAP(CChildListDlg)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

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

// CChildListDlg message handlers

BOOL CChildListDlg::OnInitDialog()

{

CDialog::OnInitDialog();

//템플레이트 포지션을 얻는다.

POSITION pos=AfxGetApp()->GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

//mode를 반복하여 GetNextDocTemplate를 하면 해당 템플레이트가

//얻어짐

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

pTemplate=(CMultiDocTemplate *)AfxGetApp()->GetNextDocTemplate(pos);

CDocument *pDoc;

CView *pView;

CMDIChildWnd *pFrame;

CString data;

//템플레이트에서 첫번째 도큐먼트 포지션을 얻는다.

POSITION p=pTemplate->GetFirstDocPosition();

for(;;)

{

//다음 도큐먼트를 얻는다.

pDoc=pTemplate->GetNextDoc(p);

POSITION pd;

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

pd=pDoc->GetFirstViewPosition();

//View 윈도우를 얻는다.

pView=pDoc->GetNextView(pd);

//View윈도우를 감싸고 있는 프레임 윈도우를 얻는다.

pFrame=(CMDIChildWnd *)pView->GetParentFrame();

//윈도우 이름을 얻는다.

pFrame->GetWindowText(data);

//리스트박스에 윈도우 이름을 기록한다.

m_listBox.AddString(data);

//GetNextDoc함수를 호출할때 p가 0이면 현재 도큐먼트가

//마지막 도큐먼트라는 의미임 이때는 탈출

if(p==0)

break;

}

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

void CChildListDlg::OnOK()

{

int num;

CString data;

CString listdata;

//리스트박스에서 현재 선택한 항목을 listdata에 저장한다.

num=m_listBox.GetCurSel();

m_listBox.GetText(num,listdata);

//템플레이트 포지션을 얻는다.

POSITION pos=AfxGetApp()->GetFirstDocTemplatePosition();

CMultiDocTemplate *pTemplate;

//mode를 반복하여 GetNextDocTemplate를 하면 해당 템플레이트가

//얻어짐

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

pTemplate=(CMultiDocTemplate *)AfxGetApp()->GetNextDocTemplate(pos);

CDocument *pDoc;

CView *pView;

CMDIChildWnd *pFrame;

//템플레이트에서 첫번째 도큐먼트 포지션을 얻는다.

POSITION p=pTemplate->GetFirstDocPosition();

for(;;)

{

//다음 도큐먼트를 얻고

pDoc=pTemplate->GetNextDoc(p);

POSITION pd;

//도큐먼트에서 첫번째 View포지션을 얻고

pd=pDoc->GetFirstViewPosition();

//View윈도우를 얻은다음

pView=pDoc->GetNextView(pd);

//프레임 윈도우를 얻고

pFrame=(CMDIChildWnd *)pView->GetParentFrame();

//문자열을 얻은다음

pFrame->GetWindowText(data);

//리스트 박스에서 선택한 이름과 같으면 해당 프레임을

//활성화 한다.

if(data==listdata)

pFrame->ActivateFrame(-1);

//GetNextDoc함수를 호출할때 p가 0이면 현재 도큐먼트가

//마지막 도큐먼트라는 의미임 이때는 탈출

if(p==0)

break;

}

CDialog::OnOK();

}

(소스끝)

(낙서)

프로그램에 대한 머피의 법칙

1. [Brooks Law] 지연되는 프로젝트에 프로그래머를 더 투입하면 프로젝트를 더 지연시킨다.

2. [Lubarsky's Law of Cybernetic Entomology] 항상 잡지않은 버그가 한마리 더 남아 있다.

3. 프로그램이 실행될 때쯤이면 그 프로그램은 구식이 되어버린다.

4. 프로그램 개발 비용은 항상 더 들고 개발 기간은 예상보다 오래걸린다.

5. 프로그램이 쓸만하면 새것으로 바꾸어야한다.

6. 어떠한 프로그램도 사용 가능한 메모리를 가득 채운다.

7. 프로그램의 복잡도는 항상 관리자의 능력을 초과할 때까지 증가한다.

8. 시험판은 완벽하지만 그 다음 모든 시스템은 오동작을 한다.

9. 무계획적인 프로젝트는 예상 기간의 3배가 걸리고, 계획적인 프로젝트는 2배 걸린다.

10. 잘 되는 순간 무엇인가 잘 못된다.

11. 더 이상 나빠질 수 없는 것도 더 나빠진다.

12. 항상 버그는 마지막 찾는 곳에서 발견된다.

13. 아무리 많은 일을 해도 절대로 충분하지않다.

14. 해야할 일은 항상 한 일보다 더 중요하다.

15. 스스로 하지않는 사람에게는 어떠한 것도 불가능하다.

16. 하드 디스크 용량에 상관없이 남은 용량은 1MB이다.

(낙서끝)