.툴바

다음은 툴바를 추가해 보자. 메뉴와 단축키보다는 아무래도 툴바가 훨씬 쓰기 편리하다. 특히 이런 그래픽 프로그램은 마우스를 많이 사용하기 때문에 키보드보다는 마우스로 명령을 선택하는 것이 빠르고 좋다. 툴바도 차일드 윈도우이므로 윈도우 핸들을 먼저 선언한다.

 

HWND hToolBar;

 

그리고 툴바를 생성하는 함수를 작성한다. 툴바에 사용할 비트맵은 리소스에 이미 작성되어 있으므로 비트맵으로 버튼을 생성하기만 하면 된다.

 

void CreateToolBar()

{

   TBBUTTON ToolBtn[]={

      {0,IDM_FILE_NEW,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0},

      {1,IDM_FILE_OPEN,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,1},

      {2,IDM_FILE_SAVE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,2},

      {4,0,0,TBSTYLE_SEP,0,0},

      {3,IDM_EDIT_CUT,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,3},

      {4,IDM_EDIT_COPY,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,4},

      {5,IDM_EDIT_PASTE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,5},

      {4,0,0,TBSTYLE_SEP,0,0},

      {6,IDM_SHAPE_SELECT,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,6},

      {7,IDM_SHAPE_LINE,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,7},

      {8,IDM_SHAPE_ELLIPSE,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,8},

      {9,IDM_SHAPE_RECTANGLE,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,9},

      {10,IDM_SHAPE_TEXT,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,10},

      {11,IDM_SHAPE_BITMAP,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,11},

      {12,IDM_SHAPE_META,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,12},

      {4,0,0,TBSTYLE_SEP,0,0},

      {13,IDM_SHAPE_PROPERTY,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,13},

   };

   TCHAR *szToolText="새파일\0열기\0저장\0자르기\0복사\0붙이기"

      "\0선택\0\0\0사각형\0텍스트\0비트맵\0메타\0속성\0";

 

   hToolBar=CreateToolbarEx(hWndMain, WS_CHILD | WS_VISIBLE

      | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT ,

      99, 14, g_hInst, IDB_TOOLBAR, ToolBtn, 17,

      16,15,16,15,sizeof(TBBUTTON));

 

   SendMessage(hToolBar,TB_ADDSTRING,NULL,(LPARAM)szToolText);

   SendMessage(hToolBar,TB_AUTOSIZE,0,0);

}

 

버튼의 ID들은 이미 만들어져 있는 메뉴의 항목들과 대응되는 것들이므로 별도의 명령 처리 루틴을 따로 작성하지 않아도 된다. 메인 윈도우가 생성될 툴바를 같이 만든다.

 

LRESULT Main_OnCreate(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

   InitCommonControls();

   hWndMain=hWnd;

   hCanvas=CreateWindow("Canvas",NULL,WS_CHILD | WS_VISIBLE,

      0,0,0,0, hWnd,(HMENU)0,g_hInst,NULL);

   CreateToolBar();

   return 0;

}

 

Main_OnCreate에서 직접 툴바를 만들 수도 있지만 코드가 길어져 별도의 함수로 분리해 두었다. 한번만 사용하더라도 분리 가능한 작업 단위는 가급적이면 함수로 분리하는 것이 좋다. 메인 윈도우의 작업 영역 크기가 변경될 툴바와 캔버스를 재배치하여 툴바가 항상 캔버스위에 있도록 한다.

 

LRESULT Main_OnSize(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

   RECT wrt;

   int toolheight;

 

   if (wParam != SIZE_MINIMIZED) {

      SendMessage(hToolBar,TB_AUTOSIZE,0,0);

      GetWindowRect(hToolBar,&wrt);

      toolheight=wrt.bottom-wrt.top;

      MoveWindow(hCanvas,0,toolheight,LOWORD(lParam),HIWORD(lParam)-toolheight,TRUE);

   }

   return 0;

}

 

캔버스는 툴바의 높이를 제외한 나머지 부분을 모두 차지한다. 메인 윈도우가 직접 도형을 관리하지 않고 캔버스라는 차일드가 도형을 관리하기 때문에 툴바가 추가되었다고 해서 도형 편집 코드가 영향을 받아야 이유는 없다. 만약 애초에 캔버스를 분리해 놓지 않았다면 툴바에 의해 도형들이 인식하는 원점이 바뀌게 되므로 대대적인 코드의 수정이 불가피할 것이다. 버튼의 ID 메인 메뉴의 ID 일치하므로 버튼의 명령은 별도로 처리할 필요가 없다.

