. 메시지 전담 함수

처음 만든 당근 프로젝트의 소스를 분석해보자. 이 소스는 그다지 길지 않지만 만약 복잡하게 느껴진다면 아마도 MDI 구문에 익숙하지 않아서일 것이다. 이 예제의 큰 틀은 API 정복 28장에 있는 MDIExam 예제를 그대로 복사해온 것이며 관련 이론에 대해서도 28장에서 자세히 설명하고 있다. MDI 관련 메시지나 함수들은 윈도우즈 프로그래밍의 기본이므로 여기서는 알고 있다고 가정하기로 한다. 만약 그렇지 못하다면 MDIExam을 먼저 분석해보기 바란다.

MDI 프로그램은 기본적으로 프레임, MDI 클라이언트, 차일드 등 최소한 세 개의 윈도우로 구성된다. 이 중 프레임은 프로그램의 메인 윈도우에 해당하며 프로그램 전반의 기능을 제공하는 아주 중요한 역할을 담당하고 있다. 현재 소스에서는 프레임이 하는 일이 별로 없지만 앞으로 파일 입출력, 검색, 인쇄, 옵션 관리 등의 기능들이 대거 들어가게 될 것이고 따라서 프레임이 처리해야 하는 메시지의 종류도 무척 많아진다. 그래서 프레임의 메시지 프로시저인 DGWndProc은 모든 메시지를 직접 처리하지 않고 각 메시지별로 전담 함수를 호출하도록 하였다.

 

LRESULT CALLBACK DGWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     switch(iMessage) {

          case WM_CREATE:return OnCreate(hWnd,wParam,lParam);

          case WM_DESTROY:OnDestroy(hWnd,wParam,lParam);return 0;

          case WM_COMMAND:OnCommand(hWnd,wParam,lParam);break;

          case WM_TIMER:OnTimer(hWnd,wParam,lParam);return 0;

     }

     return(DefFrameProc(hWnd,g_hMDIClient,iMessage,wParam,lParam));

}

 

WM_CREATE 메시지를 받았으면 OnCreate를 부르고 WM_DESTROY 메시지를 받았으면 OnDestroy를 부른다. 지금은 다섯 개의 메시지만 처리하고 있지만 당근 프로젝트가 진행됨에 따라 점점 메시지 수가 늘어날 것임은 당연하다. 언뜻 보기에는 메시지크래커같이 보이지만 그렇지는 않으며 비슷하게 흉내만 낸 것이다. 메시지크래커의 HANDLE_MSG 매크로는 모든 메시지를 처리한 후 그 결과를 리턴하기 때문에 MDI 프로젝트에는 사용할 수 없다.

MDI의 프레임 윈도우는 WM_COMMAND, WM_SIZE 등의 핵심 메시지들을 반드시 DefFrameProc으로 보내야 할 의무가 있기 때문이다. MDI 클라이언트가 이 메시지들을 받아야 차일드간의 전환이나 차일드 창의 배치를 할 수 있다. 그래서 DGWndProc WM_COMMAND 메시지를 받으면 일단 OnCommand를 먼저 호출하고 break switch 블록을 나가도록 되어 있다. 만약 여기서 return 0하면 모든 MDI 기능이 하나도 동작하지 않게 된다. 다음에 처리할 WM_SIZE 메시지도 마찬가지다.

각각의 메시지를 처리하는 OnCreate, OnTimer 등의 함수는 wParam, lParam을 그대로 전달받아 메시지를 처리한 후 DGWndProc으로 리턴한다. 각 메시지별로 처리 함수를 따로 만드는 것이 코드를 관리하기도 편리하고 지역변수를 자유롭게 쓸 수 있어 여러 모로 좋다. DGWndProc 함수는 아주 많은 일을 해야 하기 때문에 전담 함수를 두었지만 앞으로 만들게 될 다른 윈도우의 메시지 프로시저는 별도의 함수를 만들지 않고 직접 모든 메시지를 처리하도록 할 것이다. 전담 함수를 만들면 보기에는 좋지만 소스 길이가 늘어나기 때문에 출판용 예제에서는 적당한 선에서 자재를 할 수 밖에 없었다. DGChildProc 함수는 차일드의 모든 메시지를 다 처리하도록 되어 있다.

WinMain의 메시지 루프에서는 액셀러레이터와 MDI 액셀러레이터를 처리하고 있는데 순서에 주의해야 한다. 만약 액셀러레이터 단축키가 MDI의 단축키와 중복될 경우 MDI의 단축키가 우선적으로 처리되어야 하므로 TranslateMDISysAccel 함수가 TranslateAccelerator 함수보다 먼저 호출되어야 한다.

ApiEdit <Ctrl+C>, <Ctrl+V> 등의 단축키를 OnChar에서 정의하고 있다. 그리고 Dangeun도 이 단축키를 액셀러레이터에서 지원하고 있어 이중으로 단축키를 정의하고 있는 셈이다. 호스트가 액셀러레이터를 정의하고 있으므로 CApiEdit::OnChar에 있는 단축키 관련 코드는 이제 삭제해도 된다. 어차피 키입력보다 액셀러레이터 입력이 우선이기 때문에 <Ctrl+C>, <Ctrl+A> 같은 키입력은 ApiEdit OnChar까지 오지도 않는다. 하지만 이 코드는 삭제하는 것보다는 계속 유지하는 것이 더 좋다. 왜냐하면 ApiEdit가 액셀러레이터를 제공하지 않는 호스트와 함께 사용될 수도 있기 때문이다. 같은 이유로 <Ctrl+Ins>, <Shift+Ins> 등의 단축키 처리도 그대로 유지한다.