. 차일드 생성

이 예제는 차일드 윈도우를 관리하기 위해 두 개의 전역변수를 사용하고 있다. g_NewNo는 새로 만들어지는 차일드에게 붙여줄 디폴트 파일명을 만들기 위해 필요하다. 최초 0으로 초기화되었다가 문서가 만들어질 때마다 1씩 증가하여 이름없음1, 이름없음2, 이름없음3 식으로 문서의 이름을 붙이게 된다. 파일을 생성해 내는 MDI 프로그램들은 새로 차일드를 만들 때마다 이름없음, noname, untitled, 문서, 제목 없음 등의 디폴트 이름을 붙이는데 이 이름들도 서로 중복되어서는 안되기 때문에 뒤에 1씩 증가하는 일련번호를 붙여주며 이 일련번호를 바로 g_NewNo 변수가 관리한다.

g_ChildNum은 열린 차일드의 개수를 저장하는 값이다. 최초 0으로 초기화되며 차일드가 만들어질 때마다 1씩 증가하고 차일드가 파괴되면 1 감소한다. DGChildProc WM_CREATE WM_DESTROY에서 g_ChildNum을 증감시키므로 이 값을 읽으면 현재 몇 개의 차일드가 열려 있는지 바로 알 수 있다. 차일드 개수는 윈도우 열거를 해보면 실시간으로 구할 수도 있지만 시간이 오래 걸리기 때문에 전역변수로 그 개수를 유지하도록 했다.

이 두 전역변수의 용도가 비슷한 것 같지만 조금 다르다. g_ChildNum은 증감하지만 g_NewNo는 무조건 증가만 하고 감소는 하지 않는다. 차일드를 만들고 닫기를 반복할 때 이 두 값이 어떻게 변하는지 보자.

 

 

g_NewNo

g_ChildNum

초기값

0

0

이름없음 1 생성

1

1

이름없음 2 생성

2

2

이름없음 3 생성

3

3

이름없음 2 닫음

3

2

이름없음 4 생성

4

3

 

언뜻 보기에 g_ChildNum g_NewNo의 역할을 대신할 수 있는 것 같아 보인다. 하지만 중간에 차일드를 닫을 수도 있기 때문에 그렇지가 못하다. 1,2,3까지 만든 후 2를 닫고 다시 차일드를 하나 더 만들었을 때 이 값은 4가 되어야지 3이 되어서는 안되기 때문이다. 그래서 두 변수가 모두 필요하다.

새로운 차일드 윈도우를 만드는 작업은 New 함수가 한다. 이 함수가 MDI 차일드를 만드는 방법은 원론적이다. MDICREATESTRUCT 구조체에 생성하고자 하는 차일드에 대한 정보를 채우고 MDI 클라이언트 윈도우로 WM_MDICREATE 메시지를 보내는 것이다. 이 메시지 대신 CreateMDIWindow 함수를 사용할 수도 있다.

차일드를 생성하는데 별 특별한 코드는 없지만 생성되는 차일드의 크기를 정하는 방법이 조금 특이하다. 차일드를 만들기 전에 WM_MDIGETACTIVE 메시지를 보내 현재 활성화된 차일드가 있는지, 그리고 그 창이 최대화된 상태인지를 조사한다. 만약 활성 차일드가 없거나 있더라도 최대화되어 있으면 새로 만들어지는 차일드도 같이 최대화된다. 활성 차일드가 없다는 말은 아무 것도 편집하고 있지 않은 빈 상태라는 뜻인데 이때 생성되는 차일드는 혼자이므로 최대화하여 작업영역 전체를 다 쓸 수 있도록 했다.

