9.툴바와 상태바

본장에서는 툴바와 상태바 그리고 풍선도움말의 기능에 대해서 설명을 합니다. 툴바란 보통 윈도우 메뉴하단부에 붙어 있는 작은 비트맵 버튼의 모음으로 특정 기능을 수행하고자 할 때 편리하게 버튼 클릭으로 설정할수 있도록 하는 윈도우입니다. 상태바는 현재의 진행상태 및 메뉴에 대한 설명을 하는 기능을 가진 윈도우입니다. 마우스가 툴바 위에 있을 경우 작은 네모 박스가 나타나면서 해당 도구바의 메뉴 내용을 표시하는 풍선도움말은 도구버튼의 기능을 한눈에 알수 있도록 합니다. 이 부분또한 컨트롤에 해당됩니다. 본장에서 제작하는 모든 기능또한 8장과 같이 commctrl.h 헤더를 포함시키고 comctl32.lib를 프로젝트에 포함시켜야 합니다.

1.툴바

툴바 만들기

툴바를 만들고자 할경우에는 CreateToolbarEx 함수를 사용합니다. 이함수는 CreateWindowEx함수를 확장한 것으로 툴바를 만들 때 필요한 복잡한 옵션들을 내장하여 CreateToolbarEx로 만든것입니다. 이함수의 형은 다음과 같습니다.

HWND CreateToolbarEx(

HWND hwnd, //툴바 윈도우 핸들러

DWORD ws, //윈도우 스타일

UINT wID, //툴바 윈도우 아이디

int nBitmaps, //툴바에 포함하는 비트맵 이미지 수

HINSTANCE hBMInst, //프로그램 인스턴스 핸들

UINT wBMID, //비트맵 ID

LPCTBBUTTON lpButtons,//버튼 정보 구조체

int iNumButtons, //버튼의 수

int dxButton, //버튼의 가로 크기

int dyButton, //버튼의 세로 크기

int dxBitmap, //버튼에 설정할 이미지의 가로크기

int dyBitmap, //버튼에 설정할 이미지의 세로크기

UINT uStructSize //lpButtons의 구조체 크기

);

윈도우 스타일 인자인 ws에는 기본적으로 설정해야할 플러그가 WS_VISIBLE | WS_CHILD | WS_BORDER 일것입니다. 이의미는 화면에 보이면서 차일드 윈도우이며 외곽선을 가진다는 의미입니다. 이의미와 함께 툴바의 성격을 표현하는 새로운 스타일을 추가하여야 합니다.툴바가 부모 윈도우에 출력될 경우 비클라이언트 영역처럼 화면에 출력되어야 합니다. 보통 윈도우를 만들고자 할 경우 클라이언트 영역은 흰색바탕으로 출력이 되며 비클라이언트 영역은 진회색을 가진 틀형태로 포함됩니다. 쉽게 보면 메뉴와 외곽 경계선과 같이 붙어 있다는 느낌을 주어야 합니다. 이런 형태로 즉 윈도우 프레임에 붙어 있는 느낌을 주도록 하는 스타일이 WS_CLIPSIBLINGS입니다. 이스타일을 설정하면 윈도우 자체가 프레임에 붙어 있는 느낌을 줍니다. 또한 클라이언트 영역이 바로 차일드 윈도우 하단부터 시작되는 느낌을 줍니다. 예를 들어서 툴바를 메뉴 바로 다음에 붙였다면 이툴바 바로 밑에가 클라이언트 영역의 시작점 (MM_TEXT모드에서 (0,0)) 일것으로 생각이 됩니다. 그러나 실지 클라이언트 영역은 메뉴 바로 밑에 즉 툴바가 붙어 있는 영역이 됩니다. 이런 문제점 때문에 SetWindowOrgEx 와 SetViewportOrgEx를 이용하여 윈도우 영역의 초기점을 갱신할 필요성이 있습니다. 세로 크기가 16인 툴바를 붙였다고 가정을 하고 "this is test"라는 문자열을 (0,0)에 출력해보면 이문자는 툴바에 가려서 화면에 나타나지 않습니다. 그이유는 0,0 좌표위에는 툴바가 위치하고 있기 때문입니다.

지금까지의 결론으로 메뉴 바로 밑에 붙어 있는 툴바를 만들기 위한 기본 옵션은

WS_CHILD|WS_VISIBLE | WS_CLIPSIBLINGS |WS_BORDER|

이것이 됩니다. 이기본 스타일과 함께 또 추가 되어야 할스타일이 CCS_ 계열의 스타일입니다. 이 스타일은 툴바를 윈도우 어느 위치에 붙일것인가를 설정해주는 스타일입니다. 상단에 붙일경우에는 CCS_TOP이며 하단부는 CCS_BOTTOM입니다. 일반적인 툴바 형태로 툴바를 윈도우 상단부에 붙이고자 한다면 위의 옵션에 또하나의 옵션을 추가합니다.

WS_CHILD|WS_VISIBLE | WS_CLIPSIBLINGS |WS_BORDER|

CCS_TOP |

툴바에 마우스가 움직이거나 특정 버튼위에 정지해 있을 경우 일반 어플리케이션 프로그램에서는 마우스 커서 하단부에 작은 박스가 나타나고 이 버튼의 항목을 표현하는 글자가 출력되는 것을 보았을것입니다. 이것을 풍선 도움말이라고 하는데 이 풍선 도움말이 툴바위에서 가능하게 하고자 한다면 스타일에 TBSTYLE_TOOLTIPS 이라는 툴바스타일을 설정해야 합니다. 이스타일을 설정하면  마우스가 툴바위에 있게되면 메인윈도우에 WM_NOTIFY 가 발생되면서 lParam은 NMHDR 값의 포인터로 넘어오게 됩니다. NMHDR의 맴버인 code에 TTN_NEEDTEXT 값이 설정되면 툴바특정 버튼위에 있기 때문에 풍선 도움말에 출력할 문자가 필요하다는 의미입니다. 이때 필요한 문자열을 넘겨줌으로써 풍선도움말을 가능하게 할수 있는 것입니다.

