본문 바로가기

Language/MFC

MDI 다른 형식의 Child 만들기

MDI 의 꿈을 버리지 못하고 몇 개월째 해야지~하면서 찾아만 다녔던 부분이다.

MDI 에서 기본View는 내가 지정을 할 수 있지만 다른 형식의 View, 그러니까 예를 들어서 일반 View가 기본인데 FormView가 필요하다던지 그 반대가 되던지 아니면 머..스크롤 뷰라던지 등 기본과 다른 View를 사용해서 Child를 띄우는 방법이다. 


솔직히 그냥 DoModel 이나 Modeless 로 띄워도 되긴 하지만 일단 간지가 나지 않는다 -_- MDI 의 최대 장점은 독립적인 Frame 구조이기 때문이다. 독립적인 Frame이 지원이 되는데 왜 굳이 저렇게 쓴단 말인가? 그럴빠엔 SDI로 설계를 하는게 더 편하다. 


서론이 길었지만 매우 간단하더라 (이전엔 해도 않됐었다. 아무리 많은 포스팅을 봐도 해보면 에러뜨고 죽고 머가 문제였을까?-_- [<-- 니 스킬....])


일단 포스팅 되어있는 대부분의 글을 보면 C[프로젝트이름]App::Initstance() 함수를 건드려서 코드를 조작한다. 맞는 말이다. 초기에 프레임을 생성 및 초기화 및 등록 연결 해주는 곳이 initstance 함수이기 때문이다. 


일단 재료를 준비한다면 MDI로 만든 프로젝트의 기본 프레임이 구축을 한다. 

[VS->new->Project....이부분 생략해도 되겠지....;;]

그리고 예의상 컴파일 한번 해주고 의미 없이 실행 한번 해준다. (그러면 바인딩까지 다 되고 내부적으로 환경변수도 한번씩 등록해주고 의미는 없지만 그냥....)


그리고 두 번째 준비물이 필요하다. 바로 다른 기능을 할 View이다. (난 일반 View를 기본으로 하고 Form View를 추가하였다.)


이제는 실제로 만들어야 한다. 물론 아직 코딩은 할 부분은 아니다. (차라리 코딩할 부분이면 편하겠다 난 왜이렇게 인터페이스가 어려운지...)


클래스 추가 마법사님의 도움을 받아서 MFC Class를 추가한다. 

총 3개의 Class를 추가해야한다. 


(순서는 관계없다.) 나 같은 경우는 첫 번째로 CFrameWnd를 상속받은 Frame class를 하나 추가해준다. 

이름은 알아서 지어주면 되는데 왠만하면 3개의 Class의 이름을 동일하게 맞춰주는 게 나중에 편하다. 

CFrameWnd::CTestChildFrame


두 번째는 View와 연결할 Docunemt Class를 만들어 준다. 역시 잘 알다시피 CDocument를 상속 받는다. 

CDocument::CTestChildDoc


마지막으로 View를 만든다. 위에서 언급한대로 FormView를 만든다. CFormView를 상속받으면 된다.(몰랐는데 FormView를 상속받은 View클래스를 만드니까 Form이 생기더라? 개깜놀-_-이전엔 Dialog ID랑 다 연결을 내가 한기억이 있는데 말이지)

CFormView::CTestChildFormView


일단 3개의 Class가 만들어지면 이제 부터 initstance() 를 손을 댈 차례다 


이 글을 보러온 분들은 적어도 한번씩 아래 구문을 봤을꺼라 생각한다. (왜냐면 겁나 검색 해봤을 거거든...)


// 응용 프로그램의 문서 템플릿을 등록합니다. 문서 템플릿은

//  문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(IDR_[프로젝트이름]TYPE,

RUNTIME_CLASS(C[프로젝트이름]Doc),

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

RUNTIME_CLASS(C[프로젝트이름]View));

if (!pDocTemplate)

return FALSE;

AddDocTemplate(pDocTemplate);


