. 툴바 만들기

툴바는 메뉴 바로 아래쪽에 있으며 메뉴 항목 중 자주 사용되는 항목을 좀 더 빨리 선택할 수 있도록 해 준다. 메뉴는 팝업을 펼친 후 원하는 항목을 선택해야 하지만 툴바는 항상 보이기 때문에 마우스를 한번만 클릭하여 명령을 내릴 수 있다. 반드시 메뉴와 일대일로 대응되어야 하는 것은 아니며 메뉴에 없는 명령을 툴바에 넣을 수도 있다.

워낙 편리하고 보편화되어 있기 때문에 요즘 나오는 프로그램치고 툴바가 없는 프로그램은 거의 없다. Win32 이전에는 차일드 윈도우를 만들고 그 안에 버튼 컨트롤을 배치하여 툴바를 만들었지만 Win32에서는 툴바가 컨트롤로 제공되므로 아주 쉽게 만들 수 있다. 그러나 만들기만 쉽지 사용하기는 다소 까다로운 편이며 특히 API 함수를 직접 호출해서 사용하기에는 솔직히 신경써 주어야 할 부분이 너무 많다.

왜냐하면 제공하는 기능이 상당히 많으며 고급 기능들이기 때문에 프로그래밍하기도 어렵기 때문이다. 하지만 메뉴 대체용 정도의 간단한 용도로만 사용한다면 그리 어렵지도 않다. ToolBar1 예제는 툴바 컨트롤을 만드는 두가지 방법을 보여준다. 리소스에는 네개의 버튼 이미지를 가지는 비트맵이 정의되어 있다.

툴 버튼 하나당 하나씩의 비트맵을 만드는 것이 아니라 툴바 하나에 비트맵 하나를 만든다. 세로 높이는 버튼 하나의 높이로 하고 가로폭은 버튼 폭*버튼 개수로 한 후 이 비트맵에 연속적으로 버튼의 모양을 그리면 된다. ToolBar1 예제의 툴 버튼은 16*16크기를 가지며 4개의 툴 버튼이 있으므로 비트맵은 48*16크기이면 된다. 전체 소스는 다음과 같다.

 

#include <commctrl.h>

#include "resource.h"

#define ID_TOOLBAR 100

HWND hToolBar;

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

