본문 바로가기

코드^학습/메모한 지식

[MFC] MDI의 구조



[이미지 출처 : http://skmagic.tistory.com/entry/MFC-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%A1%B0 ]


위 그림을 기본으로 공부하고 MFC를 다룬다면 아마도 조금이라도 덜 헤멜 것이라 생각합니다.

사실 제가 헤메고 있기 때문이지요!


저 그림 중에 제가 부분이나마 이해하고 있는 부분은 CMyApp, CMyView, CMyDoc, CDocTemplate, CFrameWnd  정도 이겠군요...

이렇게 낮은 이해도를 가지고 MFC 프로그램을 만들고 있으니 어찌 헤메지 않겠습니까...

아직 본격적(회사)으로 하시기 이전인 분들이 계시다면 이런 지식을 튼튼히 하고 가셔야 실제 작업할 때 시간을 아낄 수 있습니다.


이런 구조가 머릿속에 있으면서 요구조건을 받는다면 방법을 떠올리기가 쉽습니다.

없으면 추상적으로 생각하여 삽질하는 시간이 길어질 수 밖에 없습니다.


명확하지 않은 구조로 만든 코드는 실수를 반드시 내포하기 마련이구욧! (예 그게 바로 접니다!)

아무튼 저도 정리를 하면서 적어보겠습니다.



팁스 소프트에서 제공하는 자료에선 저 그림보다 좀 더 자세하지만 제가 알고있는 내용을 표현하기엔 상단의 그림이 비슷하다고 생각합니다.

링크 : http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=534


CObject

MFC의 최상위 클래스로 메모리에 클래스를 설정하는 기능을 가진 기본 클래스이다.

대부분의 클래스가 이 클래스로부터 파생되었다.

기본 클래스는 데이터를 직렬화 하고 Run-time 클래스 정보를 얻어내거나 Debugging 시에 출력 진단의 역활을 담당한다.


제가 참조하고 있는 블로그(이미지 출처)에 있는 내용입니다.


일반적인 코드에선 CObject를 상속받는다거나 할일이 매우 드물것이기 때문에 인용구의 설명으로 그치겠습니다.


CCmdTarget

명령 관련 클래스로 윈도우 메시지를 받고, 응답하는 오브젝트 클래스들의 기본 클래스이며, 
MFC의 메시지맵 아키텍처를 지원하기 위한 기저 클래스이다.

윈도우 메시지를 처리하는 MFC 클래스는 모두 CCmdTarget클래스나 CCmdTarget의 파생 클래스로부터 상속받은 클래스 이다

네 이부분도 지금 당장 긴박한 부분은 아니군요. 인용구의 설명으로 대체하겠습니다.


CWinThread

하나의 윈도우를 스레드로 구동될 수 있게 하는 클래스이다.

여러가지 프로그램을 동시에 실행시킬 수 있기 때문에 MFC 프로그램이 구동되기 위하여 CwinThread 클래스부터 상속 받는다.

스레드는 부분은 아직 공부중인 부분이라 뭐라 말씀드리기가 애메합니다만,

제가 아는 바로는 윈도우는 스레드와 프로세스를 구분하여 동작하기때문에

(리눅스에 스레드가 없다는 것이 아니구 리눅스에서는 스레드도 프로세스로 분류하는 걸로 알고 있습니다. 틀렸다면 정정 부탁드립니다.)


따로 프로세스나 스레드에 대해 정의하지 않았더라도 자체적으로 프로세스 하나에서 스레드가 돌아가고 있습니다.

디버그 모드에서 보시면 스레드가 계속 돌아가고 있음을 확인할 수 있습니다.(어떤 스레드인지는 알 수 없지만...)


CWinApp → C***App

윈도우 애플리케이션 클래스로 윈도우 애플리케이션 오브젝트를 생시키는 기본 클래스이다.

앞으로 많이 쓰게될 CWinApp입니다. 얘를 자주 쓴다기보단 이 친구를 상속받은 CMyApp을 많이 사용하게 됩니다.

만약 사용할 프로그램이 사용할 데이터들이 있다면 대부분은 이 App과 MainFrame에서 처리한 이후

Doc과 View에서 미리 처리한 데이터를 가져다 쓰는 것이 옳기 때문입니다.


제가 Document가 자료를 처리한다는 단편적인 설명만 가지고 Document에서 모든 자료를 처리하게 했더니
View하나 띄우는데 엄청난 시간이 걸렸음을 고백합니다.


CDocument  C***Doc

애플리케이션 내부에서 데이터를 읽고, 저장하는 기능을 가진 클래스로 애플리케애션이 지정한 도큐먼트의 기본 클래스이다. 입출력을 제공하므로 데이터의 입출력에 관한 내용은 이 클래스를 사용하는 것이 좋다.

사용자의 도큐먼트(CMyDocument)들은 이 CDocument 클래스로부터 상속받아 사용한다.

자 이분께서 설명하신 내용대로 Document는 내부에서 데이터를 읽고, 저장하는 기능을 가졌습니다.

MDI에서 화면을 구성하게 될 View는 이 Doc을에 물려있다고 생각하시면 됩니다.

(나중에 포인터를 얻을 때는 Doc을 얻은 후에 Doc에서 View포인터를 얻어나가는 식으로 진행합니다.)


그러나 여러분 주의하서야할 것은 애플리케이션 내부에서 데이터를 읽고 저장하는 기능이란 이 말입니다.

저는 이 어구를 프로그램(애플리케이션)이 사용할 데이터를 처리하는 부분은 Document 에 있는편이 좋다라고 해석했습니다.


그러나 아닙니다. View에서 잠깐 써야할 데이터라면 Doc에서 처리하는게 맞을지도 모릅니다.

이미 처리한 데이터를 받아오고 View에서 편집한 데이터를 가공하여 시스템에 적용한다거나. \

다른View에도 적용해야 한다거나 등의 경우에는 절대 Doc에서 데이터를 처리하는 로직을 구성하지 마시길 추천합니다.


이게 절대적인 답은 아니지만 제 경험상으론 그랬습니다. 저도 Doc에서 데이터를 모두 읽고 처리하게 하였더니 Doc은 View의 개수만큼 존재합니다.

음... 이 설명은 부적합 하군요. 다음 그림을 보시죠!


[이미지 출처 : http://skmagic.tistory.com/entry/MFC-%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%A1%B0 ]



이런 형태로 존재합니다. MainFrame과 ChildFrame은 View를 짚고 넘어간 이후에 다루겠습니다.

그렇기에 Document에서 처리한 데이터는 해당 View 에서만 접근할 수 있고 공유하기가 어렵습니다.

좀 더 상위(C***App, CMainFrame)에서 데이터를 처리한다면 남의 Doc에 접근하겠다고 고생할 필요가 줄어듭니다. 꼭 기억하세요!


CView → C***View

애플리케이션의 클라이언트 또는 작업 영역을 나타내는 클래스로 도큐먼트 안에 있는 데이터를 뷰를 통해 화면에 보일수 있도록 하는 기저 클래스이다.

CView 클래스로부터 상속받은 여러 가지 형태의 폼 윈도우들이 있다.

작업 영역으로 들어오는 메시지들을 처리 할 수 있다.

사용자뷰(CMyView)들은 이 CView 클래스로부터 상속받아 사용한다.

이제 사용자들이 볼 화면인 View입니다. 알고리즘이 적용된 코드와 UI를 구성할 코드가 버무려진 혼돈의 장소입니다.(제 코드를 보니 그렇네요)

작업영역에서 사용자들이 할 동작을 예상하고 그에 해당하는 메시지를 적절히 처리하게 됩니다.

단축키 등은 PreTranslateMessage에서 처리하고 더블클릭 메시지는 OnLButtonDblClk에서 저장 동작에 대한 처리는 OnFileSave에서 처리하는 식입니다.


여기에서 처리되는 데이터는 모두 잠깐잠깐씩만 쓰이거나 가공하여 상위단계로 넘겨주기위한 가공단계가 많이 포함됩니다.

구성은 여러분이 꾸미기 나름대로 흘러가게 되죠.


CMainFrame, CChildFrame

메인 프레임에 관한 윈도우 클래스로 윈도우의 외곽 경계를 정의하는 기저 클래스이다.

CWnd 클래스로부터 상속받았기 때문에 공통적으로 윈도우 메시지를 처리한다.

SDI 프로젝트 유형에서는 CFrameWnd 클래스로부터 상속받아 CMainFrame 클래스를 생성한다.

MDI 프로젝트 유형에서는 CFrameWnd 클래스부터 상속받아 CMDIFrameWnd 클래스와 CMDICHildWnd 클래스를 생성한다.

윈도우 애플리케이션 클래스로 윈도우 애플리케이션 오브젝트를 생성시키는 기본 클래스이다.

저는 현재 MFC에서 제공하는 UI중에 비교적 최신UI인 RIBBON에 대해서 작업하고 있습니다.

리본UI는 MainFrame이 가장 외부 틀이며 그 내부의 탭(하나의 View)는 하나의 ChildFrame이 관리합니다.  위의 그림처럼 말이죠.

만약 여러가지 뷰를 하나의 화면에 띄우고 싶다면 ChildFrame에서 여러뷰를 띄우도록 만들면 됩니다. 방법은 Google에서!


RIBBON의 경우 상단 툴바는 MainFrame에서 처리합니다. 버튼, 패널 등을 추가하고 UI를 결정하고 어디 함수에 연결할지 등등을 말이죠.

아! 참고로 RIBBON UI 코딩을 하면서 알게 된건 RIBBON이라는 특이한 모습을 취하고 있지만 본질은 메뉴입니다.

리소스 - Menu 에서 IDR_***TYPE라는 것을 열어보시면 그 안에 RIBBON 툴바의 기능들이 정의되어 있습니다.


즉, 메뉴를 감추고 거기에 새로운 UI를 덮어씌운 것입니다. 너무 어렵게 생각하지 마세요.

메뉴의 아이콘 같은건 직접 정의할 수도 있으니, 찾아보셔서 직접 아이콘을 디자인해보시는 것도 좋습니다.

(물론 제가 만든건 다 만들고 보면 개떡같긴 하지만...)


CDocTemplate, CMultiDocTemplate

여러분께서 동일한 View만으로 MDI를 구성한다면 당장은 필요한 내용은 아닐 것입니다.

그러나 동일한 View만 필요한 프로그램은 그렇게 많지 않고 동일한 데이터에 대해서도 다양한 모습으로 보여줄 필요도 생기게 됩니다.


CMultiDocTemplate* pDocTemplate = new CMultiDocTemplate(IDR_***TYPE,

RUNTIME_CLASS(C***Doc),

RUNTIME_CLASS(CChildFrame), // 사용자 지정 MDI 자식 프레임입니다.

RUNTIME_CLASS(C***View));

if (!pDocTemplate)

return FALSE;

AddDocTemplate(pDocTemplate);


위와 같은 형태로 도큐먼트 템플릿을 추가하게 됩니다.

저기에 적힌 Doc은 동일해도 되지만 다른 모습을 보여주기 위해서는 View는 따로 정의해야합니다.(클래스 추가 ㄱㄱ)


그리고 여러뷰를 적용했을 때는 프로그램을 구동했을 때 어떤 뷰를 먼저 보여줄지 선택하도록 윈도우가 뜹니다.

그러나 저는 그렇게 선택이 필요한게 아니라 템플릿에 추가한 모든 뷰가 순서대로 떠야했기 때문에

// 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.

/*CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);*/


// 명령줄에 지정된 명령을 디스패치합니다.

// 응용 프로그램이 /RegServer, /Register, /Unregserver 또는 /Unregister로 시작된 경우 FALSE를 반환합니다.

/*if (!ProcessShellCommand(cmdInfo))

return FALSE;*/


pDocTemplate1->OpenDocumentFile(NULL);

pDocTemplate2->OpenDocumentFile(NULL);


저 부분을 주석처리 해주고 추가한 템플릿에 대해서 OpenDocumentFile을 실행하면 위의 코드 순서대로 서로 다른 특성의 View가 뜨게 됩니다.



뭔가 쓰다보니 시간이 늦어졌네요.

오늘은 이만 마치겠습니다.


조금은 도움이 되었길 바랍니다!!