위 코드는 실제로 MDI를 만들기만 해도 생성되는 코드이다. 
이 코드의 정체는 주석으로 해석되어 있는 그대로 모든 MDI의 프레임을 연결하기 위한 로직이다. 
이 코드 밑에 CMultiDocTemplate 객체를 선언하는 라인 밑부터 AddDocTemplate() 함수까지 복사해서 붙여넣는다. 

// 응용 프로그램의 문서 템플릿을 등록합니다. 문서 템플릿은

//  문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(IDR_[프로젝트이름]TYPE,

RUNTIME_CLASS(C[프로젝트이름]Doc),

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

RUNTIME_CLASS(C[프로젝트이름]View));

if (!pDocTemplate)

return FALSE;

AddDocTemplate(pDocTemplate);


pDocTemplate = new CMultiDocTemplate(IDR_[프로젝트이름]TYPE,

RUNTIME_CLASS(C[프로젝트이름]Doc),

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

RUNTIME_CLASS(C[프로젝트이름]View));

if (!pDocTemplate)

return FALSE;

AddDocTemplate(pDocTemplate);


그러면 위와 같이 되겠지? 여기서 수정을 해야 한다. 복사한 구문에서 프로젝트 아래와 같이 내가 만든 Class명을 넣어준다.


pDocTemplate = NULL;//개념이 있다면 이 부분은 추가해주자...

pDocTemplate = new CMultiDocTemplate(IDR_[프로젝트이름]TYPE,

RUNTIME_CLASS(CTestChildDoc),

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

RUNTIME_CLASS(CTestChildFormView));

if (!pDocTemplate)

return FALSE;

AddDocTemplate(pDocTemplate);


사실 이러면 끝이다. 하지만 기본적으로 프로그램이 로드가 되면 자동으로 ChildFrame이 열리게 된다. 

여기서 어떤 프레임을 열것인지 아래와 같이 물어보게 된다. 




(딱! 개념있으신 분들은 알겠지만 CFrameWnd를 사용하지 않았다. -_- 나도 이리저리 찾아서 연구해서 나온거라 솔직히 지금은 필요가 없는데 나중에 쓰이겠지...;; 그리고 RUNTIME_CLASS(CChildFrame) 이 부분에 CMainFrame을 하게되면 Child가 안에 생성되지 않고 밖으로 빠져나온다. 왜냐면 MainFrame을 사용하기 때문에 독립적으로 빠져버리는 것이다. 그러면 매뉴나 툴바 역시 MainFrame의 그것을 사용한다. ChildFrame을 사용하게 되면 형식이 다른 Child라도 매뉴와 툴바를 공유하겠지? 그 부분은 나중에 또 공부해서 포스팅할 예정이다.)


솔직히 이까진 빠르게 작업을 했었다. 내가 원하는건 저 뒷판만 나오는 거지 새로 만들기 창이 떠서는 않된다. 특정한 기능이 호출될때 원하는 창이 뜨도록 해야 하는 것이다. 저걸 없애기 위해서 6개월을 검색만(아! 이것만 보고 있었던게 아니라서...물론 다른 일 하느라 이거 공부를 미룬건 사실이다...(엄청 미뤘지...))했더랬다. 


여튼 일단 아무것도 하지않고 위 클래스와 코드를 추가 했다면 실행하자마자 저 창이 뜬다. 

이제 저 창이 자동으로 뜨는걸 막아야 한다. 반드시 막아야 하느니라....!!


App::Initstance() 함수에 위 코드를 입력 했었다. 그 아래 살짝 보면 아래와 같은 구문이 있다. 


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

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);


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

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

if (!ProcessShellCommand(cmdInfo))

return FALSE;


추가적인 Child를 등록하지 않아도

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

이 두 구문을 주석처리 하면 Child가 자동으로 띄워지는 현상을 방지 할 수 있다. Child가 뜨는 이유는 머가 잘못되서 그런건 아니고 내부적으로 FileNew를 때리기 때문이다. 