툴바 윈도우 ID란 보통 차일드 메뉴를 만들 경우 HMENU에 설정한 차일드 윈도우 아이디와 같습니다. wBMID 는 툴바에 설정할 비트맵 아이디입니다. 툴바에 설정할 비트맵은 작은 그림들을 연속적으로 저장한 한 개의 비트맵을 사용합니다.

그림 1은 툴바에 사용되는 간단한 비트맵 그림입니다.

(그림 1) 툴바에 사용될 비트맵 버튼

만일 툴바의 크기를 가로 16 세로 15로 하고자 한다면 각비트맵은 가로 16단위로 그려져야 합니다. 이비트맵 아이디와 함께 dxBitmap 과 dyBitmap에 비트맵 이미지에서 비트맵을 분리하여 로드할수 있도록 16과 15의 값을 설정해 줍니다. dxButton과 dyButton은 비트맵을 포함하고 있는 버튼의 크기입니다. 이버튼의 크기가 너무 크게 되면 비트맵과 조화있게 설정되지 않기 때문에 dxBitmap와 dyBitmap에 조화있게 구성할수 있도록 값을 설정해줍니다. 보통 가로크기는 dxBitmap 와 같고 세로크기는 dyBitmap보다 1~2정도로 크게 하여주는 것이 화면에 보기가 좋았습니다. lpButtons은 각버튼의 속성을 이야기 합니다. 예를 들어서 첫 번째 버튼은 들어간 형태이고 두 번째 버튼은 일반버튼처럼 나와있으며 세 번째 버튼은 버튼이 아닌 공백으로 약간 띄어 놓으라는 등등의 정보가 툴바에는 필요합니다. 툴바에는 버튼 한 개당 이런 정보가 1개씩 필요합니다. 이런 정보를 설정하는 구조체가 TBBUTTON입니다.

typedef struct _TBBUTTON { \\ tbb

int iBitmap; //비트맵 번호

//WM_COMMAND의 LOWORD(wParam) 에 설정될 이벤트 메시지

int idCommand;

BYTE fsState; //버튼의 활성 비활성 상태

BYTE fsStyle; //버튼의 스타일

DWORD dwData; //어플리케이션에서 설정한 값

int iString; //버튼의 문자열 스트링

} TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;

typedef const TBBUTTON FAR* LPCTBBUTTON;

TBBUTTON구조체에서 iBitmap는 비트맵 번호를 의미합니다. CreateToolbarEx 의 dxBitmap 과 dyBitmap에 의해서 이번호에 해당하는 이미지를 복사하여 툴바에 출력합니다. 예를 들어서 dxBitmap가 16이고 dyBitmap가 15이라고 가정하고 iBitmap가 1이라고 한다면 가로 16위치부터 32 세로는 0부터 15까지의 영역을 복사하여 버튼으로 설정합니다.

idCommand는  WM_COMMAND와 함께 설정될 이벤트 메시지입니다. 메뉴에서 각 메뉴의 ID를 설정하면 WM_COMMAND에 이 ID가 LOWORD(wParam) 에 설정된다고 설명하였습니다. 툴바도 idCommand에 설정된 ID가 메뉴같은 형태로 메시지에 전송됩니다. 보통 툴바는 메뉴에 있는 항목을 대신하는 경우가 많기 때문에 특정 메뉴 항목에 해당하는 툴바버튼은 메뉴와 같은 ID를 idCommand에 설정해주는 것이 좋습니다. fsState는 버튼의 상태를 설정하는 플러그입니다. TBSTATE_CHECKED 로 설정하면 체크된 상태로 버튼이 약간 들어간 형태로 도시되며 TBSTATE_ENABLED 는 툴바의 버튼 상태가 활성화 되어 있는 상태를 의미하고 TBSTATE_HIDDEN□은 현재 툴바는 화면에 보이지 않는 상태이며 TBSTATE_INDETERMINATE 는 회색형태로 툴바가 보이게 됩니다.

버튼이 눌려진 형태를 설정하고자 한다면 TBSTATE_PRESSED 로 설정하며

TBSTATE_WRAP 은 툴바가 세로 형태로 출력이 됩니다. 만일 좌측이나 우측으로 툴바를 설정하고자 할경우에는 이방법을 사용하시면 됩니다. fsStyle 는 버튼의 스타일로써 다음과 같은 값을 가질수 있습니다.

TBSTYLE_BUTTON□: 버튼 형태

TBSTYLE_CHECK : 버튼을 누르면 들어가 있느 형태로 다시 누르면

나온 형태로

TBSTYLE_SEP : 버튼이 아니라 공백인 형태 버튼과 버튼사이을 띄우고자

할 경우 사용

dwData 와 iString는 사용자가 툴바에 필요한 특정 정보를 넣고자 할 경우 사용되는 값들입니다. iString에는 보통 풍선 도움말의 정보를 dwData는 그외 사용자가 필요한 데이터를 넣을수 있습니다.

만일 툴바에 버튼을 2개를 만들고자 한다면 TBBUTTON 의 구조체의 변수 배열을 2개를 만들어야 하며 이2개의 배열의 포인터를 CreateToolbarEx 의 lpButtons에 설정해주어야 합니다.