여기까지 작성한 테스트해 보면 툴바는 이미 동작한다. 툴바에서 직선이나 타원을 선택한 그리기를 보면 선택된 도형이 그려질 것이다. 그러나 메뉴나 단축키로 도구를 변경하면 바에는 사실이 즉시 반영되지 않는다. 툴바는 명령을 입력받는 도구이기도 하지만 현재 프로그램의 상태를 표시하는 출력도구이기도 하므로 사용자가 선택한 도구를 보여 주어야 한다.

, 툴바도 메뉴처럼 상태를 관리하여 선택된 도구도 보여주고 사용할 없는 버튼은 사용 금지시키기도 해야 한다. 그런데 바는 화면에 항상 보이는 상태이므로 메뉴가 펼쳐질 보내지는 WM_INITMENU 해당하는 메시지가 따로 없다. 그래서 툴바를 관리할 시점을 정하기 어려운데 주기적으로 프로그램의 상태를 점검하여 툴바의 상태를 관리해야 한다. 이런 목적으로 타이머를 쓰는 것은 다소 낭비가 심하기 때문에 주로 처리할 메시지가 없는 시간인 아이들 타임을 활용한다.

 

void OnIdle()

{

   static DTool OldTool=(DTool)-1;

 

   if (OldTool == NowTool) {

      return;

   }

   OldTool=NowTool;

   SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_SELECT,MAKELONG(TBSTATE_ENABLED,0));

   SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_LINE,MAKELONG(TBSTATE_ENABLED,0));

   SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_ELLIPSE,MAKELONG(TBSTATE_ENABLED,0));

   SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_RECTANGLE,MAKELONG(TBSTATE_ENABLED,0));

   SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_TEXT,MAKELONG(TBSTATE_ENABLED,0));

   SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_BITMAP,MAKELONG(TBSTATE_ENABLED,0));

   SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_META,MAKELONG(TBSTATE_ENABLED,0));

   switch (NowTool) {

   case DT_SELECT:

      SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_SELECT,

          MAKELONG(TBSTATE_CHECKED | TBSTATE_ENABLED,0));

      break;

   case DT_LINE:

      SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_LINE,

          MAKELONG(TBSTATE_CHECKED | TBSTATE_ENABLED,0));

      break;

   case DT_ELLIPSE:

      SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_ELLIPSE,

          MAKELONG(TBSTATE_CHECKED | TBSTATE_ENABLED,0));

      break;

   case DT_RECTANGLE:

      SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_RECTANGLE,

          MAKELONG(TBSTATE_CHECKED | TBSTATE_ENABLED,0));

      break;

   case DT_TEXT:

      SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_TEXT,

          MAKELONG(TBSTATE_CHECKED | TBSTATE_ENABLED,0));

      break;

   case DT_BITMAP:

      SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_BITMAP,

          MAKELONG(TBSTATE_CHECKED | TBSTATE_ENABLED,0));

      break;

   case DT_META:

      SendMessage(hToolBar,TB_SETSTATE,IDM_SHAPE_META,

          MAKELONG(TBSTATE_CHECKED | TBSTATE_ENABLED,0));

      break;

   }

}

 

모든 도구를 비선택 상태로 만든 NowTool 대응되는 버튼만 선택 상태로 만들었다. 코드는 툴이 변경될 때만 실행해야 하므로 최후로 적용된 툴을 OldTool 정적 변수에 저장해 두고 NowTool OldTool 달라질 때만 버튼 상태를 관리하도록 했다. WinMain 메시지 루프에서 아이들 타임을 찾아 적절한 시점에 함수를 호출한다.

 

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance

     ,LPSTR lpszCmdParam,int nCmdShow)

   ....

   HACCEL hAccel;

   BOOL AllowIdle=TRUE;

   hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1));

   for (;;) {

      do {

          if (!GetMessage(&Message,NULL,0,0))

             goto endloop;

          if (Message.message != 0x118/*WM_SYSTIMER*/) {

             AllowIdle=TRUE;

          }

          if (!TranslateAccelerator(hWnd,hAccel,&Message)) {

             TranslateMessage(&Message);

             DispatchMessage(&Message);

          }

      } while (PeekMessage(&Message,NULL,0,0,PM_NOREMOVE));

 

      if (AllowIdle) {

          OnIdle();

          AllowIdle=FALSE;

      }

   }

 

endloop:

   return (int)Message.wParam;

}

 

메시지 루프에서 큐의 메시지를 꺼내 처리하는 틈틈이 한번씩 OnIdle 불러 버튼의 상태를 관리할 것이다. 외에 아이들 타임에 필요한 처리가 있으면 함수에 작성하면 된다.