그리고 밑에  if (!ProcessShellCommand(cmdInfo)) 이 구문은 여러개의 Child 형식이 있다면(다중 템플레이트 라고 하며 위에서 DocTemplate 라는 이름을 가지고 있는 객체로 여러개를 등록하는 것이다.)어느 템플레이트를 로드할 것인가를 물어보게 된다. 

그래서 우리는 다 필요없으니 다 주석 처리 하면된다. 


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

// CCommandLineInfo cmdInfo;

// ParseCommandLine(cmdInfo);


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

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

// if (!ProcessShellCommand(cmdInfo))

// return FALSE;


그리고 실행하면 아무것도 않뜬다! 크아~~ 해냈구만!!! 하면서 File new를 해보니 또 뜨더라....



참 저 새로 만들기 업애기 힘들지...그렇지 

조금만 더 해주면 된다. 얼마 않남았다. 


일단 사전작업은 끝이났고 내가 원하는 것은 File New 할때는 그냥 일반 View를 가진 Child를 원하는 거고 어떤 이벤트가 발생되면 FormView를 가진 Child를 띄우는 것이니까 일단 디폴트를 먼저 띄우는게 순서다. 


C[프로젝트명]App::OnFileNew() 함수르 제정의 한다. 

void CMDI_ChildTestApp::OnFileNew()

{

// TODO: 여기에 명령 처리기 코드를 추가합니다.

}


그리고 아래와 같이 코딩 한다. 


void CMDI_ChildTestApp::OnFileNew()

{

// TODO: 여기에 명령 처리기 코드를 추가합니다.


POSITION pos = GetFirstDocTemplatePosition();

CDocTemplate* pTemplate;

pTemplate = GetNextDocTemplate(pos);

pTemplate->OpenDocumentFile(NULL);

}


다중 템플레이트의 위치를 받아서 첫 번째 위치를 Open하겠다는 의미다. 언제!? OnFileNew 가 호출이 되면!!! 

매우 직설적인 코드 그리고 직설적인 이벤트!!!!! 


컴파일 에러 없고 실행해서 File New 하면!!!!



빡친다. 더욱이 중단점 찍어서 컴파일 해보면 저 함수 타지도 않는다. 진짜 욕이 나오는 부분이다. 이전에 데브피아에서 파일에 관한 이벤트 핸들러가 먹지 않아 질문 수두룩 했는데 제대로된 답변은 못받았었다. ㅠㅠ 


사실 잘못된 부분은 아니다. 구조를 모르고 있었을뿐...SDI도 이런가?(기억이 않나는군...)


C프로젝트명App.cpp 윗쪽으로 올라가면 App에 대한 BEGIN_MESSAGE_MAP 부분이 있다. 이 부분은 매시지랑 함수랑 연결해주는 말 그대로 매시지 맵인데 ... 내가 이 부분을 자세히 보니까 C프로젝트명App 외 다른 Class가 존재하더라 


&CWinAppEx::OnFileNew


위 클레스....바로 이녀석 때문에 내가 제정의한 함수가 씹혀버리는 것이다. 

CWinAppEx 는 딱히 상위는 아니지만 내가 만든 클래스보다 먼저 실행이 되는 녀석인가 보다. (이건 깊게 않팠음)


때문에 App에서 제 정의를 그렇게 해도 원하는 곳으로 호출이 되지 않는 것이 문제였다. 기본적으로도 여기서 제정의된 함수에서 FileNew를 해줬기 때문이다. 


과감히 제거 


// ON_COMMAND(ID_FILE_NEW, &CWinAppEx::OnFileNew)


그리고 다시 컴파일 후 실행한뒤 파일->새로 만들기 해준다. 




입가의 미소가 씨~익 한다. 울지말자 아직 울 때가 아니다. 두 번째 FormView속성의 창을 띄우기 전까진 긴장 풀면 않된다. 


사실 여기 까지 했다면 대충 알아차렸을 것이다. OnFileNew에서 코드만 변형해주면 두 번째 창을 기본으로 할 수 도 있다. 


