. 위치 저장

프로그램이 실행되던 위치와 크기도 일종의 설정값이므로 저장하는 것이 좋다. 종료하기 전에 화면의 오른쪽 아래에 있었다면 다음 실행할 때도 역시 그 자리에서 시작한다면 좋을 것이다. 사용자가 프로그램을 가장 편한 위치에 배치하는 것도 일종의 선택 동작이므로 이 선택을 유지하도록 하자. 실시간으로 구하고 적용되는 설정값이기 때문에 SOption에는 이 정보가 포함되어 있지 않다. 정확한 위치와 상태 복원을 하기 위해서는 마지막 윈도우 좌표와 크기는 물론이고 최대화 상태도 레지스트리에 저장해야 한다.

다음 두 함수는 윈도우의 실행 위치를 저장 및 복구하는 기능을 가지는데 최대화 상태에 대한 저장 기능까지 갖추고 있다. 보통 GetWindowRect 함수로 윈도우 위치를 조사하고 MoveWindow 함수로 위치를 재설정하는데 이 방법은 단순히 윈도우의 좌표와 크기만 저장할 뿐이다. Get(Set)WindowPlacement 함수를 사용하면 최대화 상태와 노멀 윈도우 위치까지 같이 저장, 복구할 수 있다. Util.cpp에 다음 두 함수를 추가한다.

 

void SavePosition(HWND hWnd, TCHAR *Key)

{

     WINDOWPLACEMENT wndpl;

 

     wndpl.length=sizeof(WINDOWPLACEMENT);

     GetWindowPlacement(hWnd,&wndpl);

     if (wndpl.showCmd == SW_SHOWMAXIMIZED) {

          SHRegWriteInt(SHCU,Key,"Max", 1);

     } else {

          SHRegWriteInt(SHCU,Key,"Max", 0);

     }

 

     SHRegWriteInt(SHCU,Key,"left", wndpl.rcNormalPosition.left);

     SHRegWriteInt(SHCU,Key,"top", wndpl.rcNormalPosition.top);

     SHRegWriteInt(SHCU,Key,"right", wndpl.rcNormalPosition.right);

     SHRegWriteInt(SHCU,Key,"bottom",wndpl.rcNormalPosition.bottom);

}

 

void LoadPosition(HWND hWnd, TCHAR *Key, RECT *Def/*=NULL*/)

{

     WINDOWPLACEMENT wndpl;

     RECT drt;

 

     if (Def==NULL) {

          SetRect(&drt,10,10,600,400);

     } else {

          CopyRect(&drt,Def);

     }

     wndpl.length=sizeof(WINDOWPLACEMENT);

     wndpl.flags=0;

     wndpl.rcNormalPosition.left=SHRegReadInt(SHCU,Key,"left",drt.left);

     wndpl.rcNormalPosition.top=SHRegReadInt(SHCU,Key,"top",drt.top);

     wndpl.rcNormalPosition.right=SHRegReadInt(SHCU,Key,"right",drt.right);

     wndpl.rcNormalPosition.bottom=SHRegReadInt(SHCU,Key,"bottom",drt.bottom);

 

     if (GetSystemMetrics(80/*SM_CMONITORS*/) <= 1) {

          if (wndpl.rcNormalPosition.left > GetSystemMetrics(SM_CXSCREEN) ||

              wndpl.rcNormalPosition.left < 0) {

              wndpl.rcNormalPosition.left=drt.left;

          }

 

          if (wndpl.rcNormalPosition.top > GetSystemMetrics(SM_CYSCREEN) ||

              wndpl.rcNormalPosition.top < 0) {

              wndpl.rcNormalPosition.top=drt.top;

          }

     }

 

     if (SHRegReadInt(SHCU,Key, "Max", 0) == 1) {

          wndpl.showCmd=SW_SHOWMAXIMIZED;

     } else {

          wndpl.showCmd=SW_RESTORE;

     }

     wndpl.ptMinPosition.x=wndpl.ptMinPosition.y=0;

     wndpl.ptMaxPosition.x=wndpl.ptMaxPosition.y=0;

     SetWindowPlacement(hWnd,&wndpl);

}

 

위지 저장 및 복구의 대상이 되는 윈도우 핸들 hWnd를 첫 번째 인수로 주고 정보를 저장할 레지스트리 키와 레지스트리에 값이 없을 때 어떤 위치를 적용할 것인지를 지정하는 Def 인수를 전달한다. 최초 실행시를 위해 적절한 디폴트 위치를 Def 인수에 전달하되 만약 Def 인수가 NULL이면 (10,10)에서 (600,400)에 윈도우를 옮겨주도록 하였다.

저장된 윈도우 위치가 화면의 바깥이면 강제로 기본 위치의 좌상단으로 이동시키는데 이 처리는 꼭 필요하다. 만약 1280*1024의 넓은 화면에서 작업하다가 해상도를 640*480으로 바꾸었다면 저장된 위치가 화면 바깥이 될 수도 있다. 이때는 윈도우 위치를 화면 안쪽으로 강제로 옮겨주어 어쨌든 보이도록 해야 한다. 이 처리를 생략하면 한바탕 숨바꼭질을 하는 해프닝이 일어나기도 하는데 예상외로 그런 프로그램이 많다.