{

   TBBUTTON ToolBtn[5]={

      {0,10,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0},

      {1,11,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0},

      {5,0,0,TBSTYLE_SEP,0,0,0,0},

      {2,12,TBSTATE_ENABLED | TBSTATE_CHECKED,TBSTYLE_CHECKGROUP,0,0,0,0},

      {3,13,TBSTATE_ENABLED,TBSTYLE_CHECKGROUP,0,0,0,0}

   };

 

   switch(iMessage) {

   case WM_CREATE:

      InitCommonControls();

//* 방법 1

      hToolBar=CreateToolbarEx(hWnd, WS_CHILD | WS_VISIBLE | WS_BORDER,

          ID_TOOLBAR, 4, g_hInst, IDB_BITMAP1, ToolBtn, 5,

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

//*/

 

/* 방법 2

      hToolBar=CreateWindow(TOOLBARCLASSNAME,NULL,WS_CHILD | WS_VISIBLE | WS_BORDER,

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

      SendMessage(hToolBar,TB_BUTTONSTRUCTSIZE,(WPARAM)sizeof(TBBUTTON),0);

      TBADDBITMAP ab;

      ab.hInst=g_hInst;

      ab.nID=IDB_BITMAP1;

      SendMessage(hToolBar,TB_ADDBITMAP,4,(LPARAM)&ab);

      SendMessage(hToolBar,TB_ADDBUTTONS,(WPARAM)5,(LPARAM)ToolBtn);

//*/

      return 0;

   case WM_COMMAND:

      switch (LOWORD(wParam)) {

      case 10:

          MessageBox(hWnd,"첫번째 버튼을 선택했습니다","알림",MB_OK);

          break;

      case 11:

          MessageBox(hWnd,"두번째 버튼을 선택했습니다","알림",MB_OK);

          break;

      }

      return 0;

   case WM_SIZE:

      SendMessage(hToolBar,TB_AUTOSIZE,0,0);

      return 0;

   case WM_DESTROY:

      PostQuitMessage(0);

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

실행시켜 보자. 작업영역의 상단에 툴바가 나타나며 툴 버튼을 누르면 메시지 박스를 보여 줄 것이다.

툴바는 두가지 방법으로 만들 수 있는데 ToolBar1 예제는 두가지 방법에 대한 코드가 모두 작성되어 있으며 방법2는 주석으로 되어 있다. 두 코드 모두 동일한 툴바를 만들어내는데 예제를 열어 놓고 주석을 풀어가며 두 방법을 비교해 보기 바란다.

TBBUTTON

툴 버튼 하나의 정보는 TBBUTTON 구조체로 지정하며 이 구조체가 배열을 이루어 툴바 전체의 버튼들을 정의한다. 툴바와 툴 버튼은 다른 것이므로 용어에 주의하기 바란다. 툴바는 컨트롤 그 자체이고 툴 버튼은 툴바에 포함되는 그래픽 컨트롤이다. TBBUTTON 구조체는 commctrl.h에 다음과 같이 선언되어 있다.

 

typedef struct _TBBUTTON {

    int       iBitmap;

    int       idCommand;

    BYTE      fsState;

    BYTE      fsStyle;

#ifdef _WIN64

    BYTE      bReserved[6]     // padding for alignment

#elif defined(_WIN32)

    BYTE      bReserved[2]     // padding for alignment

#endif

    DWORD_PTR dwData;

    INT_PTR   iString;

} TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;

 

iBitmap 멤버는 툴 버튼 비트맵 중에 몇 번째인가를 나타내는 인덱스이다. 앞에서 보았다시피 버튼 하나당 비트맵이 하나씩 제공되는 것이 아니라 하나의 비트맵에 모든 버튼의 이미지가 제공되므로 그 중 몇 번째 이미지를 사용할 것인가를 지정해 주어야 한다. 비트맵의 첫번째 이미지가 0번이며 두번째는 1번 등으로 번호가 증가한다.

idCommand는 사용자가 이 버튼을 눌렀을 때 WM_COMMAND로 전달될 버튼의 ID이다. 버튼이 눌러지면 툴바의 부모 윈도우로 WM_COMMAND 메시지가 전달되는데 이 때 LOWORD(wParam)으로 idCommand가 전달된다. 툴 버튼은 보통 메뉴 명령을 빠르게 선택하기 위해 사용되는데 이럴 경우 idCommand에 대응되는 메뉴 명령의 ID를 지정해야 한다. 이 예제는 메뉴가 없으므로 10번부터 계속 증가하는 ID를 주었다.

fsState멤버는 버튼의 상태를 나타내며 다음 값 중 하나, 또는 여러 개의 조합을 지정한다. TBBUTTON 구조체의 이 멤버는 버튼의 초기 상태를 지정하며 이 상태는 실행중에 언제든지 변경할 수 있다.

 

상태

TBSTATE_CHECKED

체크된 상태. 즉 버튼이 눌러져 있는 상태

TBSTATE_ENABLED

사용 가능한 상태

TBSTATE_HIDDEN

숨겨진 버튼

TBSTATE_INDETERMINATE

사용 불가능한 상태

TBSTATE_PRESSED

눌러져 있는 상태

TBSTATE_WRAP

이 버튼 이후는 다음 줄로 개행된다. 이 상태를 사용하면 여러 줄로 툴 버튼을 배치할 수 있다. 단 툴바가 여러줄 버튼을 지원하는 스타일을 가져야 한다.

TBSTATE_ELLIPSES

툴 버튼의 텍스트 일부가 잘려져 보이지 않으며 생략 표시(...)이 출력되어 있는 상태

TBSTATE_MARKED

버튼이 마크되어 있는 상태이다. 마크되어 있는 상태란 응용 프로그램에서 정의하기 나름이다.

 

예제에서는 세번째 버튼에 TBSTATE_CHECKED 플래그를 주어 처음부터 선택된 상태로 툴 버튼을 만들도록 하였다. 나머지 버튼들은 TBSTATE_ENABLED 플래그만 주어 사용 가능하도록만 해 주었다. fsStyle 멤버는 툴 버튼의 스타일을 지정하며 버튼의 모양이나 기능을 지정한다. 다음 플래그 중 하나 또는 조합을 지정한다.

 

스타일

설명

TBSTYLE_BUTTON

일반적인 푸쉬 버튼 모양의 툴바를 만든다. 누르면 쑥 들어가고 놓으면 다시 튀어나오는 표준 버튼과 같다.

TBSTYLE_CHECK

버튼을 누를 때마다 눌러진 상태와 튀어나온 상태를 토글하는 형식의 버튼이다.

TBSTYLE_GROUP

버튼끼리 그룹을 이룬다. 그룹 내의 한 버튼이 눌러져 있으면 나머지 버튼은 튀어나온다.

TBSTYLE_SEP

실제 버튼이 아니라 버튼간을 구분하기 위한 여백(Separator)을 만든다.

TBSTYLE_CHECKGROUP

TBSTYLE_CHECK | TBSTYLE_GROUP

TBSTYLE_AUTOSIZE

툴 바의 표준폭을 사용하지 않고 자신의 폭을 사용한다.

TBSTYLE_DROPDOWN

명령 메시지 대신 TBN_DROPDOWN 통지 메시지가 전달된다.

TBSTYLE_NOPREFIX

&문자를 단축키로 해석하지 않고 문자 그대로 출력한다.

BTNS_SHOWTEXT

리스트 스타일의 툴에 확장 스타일이 적용되었을 때 텍스트를 보여준다.

BTNS_WHOLEDROPDOWN

버튼 이미지 밑에 아래쪽 화살표를 보여준다.

 

툴 버튼의 스타일은 TBSTYLE_로 시작되는데 이 스타일 상수값이 툴바의 스타일과 혼동될 여지가 있어 공통 컨트롤 5.80이후에는 툴 버튼의 스타일이 일괄적으로 BTNS_로 시작되도록 변경되었다. 즉 TBSTYLE_SEP 대신 BTNS_SEP을 사용하는 것이 좋다. 하지만 이렇게 되면 공통 컨트롤 4.72이전의 버전과는 호환성을 읽게 되므로 기존 스타일값은 계속 사용하는 것이 좋을 것 같다.

예제에서는 두 개의 푸쉬 버튼과 두 개의 체크 그룹 버튼을 만들었다. 툴 버튼의 스타일에 대해서는 일단 도표로 정리하고 잠시 후에 개별 스타일들에 대해 다시 자세히 알아볼 것이다.

다섯 번째 dwData 멤버는 프로그램이 정의해서 사용할 수 있는 사용자 정의 데이터이며 iString은 버튼 문자열의 인덱스이다. 툴바는 툴 버튼에 출력할 문자열을 배열로 정의하는데 iString은 이 배열에서 몇번째 문자열을 사용할 것인가를 지정한다. 문자열없이 이미지만 출력할 때는 이 멤버에 0을 대입해 준다.

WndProc의 선두에서는 툴 버튼에 대한 정보를 TBBUTTON 구조체 배열인 ToolBtn에 작성하고 있다. 이 배열의 초기값을 유심히 살펴보면 툴 버튼들이 어떤 스타일과 명령 ID를 가지는지 알 수 있다. 이 예제는 짧고 간단하기 때문에 ToolBtn 배열을 WndProc에 선언했지만 매번 구조체가 초기화되므로 실전에서는 전역으로 선언하거나 static으로 선언하는 것이 좋다.

CreateToolbarEx

툴바도 윈도우이므로 CreateWindow 함수로 만들 수 있지만 그보다는 툴바를 만드는 다음 함수를 사용하는 것이 좋다. 인수가 아주 징그럽게 많은데 주석으로 짧게 설명을 하고 필요한 인수에 대해서만 보충 설명을 하도록 하겠다.

 

HWND CreateToolbarEx(

HWND hwnd,                         // 툴바의 부모 윈도우

DWORD ws,                              // 툴바 윈도우의 스타일. WS_CHILD 반드시 있어야 한다.

UINT wID,                             // 툴바의 ID

int nBitmaps,                      // 버튼 비트맵의 개수

HINSTANCE hBMInst,                    // 리소스를 가진 인스턴스 핸들

UINT wBMID,                        // 툴바 비트맵 핸들

LPCTBBUTTON lpButtons,                 // 버튼들에 대한 정보를 가진 TBBUTTON 구조체 배열

int iNumButtons,                      // 버튼의 개수

int dxButton, int dyButton,        // 버튼의 크기

int dxBitmap, int dyBitmap,        // 비트맵의 크기

UINT uStructSize                      // sizeof(TBBUTTON)

);

 

두 번째 인수로 툴바 자체의 스타일을 지정하는데 WS_CHILD나 WS_VISIBLE은 선택의 여지없이 주어야 한다. 이외에 툴바에 줄 수 있는 고유의 스타일들이 있는데 이 스타일에 따라 툴바의 모양이나 동작에 많은 차이가 발생하게 된다. 툴바의 스타일은 잠시 후 따로 정리하기로 하되 예제에서는 특별한 스타일을 사용하지 않았다.

개별 툴 버튼의 정보는 7번째 인수로 전달되는 TBBUTTON 구조체 배열로 지정한다. 툴바를 만들기 전에 먼저 이 구조체에 툴 버튼에 대한 정보부터 대입해 주어야 한다. 툴바는 이 구조체 정보를 참조하여 개별 툴 버튼을 만들 것이다. 나머지 인수들에 대해서는 쉽게 이해가 가겠지만 두 가지 아리송한 점이 있다.

우선 툴 버튼의 개수(iNumButtons)와 툴 버튼 비트맵의 개수(nBitmaps)를 지정하는 인수가 따로 있는데 이 두 값은 당연히 같아야 할 걸로 생각되지만 실제로는 그렇지 않을 수도 있다. 왜냐하면 버튼간의 간격을 띄어주는 구분 여백(Separator)도 하나의 버튼으로 취급되는데 여백은 비트맵을 필요로 하지 않기 때문이다. 다음과 같은 경우 버튼은 12개이지만 비트맵은 10개만 있으면 된다. 필요한 비트맵=버튼 개수-구분여백 개수 라고 생각하면 되겠다.

두 번째로 버튼의 크기와 비트맵의 크기를 지정하는 인수가 역시 각각 따로 있는데 이 값도 당연히 같아야 할 것 같지만 그렇지 않을 수도 있다. 비트맵의 크기보다 버튼의 크기가 크면 나머지 부분은 여백으로 남겨지게 된다. 단, 버튼과 비트맵의 폭은 같아야 한다.

TBBUTTON 구조체와 CreateToolbarEx 함수의 사용예가 예제의 WM_CREATE에 잘 나와 있으므로 참고하기 바란다. 예제 코드에서는 비트맵 4개를 사용하여 16*16크기의 툴 버튼 5개를 가지는 툴 바를 만들었다.

CreateWindow

툴바 컨트롤도 윈도우이므로 CreateWindow(Ex) 함수로 만들 수 있다. 단, 이 함수는 TBBUTTON 구조체나 버튼의 크기 등을 전달받는 인수가 없으므로 툴 버튼을 가지지 않는 빈 툴바만 만들어 준다. 일단 툴바를 만든 후 다음 세 단계를 거쳐 툴바에게 필요한 정보를 제공해 주어야 한다.

 

TB_BUTTONSTRUCTSIZE 메시지를 보내 주어 TBBUTTON 구조체의 크기를 wParam으로 전달해 주어야 한다. 이 크기는 공통 컨트롤의 버전 확인에 사용된다.

TB_ADDBITMAP 메시지를 보내 주어 툴 버튼 표면에 출력할 비트맵 정보를 제공한다. 이 메시지의 wParam으로 비트맵의 개수를, lParam으로는 다음 구조체의 포인터를 전달한다.

 

typedef struct {

    HINSTANCE hInst;

    UINT_PTR nID;

} TBADDBITMAP, *LPTBADDBITMAP;

 

비트맵 리소스를 가진 인스턴스의 핸들과 비트맵 리소스의 ID를 멤버로 가지고 있다. 인스턴스 핸들과 비트맵 리소스 ID를 줄 수도 있고 hInst에 NULL을 주고 nID에 비트맵 핸들을 줄 수도 있다.

TB_ADDBUTTONS 메시지로 버튼을 추가한다. wParam으로 버튼의 개수를 전달하며 lParam으로 버튼에 대한 정보를 가지는 TBBUTTON 구조체 배열을 전달한다.

 

결국 CreateWindow로 툴바를 만들 때도 CreateToolBarEx함수로 전달되는 모든 정보를 제공해 주는 셈이다. 특별한 이유가 없다면 툴바는 가급적이면 CreateToolBarEx 함수로 만드는 것이 훨씬 편리하다. 하지만 CreateWindow 함수를 사용할 경우 비트맵 대신 이미지 리스트를 사용할 수 있다는 큰 장점이 있으므로 이 방법도 알아 두어야 한다.

툴 바의 메시지

툴 바도 컨트롤의 일종이므로 부모 윈도우가 툴바에게 명령을 내릴 때는 예외없이 메시지를 사용한다. 툴바의 메시지는 모두 TB_로 시작되며 앞에서 이미 TB_ADDBITMAP, TB_ADDBUTTONS 메시지를 사용해 보았고 앞으로도 계속 더 많은 메시지를 사용하게 될 것이다. 이쯤 되면 여러분들은 메시지를 제대로 사용하기 위해서는 wParam, lParam과 이 인수들로 전달되는 구조체를 따로 공부해야 한다는 것 정도는 잘 알고 있을 것이다.

공통 컨트롤 5.81버전을 기준으로 윈도우 표준 메시지는 빼고 툴바 자신만의 고유 메시지만 84개에 달한다. 버튼이나 에디트 등에 비한다면 메시지 종류가 무척 많은데 그만큼 제공하는 기능이 많다는 뜻이다. 이 많은 메시지들을 여기서 다 설명할 수는 없으므로 필요할 때마다 레퍼런스를 참고하기 바란다. 앞으로 실습에서 필요할 때 몇가지 메시지는 자세하게 설명하겠지만 모든 메시지를 다 정리하기는 솔직히 어렵고 그럴 필요도 없다.