. 차일드 배치

OnCreate에서 차일드들을 모두 생성하기는 했으나 모든 차일드들이 항상 화면에 나타나는 것은 아니다. 어떤 사람은 툴바나 상태란 대신 좀 더 넓은 편집 영역을 원할 수도 있고 검색결과창의 높이나 파일 창의 폭도 개인적인 기호에 따라 조정할 수 있다. 그래서 차일드의 배치상태는 사용자가 마음대로 선택할 수 있는 설정상태여야 한다.

앞장에서 만들었던 bShowOutput OutputHeight도 이런 배치 정보의 하나라고 할 수 있다. 이 두 변수의 값에 따라 검색결과창이 보일 것인가 아닌가, 보인다면 높이는 얼마로 할 것인가가 결정된다. SOption 구조체에는 나머지 차일드에 대한 배치 정보들이 이미 포함되어 있고 SOption::Init에서 적절한 값으로 초기화하였다.

 

struct SOption

{

     ....

     BOOL bShowToolBar;

     BOOL bShowStatus;

     BOOL bShowFileWnd;

     int FileWndWidth;

     BOOL bShowFileTab;

 

void SOption::Init()

{

     ....

     bInitNextFind=FALSE;

     bShowToolBar=TRUE;

     bShowStatus=TRUE;

     bShowFileWnd=FALSE;

     FileWndWidth=200;

     bShowFileTab=FALSE;

 

bShow로 시작되는 변수들은 해당 차일드가 보이는 상태인가 아닌가를 지정하며 FileWndWidth는 파일창의 폭을 픽셀 단위로 지정한다. 각 차일드의 보기 상태는 보기 메뉴에서 확인 및 변경할 수 있다. OnCommand에 다음 코드를 작성하도록 하자.

 

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

{

     ....

     case IDM_VIEW_TOOLBAR:

          Option.bShowToolBar = !Option.bShowToolBar;

          Relayout();

          break;

     case IDM_VIEW_STATUS:

          Option.bShowStatus = !Option.bShowStatus;

          Relayout();

          break;

     case IDM_VIEW_FILE:

          Option.bShowFileWnd = !Option.bShowFileWnd;

          Relayout();

          break;

     case IDM_VIEW_FILETAB:

          Option.bShowFileTab = !Option.bShowFileTab;

          Relayout();

          break;

 

보기/툴바 메뉴항목을 선택할 경우 bShowToolbar 옵션 변수의 값을 반대로 바꿈으로써 툴바의 보기 상태를 토글시킨다. 즉 툴바가 보이는 상태이면 숨기고 숨겨져 있는 상태이면 다시 보이도록 만든다. 옵션 변수값을 변경한 후 Relayout 함수를 호출하여 차일드들을 전부 다시 배치하도록 하였다. 나머지 메뉴항목들의 처리도 모두 동일하다. OnInitMenu 함수에서는 메뉴에 이 변수들의 상태를 보여준다.

 

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

{

     ....

     hActive=(HWND)SendMessage(g_hMDIClient,WM_MDIGETACTIVE,0,NULL);

     if (hActive) {

          ....

     }

 

     if (Option.bShowOutput) {

          CheckMenuItem(hMenu, IDM_VIEW_OUTPUT, MF_BYCOMMAND | MF_CHECKED);

     } else {

          CheckMenuItem(hMenu, IDM_VIEW_OUTPUT, MF_BYCOMMAND | MF_UNCHECKED);

     }

 

    if (Option.bShowToolBar) {

        CheckMenuItem(hMenu, IDM_VIEW_TOOLBAR, MF_BYCOMMAND | MF_CHECKED);

    } else {

        CheckMenuItem(hMenu, IDM_VIEW_TOOLBAR, MF_BYCOMMAND | MF_UNCHECKED);

    }

 

    if (Option.bShowStatus) {

        CheckMenuItem(hMenu, IDM_VIEW_STATUS, MF_BYCOMMAND | MF_CHECKED);

    } else {

        CheckMenuItem(hMenu, IDM_VIEW_STATUS, MF_BYCOMMAND | MF_UNCHECKED);

    }

 

    if (Option.bShowFileWnd) {

        CheckMenuItem(hMenu, IDM_VIEW_FILE, MF_BYCOMMAND | MF_CHECKED);

    } else {

        CheckMenuItem(hMenu, IDM_VIEW_FILE, MF_BYCOMMAND | MF_UNCHECKED);

    }

 

    if (Option.bShowFileTab) {

        CheckMenuItem(hMenu, IDM_VIEW_FILETAB, MF_BYCOMMAND | MF_CHECKED);

    } else {

        CheckMenuItem(hMenu, IDM_VIEW_FILETAB, MF_BYCOMMAND | MF_UNCHECKED);

    }

}

 

차일드의 보기 상태 메뉴는 활성창이 없어도 사용할 수 있으므로 if (hActive) 블록 바깥에 코드를 작성해야 한다. 보기 메뉴에는 지금 어떤 차일드가 보이는 상태인지 체크 표시가 나타날 것이다. 차일드가 여러 개로 늘어났으므로 OnSize도 수정되어야 한다.

 

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

{

     if (Option.bShowOutput==FALSE && Option.bShowToolBar==FALSE

          && Option.bShowStatus==FALSE && Option.bShowFileWnd==FALSE

          && Option.bShowFileTab==FALSE) {

          return FALSE;

     } else {

          Relayout();

          return TRUE;

     }

}

 

차일드 중 하나라도 보이는 상태이면 Relayout을 호출하여 보이는 모든 차일드를 재배치하도록 했다. 하나도 안보이는 상태이면 DefFrameProc이 알아서 처리하도록 내버려 두는데 이 경우 MDI 클라이언트가 작업영역 전체를 가득 채우게 된다.

차일드를 실제로 재배치하는 작업은 Relayout 함수가 하게 된다. 이전 프로젝트까지는 이 함수에서 검색결과창만 배치했으므로 간단했지만 이제는 MDI 클라이언트까지 여섯 개의 차일드를 동시에 배치해야 하므로 무척 복잡해진다. 뿐만 아니라 차일드들의 보임/숨김 조합이 무려 32가지나 되고 검색결과창과 파일창은 고유의 높이와 폭까지 고려해야 한다. 그래서 Relayout을 보조하는 도우미 함수 하나를 만들도록 하자.

 

#define GAP 3

....

void GetChildSize(int &t,int &s,int &f,int &o,int &w)

{

     RECT trt;

     RECT crt;

     int c;

     t=0;

     s=0;

     f=0;

     o=0;

     w=0;

 

     if (Option.bShowToolBar) {

          GetWindowRect(hToolBar,&trt);

          t=trt.bottom-trt.top;

     }

 

     if (Option.bShowStatus) {

          GetWindowRect(hStatus,&trt);

          s=trt.bottom-trt.top;

     }

 

     if (Option.bShowFileTab) {

          GetWindowRect(hTabFrame,&trt);

          f=trt.bottom-trt.top;

     }

 

     if (Option.bShowOutput) {

          o=Option.OutputHeight;

     }

 

     if (Option.bShowFileWnd) {

          w=Option.FileWndWidth;

     }

 

     GetClientRect(g_hFrameWnd,&crt);

     c=crt.bottom-(t+s+f+o+(o ? GAP:0));

     if (c < 0 && o) {

          o=crt.bottom-(t+s+f+GAP);

     }

}

 

GAP 매크로 상수는 차일드간의 간격값이며 인접한 차일드를 적당한 간격으로 띄우기 위해 필요하다. 차일드끼리 너무 붙어 있으면 갑갑해보이므로 약간의 간격을 띄우는 것이 좋다. 또한 이 여백은 드래그에 의해 두 차일드의 크기를 조정하는 용도로도 사용할 수 있다. 기본값은 가장 무난하다고 생각되는 3픽셀이되 2 4로 바꿀 수도 있다. 이 간격은 매크로 상수로 하드 코딩되어 있는데 사용자들이 직접 선택하도록 하고 싶다면 설정상태에 포함시키면 된다.

GetChildSize 함수는 차일드의 보임/숨김 상태를 고려하여 폭과 높이를 구한다. 동시에 다섯 개의 차일드 크기를 구해야 하기 때문에 참조 인수 다섯 개를 전달받아 이 변수에 결과값을 대입하였다. t,s,o는 툴바, 상태란, 검색결과창의 높이이며 f, w는 파일탭, 파일창의 폭이다. 만약 MDI 클라이언트의 높이가 0보다 작으면 검색결과창의 높이를 강제로 줄이도록 했다. 그렇지 않으면 툴바와 검색결과창이 겹치게 되어 보기에 좋지 않다. 차일드간의 겹침은 운영체제가 자동으로 처리하지 않으므로 응용 프로그램이 알아서 겹치지 않도록 해야 한다.

GetChildSize 함수는 주로 Relayout 함수에서 호출하지만 차일드의 크기를 필요로 하는 함수들에서 공통적으로 사용된다. 이 함수가 차일드의 크기를 어떻게 구하는가에 따라 차일드의 배치상태나 동작 상태가 영향을 받게 된다. 실제로 차일드를 배치하는 Relayout 함수는 다음과 같이 수정된다.

 

void Relayout()

{

     RECT crt;

     int t,s,f,o,w;

 

     GetClientRect(g_hFrameWnd,&crt);

     GetChildSize(t,s,f,o,w);

 

     if (Option.bShowToolBar) {

          SendMessage(hToolBar,TB_AUTOSIZE,0,0);

          ShowWindow(hToolBar,SW_SHOW);

     } else {

          ShowWindow(hToolBar,SW_HIDE);

     }

 

     if (Option.bShowStatus) {

          SendMessage(hStatus,WM_SIZE,0,0);

          ShowWindow(hStatus,SW_SHOW);

     } else {

          ShowWindow(hStatus,SW_HIDE);

     }

 

     if (Option.bShowFileTab) {

          MoveWindow(hTabFrame,0,t,crt.right,f,TRUE);

          ShowWindow(hTabFrame,SW_SHOW);

     } else {

          ShowWindow(hTabFrame,SW_HIDE);

     }

 

     if (Option.bShowOutput) {

          MoveWindow(hOutput,0,crt.bottom-o-s,crt.right,o,TRUE);

          ShowWindow(hOutput,SW_SHOW);

     } else {

          ShowWindow(hOutput,SW_HIDE);

     }

 

     if (Option.bShowFileWnd) {

          MoveWindow(hFileWnd,0,t+f,w,crt.bottom-(o+t+s+f+GAP),TRUE);

          ShowWindow(hFileWnd,SW_SHOW);

     } else {

          ShowWindow(hFileWnd,SW_HIDE);

     }

 

     MoveWindow(g_hMDIClient,w+(w ? GAP:0),t+f,crt.right-w-(w ? GAP:0),

          crt.bottom-(t+s+f+o+(o ? GAP:0)),TRUE);

 

     HWND hChild;

     hChild=GetWindow(g_hMDIClient,GW_CHILD);

     if (hChild && !IsZoomed(hChild)) {

          while (hChild) {

              RedrawWindow(hChild,NULL,NULL,RDW_FRAME | RDW_UPDATENOW | RDW_INVALIDATE);

              hChild=GetWindow(hChild,GW_HWNDNEXT);

          }

     }

}

 

GetChildSize 함수로 각 차일드의 폭과 높이를 먼저 구했다. 그리고 bShow* 옵션값의 상태에 따라 보이는 차일드의 좌표를 계산하여 배치한다. 이때 차일드가 다른 차일드의 배치상태에 영향을 받을 수 있는데 예를 들어 파일탭은 툴바 아래에 배치되므로 툴바가 보이는 상태인가 아닌가에 따라 좌표가 달라진다. 검색결과창도 상태란의 보임 상태에 영향을 받으며 파일창은 나머지 모든 창의 배치상태에 따라 자신의 좌표가 달라진다.

Relayout은 이런 복잡한 조건들을 점검하여 적절하게 차일드를 배치하고 있다. MDI의 문서창은 최대화되어 있지 않을 때 다른 차일드에 의해 비작업영역이 지워질 수 있는데 이것은 일종의 버그인 것으로 생각된다. 그래서 문서창이 최대화되어 있지 않을 때는 모든 차일드를 강제로 다시 그리도록 하였다. 여기까지 코드를 작성한 후 실행해보자. 다음은 모든 차일드를 다 보이는 상태로 캡처한 것이다.

정확한 위치에 차일드들을 생성하기는 했지만 아직 기능 구현이 되지 않았기 때문에 제대로 모양이 나오지는 않는다.