이방법은 우선 TBBUTTON의 배열을 다음과 같이 두 개를 만들어 값을 기록합니다.

TBBUTTON tbb[2]={

0,ID_FILENEW,TBSTATE_ENABLED ,TBSTYLE_BUTTON,0,0,0,0,

1,ID_FILEOPEN,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0,

};

위의 값을 LPTBBUTTON 형태로 전환하여 넘겨주게 합니다.

LPTBBUTTON ptb;

ptb=tbb;

다음 CreateToolbarEx를 다음과 같이 실행시킵니다.

CreateToolbarEx

(hwnd,WS_CHILD|WS_VISIBLE | WS_CLIPSIBLINGS |WS_BORDER|

CCS_TOP |TBSTYLE_TOOLTIPS,

1,2,hInst,IDB_BITMAP,ptb,7,16,16,16,15,sizeof(TBBUTTON));

이와 같이 하면 화면에 2개의 툴바가 출력되게 됩니다.

툴바에서 이벤트 얻기

툴바를 클릭하였을 경우 어느항목이 클릭되었는가를 알아야 합니다. 이것을 알아낸후에  해당 이벤트 메시지에 따라서 프로그램을 해주어야 하기 때문입니다. 위의 예에서 2개의 툴바를 설정하였을 때 유저가 첫 번째 버튼을 클릭하여 ID_FILENEW항목을 클릭하였다고 한다면 메시지는 WM_COMMAND가 전달되며 LOWORD(wParam)에 ID_FILENEW 이벤트 메시지가 발생됩니다. ID_FILENEW라는 것을 정의해주는 곳은 위에서 설명한 TBBUTTON 구조체의 두 번째입니다.

예를 들어 ID_FILENEW라는 항목을 설정하였을 때 새로운 파일로 만들고자 한다면 다음과 같이 할수 있습니다.

case WM_COMMAND:

switch(LOWORD(wParam)

{

case ID_FILENEW:

//ID_FILENEW항목의 메뉴나 또는 툴바를 클릭했을 때

//내용 코딩

:

}

:

툴바에서 ID_FILENEW를 발생하거나 또는 메뉴에서 ID_FILENEW를 발생하든 같은 항목으로 옵니다. 따라서 현재 명령 이벤트가 툴바인지 또는 메뉴인지를 알수는 없습니다. 툴바에서 오는 명령과 메뉴에서 오는 명령을 분리하고자 한다면 툴바에설정한 ID를 메뉴에 설정한 ID와는 별개로 사용하시면 됩니다.

툴바에 풍선 도움말 붙이기

풍선 도움말을 붙이고자 할 경우 툴바를 만들 때 즉 CreateToolbarEx 함수를 실행시킬 때 두 번째 인자 스타일에 OR 연산을 이용하여 TBSTYLE_TOOLTIPS을 설정해 주어야 합니다. 이것을 설정해주면 WM_NOTIFY 메시지가 발생되면서 전해지는 lParam이 NMHDR 구조체의 정보로 들어옵니다.

case WM_NOTIFY:

pnmh=(LPNMHDR)lParam; //NMHDR구조체로 lParam을 전환

위의 예처럼 WM_NOTIFY에서 lParam을 NMHDR 구조체의 변수인 pnmh를 만들고 이곳으로 형전환을 시키면 NMHDR의 각 맴버의 값을 알수 있습니다. 이멤버중에 code에 TTN_ 계열의 이벤트 코드가 설정되면 풍선도움말에 대한 이벤트인 것을 알수가 있습니다. 다음은 TTN_계열의 이벤트 코드입니다.

TTN_NEEDTEXT : 마우스가 툴바 위치에 있으므로 풍선 도움말 필요

TTN_SHOW : 현재 풍선 도움말이 출력되었음

TTN_POP: 풍선도움말이 감추어져 있음

TTN_NEEDTEXT 란 현재 마우스가 툴바의 특정 버튼위에 있으므로 풍선 도움말에 출력할 문자열이 필요하다는 메시지입니다. 이메시지를 받은 후에 필요한 텍스트를 설정하여 전달하면 됩니다. 전에도 말씀 드렸습니다만 WM_NOTIFY에서 lParam의 첫 번째 블록은 NMHDR의 구조체이고 이구조체를 포함한 또다른 구조체를 이용하여 lParam의 값을 받으면  그이외의 정보를 얻을수 있다고 하였습니다. TTN_NEEDTEXT 가 발생되고 해당되는 문자열을 설정하여 다시 전달하고자 한다면 툴바의 어느 위치에 설정되어 있는가 등의 여러 정보를 받아야 합니다. 이정보또한 lParam에 있으며 이값을 얻고자 한다면 또다른 구조체를 이용하여 lParam의 값을 얻어야 합니다. 풍선 도움말에서 사용되는 구조체는 TOOLTIPTEXT입니다. 이구조체의 형태는 다음과 같습니다.

typedef struct {

NMHDR     hdr; //NMHDR 헤더

LPTSTR    lpszText; //툴바에 포함되어 있는 텍스트

char      szText[80]; //풍선 도움말에 출력할 텍스트

HINSTANCE hinst; //인스턴스 핸들

UINT      uFlags; //hdr의 idFrom의 핸들을 해석하는 플러그

} TOOLTIPTEXT, FAR *LPTOOLTIPTEXT;

hdr은 바로 NMHDR구조체이며 이구조체의 idFrom에는 툴바의 ID가 설정되어 있습니다. 이 ID에 의해서 툴바의 어느부분에 위치했는가를 알수 있습니다. lpszText는 툴바에 포함되어 있는 텍스트입니다. 이값은 HBUTTON의 구조체에서 dwData에 포인터로 연결할수 있습니다. szText는 풍선도움말에 출력할 텍스트입니다. TTN_NEEDTEXT 에서는 이값이 설정되어 있지 않습니다. 이변수에 우리가 설정하고자 하는 풍선도움말의 문자를 설정할수 있는 것입니다. uFlags는 hdr의 맴버인 idFrom이 어떤 형태로 온것인가를 확인합니다. 이값이 TTF_IDISHWND 이면 idFrom은 툴바의 핸들이며 TTF_RTLREADING 일경우에는 문자가 오른쪽에서 왼쪽으로 출력되는 형태입니다. (보통 아랍에서는 이렇게 출력되죠?) 이 플러그는 TTN_NEEDTEXT 에 의해서 자동적으로 설정하여 보내주기 때문에 직접 수정할 필요는 없습니다. TOOLTIPTEXT 우리가 설정해주어야 할 것은 결국 szText입니다. 예를 들어서 툴바의 ID_FILENEW라는 버튼위에 마우스가 위치해 있다면 다음과 같은 형태로 하여서 풍선 도움말을 설정할수 있습니다.

case WM_NOTIFY:

pnmh=(LPNMHDR)lParam;

if(pnmh->code==TTN_NEEDTEXT)//풍선도움말 문자열 필요

{

//풍선 도움말 구조체로 lParam을 다시 받는다.

lptoltip=(LPTOOLTIPTEXT)lParam;

if(lptoltip->hdr.idFrom==ID_FILENEW)

{

//풍선 도움말 문자를 "새파일□ 한다

strcpy(lptoltip->szText,"새파일□);

}

지금까지한 것은 바로 마우스가 툴바의 특정 버튼에 위치했을 때 풍선 도움말 문자를 설정한것입니다. 이와 같이 하게되면 자동적으로 풍선 도움말이 출력되게 됩니다.

툴바 도킹과 플로팅

툴바 도킹이란 툴바가 윈도우프레임에 붙어 있는 형태이며 플로팅이란 툴바가 윈도우에서 떨어진 형태입니다. 툴바를 윈도우붙이고 떨어 뜨리는 것은 간단하게 윈도우 스타일을 바꾸는것입니다. 즉 처음에는 WS_CHILD|WS_VISIBLE | WS_CLIPSIBLINGS |WS_BORDER|CCS_TOP |TBSTYLE_TOOLTIPS 스타일로 툴바가 윈도우 프레임에 붙어 있는 형식이었습니다. 이형식을 다음과 같이 바꾼다면 어떻게 될까요?

WS_DLGFRAME|WS_POPUPWINDOW | TBSTYLE_TOOLTIPS;

위의 형식은 팝업 윈도우이면서 대화상자이며 풍선도움말을 가능하게 하는 것입니다. 즉 팝업 윈도우란 부모 윈도우 독립적으로 떨어져 있는 형태이며 WS_DLGFRAME 대화상자와 형태로 보여주게 하는 것입니다. 이렇게 윈도우 스타일을 바꾸게 되면 툴바가 윈도우에 붙어 있는 것이 아니라 떨어져 있는 것으로 보입니다. 윈도우의 스타일을 바꾸고자 할 경우 필수적으로 사용하는 함수가 SetWindowLong입니다. 이함수에 대해서는 지금까지 사용을 많이 했기 때문에 알수 있을것입니다. 이함수의 두 번째 인자에 GWL_STYLE를 설정하고 윈도우 스타일값을 위의3번째 인자로 설정하면 윈도우 스타일이 바뀌어 집니다.

측 도킹을 하고자 한다면 도킹 스타일로 또는 툴바를 떨어뜨리고자 한다면 플로팅 스타일로 설정하면 됩니다.

//플로팅을 하는 경우

//플로팅 스타일

style=WS_DLGFRAME|WS_POPUPWINDOW | TBSTYLE_TOOLTIPS;

//윈도우를 감춘다.

ShowWindow(toolbar,SW_HIDE);

//스타일을 바꾼다.

SetWindowLong(toolbar,GWL_STYLE,(LONG)style);

//윈도우를 특정 위치로 이동시킨다.

MoveWindow(toolbar,100,100,145,50,FALSE);

//윈도우를 붙인다.

ShowWindow(toolbar,SW_SHOW);

위의 예는 툴바를 떨어뜨리는 예입니다. 툴바를 떨어 뜨린다는 것을 보통 플로팅한다고 합니다. 툴바를 플로팅할 경우 우선 생각을 해주어야 할것인 붙어 있을 경우 윈도우 위치입니다. 도킹된상태에서는 툴바 위치는 (0,0,0,0)입니다. 즉 (0,0)좌표에 가로 크기와 세로크기가 없는  형태입니다. 이형태에서 스타일을 플로팅 스타일로 한다고 하여도 윈도우는 나타나지 않습니다. 그이유는 가로와 세로의 크기가 0이기 때문입니다. 따라서 MoveWindow로 툴바윈도우의 가로크기 세로크기를 설정해 주어야 하며 플로팅 위치를 설정해 주어야 합니다. 또한 이렇게 하기전에 도킹된 윈도우를 우선적으로 감추고 속성을 바꾼다음 윈도우의 위치를 바꾼후에 윈도우를 다시 화면에 보여주도록 합니다. 즉 도킹된 툴바를 감춘다음 스타일을 바꾸고 윈도우를 적당한 위치로 이동한다음 다시 윈도우를 보여주면 되는것입니다.

이때 플로팅되는 위치는 이미 가지고 있는 정보에 의거하는 것이 좋습니다. 위의 예에서는 툴바가 플로팅되는 것이 무조건 100,100좌표에 145,50의 크기로 됩니다.

즉 툴바가 플로팅되는 위치가 같다는 것입니다. 실제 어플리케이션 프로그램에서는 툴바의 플로팅하고 이동을 시키다가 다시 도킹을 하면 최근에 플로팅된 위치에 다시 플로팅됩니다. 위의 예를 다음과 같이 할수 있는것입니다.

//플로팅을 하는 경우

//플로팅 스타일

style=WS_DLGFRAME|WS_POPUPWINDOW | TBSTYLE_TOOLTIPS;

//윈도우를 감춘다.

ShowWindow(toolbar,SW_HIDE);

//스타일을 바꾼다.

SetWindowLong(toolbar,GWL_STYLE,(LONG)style);

//이전에 플로팅 된 위치로 출력한다.

MoveWindow(toolbar,prevx,prevy,145,50,FALSE);

//윈도우를 붙인다.

ShowWindow(toolbar,SW_SHOW);

prevx와 prevy는 정수형 변수로써 툴바가 이동될 경우 이동된 위치를 확인합니다. 보통 툴바를 이동하고자 할 경우 마우스 버튼을 툴바위 타이틀 바에 클릭하고 이동을 합니다. 위의 방법으로 플로팅을 하였을 경우 마우스 버튼을 캡션에 클릭한다 하여도 툴바가 이동되지 않습니다. 결국 이동되게 하기 위해서는 툴바 프로시저를 받은다음 이프로시저에서 NC_HITTEST나 NC_LBUTTONDOWN과 NC_LBUTTONUP를 메시지를 받고 이메시지후에 마우스가 움직이는 대로 윈도우를 이동시켜 주어야 합니다. 즉 버튼이 눌렸을 때 윈도우에 진한 라인을 그려주고 (보통 이때는 SetROP2 함수를 이용하여 XOR식으로 선을 그립니다) 그리고 버튼을 띄기 전까지 마우스이동에 따라 윈도우를 이동시켜주면 됩니다.) 이렇게 한후에 마우스 이동이 끝나면 바로 현재 위치를 prevx와 prevy로 저장하면 됩니다.  이방법외에 툴바를 이동하는 방법은 여러 가지입니다. 다음장에서 배우는 MDI형식을 빌려서 툴바 윈도우하나를 등록한다음 이것을 플로팅하여 출력하는 방법이 있습니다. 이방법은 마우스 이벤트를 출력할필요없이 그대로 이동이 됩니다. 또하나의 방법은 대화상자를 만들고 이 대화상자를 모달리스로 만들어 놓은다음 플로팅이 일어나면 바로 화면에 대화상자를 출력 방법입니다. 이방법은 매우 편리합니다. 필자는 이방법을 매우 많이 사용합니다. 쉽게 보면 툴바를 가지고 있는 윈도우가 두 개인것입니다. 하나는 대화상자 윈도우이고 하나는 메인프레임에 설정된 윈도우입니다.대화상자를 만들고 프로시저에서 WM_INITDIALOG 가 올 때 이곳에서도 메인 윈도우와 같은 툴바를 하나 만들어서 출력시켜놓습니다. 즉 대화상자에 툴바만 있는 윈도우형태를 가지고 있는 것입니다. 도킹 상태에서는 대화상자 윈도우를 ShowWindow를 이용하여 화면에서 감추고 메뉴에 밑에 붙어 있는 툴바를 화면에 보여주며 플로팅 상태에서는 툴바 윈도우를 감추고 대화상자 윈도우를 화면에 보여주는 것입니다. 이렇게 되면 아주 간단하게 툴바를 플로팅과 도킹을 할 수가 있습니다.

case ID_DOCKING: //툴바를 도킹할경우

//툴바대화상자를 감추고

ShowWindow(floatWnd,SW_HIDE);

//툴바윈도우를 보인다.

ShowWindow(toolbar,SW_SHOW);

break;

case ID_MOVEFLOAT://툴바를 플로팅할경우

//메뉴를 얻는다.

//툴바윈도우를 감추고

ShowWindow(toolbar,SW_HIDE);

//대화상자를 보인다.

ShowWindow(floatWnd,SW_SHOW);

break;

툴바를 도킹할때는 먼저 윈도우를 감춘다음 스타일을 도킹스타일로 설정하고 화면에 출력하면 됩니다.

1.상태바

상태바란 보통 어플리케이션 프로그램은 윈도우 하단부에 가로로 놓여 있는 막대 바와 같은 윈도우를 이야기 합니다. 이윈도우에는 현재 상태를 출력하며 키눌림등 각종 정보를 출력합니다. 상태바는 일반 프로그램에서는 필수적으로 사용되며 어떤 프로그램은 상태바에 많은 정보를 출력하여 사용자가 현재 상태를 한눈에 알아볼수 있도록 하여놓았습니다.

상태바 만들기

상태바를 만드는 함수는 CreateStatusWindow 함수입니다. 이함수는 다음과 같습니다.

HWND CreateStatusWindow(

LONG style, //윈도우 스타일

LPCTSTR lpszText, //상태바에 출력될 글자

HWND hwndParent, //부모 윈도우

UINT wID //상태바 윈도우 ID

);

style는 윈도우 스타일입니다. 상태바에서 사용하는 윈도우 스타일은 다음과 같습니다.

WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|CCS_BOTTOM

차일드윈도우 이면서 프레임과 같은 형태이며 하단부에 붙어 있는 윈도우를 만들경우입니다. 상태바 윈도우의 스타일은 CCS_ 계열입니다.

CCS_BOTTOM 이면 하단부에 설정되는 형태이며 CCS_TOP는 상태바가 윈도우 상단에 위치하는 형태입니다. lpszText는 상태바가 만들어지면서 제일 처음에 출력되는 문자열을 의미합니다. 실제 상태바에는 메뉴 항목이 선택될 때 현재 메뉴항목을 설명하기 위한 문장들이 출력됩니다. 따라서 처음에 설정된 lpszText는 처음 출력될때마 유효하며 그이후에는 SetWindowText에 의해서 상태바에 여러 문장들을 수정합니다.hwndParent는 부모 윈도우이며 wID는 상태바 윈도우 ID입니다. 모든 차일드 윈도우는 자신의 ID를 가지게 됩니다. CreateWindow에너 HMENU값에 설정한 ID과 같다고 보시면 됩니다.

상태바 크기 조절

윈도우가 줄어 들거나 또는 이동하면서 늘어날 경우 즉 윈도우 크기가 변할 경우 상태바의 크기도 함께 변경되어야 합니다. 상태바는 처음에 설정되면 윈도우 프레임이 변할 때 함께 변하지 않습니다. 즉 부모 윈도우가 변할 때 마다 이 부모 윈도우의 크기에 맞추어서 상태바를 변경 시켜 주어야 합니다. 부모 윈도우의 크기가 변할 때 이때 발생하는 메시지가 WM_SIZE입니다. 이메시지와 함께 wParam의 LOWORD와 HIWORD에는 각각 가로의 세로의 크기가 설정됩니다.

이값을 받아서 가로의 크기는 부모 윈도우의 크기로 상태바 위치는 부모 윈도우의 세로크기에서 상태바의 세로 크기의 값을 뺀 위치로 이동시킵니다. 이 예는 다음과 같습니다.

case WM_SIZE:

//가로 크기

sx=LOWORD(lParam);

//세로 크기

sy=HIWORD(lParam);

//상태바 윈도우 크기를 얻고

GetWindowRect(statusbar,&rect);

//세로 크기는 그대로 사용

cy=rect.bottom-rect.top;

//x좌표도 그대로 사용

x=0;

//윈도우의 전체 크기에서 상태바 크기를 뺀다

y=sy-cy;

//윈도우 이동

MoveWindow(statusbar,x,y,sx,cy,TRUE);

break;

예를 들어 현재 500,500의 크기인 부모 윈도우 사이즈가 1000,1000으로 바뀌었다고 가정을 하면 상태바의 가로길이는 1000으로 바뀌어야 합니다. 상태바의 세로길이가 40이라고 가정을 하면 500,500의 위치에서 상태바의  세로위치는 460이됩니다. 그러나 1000에서는 960이 됩니다. 즉 전체 크기에서 상태바의 크기를 뺀 값이 상태바의 세로위치가 되는 것입니다. 위 예는 이와 같은 방법으로 상태바를 이동시킨 예입니다.

상태바에 메뉴 선택시 도움말 출력

팝업 메뉴를 가동시키고 팝업 메뉴의 특정 항목이 반전 되었을 때 이때 항목에 대한 도움말이 상태바에 출력되는 프로그램들이 매우 많습니다. 사용자들은 현재 메뉴항목이 어떤 기능을 하는가에 대해 매우 궁금해 하고 때로는 메뉴에서 항목을 실행하는데 다소 두려워 합니다. 그이유는 잘못 눌러서 프로그램이 이상하게 동작을 할까봐 두려운 것입니다. 사용자가 프로그램에서 특정 메뉴위에 마우스를 위치했을 때 현재 메뉴는 어떤 내용이라는 내용을 상태바에 출력함으로써 사용자는 보다 편리하게 프로그램을 사용할것입니다. 이런 도움말 메시지를 상태바에 출력하고자 할 경우 간단하게 SetWindowText함수를 사용할수 있습니다. 다음은 상태바에 특정 내용을 출력하는 예입니다.

SetWindowText(statuswind,"새로운 파일을 오픈합니다□);

파일메뉴에서 새로운 파일이라는 메뉴위에 마우스가 있을 경우( 선택하여 실행하였을때가 아니라 단순히 마우스가 위치해 있는 경우) 상태바에 문장을 출력하고자 할 때 위의 내용을 상용하면 됩니다. 그러나 중요한 것은 어느메시지에서 이 함수를 사용하는가입니다. 메뉴위에 마우스가 있어서 특정 항목이 반전된 상태를 알려주는 메시지를 우리가 알지 못하기 때문입니다. 이메시지는 WM_MENUSELECT입니다. WM_MENUSELECT 메시지와 함께 전돨되는 wParam과  lParam의 정보는 다음과 같습니다.

LOWORD(wParam): 메뉴 아이템이나 또는 서브 아이템 번호

HIWORD(wParam): 메뉴 정보 플러그

lParam : 메뉴 핸들

HIWORD(wParam)에는 MF_ 게열의 코드 값이 설정됩니다. MF_SYSMENU 가 설정되면  현재 발생된 메뉴가 사용자가 설정한 메뉴가 아니라 윈도우 좌측 상단에 있는 시스템 메뉴이다라는 의미이며 MF_POPUP  팝업 메뉴를 클릭하였을 때 의 코드입니다. 이외에 현재 아이템이 비트맵이라는 MF_POPUP  현재 아이템이 체크되어 있다는 MF_CHECKED  현재 메뉴가 비활성화라는 MF_DISABLED와 MF_GRAYED등등의 메시지가 있습니다. 메뉴에 대한 정보는 어느곳이나 비슷하게 들어오기 때문에 전장의 메뉴 항목에서 들어오는 메시지를 상기하시면 될것입니다.

위의 메시지를 이용하여 ID_FILENEW항목위에 메시지가 있을경우에 □새로운 파일을 출력합니다□ 라는 메시지를 발생하고자 한다면 다음과 같이 할수 있습니다.

case WM_MENUSELECT:

idx=LOWORD(wParam);

if(idx==ID_FILENEW)

{

SetWindowText(statusbar,"새로운 파일을 출력합니다□);

}

실제로 프로그램을 할경우에는 WM_MENUSELECT에서 idx를 받고 스위치문으로 만들경우도 있습니다.

case WM_MENUSELECT:

idx=LOWORD(wParam);

switch(idx)

{

case ID_FILENEW:

SetWindowText(statusbar,"새로운 파일을 출력합니다□);

break;

:

}

만일 메뉴가 수십개가 된다면 위의 방법으로 하였을 경우 소스가 매우 길어지게 됩니다. 이런 단점을 없애기 위해서 메뉴에 대한 도움말을 자원에 String Table에 등록을 하는 것입니다. 문자열 테이블에 메뉴ID와 같은 번호의 문자열을 등록하고 이문자열을 로드하는 방식을 선택하면 매우 간단하게 출력할수 있습니다.

case WM_MENUSELECT:

//시스템 메뉴가 아닐경우

if(!(HIWORD(wParam)&MF_SYSMENU))

{

//메뉴 도움말을 상태바에 출력한다.

idx=LOWORD(wParam);

LoadString(hInst,idx,temp,80);

SetWindowText(statusbar,temp);

}

break;

위의 예는 시스템 메뉴가 아닐경우에는 모든 메뉴에 대한 도움말을 상태바에 출력하는것입니다. 메뉴 ID를 idx에 받고 LoadString를 이용하여 해당 아이디와 같은 문자열을 temp에 적재한다음 이 문자열을 상태바 윈도우에 출력을 하는 것입니다.

이렇게 출력을 하기 위해서는 메뉴 ID와 같은 ID로 문자열을 그림 2와 같이 등록해야 합니다.

(그림 2) 메뉴 도움말을 출력할 문자 테이블

툴바와 상태바 예제 ToolBarEx

ToolBarEx는 툴바와 상태바를 설정한 예입니다. 툴바는 도킹과 플로팅을 할수 있습니다. 이때 플로팅의 첫 번째 방법은 단순히 플로팅을 한방법이며 두 번째는 대화상자를 이용하여 이동할수 있도록 한것입니다.

상태바에는 메뉴위에 마우스가 있을경우나 또는 툴바위에 있을 경우 해당 되는 도움말이 출력되며 풍선 도움말 기능을 넣은 예입니다. 이 예에서 단순히 플로팅을 한 툴바 윈도우에 새로운 프로시저를 넣고 각각에 해당하는 메시지 이벤트를 설정하면 이 툴바도 이동가능할것입니다. 특정 윈도우에서 프로시저를 가로채서 자신의 프로시저를 만드는 방법은 차일드 윈도우 만들기에서 이미 설명한 내용입니다. 본예를 수정하여 한번 이동되지 않는 툴바를 이동시키도록 만들어 보시기 바랍니다.

(프로그램 소스)

//풀바와 상태바 예제

//ToolBarEx.c

#include <windows.h>

#include <commctrl.h>

#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

HINSTANCE hInst;

//툴바 버튼 정보

TBBUTTON tbb[7]={

0,ID_FILENEW,TBSTATE_ENABLED ,TBSTYLE_BUTTON,0,0,0,0,

1,ID_FILEOPEN,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0,

2,ID_FILESAVE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0,

0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0,0,0,

3,ID_CUT,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0,

4,ID_COPY,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0,

5,ID_PASTE,TBSTATE_ENABLED,TBSTYLE_BUTTON,0,0,0,0,

};

//툴바 프로시저

BOOL CALLBACK ToolbarProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam);

//어플리케이션 이름

static char szAppName[] = "툴바 상태바예제:ToolBarEx" ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

hInst=hInstance;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = "MENU" ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindowEx (0L,szAppName,

szAppName,

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

InitCommonControls();

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

LPTBBUTTON ptb;

LPNMHDR pnmh;

LPSTR pReply;

LPTOOLTIPTEXT lptoltip;

static HWND toolbar,statusbar;

DWORD style;

HMENU hMenu;

char temp[80];

UINT idx;

static HWND floatWnd;

int sx,cy,x,y,sy;

RECT rect;

switch (iMsg)

{

case WM_CREATE:

ptb=tbb;

//툴바를 만든다.

toolbar=CreateToolbarEx(hwnd,WS_CHILD|WS_VISIBLE | WS_CLIPSIBLINGS |WS_BORDER|

CCS_TOP |TBSTYLE_TOOLTIPS,

1,8,hInst,IDB_BITMAP,ptb,7,16,16,16,15,sizeof(TBBUTTON));

//상태바를 만든다.

statusbar=CreateStatusWindow(WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|CCS_BOTTOM ,"준비되었습니다",

hwnd,2);

//플로팅 된 툴바를 적재할 대화상자를 만든다.

floatWnd=CreateDialog(hInst,(LPSTR)IDD_DIALOG,hwnd,ToolbarProc);

return 0 ;

case WM_NOTIFY:

pnmh=(LPNMHDR)lParam;

//풍선 도움말이 필요하다면

if(pnmh->code==TTN_NEEDTEXT)

{

//각 항목에 따라 풍선 도움말을 설정한다.

lptoltip=(LPTOOLTIPTEXT)lParam;

switch(lptoltip->hdr.idFrom)

{

case ID_FILENEW:

pReply="File New";

break;

case ID_FILEOPEN:

pReply="File Open";

break;

case ID_FILESAVE:

pReply="File Save";

break;

case ID_CUT:

pReply="Edit Cut";

break;

case ID_COPY:

pReply="Edit Copy";

break;

case ID_PASTE:

pReply="Edit Paste";

break;

}

strcpy(lptoltip->szText,pReply);

//상태바에도 필요한 도움말을 문자열 테이블에서

//받아서 출력한다.

idx=lptoltip->hdr.idFrom;

LoadString(hInst,idx,temp,80);

SetWindowText(statusbar,temp);

}

break;

case WM_MENUSELECT:

//시스템 메뉴가 아닐경우

if(!(HIWORD(wParam)&MF_SYSMENU))

{

//메뉴 도움말을 상태바에 출력한다.

idx=LOWORD(wParam);

LoadString(hInst,idx,temp,80);

SetWindowText(statusbar,temp);

}

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

//움직이지 않는 플로팅

case ID_FLOAT:

hMenu=GetMenu(hwnd);

//스타일을 바꾼다.

style=WS_DLGFRAME|WS_POPUPWINDOW | TBSTYLE_TOOLTIPS;

//툴바를 감추고

ShowWindow(toolbar,SW_HIDE);

//스타일을 바꾼다음

SetWindowLong(toolbar,GWL_STYLE,(LONG)style);

//윈도우를 이동시키고

MoveWindow(toolbar,100,100,165,50,FALSE);

//툴바를 보여준다.

ShowWindow(toolbar,SW_SHOW);

//도킹메뉴는 활성 나머지 플로팅 메뉴는 비활성

EnableMenuItem(hMenu,ID_FLOAT,MF_BYCOMMAND | MF_GRAYED);

EnableMenuItem(hMenu,ID_DOCKING,MF_BYCOMMAND | MF_ENABLED);

EnableMenuItem(hMenu,ID_MOVEFLOAT,MF_BYCOMMAND | MF_GRAYED);

break;

case ID_DOCKING:

//툴바대화상자를 감추고

ShowWindow(floatWnd,SW_HIDE);

hMenu=GetMenu(hwnd);

//도킹 스타일

s	tyle=WS_CHILD|WS_VISIBLE | WS_CLIPSIBLINGS

|WS_BORDER|CCS_TOP |TBSTYLE_TOOLTIPS;

//툴바 윈도우를 감추고

ShowWindow(toolbar,SW_HIDE);

//스타일을 바꾼다음

SetWindowLong(toolbar,GWL_STYLE,(LONG)style);

//위치를 0으로

MoveWindow(toolbar,0,0,0,0,FALSE);

//툴바윈도우를 보인다.

ShowWindow(toolbar,SW_SHOW);

//프로팅 메뉴 활성화 도킹 메뉴 비활성

EnableMenuItem(hMenu,ID_FLOAT,MF_BYCOMMAND | MF_ENABLED);

EnableMenuItem(hMenu,ID_DOCKING,MF_BYCOMMAND | MF_GRAYED);

EnableMenuItem(hMenu,ID_MOVEFLOAT,MF_BYCOMMAND | MF_ENABLED);

break;

case ID_MOVEFLOAT:

//메뉴를 얻는다.

hMenu=GetMenu(hwnd);

EnableMenuItem(hMenu,ID_FLOAT,MF_BYCOMMAND | MF_GRAYED);

EnableMenuItem(hMenu,ID_MOVEFLOAT,MF_BYCOMMAND | MF_GRAYED);

EnableMenuItem(hMenu,ID_DOCKING,MF_BYCOMMAND | MF_ENABLED);

//툴바윈도우를 감추고

ShowWindow(toolbar,SW_HIDE);

//대화상자를 보인다.

ShowWindow(floatWnd,SW_SHOW);

break;

}

break;

case WM_SIZE:

//가로 크기

sx=LOWORD(lParam);

//세로 크기

sy=HIWORD(lParam);

//상태바 윈도우 크기를 얻고

GetWindowRect(statusbar,&rect);

//세로 크기는 그대로 사용

cy=rect.bottom-rect.top;

//x좌표도 그대로 사용

x=0;

//윈도우의 전체 크기에서 상태바 크기를 뺀다

y=sy-cy;

//윈도우 이동

MoveWindow(statusbar,x,y,sx,cy,TRUE);

break;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

TextOut(hdc,0,0,"this is 0,0",11);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

//대화상자 프로시저

BOOL CALLBACK ToolbarProc(HWND hwnd,UINT iMsg,WPARAM wParam,LPARAM lParam)

{

static HWND toolbar;

LPTBBUTTON ptb;

LPNMHDR pnmh;

switch(iMsg)

{

case WM_CREATE:

return 0;

case WM_NOTIFY:

//풍선 도움말 메시지이면

pnmh=(LPNMHDR)lParam;

// 부모 윈도우로 메시지를 보낸다.

if(pnmh->code==TTN_NEEDTEXT)

SendMessage(GetParent(hwnd),iMsg,wParam,lParam);

break;

case WM_INITDIALOG:

//대화상자에 툴바를 붙인다.

ptb=tbb;

toolbar=CreateToolbarEx(hwnd,WS_CHILD|WS_VISIBLE |WS_BORDER|TBSTYLE_TOOLTIPS,

1,8,hInst,IDB_BITMAP,ptb,7,16,16,16,15,sizeof(TBBUTTON));

return 0;

case WM_COMMAND:

break;

}

return FALSE;

}

(프로그램 소스끝)

(그림 3) ToolBarEx출력 결과