. 미보관 문서 처리

사용자가 문서를 편집한 후 저장하지 않고 윈도우를 닫고자 할 때는 이를 확인할 필요가 있다. 편집된 내용을 의도적으로 버리는 경우도 있겠지만 그보다는 실수로 윈도우를 닫는 경우가 더 많기 때문이다. 사용자는 이런 확인 과정을 귀찮아 할 수도 있지만 장시간 작업한 미보관 문서를 실수로 잃어버리는 것보다는 확인을 하는 것이 더 낫다. 차일드의 WM_CLOSE 메시지에 확인 코드를 작성하도록 하자.

 

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

{

     case WM_CLOSE:

          if (ConfirmSave(hWnd) == IDCANCEL) {

              return 1;

          }

          break;

     ....

 

ConfirmSave 함수는 파일이 변경되었는지 먼저 확인한 후 변경된 파일이면 사용자에게 저장할 것인지 질문을 한다. 사용자의 대답에 따라 파일을 저장하든가 아니면 버리며, 취소를 선택할 경우는 IDCANCEL을 리턴한다. WM_CLOSE에서는 사용자가 윈도우 종료를 취소할 경우 return 1로 그냥 리턴하여 DefMDIChildProc으로 제어가 가지 않도록 함으로써 윈도우 종료를 취소한다. 여기서 리턴값은 당장은 큰 의미가 없지만 이 메시지를 명시적으로 보낼 때는 취소했는지(1) 아니면 DefMDIChildProc에 의해 디폴트 처리되었는지(0)를 구분하는 용도로 사용된다.

미보관 문서에 대한 처리는 개별 차일드 윈도우도 해야 하지만 응용 프로그램 수준에서도 필요하다. 차일드 윈도우를 닫지 않고 응용 프로그램을 바로 종료할 경우 모든 열린 파일에 대해 미보관 여부를 확인해야 한다. 만약 확인중에 단 하나의 윈도우라도 종료를 거부할 경우 프로그램 종료 자체를 취소해야 한다.

차일드와 마찬가지로 프레임의 미보관 문서 처리도 WM_CLOSE 메시지에서 하면 된다. 또한 운영체제가 종료될 때 WM_QUERYENDSESSION 메시지가 보내지는데 이때도 미보관 문서 처리를 해야 한다. 운영체제가 종료될 때는 윈도우를 파괴하지 않기 때문에 WM_CLOSE 메시지가 전달되지 않는다. 이 두 메시지 처리를 위해 OnClose, OnQueryEndSession 함수를 선언하고 DGWndProc에서 이 함수를 호출하도록 한다. 먼저 원형을 선언하도록 하자. OnInitMenu OnDropFiles 함수도 잠시 후에 사용할 것이므로 미리 추가해두었다.

 

BOOL OnClose(HWND hWnd,WPARAM wParam,LPARAM lParam);

BOOL OnQueryEndSession(HWND hWnd,WPARAM wParam,LPARAM lParam);

void OnInitMenu(HWND hWnd,WPARAM wParam,LPARAM lParam);

void OnDropFiles(HWND hWnd,WPARAM wParam,LPARAM lParam);

 

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;

        case WM_CLOSE:if (OnClose(hWnd,wParam,lParam)) return 0;break;

        case WM_QUERYENDSESSION:if (OnQueryEndSession(hWnd,wParam,lParam)) return 0;break;

        case WM_INITMENU:OnInitMenu(hWnd,wParam,lParam);return 0;

        case WM_DROPFILES:OnDropFiles(hWnd,wParam,lParam);return 0;

     }

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

}

 

OnClose, OnQueryEndSession 함수는 프로그램 종료를 할 것인지 아닌지를 리턴하므로 이 함수가 TRUE를 리턴할 때는 그냥 return 0하고 FALSE를 리턴할 때만 DefFrameProc으로 이 메시지를 보내 종료 처리를 하도록 하였다. 이 함수의 코드는 다음과 같다.

 

BOOL OnClose(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

#ifndef _DEBUG

     HWND hCloseChild;

     hCloseChild=GetWindow(g_hMDIClient,GW_CHILD);

     while (hCloseChild) {

          if (ConfirmSave(hCloseChild)==IDCANCEL)

              return TRUE;

          hCloseChild=GetWindow(hCloseChild,GW_HWNDNEXT);

     }

#endif

     return FALSE;

}

 

BOOL OnQueryEndSession(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     HWND hCloseChild;

     hCloseChild=GetWindow(g_hMDIClient,GW_CHILD);

     while (hCloseChild) {

          if (ConfirmSave(hCloseChild)==IDCANCEL)

              return TRUE;

          hCloseChild=GetWindow(hCloseChild,GW_HWNDNEXT);

     }

     return FALSE;

}

 

모든 차일드 윈도우를 순회하면서 파일 저장 여부를 확인하고 만약 한 윈도우라도 취소를 했으면 TRUE를 리턴하여 프로그램 종료 자체를 취소한다. WM_CLOSE에서 문서 저장을 확인하는 기능은 _DEBUG 매크로가 정의되어 있지 않을 때만 조건부로 컴파일하도록 했다. 왜냐하면 개발중에는 문서 작성을 목적으로 하지 않고 테스트를 위해 문자열을 입력해보는데 매번 저장 확인을 하는 것은 무척이나 번거롭기 때문이다. 그래서 개발중에는 미보관 문서를 무시하도록 했으며 릴리즈할 때만 이 기능을 포함하도록 하였다. OnQueryEndSession의 코드도 OnClose와 완전히 동일하다.

파일/종료(IDM_FILE_EXIT) 항목을 선택하여 프로그램을 종료할 때도 미보관 문서 처리를 해야 한다. 그래서 OnCommand의 이 메시지 처리코드를 보면 DestroyWindow 함수로 메인 윈도우를 파괴하지 않고 WM_CLOSE 메시지를 보내도록 되어 있다. DestroyWindow로 곧바로 종료하면 미보관 문서를 확인할 기회가 없으므로 반드시 WM_CLOSE 메시지를 보내 간접적으로 종료하도록 해야 한다. WM_DESTROY에서는 윈도우 파괴가 이미 확정된 상태이기 때문에 미보관 문서를 발견하더라도 종료를 취소할 수가 없다.

여기까지 코드를 작성하고 실행해보면 메뉴를 통해 파일 입출력을 할 수 있을 것이다. 드디어 ApiEdit가 메모장 수준의 기능을 갖추게 된 것이다.