New 함수는 파일/새 파일(IDM_FILE_NEW) 항목을 선택할 때 호출된다. 새 파일을 만들라는 명령이므로 이 함수가 호출되는 것이 당연하다. 그 외에 New 함수가 호출되는 곳이 한 군데 더 있는데 메인 윈도우가 처음 만들어질 때이다. 편집기를 실행했다는 것은 파일을 만들겠다는 뜻이므로 시작할 때 아예 새 문서를 하나 만드는 것이다. 만약 그냥 빈 상태로 실행되도록 한다면 실행 직후에 항상 파일/새 파일을 선택해야 하므로 쓰기 불편할 것이고 또 개발중에도 테스트하기가 여간 피곤한 것이 아니다. 그래서 실행하자마자 New 함수를 호출하여 차일드 하나를 만들도록 하였다.

이런 목적이라면 메인 윈도우가 생성되는 시점인 OnCreate에서 New 함수를 호출하면 될 것 같다. 그러나 이 예제는 그렇게 하지 않았고 OnCreate에서는 타이머만 설치하고 OnTimer에서 New 함수를 호출하였다. 이 타이머는 발생 직후에 곧바로 파괴되는 일회용 타이머이다. 왜 굳이 타이머를 사용하는가 하면 초기화가 완전히 완료된 후에 차일드를 만들기 위해서이다. OnCreate는 부모 윈도우가 만들어지고 있는 중인 상태이므로 New 함수를 호출하기는 부적당하며 만약 OnCreate에서 New 함수를 호출하면 MDI 클라이언트의 크기가 결정되지 않았기 때문에 크기 0의 차일드가 만들어진다. New 함수에 의해 차일드는 최대화 상태로 만들어지지만 최대화가 풀리면 아주 작은 크기가 되어 버린다.

딱 한 번만 하면 될 일을 위해 타이머를 사용하는 방식이 무척 마음에 안 들어 보일 것이며 사실 나도 별로 마음에 안 든다. 문제는 WM_CREATE가 초기화 완료 메시지가 아니라 초기화중이라는 메시지이기 때문인데 그렇다면 초기화가 완전히 완료된 후에 받는 메시지에서 New 함수를 호출하면 될 것 아닌가하는 생각이 들 것이다. 그러나 불행하게도 그런 메시지는 없으며 있을 수도 없다. 왜냐하면 메시지를 처리하고 있는 중이라는 자체가 이미 초기화가 아직 덜 되었다는 뜻이기 때문이다.

그래서 타이머를 설치하고 이 메시지가 최초로 발생되는 시점을 초기화 완료 시점으로 정의하였다. 즉 일회용 타이머 메시지를 초기화 완료 메시지로 만들어 쓰는 것이다. SetTimer문의 1이라는 타이머 주기는 사실상 의미가 없는 값이며 0.001초 후를 뜻하는 것이 아니라 그냥 단순히 최대한 빨리라는 뜻이다. WM_TIMER WM_PAINT 다음으로 우선 순위가 늦은 메시지이며 메시지 큐가 비는 최초의 순간에 이 메시지가 전달되므로 초기화 완료 메시지로 쓰기에 적절하다.

그래도 타이머를 쓰는 방식이 정 마음에 들지 않는다면 원칙을 좀 따져 보도록 하자. 원래 원칙대로 하자면 초기화가 완료되는 시점은 WinMain CreateWindow가 리턴하는 시점이다. 그래서 New 함수를 다음과 같이 호출하면 동일한 기능을 완벽하게 구현할 수 있다.

 

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance

            ,LPSTR lpszCmdParam,int nCmdShow)

{

     ....

     hWnd=CreateWindow(lpszClass,"당근",WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,

          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,

          NULL,(HMENU)NULL,hInstance,NULL);

    New();

 

하지만 WinMain에 메인 윈도우 초기화 관련 코드를 작성한다는 것도 타이머를 쓰는 것에 비해 그다지 좋은 방법이라고는 할 수 없다. 좀 보기에 안 좋아 보여도 그 윈도우와 관련된 코드는 메시지 프로시저 내에서 처리하는 것이 더 좋다. 프로젝트가 진행되면 초기화 완료 시점에서 해야 할 일이 많아지는데 이런 대량의 코드를 WinMain에 두는 것은 그다지 좋은 생각이 아니다.