하지만 일단 내가 해본거 까지 포스팅은 마쳐야 하니까...

다른 포스트를 보고 예를 만든거라서 그 포스트의 예를 동일하게 따라 했다. 


참고한 포스트에서는 MainFrame 매뉴에다가 이벤트 추가 해라는 이야기를 엄청나게 어렵게 해놔서 ;;; 일단 힘들게 했다만 간단하게 말하면 Child 매뉴가 아닌 메일 프레임의 메뉴 , 그러니까 Child가 없는 상태에서 뜨는 매뉴(이게 MainFrame이다.)에다가 매뉴를 추가 하라는 것이다. 


리소스뷰 에서 Menu탭을 열면 IDR_MAINFRAME 이 있다 !! 그래 이거야 이게 메인프레임 메뉴야...


여기서 편하게 하기 위하여 파일탭에 매뉴하나 만들어 준다. 아래와 같이 




매뉴 설정하는건 알꺼라 생각한다;; ID 지정해주고 이벤트 처리기 추가한다. 여기서 중요한건 반드시 C프로젝트명App 클래스에 이벤트를 추가한다. 


난 OnDlgView 라는 이름으로 추가 했다. (함수 명은 ID를 따라간다. 내가 지정한 ID 는 ID_DLGVIEW 이다. )

그리고 코딩해준다. OnFileNew와 비슷한데 For 문하나 더 있다. 


void C프로젝트명App::OnDlgview()

{

// TODO: 여기에 명령 처리기 코드를 추가합니다.

POSITION pos = GetFirstDocTemplatePosition();

CDocTemplate* pTemplate;

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

{

pTemplate = GetNextDocTemplate(pos);

}

pTemplate->OpenDocumentFile(NULL);

}


구문은 간단하지...두 번째 Template를 받아온다 그리고 Open 한다는 뜻이다. 

실행해서 연결된 매뉴를 눌러보자 




그러면 두둥!!




크....FormView가 뜬다. 

그리고 다시 원래 있는 FileNew를 해보면 




이거다. 이게 내가 원했던 것이다!! 



물론 모든걸 다 해결된 건 아니다. 

여기서 문제가 Child 가 떠있으면 매뉴가 MainFrame의 매뉴로 돌아가질 않는다. Child가 아닌 허공에 찍어도 자동으로 MainFrame의 매뉴로 변경되지 않기 때문에 FormView는 딱~~하나 만 생성 그것도 처음에 

하지만 어짜피 하나는 내가 원하는 이벤트가 발생 할때 띄울 생각이기 때문에 그렇게 큰 문제는 않된다. 필요하면 나중에 연구해서 포스팅 해야지 


참 힌트는 좀 찾긴 했다만 Initstance()함수 하단부 그러니까 아까 주석처리로 막은 곳 바로 위를 보면 아래와 같은 문장이 있다. 


// 주 MDI 프레임 창을 만듭니다.

CMainFrame* pMainFrame = new CMainFrame;

if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))

{

delete pMainFrame;

return FALSE;

}

m_pMainWnd = pMainFrame;


이 부분은 MainFrame을 만들고 연결하는 부분이며 프레임이 존재하지 않을때 메뉴와 툴바를 매인프레임의 매뉴와 툴바를 쓰겠다는 것이다. 


이걸 활용해서 허공을 찍을때 위 코드를 넣어주면 매인 프레임의 매뉴를 사용 할 수 있다. 난 필요없어서 않했어 나중에 하지 머....



Reference by : 

1.

http://alleysark.tistory.com/entry/MDI-tab%EC%97%90-%EB%8B%A4%EB%A5%B8-%EA%B8%B0%EB%8A%A5%EC%9D%98-%EC%9E%90%EC%8B%9D-%EC%B6%94%EA%B0%80


2.

http://blog.naver.com/PostView.nhn?blogId=chaose21c&logNo=150133862259


3.

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