, 이 처리는 모니터가 여러 대인 시스템에서는 정확하지 않으므로 모니터가 한대 또는 모니터 대수를 아예 조사할 수 없는 시스템에만 하도록 했다. 멀티 모니터 시스템에서는 좌표가 음수가 될 수도 있고 GetSystemMetrics 함수는 주 모니터의 해상도만 조사하기 때문에 이런 간단한 방법으로 가상 화면(Virtual Screen) 안쪽에 있는지 비교할 수 없다. 이 코드는 멀티 모니터를 제대로 지원할 수 있도록 좀 더 수정되어야 하나 안타깝게도 나의 노트북에 그런 능력이 없어 코드를 작성하지 못했다. SOption과 마찬가지로 메인 윈도우가 파괴될 때인 OnDestroy에서 위치를 저장한다.

 

void OnDestroy(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     Option.Save(KEY);

 

     if (arFont)

          free(arFont);

     PostQuitMessage(0);

    SavePosition(hWnd,KEY"Position");

}

 

이렇게 저장된 위치를 다시 읽어오는 시점은 다른 옵션과 달리 WM_ACTIVATEAPP 메시지를 받았을 때인데 이 메시지는 응용 프로그램이 활성/비활성화될 때 보내진다. 이 메시지의 핸들러인 OnActivateApp 함수를 추가하고 이 함수에서 LoadPosition 함수를 호출하여 마지막 실행 위치를 복구한다.

 

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

{

     switch(iMessage) {

          ....

        case WM_ACTIVATEAPP:OnActivateApp(hWnd,wParam,lParam);return 0;

     }

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

}

 

void OnActivateApp(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     static BOOL bOnlyOne=TRUE;

     RECT drt;

 

     if (bOnlyOne) {

          if (GetSystemMetrics(SM_CXSCREEN) < 800) {

              SetRect(&drt,10,10,500,300);

          } else {

              SetRect(&drt,10,10,700,500);

          }

          LoadPosition(hWnd,KEY"Position",&drt);

          bOnlyOne=FALSE;

     }

}

 

OnCreate에서 LoadPosition을 호출하지 않는 이유는 이 메시지를 받았을 때 노멀 위치(최대화되지 않았을 때의 위치)를 지정하는 것이 의미가 없기 때문이다. 여기서 노멀 위치를 복구해 봐야 윈도우 생성이 완료된 후 다시 재설정되어 버린다. 그래서 윈도우가 완전히 생성되고 최초로 활성화될 때인 WM_ACTIVATEAPP 메시지에서 위치를 복구하도록 하였다. 딱 한 번만 실행되어야 할 코드이므로 bOnlyOne 정적 변수를 사용했다. 최초 실행될 때의 위치와 크기는 화면 해상도에 따라 적절한 디폴트 크기를 선택하도록 했다. 모니터가 크면 좀 크게 보여주고 그렇지 않으면 아담한 크기로 생성될 것이다.

앞서 Dangeun1 예제에서도 초기화가 완료된 시점에서 New 함수를 호출하기 위해 OnTimer를 사용한 적이 있었다. 비슷한 경우이지만 윈도우의 위치 복구에 OnTimer를 사용하는 것도 적합하지 않다. 왜냐하면 OnTimer 메시지를 받았을 때는 이미 윈도우가 화면에 보여진 상태이며 이 상태에서 위치를 옮기면 순간적으로 번쩍거림이 발생한다. 즉 대충 보였다가 다시 저장된 위치로 이동하는 것이 사용자 눈에 보이게 되는데 결코 깔끔한 모습이라 할 수 없다. 특히 최대화 상태로 종료했을 때는 노멀 위치에 일단 나타났다가 다시 최대화되는데 보기에 굉장히 좋지 않다.

WinMain CreateWindow 리턴 직후도 마찬가지로 이미 윈도우가 화면에 보인 상태이기 때문에 LoadPosition을 부르기에는 적합한 시기가 아니다. 혹시 더 좋은 메시지가 있는지 모르겠지만 여러 모로 테스트해 본 바 WM_ACTIVATEAPP 메시지가 위치 복구에 가장 적합한 장소인 것 같다. OnActivateApp 메시지를 받았을 때는 윈도우가 아직 화면에 나타나기 전이므로 최후 실행 위치로 옮기는 과정이 보이지 않는다. 마치 원래 그 자리에 있었던 것처럼 감쪽같이 제 위치를 찾은 후 비소로 사용자 눈에 보여진다. 이런 차이가 사소한 것 같지만 최종 제품의 품질을 평가할 때는 결코 무시할 수 없는 요소이다.

이제 실행했다가 종료해보면 마지막 종료한 위치가 복구될 것이다. 뿐만 아니라 최대화한 상태에서 종료하면 다시 시작할 때도 최대화 상태로 시작하며 최대화되기 전의 노멀 위치까지 같이 저장되어 있을 것이다. (100,100)-(500,600) 위치에서 최대화한 상태로 종료했다가 다시 실행하면 최대화 상태로 실행되며 이 상태에서 노말 위치로 돌아가면 (100,100)-(500,600)으로 정확하게 다시 돌아간다.