지금까지 프로그램을 제작하면서 메뉴를 계속적으로 사용했습니다. 일반적으로 AppWizard를 이용하여 프로젝트를 만들면, SDI일 경우에는 IDR_MAINFRAME 형태로 메뉴가 Resource에 등록되어 있으며, MDI일 경우에는 자식 윈도가 있을 때와 없을 때의 두 개의 메뉴가 설정되어 있습니다. 이렇게 설정된 메뉴를 우리가 원하는 형태로 수정하여 메뉴를 만들 수 있습니다. 그림 1-6-1은 메뉴 자원에서 속성 대화 상자를 이용하여 메뉴를 수정하는 형태입니다.

메뉴 자원을 설정한 상태에서 Alt+Enter 키를 눌러 메뉴의 속성을 출력하는 대화 상자가 나타난 화면이 그림 1-6-1의 ⓐ와 같습니다. ① 항목은 메뉴의 ID입니다. 이 ID를 이용하여 메뉴 항목이 선택되었을 때의 함수를 ClassWizard로 만들 수 있습니다.

② 항목은 이 메뉴에 출력되는 문자열을 의미합니다. 이 문자열 안에는 &, \t 문자 코드 등을 사용할 수 있습니다. & 코드를 사용하면 해당 문자열 하단부에 라인이 설정되고, 메뉴가 현재 가동되고 있을 때 &와 함께 사용한 문자 코드를 키보드에서 클릭하면 해당 메뉴가 실행됩니다. \t 키는 탭키를 의미합니다. 이 코드를 사용하면 메뉴 문자열 사이를 탭키를 누른 것과 같은 형태로 띄어줍니다. 보통 이 코드와 함께 엑셀러레이터에 설정된 문자열 코드를 설정하여 줍니다.

그림 1-6-1의 ③ 항목은 메뉴가 선택되었을 때 해당 메뉴의 설명을 상태바에 출력해 줍니다. 이 문자열 사이에 “\n” 코드를 설정하고 문자열을 넣으면 이 메뉴 ID와 연계되는 툴바에 풍선 도움말 형태로 도움말이 출력됩니다. 메뉴를 설정하는 방법은 사실 리소스를 이용하면 매우 편리하게 사용할 수 있습니다. 지금까지 예제를 만들면서 많은 형태의 메뉴를 작성했었기 때문에 메뉴에 항목을 추가하거나 삭제하는 부분에 대해서는 더 이상 설명이 필요없을 것입니다.

항목 선택의 전체 구조

AppWizard를 이용하여 프로젝트를 만들면 특정 항목을 선택하고자 할 때 메뉴, 툴바, 상태바, 엑셀러레이터가 함께 조화있게 움직입니다. 즉 프로그램에 어떤 명령을 내리고자 할 때 메뉴를 선택하거나, 툴바를 선택하거나, 또는 핫키를 사용할 수가 있다는 것입니다. 그림 1-6-2는 항목을 선택할 수 있는 전체 형태를 도시화한 것입니다.

그림 1-6-2와 같이 메뉴, 툴바, 엑셀러레이터는 같은 ID_FILE_NEW 항목을 사용합니다. 쉽게 말하면 메뉴을 선택하여 ID_FILE_NEW 명령 메시지를 출력할 수 있으면 키보드에서 Ctrl+N 키를 눌러서도 실행할 수 있고, 툴바에서 처음 아이콘을 클릭하여 실행할 수도 있는 것입니다. 컴퓨터에서 항목을 선택할 때 메뉴만 이용하는 단점, 툴바만 이용하는 단점, 또는 키보드에서 키를 눌러서만 이용하는 단점을 모두 보안한 것입니다. MFC로 프로그램을 제작할 때는 그림 1-6-2와 같이 키와 메뉴와 툴바를 한꺼번에 이용할 수 있도록 구성하는 것이 좋다는 의미입니다. 중요한 메뉴는 그림 1-6-2와 같이 세 개의 자원에 등록할 수 있으나 모든 항목을 다 그렇게 할 수는 없습니다. 이유는 복잡한 프로그램에서는 선택 항목이 매우 많기 때문에 이 많은 항목을 전부 툴바와 엑셀러레이터에 등록하면 사용자를 더욱 복잡하게  만듭니다. 그러나 중요 항목은 그림 1-6-2와 같은 세 개의 자원에 모두 등록하여 사용하는 것이 좋습니다.

메뉴 활성화와 비활성화

애플리케이션 프로그램에서 특정 내용이 수행되기 전까지는 메뉴 항목이 비활성화 되었다가 특정 항목을 수행하면 활성화되는 부분을 보았을 것입니다(쉽게 말하면 회색 형태로 되어 선택이 되지 않았다가 특정 항목을 선택하면 항목 선택이 가능해지는 상태). 이런 메뉴 활성화/비활성화는 MFC에서는 매우 쉽게 사용할 수 있습니다. 그림 1-6-2에서 ID_FILE_NEW 항목을 활성/비활성화하고자 한다면 클래스 위저드에서 ID_FILE_NEW 항목을 Object IDs에서 선택하고, 메시지에서 UPDATE_COM- MAND_UI 항목을 클릭하여 함수를 만들고, 이 함수에서 메뉴를 활성/비활성화하면 됩니다. 그림 1-6-3은 ClassWizard에서 ID_FILE_NEW 항목을 선택하고 UP- DATE_COMMAND_UI 항목을 선택하여 함수를 만드는 형태입니다.

그림 1-6-3과 같이 하여 함수를 만들면 다음과 같은 함수가 만들어집니다.

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

}

이 함수에서 pCmdUI 클래스 변수를 이용하여 메뉴를 비활성/활성을 할 수가 있습니다. pCmdUI 멤버 함수 중 Enable이라는 함수가 있습니다. 이 함수의 인자를 TRUE로 설정하면 활성화되고 이것을 FALSE로 설정하면 비활성화됩니다. 다음 예는 메뉴의 ID_FILE_NEW에서 특정 플러그가 3이면 활성, 그렇지 않으면 비활성화되는 예입니다.

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)

{

if(m_nFlag==3)

pCmdUI->Enable(TRUE);

else

pCmdUI->Enable(FALSE);

}

ID_FILE_NEW 항목을 활성화하고자 한다면 m_nFlag에 3을 설정하면 되고, 그렇지 않을 경우에는 다른 값을 설정하면 됩니다. 이렇게 설정한 후 OnUpdateFileNew 항목을 호출할 필요는 없습니다. OnUpdateFileNew 항목은 메뉴가 선택되어 가동될 경우에 항상 호출되는 함수입니다. 그렇기 때문에 m_nFlag에 3만 설정하면 자동으로 메뉴에서 ID_FILE_NEW 항목이 활성화됩니다.

메뉴 항목의 활성/비활성 외에 메뉴에 체크 표시가 되어 있다든지 라디오 버튼처럼 까만 원이 그려져 있는 경우를 본 적이 있을 것입니다. 이것 또는 pCmdUI 인자의 두 개의 멤버 함수 SetCheck와 SetRadio를 이용하면 됩니다. 이 두 개의 함수도 Enable 함수와 같이 TRUE를 선택하면 체크되거나 라디오 버튼이 설정되며 FALSE를 선택하면 해제됩니다. 다음은 ID_FILE_NEW 항목을 m_nFlag가 3일 경우 체크 상태나 라디오 상태로 설정하는 예입니다.

//m_nFlag가 3일 경우 체크 상태로 만들기

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)

{

if(m_nFlag==3)

pCmdUI->SetCheck(TRUE);

else

pCmdUI->SetCheck(FALSE);

}

//m_nFlag가 3일 경우 라디오 버튼을 설정하기

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)

{

if(m_nFlag==3)

pCmdUI->SetRadio(TRUE);

else

pCmdUI->SetRadio(FALSE);

}

메뉴 항목을 만들고 특정 메뉴를 체크하거나 비활성화시키는 방법들은 본 항목에 있는 방법을 이용하면 됩니다. 위와 같이 하면 ID_FILE_NEW의 메뉴 항목만 영향을 주는 것이 아니라 툴바에도 영향을 줍니다. pCmdUI->SetCheck(TRUE); 함수를 실행하면 툴바에서는 버튼이 약간 들어간 형태로 됩니다. 그림 1-6-2에서 세 개 항목의 연관 관계를 보면 이해가 될 것입니다. UPDATE_COMMAND_UI는 메뉴에만 연결되는 것이 아니라 WM_COMMAND 메시지를 발생시킬 수 있는 모든 자원과 연결되기 때문에 pCmdUI의 멤버 함수에 설정한 값이 메뉴, 툴바, 엑셀러레이터에 모두 영향을 줍니다.

동적으로 메뉴 구성하기

본 장에서는 자원에서 메뉴를 설정하고 삽입하는 방법에 대한 설명은 생략하겠습니다. 이 부분은 지금까지 사용해 보았으므로 충분히 이해하고 있을 것으로 생각되기 때문입니다. 자원에서 메뉴를 설정하고 컴파일하여 프로그램을 실행시키는 방법을 필자는 정적 메뉴 구성이라고 하였습니다. 즉 한 번 메뉴를 설정하면 프로그램 내부에서는 고칠 수가 없기 때문입니다.

그러나 애플리케이션 프로그램 중에서는 메뉴의 항목이 변하는 프로그램들도 있습니다. 이럴 경우에는 메뉴 자체를 동적으로 설정해야 합니다. 메뉴를 동적으로 설정하기 위해서는 현재 설정된 메뉴를 얻은 다음 이 메뉴를 수정하고 메뉴를 다시 윈도에 설정합니다. C++로 프로그램을 하기 때문에 우리는 이렇게 메뉴를 얻거나 수정하는 클래스를 알아야 합니다. MFC에서는 메뉴를 자유롭게 컨트롤하기 위해서 CMenu라는 클래스가 제공됩니다. 이 클래스를 이용하여 동적으로 메뉴를 구성할 수가 있습니다. 그림 1-6-4는 메뉴를 얻고 수정하는 방법을 도표화한 것입니다.

메뉴를 수정하기 위해서는 CMenu 클래스의 변수를 만든 다음 현재 윈도에 등록되어 있는 메뉴를 받습니다. 이때 CWnd::GetMenu() 함수를 이용하여 CMenu 포인터를 얻을 수 있습니다. 그림 1-6-4의 ② 항목이 바로 이 내용을 설명한 항목입니다. 이와 같이 메뉴를 받은 다음 이 메뉴를 수정하려면, ModifyMenu 함수를 새로 삽입하고자 할 때는 InsertMenu 함수를, 현재 메뉴의 뒤에 추가하고자 할 때는 AppendMenu를, 삭제하고자 할 때는 DeleteMenu를 사용하면 됩니다. 이렇게 하면 메뉴가 수정됩니다. 이렇게 수정된 메뉴를 SetMenu 함수를 이용하여 다시 윈도에 등록하면 됩니다. 그림 1-6-4의 ③, ④, ⑤ 항목대로 프로그래밍하는 방법은 다음과 같습니다.

(1) 메뉴 추가하기-AppendMenu

AppendMenu의 함수는 다음과 같습니다.

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

nFlags는 메뉴의 스타일을 의미합니다. 이 스타일은 MF_ 계열의 플러그값을 사용할 수 있습니다. 이 플러그값이  MF_OWNERDRAW이면 사용자 그리기입니다. 이 플러그를 설정하면 윈도에서 WM_DRAWITEM이 발생될 때 사용자 그리기 방법으로 그릴 수 있으며, WM_MEASUREITEM 메시지에 의해 수행되는 함수에서 메뉴 항목의 크기를 설정할 수 있습니다. nFlags의 값이 MF_STRING일 경우에는 문자열을 설정하는 일반적인 메뉴 형태이며, MF_SEPARATOR이면 라인이 그어지면서 분할되는 형태를 만듭니다. nFlags의 값이 MF_POPUT일 경우에는 하나의 메뉴를 팝업 형태로 삽입합니다. 이때에는 nIDNewItem에 메뉴 핸들 포인터를 넘겨주어야 합니다.

nIDNewItem은 메뉴의 ID입니다. 위에서 설명한 ID_FILE_NEW 같은 메뉴의 ID를 설정하는 것입니다. AppendMenu에서는 두 가지 방법으로 메뉴를 설정할 수 있습니다. 하나는 문자열이고 또 하나는 비트맵입니다. 문자열로 메뉴를 설정할 때는 첫 번째 함수를 사용하여 lpszNewItem에 문자열을 설정하면 됩니다. 만일 비트맵을 설정하고자 한다면 AppendMenu의 두 번째 함수를 이용하여 pBmp에 비트맵 포인터를 넘겨주면 됩니다.

다음은 AppendMenu를 이용하여 “그리기”라는 메뉴 항목을 ID가 ID_DRAW라는 값을 가지도록 설정한 예입니다.

#define ID_DRAW 101

:

pMenu->AppendMenu(MF_STRING,ID_DRAW,”그리기”);

(2) 메뉴 삽입하기-InsertMenu

InsertMenu는 특정 위치 다음으로 메뉴를 삽입하는 함수입니다. 이 함수의 형태는 다음과 같습니다.

BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

nPosition은 삽입할 위치 번호입니다. 이 번호는 nFlags의 스타일에 따라, ID 번호에 따라, 또는 순서 번호에 따라서 설정됩니다. nFlags에는 AppendMenu에서 설명하지 않은 두 개의 MF_ 계열의 플러그가 있습니다. MF_BYCOMMAND와 MF_BYPOSITION이 그것입니다. nFlags에 MF_BYCOMMAND라고 설정하였으면 nPosition에는 삽입할 위치의 ID를 설정해주어야 합니다. MF_BYPOSITION은 메뉴를 0부터 시작하는 순서대로 찾아가서 해당 메뉴를 삽입합니다. 나머지 인자들은 AppendMenu에서 설명한 내용과 같습니다.

만약 메뉴에서 다섯 번째 위치에 “그리기” ID_DRAW라는 항목을 삽입하려면 다음과 같이 하면 됩니다.

pMenu->InsertMenu(4,MF_BYPOSITION|MF_STRING,ID_DRAW,”그리기”);

또 하나의 예로 “그리기”를 ID_FILE_NEW 항목 다음으로 삽입하고자 한다면 다음과 같이 할 수 있습니다.

pMenu->InsertMenu(ID_FILE_NEW,MF_BYCOMMAND|MF_STRING,ID_DRAW,”그리기”);

(3) 메뉴 수정하기 - ModifyMenu

ModifyMenu 함수는 다음과 같습니다.

BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL );

BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp );

ModifyMenu의 인자들은 InsertMenu와 같습니다. ModifyMenu를 실행하면 해당 메뉴가 수정됩니다. 다음은 ID_FILE_NEW라는 메뉴를 “그리기”라는 항목으로 바꾼 예입니다.

pMenu->ModifyMenu(ID_FILE_NEW,MF_BYCOMMAND | MF_STRING,

ID_DRAW,”그리기”);

(4) 메뉴 삭제하기 - DeleteMenu

메뉴를 삭제하는 DeleteMenu 함수는 다음과 같습니다.

BOOL DeleteMenu( UINT nPosition, UINT nFlags );

nPosition과 nFlags는 위에서 설명한 인자값입니다. DeleteMenu는 새로 추가되는 항목이 없기 때문에 nIDNewItem이나 문자열 비트맵에 해당하는 인자가 없습니다. 예를 들어서 ID_FILE_NEW 항목을 삭제하고자 한다면 다음과 같이 할 수 있습니다.

DeleteMenu(ID_FILE_NEW,MF_BYCOMMAND);

또한 메뉴 중 두 번째 있는 메뉴를 삭제하고자 한다면 다음과 같이 할 수 있습니다.

DeleteMenu(1,MF_BYPOSITION);

(5) 팝업 메뉴의 수정

AppWizard를 이용하여 프로젝트를 만들고 이 프로젝트의 메뉴를 보면 팝업 메뉴 항목과 그 팝업 메뉴 안에 부메뉴들이 있는 형태입니다. 예를 들면 “파일”이라는 팝업 메뉴에 “열기”, “새 파일” 등등의 항목이 설정되어 있다는 것입니다. DeleteMenu나 ModifyMenu 함수 등은 그냥 현재 메뉴에서 수정이 가능하나, 추가나 삽입의 함수, 즉 InsertMenu나 AppendMenu 등은 팝업 메뉴일 경우에는 팝업  메뉴를 얻은 다음에 이 메뉴를 수정하고, 또는 팝업 메뉴를 만든 다음에 이 메뉴를 추가해야 합니다.

예를 들어 “파일”, “편집”, “보기”, “도움말” 중에서 “파일” 메뉴 안에서 새로운 메뉴를 수정하고자 한다면 먼저 메뉴에서 “파일” 메뉴 핸들을 다시 얻어야 합니다. 다음은 현재 메뉴에서 “파일” 메뉴만 다시 얻는 예입니다.

CMenu *pMenu=GetMenu();

CMenu *pSubMenu=pMenu->GetSubMenu(0);

위와 같이 하면 pSubMenu에 “파일”에 해당하는 메뉴들이 들어오게 됩니다. 이 상태에서 삽입이나 추가를 할 수가 있습니다. 만일 “파일”, “편집”, “보기”, “도움말” 메뉴 사이에 “그리기”라는 팝업 메뉴를 두고 그 안에 “박스 그리기”, “라인 그리기” 등의 항목을 두고자 한다면 서브 메뉴를 하나 만든 다음 이 메뉴에 항목을 넣고 이 서브 메뉴를 메인 메뉴에 추가합니다. 메뉴를 만드는 함수는 CreateMenu입니다. 이 함수는 인자가 없으므로 간단하게 CreateMenu()로 호출하면 됩니다.

다음은 메뉴에 “데이터”라는 팝업 항목을 추가하고 이 팝업에 몇 개의 메뉴를 설정한 예입니다.

CMenu *pMainMenu,subMenu;

//메뉴를 얻는다.

pMainMenu=GetMenu();

//메뉴를 만든다.

subMenu.CreateMenu();

//만든 메뉴에 항목 추가

subMenu.AppendMenu(MF_STRING,ID_SET_BMP,”그림파일”);

subMenu.AppendMenu(MF_STRING,ID_SET_AVI,”동영상”);

subMenu.AppendMenu(MF_STRING,ID_SET_VOICE,”음성”);

subMenu.AppendMenu(MF_SEPARATOR);

subMenu.AppendMenu(MF_STRING,ID_SET_MODE,”수정”);

subMenu.AppendMenu(MF_STRING,ID_SET_DEL,”삭제”);

//만든 메뉴를 메인 메뉴에 삽입한다.

pMainMenu->InsertMenu(2,MF_POPUP|MF_BYPOSITION,

(UINT)subMenu.m_hMenu,”데이터”);

//메인 메뉴를 설정한다.

SetMenu(pMainMenu);

메뉴 구성 예제 ExMenu

ExMenu는 현재 메뉴에 새로운 팝업 메뉴를 삽입하고 메뉴를 체크 상태, 라디오 상태, 비활성화 상태로 설정하는 예제입니다. 이 예제의 전체 구성도는 그림 1-6-5와 같습니다.

ExMenu는 지금까지 설명한 모든 내용을 예제로 설정한 것입니다. “새 파일”과 “열기” 항목을 선택하면 그림 1-6-5의 ⓒ 메뉴가 ⓑ의 “편집” 메뉴 사이로 들어가게 됩니다. 이렇게 들어가 있는 상태에서 ⓐ 메뉴에서 “닫기” 항목을 선택하면 ⓒ 메뉴가 다시 삭제됩니다. ⓒ 메뉴가 설정된 상태에서 “그림파일”, “동영상”, “음성” 등의 메뉴를 선택하면 비활성화되어 있던 “수정”, “삭제” 메뉴가 활성화됩니다. “편집” 메뉴에서 “잘라내기”나 “복사” 항목을 선택하면 ⓓ 항목의 “취소” 항목이 활성화되며 ⓐ 메뉴에서 “체크”와 “라디오” 항목을 선택하면 토글 형식으로 각각 체크와 라디오 상태가 됩니다.

ExMenu는 SDI 형태의 프로젝트를 만듭니다. 이후의 제작 방법은 다음과 같습니다.

(1) “새 파일”과 “열기” 항목 선택시에 메뉴 삽입하기

새 파일과 열기를 선택하였을 때의 함수를 ClassWizard로 만듭니다. 이렇게 만든 두 개의 함수는 다음과 같습니다.

void CMainFrame::OnFileNew()

{

}

void CMainFrame::OnFileOpen()

{

}

위 두 개의 메뉴에서 현재 메뉴에 “데이터” 항목이 설정되어 있지 않으면 새로 설정하고, 설정되어 있으면 아무런 작업도 하지 않아야 합니다. 즉 한 번 메뉴가 설정되면 다시 메뉴를 설정하지 않는다는 의미입니다. 이럴 경우에는 한 번만 설정하도록 플러그를 둡니다. 헤더에 다음과 같이 변수를 설정합니다.

BOOL m_bOpen;

위와 같이 설정한 후에 m_bOpen을 FALSE로 설정해 놓고(생성자에서) OnFileNew나 OnFileOpen 함수가 실행되었을 때 m_bOpen이 FALSE이면 새롭게 메뉴를 만들고 m_bOpen을 TRUE로 설정합니다. 이렇게 설정해 놓으면 다음 번에 같은 항목을 또 선택하여도 m_bOpen이 TRUE이기 때문에 실행이 되지 않습니다. OnFileNew와 OnFileOpen에 설정하는 내용은 같습니다. 새로운 메뉴를 만들기 전에 메뉴에 설정할 아이디를 리소스에 기록해야 합니다. 리소스에 아이디를 설정하려면 ProjectWorkspace의 ResourceView 항목의 루트 항목에서 우측 버튼을 클릭하고 이때 나타나는 메뉴에서 Resource Symbols 항목을 선택하면 됩니다.

다음은 OnFileNew에서 메뉴를 삽입하는 내용입니다. OnFileOpen은 OnFileNew의 내용과 같게 설정하면 됩니다.

void CMainFrame::OnFileNew()

{

//메뉴가 만들어지지 않았으면

if(!m_bOpen)

{

CMenu *pMainMenu,subMenu;

//메뉴를 얻는다.

pMainMenu=GetMenu();

//메뉴를 만든다.

subMenu.CreateMenu();

//만든 메뉴에 항목 추가

subMenu.AppendMenu(MF_STRING,ID_SET_BMP,”그림파일”);

subMenu.AppendMenu(MF_STRING,ID_SET_AVI,”동영상”);

subMenu.AppendMenu(MF_STRING,ID_SET_VOICE,”음성”);

subMenu.AppendMenu(MF_SEPARATOR);

subMenu.AppendMenu(MF_STRING,ID_SET_MODE,”수정”);

subMenu.AppendMenu(MF_STRING,ID_SET_DEL,”삭제”);

//만든 메뉴를 메인 메뉴에 삽입한다.

pMainMenu->InsertMenu(2,MF_POPUP|MF_BYPOSITION

,(UINT)subMenu.m_hMenu,”데이터”);

m_bOpen=TRUE;

//메인 메뉴를 설정한다.

SetMenu(pMainMenu);

//① 수정과 삭제 항목을 비활성화한다.

pMainMenu->EnableMenuItem(ID_SET_MODE,

MF_BYCOMMAND|MF_GRAYED  );

pMainMenu->EnableMenuItem(ID_SET_DEL,

MF_BYCOMMAND|MF_GRAYED  );

}

}

위과 같이 하면 그림 1-6-5의 ⓒ 메뉴가 “편집” 메뉴 사이로 설정됩니다.

새로 설정된 메뉴를 활성/비활성으로 만들고자 할 때는 UPDATE_COMMAND_UI를 이용할 수 없습니다. 따라서 메뉴의 함수를 이용합니다. EnableMenuItem은 메뉴를 활성화/비활성화하는 CMenu의 함수입니다. 위 소스의 ① 항목이 이 함수를 이용하여 메뉴를 비활성화한 것입니다. 지금까지 설명하지 않은 MF_ 계열의 메시지 중에 MF_GRAYED는 메뉴를 비활성화합니다. 활성화하고자 할 경우에는 MF_ENABLED 플러그를 사용하면 됩니다.

(2) “닫기” 항목 선택시 메뉴 지우기

닫기 항목을 선택하면 그림 1-6-5의 ⓒ 메뉴를 삭제합니다. 이때 ⓒ 메뉴가 설정되어 있을 경우에만 삭제합니다. m_bOpen은 ⓒ 메뉴가 설정되어 있는 것을 알 수 있는 플러그입니다. 플러그가 TRUE일 경우에는 ⓒ 메뉴가 설정되어 있는 상태이므로 이때에는 메뉴를 삭제합니다. ClassWizard를 이용하여 “닫기” 항목을 선택하였을 때 함수를 만듭니다. 이곳에서 그림 1-6-5의 ⓒ 메뉴를 삭제하는 내용은 다음과 같습니다.

//닫기 항목을 선택하면

void CMainFrame::OnCloseFile()

{

//현재 삽입된 메뉴가 있으면

if(m_bOpen)

{

//메뉴를 삭제한다.

CMenu *pMainMenu;

pMainMenu=GetMenu();

pMainMenu->DeleteMenu(2,MF_POPUP|MF_BYPOSITION);

SetMenu(pMainMenu);

m_bOpen=FALSE;

}

}

(3) “데이터” 메뉴 선택시에 “수정”, “삭제” 항목 활성화

“데이터” 메뉴의 ID_SET_AVI와 ID_SET_VOICE, 그리고 ID_SET_BMP는 ClassWizard를 이용하여 메뉴 선택시의 함수를 만들 수 없습니다. 이때는 직접 코딩해주어야 합니다. 먼저 헤더에 다음과 같이 설정합니다.

afx_msg void OnSetAvi();// “동영상” 메뉴 선택시

afx_msg void OnSetVoice();// “음성” 메뉴 선택시

afx_msg void OnSetBmp();// “그림파일” 메뉴 선택시

위와 같이 선언한 후에 메시지 매핑 매크로에 다음과 같이 설정합니다.

ON_COMMAND(ID_SET_AVI, OnSetAvi)

ON_COMMAND(ID_SET_VOICE, OnSetVoice)

ON_COMMAND(ID_SET_BMP, OnSetBmp)

위와 같이 선언하고 세 개의 함수를 만들어 그 안에서 “수정”, “삭제” 항목을 활성화하면 됩니다. 다음은 위에 설정한 세 개의 함수입니다. 이 함수에서는 현재 선택한 항목을 메시지 상자로 출력하고 “수정”, “삭제” 항목을 활성화합니다.

void CMainFrame::OnSetAvi()

{

AfxMessageBox(“동영상 선택”);

CMenu *pMainMenu;

pMainMenu = new CMenu;

pMainMenu=GetMenu();

//메뉴를 활성화한다.

pMainMenu->

EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_ENABLED);

pMainMenu->

EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_ENABLED);

}

void CMainFrame::OnSetVoice()

{

AfxMessageBox(“음성 선택”);

CMenu *pMainMenu;

pMainMenu=GetMenu();

//메뉴를 활성화한다.

pMainMenu->

EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_ENABLED);

pMainMenu->

EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_ENABLED);

}

void CMainFrame::OnSetBmp()

{

AfxMessageBox(“그림파일 선택”);

CMenu *pMainMenu;

pMainMenu=GetMenu();

//메뉴를 활성화한다.

pMainMenu->

EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_ENABLED);

pMainMenu->

EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_ENABLED);

}

(4) “라디오”, “체크” 항목 상태 바꾸기

“라디오” 메뉴 항목을 한 번 클릭하면 라디오 버튼이 설정되고 다시 클릭하면 해제되며, “체크” 메뉴 항목을 클릭하면 체크되고 다시 클릭하면 해제되는 내용을 만듭니다. 이것은 ClassWizard에서 “라디오”, “체크” 메뉴의 COMMAND 메뉴와 UPDATE_COMMAND_UI를 만들고 이 안에 다음과 같이 코딩하면 됩니다.

//체크 메뉴

void CMainFrame::OnCheckmenu()

{

m_bCheck=1-m_bCheck;

}

void CMainFrame::OnUpdateCheckmenu(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(m_bCheck);

}

//라디오 메뉴

void CMainFrame::OnRadiomenu()

{

m_bRadio=1-m_bRadio;

}

void CMainFrame::OnUpdateRadiomenu(CCmdUI* pCmdUI)

{

pCmdUI->SetRadio(m_bRadio);

}

두 개의 m_bRadio와 m_bCheck 변수는 헤더에 BOOL 형태로 설정되어 있습니다. 항목을 누를 때마다 0, 1이 교차되면서 값이 설정되고 OnUpdate 계열 함수에서는 이 값에 따라 각각 SetCheck와 SetRadio 함수가 실행되기 때문에 토글 형식으로 설정되는 것입니다.

(5) “잘라내기”, “복사” 항목을 선택하면 “취소” 항목이 활성

“편집” 메뉴에서 “잘라내기”와 “복사” 항목을 선택하면 “취소” 항목이 활성화되고 “취소” 항목이 선택되면 다시 “취소” 항목이 비활성화됩니다. 이것은 먼저 m_bCopy를 BOOL 형태로 헤더에 설정하고 “잘라내기”, “복사” 항목을 선택하면 이 값이 TRUE로, 그리고 “취소” 항목을 선택하면 이 값이 FALSE로 설정된 후에 “취소” 항목의 UPDATE_COMMAND_UI 메시지 발생시 수행하는 함수를 ClassWizard를 이용해서 만든 다음, m_bCopy값에 따라서 활성/비활성을 설정하면 됩니다. 이 내용은 다음과 같습니다.

//잘라내기 항목 선택

void CMainFrame::OnEditCut()

{

// TODO: Add your command handler code here

m_bCopy=TRUE;

}

//복사하기 항목 선택

void CMainFrame::OnEditCopy()

{

// TODO: Add your command handler code here

m_bCopy=TRUE;

}

void CMainFrame::OnEditPaste()

{

// TODO: Add your command handler code here

m_bCopy=FALSE;

}

//취소 항목 선택

void CMainFrame::OnEditUndo()

{

// TODO: Add your command handler code here

m_bCopy=FALSE;

}

위에서 설명하지 않았으나 붙여넣기를 설정할 때도 m_bCopy를 FALSE로 설정하였습니다.

메뉴 구성 예제 ExMenu 소스

ExMenu는 메인프레임에서만 코딩하였습니다. 다음은 ExMenu의 메인프레임 소스와 헤더 부분입니다.

// MainFrm.h : interface of the CMainFrame class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__66F2AA5B_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

#define AFX_MAINFRM_H__66F2AA5B_2895_11D2_8B70_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

BOOL m_bRadio;

BOOL m_bCheck;

BOOL m_bCopy;

BOOL m_bOpen;

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnEditCut();

afx_msg void OnEditCopy();

afx_msg void OnEditPaste();

afx_msg void OnEditUndo();

afx_msg void OnUpdateEditUndo(CCmdUI* pCmdUI);

afx_msg void OnFileNew();

afx_msg void OnFileOpen();

afx_msg void OnCloseFile();

afx_msg void OnCheckmenu();

afx_msg void OnUpdateCheckmenu(CCmdUI* pCmdUI);

afx_msg void OnRadiomenu();

afx_msg void OnUpdateRadiomenu(CCmdUI* pCmdUI);

afx_msg void OnUpdateFileNew(CCmdUI* pCmdUI);

//}}AFX_MSG

afx_msg void OnSetAvi();//“동영상” 메뉴 선택시

afx_msg void OnSetVoice();//“음성” 메뉴 선택시

afx_msg void OnSetBmp();//“그림파일” 메뉴 선택시

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__66F2AA5B_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include “stdafx.h”

#include “ExMenu.h”

#include “MainFrm.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_COMMAND(ID_EDIT_CUT, OnEditCut)

ON_COMMAND(ID_EDIT_COPY, OnEditCopy)

ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)

ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)

ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)

ON_COMMAND(ID_FILE_NEW, OnFileNew)

ON_COMMAND(ID_FILE_OPEN, OnFileOpen)

ON_COMMAND(ID_CLOSE_FILE, OnCloseFile)

ON_COMMAND(ID_CHECKMENU, OnCheckmenu)

ON_UPDATE_COMMAND_UI(ID_CHECKMENU, OnUpdateCheckmenu)

ON_COMMAND(ID_RADIOMENU, OnRadiomenu)

ON_UPDATE_COMMAND_UI(ID_RADIOMENU, OnUpdateRadiomenu)

ON_UPDATE_COMMAND_UI(ID_FILE_NEW, OnUpdateFileNew)

//}}AFX_MSG_MAP

ON_COMMAND(ID_SET_AVI, OnSetAvi)

ON_COMMAND(ID_SET_VOICE, OnSetVoice)

ON_COMMAND(ID_SET_BMP, OnSetBmp)

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

/////////////////////////////////////////////////////////////////////////////

// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

m_bCopy=FALSE;

m_bOpen=FALSE;

m_bCheck=FALSE;

m_bRadio=FALSE;

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT,

WS_CHILD | WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

// TODO: Delete these three lines if you don’t want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

//////////////////////////////////////////////////////////////////////////// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////// CMainFrame message handlers

//잘라내기 항목 선택

void CMainFrame::OnEditCut()

{

// TODO: Add your command handler code here

m_bCopy=TRUE;

}

//복사하기 항목 선택

void CMainFrame::OnEditCopy()

{

// TODO: Add your command handler code here

m_bCopy=TRUE;

}

void CMainFrame::OnEditPaste()

{

// TODO: Add your command handler code here

m_bCopy=FALSE;

}

//취소 항목 선택

void CMainFrame::OnEditUndo()

{

// TODO: Add your command handler code here

m_bCopy=FALSE;

}

void CMainFrame::OnUpdateEditUndo(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

if(m_bCopy)

{

pCmdUI->Enable();

}

else

pCmdUI->Enable(FALSE);

}

void CMainFrame::OnFileNew()

{

//메뉴가 만들어지지 않았으면

if(!m_bOpen)

{

CMenu *pMainMenu,subMenu;

//메뉴를 얻는다.

pMainMenu=GetMenu();

//메뉴를 만든다.

subMenu.CreateMenu();

//만든 메뉴에 항목 추가

subMenu.AppendMenu(MF_STRING,ID_SET_BMP,”그림파일”);

subMenu.AppendMenu(MF_STRING,ID_SET_AVI,”동영상”);

subMenu.AppendMenu(MF_STRING,ID_SET_VOICE,”음성”);

subMenu.AppendMenu(MF_SEPARATOR);

subMenu.AppendMenu(MF_STRING,ID_SET_MODE,”수정”);

subMenu.AppendMenu(MF_STRING,ID_SET_DEL,”삭제”);

//만든 메뉴를 메인 메뉴에 삽입한다.

pMainMenu->InsertMenu(2,MF_POPUP|MF_BYPOSITION

,(UINT)subMenu.m_hMenu,”데이터”);

m_bOpen=TRUE;

//메인 메뉴를 설정한다.

SetMenu(pMainMenu);

pMainMenu->EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_GRAYED  );

pMainMenu->EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_GRAYED  );

}

}

void CMainFrame::OnSetAvi()

{

AfxMessageBox(“동영상 선택”);

CMenu *pMainMenu;

pMainMenu = new CMenu;

pMainMenu=GetMenu();

//메뉴를 활성화한다.

pMainMenu->EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_ENABLED);

pMainMenu->EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_ENABLED);

}

void CMainFrame::OnSetVoice()

{

AfxMessageBox(“음성 선택”);

CMenu *pMainMenu;

pMainMenu=GetMenu();

//메뉴를 활성화한다.

pMainMenu->EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_ENABLED);

pMainMenu->EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_ENABLED);

}

void CMainFrame::OnSetBmp()

{

AfxMessageBox(“그림파일 선택”);

CMenu *pMainMenu;

pMainMenu=GetMenu();

//메뉴를 활성화한다.

pMainMenu->EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_ENABLED);

pMainMenu->EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_ENABLED);

}

void CMainFrame::OnFileOpen()

{

if(!m_bOpen)

{

CMenu *pMainMenu,subMenu;

pMainMenu=GetMenu();

subMenu.CreateMenu();

subMenu.AppendMenu(MF_STRING,ID_SET_BMP,”그림파일”);

subMenu.AppendMenu(MF_STRING,ID_SET_AVI,”동영상”);

subMenu.AppendMenu(MF_STRING,ID_SET_VOICE,”음성”);

subMenu.AppendMenu(MF_SEPARATOR);

subMenu.AppendMenu(MF_STRING,ID_SET_MODE,”수정”);

subMenu.AppendMenu(MF_STRING,ID_SET_DEL,”삭제”);

pMainMenu->InsertMenu(2,MF_POPUP|MF_BYPOSITION

,(UINT)subMenu.m_hMenu,”데이터”);

m_bOpen=TRUE;

SetMenu(pMainMenu);

pMainMenu->EnableMenuItem(ID_SET_MODE,MF_BYCOMMAND|MF_GRAYED  );

pMainMenu->EnableMenuItem(ID_SET_DEL,MF_BYCOMMAND|MF_GRAYED  );

}

}

//닫기 항목을 선택하면

void CMainFrame::OnCloseFile()

{

//현재 삽입된 메뉴가 있으면

if(m_bOpen)

{

//메뉴를 삭제한다.

CMenu *pMainMenu;

pMainMenu=GetMenu();

pMainMenu->DeleteMenu(2,MF_POPUP|MF_BYPOSITION);

SetMenu(pMainMenu);

m_bOpen=FALSE;

}

}

//체크 메뉴

void CMainFrame::OnCheckmenu()

{

m_bCheck=1-m_bCheck;

}

void CMainFrame::OnUpdateCheckmenu(CCmdUI* pCmdUI)

{

pCmdUI->SetCheck(m_bCheck);

}

//라디오 메뉴

void CMainFrame::OnRadiomenu()

{

m_bRadio=1-m_bRadio;

}

void CMainFrame::OnUpdateRadiomenu(CCmdUI* pCmdUI)

{

pCmdUI->SetRadio(m_bRadio);

}

void CMainFrame::OnUpdateFileNew(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

}

비트맵 메뉴

MFC를 이용하여 비트맵 메뉴를 만드는 방법은 매우 간단합니다. 지금까지 메뉴를 추가, 삭제, 수정하는 CMenu 함수를 설명하였습니다. 이 CMenu 함수는 비트맵 메뉴를 설정하는 항목과 문자열을 설정하는 두 개의 항목으로 나누어져 있습니다. 만일 비트맵으로 메뉴를 설정하고자 한다면 CMenu 함수에서 비트맵을 설정하는 항목을 이용하면 됩니다. 예를 들어 현재 메뉴 중에서 세 개 항목, 즉 ID_FILE_OPEN, ID_FILE_SAVE , ID_FILE_NEW 항목을 비트맵으로 수정하고자 한다면 다음과 같이 할 수 있습니다.

//메뉴에 설정할 비트맵을 로드한다.

bFileOpen.LoadBitmap(IDB_FILEOPEN);

bFileNew.LoadBitmap(IDB_FILENEW);

bFileSave.LoadBitmap(IDB_FILESAVE);

//현재 메뉴를 받는다.

CMenu *pMenu;

pMenu=GetMenu();

//메뉴를 비트맵으로 수정한다.

pMenu->ModifyMenu(ID_FILE_OPEN,MF_BYCOMMAND|

MF_BITMAP,ID_FILE_OPEN,&bFileOpen);

pMenu->ModifyMenu(ID_FILE_NEW,MF_BYCOMMAND|

MF_BITMAP,ID_FILE_NEW,&bFileNew);

pMenu->ModifyMenu(ID_FILE_SAVE,MF_BYCOMMAND|

MF_BITMAP,ID_FILE_SAVE,&bFileSave);

//메뉴를 설정한다.

SetMenu(pMenu);

ExOwDrawMenu는 메뉴에서 위의 세 개의 항목을 비트맵으로 설정한 예입니다. 이렇게 함으로써 간단하게 비트맵 메뉴를 설정할 수가 있습니다.

콘텍스트 메뉴

콘텍스트 메뉴란 보통 우측 마우스 버튼을 클릭하거나 할 때 팝업 메뉴가 마우스 버튼 위치에 설정되는 형태의 메뉴를 의미합니다. 그림 1-6-6은 Visual C++에서 우측 마우스 버튼을 클릭하여 콘텍스트 메뉴를 설정한 형태입니다.

그림 1-6-6과 같은 콘텍스트 메뉴를 설정하는 방법 또한 간단합니다. CMenu 클래스의 변수를 설정하고 이곳에 메뉴 항목을 설정한 다음 CMenu의 멤버 함수 TrackPopupMenu를 실행시키면 됩니다. TrackPopupMenu 함수는 다음과 같은 형태를 가집니다.

CMenu::TrackPopupMenu

BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );

nFlags는 팝업 메뉴의 형태를 설정하는 스타일 플러그입니다. 이 플러그는 TPM_ 계열의 플러그를 가지고 있는데, TPM_CENTERALIGN은 마우스 위치의 중앙으로 메뉴가 설정되는 형태이고, TPM_LEFTALIGN은 마우스 위치 좌측으로 메뉴가 정렬되는 형태입니다. 같은 방법으로 TPM_RIGHTALIGN은 메뉴가 마우스 위치 우측으로 정렬되는 형태입니다. 이 세 가지 형태 중 하나를 설정하면 해당 위치로 Track- Popup 메뉴가 설정됩니다.

TPM_LEFTBUTTON을 이 플러그에 설정하면 좌측 마우스 버튼이 클릭되었을 때 팝업 메뉴가 설정되는 형태이며, TPM_RIGHTBUTTON을 설정하면 우측 마우스 버튼이 클릭되었을 때 팝업 메뉴가 설정되는 형태입니다. 좌측 마우스 버튼을 많이 사용하기 때문에 일반적으로는 우측 마우스 버튼을 클릭하였을 때 TrackPopup 메뉴가 설정되도록 합니다.

x, y는 현재 마우스 위치를 의미합니다. 물론 마우스가 눌려졌을 때 팝업 메뉴가 이만큼 떨어져서 출력되게 하려면 마우스 위치가 아닌 특정 위치에 메뉴를 설정할 수 있습니다. 그러나 사용자 측면에서 볼 때 이 방법은 매우 불편합니다. 보통 마우스 위치를 x, y에 설정하면 됩니다. pWnd는 부모 윈도 핸들러이며 lpRect는 팝업 메뉴를 무시하는 특정 스크린 영역 좌표를 설정해주는 항목입니다.

위의 함수를 아무곳에서나 사용하는 것은 아닙니다. 즉 콘텍스트 메뉴가 필요할 경우에 출력하면 됩니다. 사용자가 우측 마우스 버튼을 클릭하였을 경우 우측 마우스 버튼 눌림과 함께 WM_CONTEXTMENU라는 메시지가 발생합니다. ClassWizard에서 이 메시지에 의한 함수를 만들면 다음과 같습니다.

void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint point)

{

}

위 함수 안에 TrackPopupMenu 함수를 이용하여 메뉴를 만들면 됩니다. pWnd는 TrackPopupMenu에 설정할 부모 윈도 포인터이며, point는 현재 마우스 위치가 설정되어 있습니다. OnContextMenu 함수에서 팝업 메뉴의 첫 번째 항목을 콘텍스트 메뉴로 출력하고자 할 때는 다음과 같이 할 수 있습니다.

void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint point)

{

//메뉴를 얻는다.

CMenu *pMenu,*subMenu;

pMenu=GetMenu();

//현재 메뉴의 첫 번째 서브 메뉴를 얻는다.

subMenu=pMenu->GetSubMenu(0);

//첫 번째 서브 메뉴를 콘텍스트 메뉴로 설정한다.

subMenu->TrackPopupMenu

(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this);

}

위와 같이 간단하게 콘텍스트 메뉴를 설정할 수 있습니다.

예제 ExOwDrawMenu

ExOwDrawMenu는 비트맵 메뉴와 콘텍스트 메뉴를 설정한 예입니다. 이 프로그램의 전체 형태는 그림 1-6-7과 같습니다.

ExOwDrawMenu는 지금까지 설명한 비트맵 메뉴와 콘텍스트 메뉴를 그대로 적용한 것입니다. 본 예제는 메인프레임에서만 코딩되었고 메인프레임의 소스 부분만 보면 이해할 수 있을 것입니다.

다음은 메인프레임의 소스 부분입니다. 나머지 부분은 제공되는 CD를 참조하기 바랍니다.

// MainFrm.cpp : implementation of the CMainFrame class

//

#include “stdafx.h”

#include “ExOwDrawMenu.h”

#include “MainFrm.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////

// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_WM_CONTEXTMENU()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

//////////////////////////////////////////////////////////////////////////// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |

WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

// TODO: Delete these three lines if you don’t want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

//메뉴에 설정할 비트맵을 로드한다.

bFileOpen.LoadBitmap(IDB_FILEOPEN);

bFileNew.LoadBitmap(IDB_FILENEW);

bFileSave.LoadBitmap(IDB_FILESAVE);

//현재 메뉴를 받는다.

CMenu *pMenu;

pMenu=GetMenu();

//메뉴를 비트맵으로 수정한다.

pMenu->ModifyMenu

(ID_FILE_OPEN,MF_BYCOMMAND|MF_BITMAP,ID_FILE_OPEN,&bFileOpen);

pMenu->ModifyMenu

(ID_FILE_NEW,MF_BYCOMMAND|MF_BITMAP,ID_FILE_NEW,&bFileNew);

pMenu->ModifyMenu

(ID_FILE_SAVE,MF_BYCOMMAND|MF_BITMAP,ID_FILE_SAVE,&bFileSave);

//메뉴를 설정한다.

SetMenu(pMenu);

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

//////////////////////////////////////////////////////////////////////////// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////// CMainFrame message handlers

void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint point)

{

//메뉴를 얻는다.

CMenu *pMenu,*subMenu;

pMenu=GetMenu();

//현재 메뉴의 첫 번째 서브 메뉴를 얻는다.

subMenu=pMenu->GetSubMenu(0);

//첫 번째 서브 메뉴를 콘텍스트 메뉴로 설정한다.

subMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point. x,point.y,this);

}

툴바는 메뉴 하단에 위치한 작은 바로서 본 장의 처음에 설명한 것과 같이 메뉴와 함께 구동되면서 항목 선택을 편리하게 하기 위해서 만든 것입니다. 툴바 안에는 작은 아이콘들이 있으며 이 아이콘들을 클릭하면 메뉴를 선택한 것과 같이 WM_COMMAND 메시지가 발생되고, 이 메시지와 함께 들어오는 정보를 이용하여 Classwizard로 특정 항목을 선택하였을 때의 함수를 만들 수 있습니다.  AppWizard를 이용하여 프로젝트를 만들면 기본적인 툴바가 리소스에 만들어집니다. 또한 새로운 툴바를 만들고자 한다면 Ctrl+6 키를 눌러서 새로운 툴바를 만들 수도 있습니다. 툴바에 대한 전체 형태는 그림 1-6-8과 같습니다.

그림 1-6-8은 AppWizard를 이용하여 프로젝트를 만들고 Projectworkspace에서 ResourceView 항목을 선택하고 툴바 자원을 선택하였을 때 나타나는 항목입니다. 그림 1-6-8의 ① 항목이 바로 툴바의 자원 ID입니다. 이런 툴바 자원을 새로 만들고자 한다면 Ctrl+6 키를 누르면 됩니다. 이렇게 툴바를 만들고 여러분이 원하는 형태의 그림을 툴바 아이콘으로 도시할 수 있습니다.

그림 1-6-8의 ② 항목은 특정 툴바에서 아이콘을 선택하고 속성 대화 상자를 출력한 형태입니다. 이때 ID는 이 아이콘의 ID를 의미합니다. SDK로 프로그래밍하면 WM_COMMAND와 함께 wParam에 이 ID가 설정됩니다. 이런 방법을 모두 합쳐서 해당 함수를 만들어 주는 것이 ClassWizard입니다.

그림 1-6-8의 ③ 항목과 같이 속성 대화 상자에서 Prompt 항목의 \n 다음에 설정된 문자열은 툴바 아이콘 위치에 마우스가 있을 경우 풍선 도움말이 설정되는 문자열입니다. SDK에서는 툴바를 만들 경우 비트맵을 만들고, 이것을 로드하여 설정해주고, 풍선 도움말까지 직접 만들었으나 MFC에서는 이것이 모두 자동적으로 처리됩니다. 물론 비트맵을 가지고 직접 툴바에 설정하고 버튼을 만드는 방법도 가능합니다. 이 부분을 포함하여 자동화하였다는 의미입니다. 이렇게 툴바 자원을 만들면 이 자원을 운용할 클래스가 필요합니다. 바로 이 클래스가 CToolBar입니다. 이 클래스를 이용하여 현재 자원을 로드하고 메인프레임에 툴바를 설정하며 도킹과 플로팅하게 되는 것입니다.

툴바 로드하기

툴바를 핸들링하는 클래스가 CToolBar라고 하였습니다. 이 클래스를 이용하여 툴바를 로드하는 형태는 먼저 그림을 보는 것이 편리할 것 같습니다. 그림 1-6-9는 툴바를 로드하는 방법을 설명한 것입니다.

그림 1-6-9가 툴바를 로드하고 컨트롤하는 핵심부가 될 것입니다. 그림 1-6-9의 형태를 이해한다면 기본적인 툴바를 로드하는 부분에 대해서는 더 이상 설명이 필요 없을 것입니다.

먼저 툴바를 로드하기 위해서는 툴바를 컨트롤할 클래스 변수를 설정합니다. 이렇게 설정하는 항목이 그림 1-6-9의 ① 항목입니다. 이렇게 툴바를 만들고 난 후에 CreateEx 함수를 이용하여 툴바 윈도를 만듭니다. Create 함수를 이용해서 만들어도 상관은 없으나 AppWizard로 프로젝트를 만들면 자동적으로 만들어진 툴바에서 CreateEx 함수를 사용하였기에 필자 또한 CreateEx 함수를 선호합니다.

이 함수의 첫 번째 인자는 부모 윈도 클래스 포인터이며, 두 번째 인자는 툴바의 컨트롤 삽입 스타일입니다. 이것은 툴바 자체가 CToolBarCtrl 클래스를 삽입할 때의 툴바 컨트롤이 어떤 스타일로 삽입될 것인가를 의미합니다. 기본적으로는 TBSTYLE_FLAT를 사용합니다. 세 번째 인자가 툴바 윈도 스타일입니다. WS_ 계열은 윈도 스타일이며 CBRS_ 계열이 툴바 스타일입니다. WS_ 계열의 스타일은 자식 형태와 화면에 보이는 형태를 기본적으로 사용하고 나머지는 CBRS_ 계열의 스타일로 현재 툴바의 스타일을 설정합니다.

CBRS_TOP은 툴바가 도킹되어 나타날 경우 화면 상단에 붙어서 나타난다는 의미이며, CBRS_GRIPPER는 스냅 단추 형태, 즉 인터넷 익스플로러와 같은 형태의 툴바를 설정할 때 사용하는 스타일이며, CBRS_TOOLTIPS는 풍선 도움말을 설정할 수 있는 스타일 플러그이며, CBRS_FLYBY의 의미는 풍선 도움말의 내용이 바로 갱신되면서 설정되는 플러그입니다.

CBRS_SIZE_DYNAMIC은 툴바가 플로팅되었을 때(즉 툴바가 떨어져 있을 때), 이때 툴바의 크기가 자유롭게 변화된다는 의미입니다. CBRS_SIZE_DYNAMIC이라는 의미를 부과하였다고 툴바 윈도의 크기가 변한다는 것이 아니라 툴바의 배열이 한 줄에서 두 줄로, 또는 세 줄 형태 등의 여러 줄로 나누어질 수 있다는 의미입니다. MFC 예제에서 툴바를 떨어뜨리고 크기를 변화시키면 느낄 수 있을 것입니다. 처음에는 한 줄 형태이나 마우스를 이용하여 여러 줄 형태로 나눌 수 있습니다. 이런 스타일 플러그가 CBRS_SIZE_DYNAMIC입니다. 이 세 개의 인자를 사용하여 툴바를 만든 다음 자원에 설정되어 있는 툴바 ID를 로드합니다. 이때 사용하는 함수가 LoadToolBar입니다. 이 함수의 자원에 있는 툴바 ID를 설정하면 로드되는 것입니다.

그림에서 ②와 ③ 항목이 지금까지 설명한 내용입니다. 이렇게 되면 툴바가 만들어지는데, 툴바에 도킹과 플로팅을 마음대로 할 수 있는 선언이 필요합니다. 도킹이란 툴바가 프레임 윈도에 붙는 것을 의미하며, 플로팅이란 툴바가 프레임 윈도에서 떨어지는 것을 의미합니다. 이렇게 도킹과 플로팅을 자유롭게 하기 위해서는 먼저 툴바를 도킹과 플로팅이 가능한 상태로 만들어주어야 합니다. 그림 1-6-9의 ④ 항목이 도킹과 플로팅을 자유롭게 하는 방법을 설명한 것입니다.

EnableDocking은 도킹이 가능하도록 설정해주는 컨트롤바와 프레임 윈도의 멤버 함수입니다. 이 함수에 인자로 CBRS_ 계열의 스타일 플러그를 설정할 수 있는데, CBRS_ALIGN_TOP은 도킹될 때 언제나 프레임 윈도 상단에 붙는 것을 의미하며, CBRS_ALIGN_BOTTOM의 하단부 CBRS_ALIGN_RIGHT는 우측으로, CBRS_ALIGN_LEFT는 좌측으로 도킹되는 형태로 설정하는 것입니다. 이전에 도킹되었던 위치로 자유롭게 도킹이 되게 하려면 CBRS_ALIGN_ANY라는 플러그를 사용하면 됩니다. 이와 같이 툴바를 도킹이 가능한 상태로 만든 다음 프레임 윈도에서도 이와 똑같은 함수를 이용하여 도킹이 가능하도록 해야 합니다. 그림 1-6-9의 ⑤ 항목이 프레임 윈도에서 도킹이 가능한 상태로 만든 것입니다. 이렇게 되면 툴바를 프레임 윈도에 붙일 수 있으며 또한 떨어뜨릴 수 있습니다.(툴바를 프레임 윈도에 붙이는 것을 도킹, 떨어뜨리는 것을 플로팅이라고 하겠습니다.)

툴바를 프레임 윈도에 도킹시키고자 한다면 DockControlBar 함수를 사용하면 됩니다. 그림 1-6-9의 ⑥ 항목이 툴바를 프레임 윈도에 도킹시킨 형태입니다. DockControlBar는 프레임 윈도 함수로써 이 함수의 인자에 툴바 윈도 포인터를 넘겨주면 해당 툴바를 도킹시켜 줍니다. 이와 대조되는 함수가 FloatControlBar입니다. 이 함수는 현재 도킹되어 있는 툴바를 다시 플로팅시켜 주는 것입니다. 그림 1-6-9의 ⑦ 항목이 바로 툴바를 플로팅시키는 방법을 설명한 것입니다.

도킹할 때 두 번째 인자를 사용하지 않으면 이전 형태로 도킹됩니다. 만일 이때 AFX_IDW_DOCKBAR_ 계열의 함수를 사용하면 도킹할 때 프레임 윈도의 특정 위치로 도킹시킬 수 있습니다. AFX_IDW_DOCKBAR_LEFT라고 하면 프레임 윈도 좌측에, AFX_IDW_DOCKBAR_TOP이라고 하면 상단부에, AFX_IDW_- DOCKBAR_BOTTOM이라고 하면 하단부에, AFX_IDW_DOCKBAR_RIGHT라고 하면 하단부 오른쪽에 도킹됩니다.

FloatControlBar에서의 두 번째 인자와 세 번째 인자는 플로팅될 때 플로팅되는 스크린의 x, y 좌표를 의미합니다. 이 좌표는 의미대로 사용할 수 있으며 애플리케이션 프로그램에서 플로팅 메뉴를 사용하여 플로팅시키면 이전에 플로팅되었던 위치를 기억하고 있다가 그 위치값을 그대로 설정해주는 방법을 주로 사용합니다.

기본적인 툴바 예제 ExToolBar

ExToolBar는 지금까지 설명한 방법을 예제로 작성한 것입니다. 그림 1-6-10은 ExToolBar의 전체 구조입니다.

ExToolBar 예제는 새로운 툴바를 하나 만들고 이 툴바를 도킹과 플로팅을 하는 예입니다. 지금까지 설명한 방법을 참조하면 쉽게 이해할 수 있을 것입니다.

먼저 그림 1-6-10의 ⓐ 항목에 해당하는 내용의 코딩입니다. 이 코딩은 메인프레임의 OnCrate 함수에서 합니다. OnCreate 함수 안에는 이미 기본적으로 설정된 툴바가 만들어져 있고 도킹되어 있습니다. 이 내용 사이에 ⓐ 항목을 설정하면 됩니다. 다음은 ExToolBar 예제의 메인프레임에서 OnCreate 함수의 내용입니다.

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

//툴바를 만든다.

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |

WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

//또 다른 툴바를 만든다. ⓐ 항목

if (!m_wndAnotherBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |

WS_VISIBLE |CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndAnotherBar.LoadToolBar(IDR_ANOTHERBAR))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

// TODO: Delete these three lines if you don’t want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

//툴바를 도킹이 가능하게 한다. ⓐ 항목

m_wndAnotherBar.EnableDocking(CBRS_ALIGN_ANY);

//프레임도 도킹이 가능하게 한다.

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

//툴바를 프레임 좌측에 도킹시킨다.

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_LEFT );

//툴바의 캡션 텍스트를 설정한다.

m_wndAnotherBar.SetWindowText(“또 다른 도구바”);

m_wndToolBar.SetWindowText(“도구바”);

return 0;

}

위의 내용은 전 항목의 내용을 그대로 이용한 것입니다. 여기에서 SetWindowText라는 함수는 툴바가 플로팅되었을 때 캡션에 들어갈 문자열을 설정하는 것입니다. SetWindowText 함수는 모든 윈도에 적용되는 CWnd 함수입니다. 따라서 툴바뿐만 아니라 특정 윈도의 캡션바에 문자열을 설정할 때 SetWindowText 함수를 이용할 수가 있습니다. 위와 같이 하면 그림 1-6-10의 ⓐ 항목처럼 새롭게 만들어진 툴바가 프레임 좌측에 붙습니다.

ⓑ 항목은 새롭게 만들어진 도구바를 화면에 나타내고 감추는 기능을 설정하는 메뉴입니다. 이때에는 CCmdUI의 SetCheck 함수를 이용하여 현재 툴바가 화면에 나타나면 체크되고 화면에서 감추어지면 체크가 해제되는 상태를 만듭니다. 이 내용은 다음과 같습니다.

//새로운 도구바의 메뉴 체크 상태 설정

void CMainFrame::OnUpdateAnotherbar(CCmdUI* pCmdUI)

{

//윈도가 화면에 나타나 있으면 체크

if(m_wndAnotherBar.IsWindowVisible())

pCmdUI->SetCheck();

//그렇지 않으면 체크하지 않는다.

else

pCmdUI->SetCheck(FALSE);

}

ClassWizard를 이용하여 “또 다른 도구바” 메뉴에서 UPDATE_COMMAND_UI 항목을 이용한 함수를 만든 것이 위의 OnUpdateAnotherbar입니다. 이 함수에서 CWnd의 함수인 IsWindowVisible 함수를 이용하여 현재 윈도가 화면에 나타나 있으면 체크시키고, 화면에 나타나 있지 않으면 체크를 해제합니다. IsWindowVisible 함수는 현재 윈도가 화면에 보이면 TRUE를, 그렇지 않으면 FALSE를 리턴합니다. 이제 “또 다른 도구바” 항목을 클릭했을 때 현재 도구바가 화면에 보이면 감추고 그렇지 않으면 보이는 항목을 만들어 보겠습니다. 이 내용은 다음과 같습니다.

//”또 다른 도구바” 항목을 선택하면

void CMainFrame::OnAnotherbar()

{

//화면에 보이면 감춘다.

if(m_wndAnotherBar.IsWindowVisible())

m_wndAnotherBar.ShowWindow(SW_HIDE);

//그렇지 않으면 보인다.

else

m_wndAnotherBar.ShowWindow(SW_SHOW);

}

ShowWindow 함수는 지금까지 많이 사용하였으므로 설명이 필요없을 것 같습니다.  OnAnoterbar는 “또 다른 도구바” 메뉴를 선택하였을 때의 함수를 ClassWizard를 이용하여 만든 것입니다. 이렇게 만든 함수에서 위에서처럼 IsWindowVisible 함수와 ShowWindow 함수를 이용하여 화면에 도구바를 보이거나 감출 수 있습니다.

그림 1-6-10의 ⓒ 항목을 선택하면 도구바를 플로팅합니다. 플로팅하는 방법은 전에 설명하였습니다. FloatControlBar 함수를 사용하면 됩니다. 다음은 ClassWizard에서 ⓒ 메뉴 항목을 선택하고 이때 플로팅되는 것을 설정한 내용입니다.

//도구바를 플로팅한다.

void CMainFrame::OnAbarFloat()

{

//현재 플로팅되어 있지 않은 상태이면

if(!m_wndAnotherBar.IsFloating())

{

//플로팅한다.

RECT rect;

GetClientRect(&rect);

FloatControlBar(&m_wndAnotherBar,

CPoint(rect.left+50,rect.top+50));

}

}

ⓒ 항목을 선택하였을 때 먼저 검사해야 할 부분이 있습니다. 그것은 현재 도구바가 플로팅이 되어 있는가입니다. 이렇게 플로팅이 되어 있을 경우에는 또다시 플로팅을 할 필요가 없습니다. IsFloating 함수는 컨트롤바 멤버 함수로서 현재 도구바가 플로팅이 되어 있을 경우에는 TRUE, 그렇지 않을 경우에는 FALSE를 리턴하게 됩니다. 만일 플로팅이 되어 있지 않다면, 즉 도킹되어 있는 상태이면 현재 프레임 윈도의 클라이언트 영역의 좌표를 얻은 다음 클라이언트 영역의 좌측 상단부에서 50, 50의 위치만큼 떨어진 곳에 플로팅한 것입니다. 이렇게 하면 그림 1-6-10의 ⓒ 항목을 선택하였을 때 클라이언트 영역 상단부에 플로팅이 됩니다.

ⓓ∼ⓖ까지의 항목은 도킹하는 항목입니다. 도킹할 때 임의의 도킹이 아니라 좌측, 상단, 우측, 하단부에 도킹하는 것입니다. ⓓ∼ⓖ까지의 항목을 선택하였을 때 도킹되는 내용은 다음과 같습니다.

//좌측 도킹

void CMainFrame::OnAbarDockingleft()

{

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_LEFT );

}

//우측 도킹

void CMainFrame::OnAbarDockingright()

{

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_RIGHT );

}

//상단 도킹

void CMainFrame::OnAbarDockingtop()

{

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_TOP );

}

//하단 도킹

void CMainFrame::OnAbarDockingbottom()

{

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_BOTTOM );

}

위와 같이 하면 ExToolBar 예제가 완성됩니다.

기본적인 툴바 예제 ExToolBar 소스

ExToolBar는 메인프레임에서만 작업하였습니다. 다음은 메인프레임의 헤더와 소스 부분입니다.

// MainFrm.h : interface of the CMainFrame class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__66F2AA87_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

#define AFX_MAINFRM_H__66F2AA87_2895_11D2_8B70_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

CToolBar m_wndAnotherBar;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnEditUndo();

afx_msg void OnEditPaste();

afx_msg void OnEditCut();

afx_msg void OnEditCopy();

afx_msg void OnAnotherbar();

afx_msg void OnUpdateAnotherbar(CCmdUI* pCmdUI);

afx_msg void OnAbarFloat();

afx_msg void OnAbarDockingleft();

afx_msg void OnAbarDockingright();

afx_msg void OnAbarDockingtop();

afx_msg void OnAbarDockingbottom();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately

// before the previous line.

#endif // !defined(AFX_MAINFRM_H__66F2AA87_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include “stdafx.h”

#include “ExToolBar.h”

#include “MainFrm.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)

ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)

ON_COMMAND(ID_EDIT_CUT, OnEditCut)

ON_COMMAND(ID_EDIT_COPY, OnEditCopy)

ON_COMMAND(ID_ANOTHERBAR, OnAnotherbar)

ON_UPDATE_COMMAND_UI(ID_ANOTHERBAR, OnUpdateAnotherbar)

ON_COMMAND(ID_ABAR_FLOAT, OnAbarFloat)

ON_COMMAND(ID_ABAR_DOCKINGLEFT, OnAbarDockingleft)

ON_COMMAND(ID_ABAR_DOCKINGRIGHT, OnAbarDockingright)

ON_COMMAND(ID_ABAR_DOCKINGTOP, OnAbarDockingtop)

ON_COMMAND(ID_ABAR_DOCKINGBOTTOM, OnAbarDockingbottom)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

//////////////////////////////////////////////////////////////////////////// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |

CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

if (!m_wndAnotherBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD

| WS_VISIBLE |CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndAnotherBar.LoadToolBar(IDR_ANOTHERBAR))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

// TODO: Delete these three lines if you don’t want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

m_wndAnotherBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_LEFT );

m_wndAnotherBar.SetWindowText(“또 다른 도구바”);

m_wndToolBar.SetWindowText(“도구바”);

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

/////////////////////////////////////////////////////////////////////////////

// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////// CMainFrame message handlers

void CMainFrame::OnEditUndo()

{

// TODO: Add your command handler code here

}

void CMainFrame::OnEditPaste()

{

// TODO: Add your command handler code here

}

void CMainFrame::OnEditCut()

{

// TODO: Add your command handler code here

}

void CMainFrame::OnEditCopy()

{

// TODO: Add your command handler code here

//”또 다른 도구바” 항목을 선택하면

void CMainFrame::OnAnotherbar()

{

//화면에 보이면 감춘다.

if(m_wndAnotherBar.IsWindowVisible())

m_wndAnotherBar.ShowWindow(SW_HIDE);

//그렇지 않으면 보인다.

else

m_wndAnotherBar.ShowWindow(SW_SHOW);

}

//새로운 도구바의 메뉴 체크 상태 설정

void CMainFrame::OnUpdateAnotherbar(CCmdUI* pCmdUI)

{

//윈도가 화면에 나타나 있으면 체크

if(m_wndAnotherBar.IsWindowVisible())

pCmdUI->SetCheck();

//그렇지 않으면 체크하지 않는다.

else

pCmdUI->SetCheck(FALSE);

}

//도구바를 플로팅한다.

void CMainFrame::OnAbarFloat()

{

//현재 플로팅되어 있지 않은 상태이면

if(!m_wndAnotherBar.IsFloating())

{

//플로팅한다.

RECT rect;

GetClientRect(&rect);

FloatControlBar(&m_wndAnotherBar,CPoint (rect.left+50 ,rect.top+50));

}

}

//좌측 도킹

void CMainFrame::OnAbarDockingleft()

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_LEFT );

}

//우측 도킹

void CMainFrame::OnAbarDockingright()

{

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_RIGHT );

}

//상단 도킹

void CMainFrame::OnAbarDockingtop()

{

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_TOP );

}

//하단 도킹

void CMainFrame::OnAbarDockingbottom()

{

DockControlBar(&m_wndAnotherBar,AFX_IDW_DOCKBAR_BOTTOM );

}

툴바에 콤보 상자 붙이기

본 항목에서는 툴바에 콤보 상자를 붙이는 방법에 대해서 설명하겠습니다. 툴바 또한 CWnd를 상속받은 윈도입니다. 이 윈도 안에는 또 하나의 윈도를 설정할 수 있습니다. 툴바 안에 콤보 상자뿐만 아니라 버튼 같은 다양한 형태의 컨트롤도 붙일 수 있습니다. 본 항목에서 설명하는 방법을 이해한다면 다양한 컨트롤을 포함하는 툴바를 만들 수 있을 것입니다.

(1) 툴바에 콤보 상자의 설정 방법

툴바에 콤보 상자를 붙이는 방법은 조금 복잡한 형태를 사용합니다. 기본적으로 툴바를 붙이고자 할 때는 그림 1-6-11과 같은 형태입니다.

그림 1-6-11의 ⓐ 항목은 자원에서 툴바를 만든 형태입니다. 이 툴바 안에 콤보 상자가 들어갈 곳을 설정합니다. 그림 1-6-11에서 ① 항목이 바로 콤보 상자가 들어갈 영역입니다. 이 영역의 ID를 ID_COMBOBOX라고 설정하였을 때 이곳에 콤보 상자를 만드는 예제는 그림 1-6-11입니다.

콤보 상자를 툴바에 삽입하기 이전에 먼저 콤보 상자를 만듭니다. 콤보 상자를 만들 때 콤보 상자의 윈도 영역 좌표를 (-300,- 300, 0, 0)으로 설정합니다. 이렇게 설정하는 이유는 윈도의 가로와 세로의 크기를 300으로 하되 이 콤보가 화면에 나타나지 않게 하려고 한 것입니다. 만일 가로 500, 세로 500의 콤보 상자를 만들려고 한다면 이 영역을 (-500, -500, 0, 0)으로 하면 됩니다. 즉 스크린 좌표 (0, 0) 위치 밖에서 윈도를 만든다는 의미입니다.

이렇게 영역을 설정한 후에 CComboBox의 Create 함수를 이용하여 콤보 상자를 만듭니다. 이때 콤보 상자의 ID를 툴바의 ID와 같게 합니다. 같게 하지 않고 그냥 또 다른 ID를 사용하여도 무방합니다. 다만 툴바에서 설정된 아이콘 ID와 같게 하면 좀더 단순해지기 때문에 주로 같은 ID를 많이 사용합니다. 이렇게 하여 콤보 상자를 만든 다음 툴바에 콤보 상자가 들어갈 자리를 만듭니다. 그림 1-6-11의 ③ 항목이 콤보 상자가 위치할 영역을 만드는 방법입니다. 먼저 콤보 상자가 들어갈 위치의 아이콘의 영역을 얻습니다. 이렇게 얻은 좌표를 ③ 항목에서는 rect에 저장하였습니다. 툴바 윈도, 즉 CToolBar의 GetItemRect는 특정 아이콘의 영역을 얻을 수 있습니다. 이때 첫 번째 인자는 아이콘 번호입니다. 이 번호는 좌측 처음부터 순서적으로 증가되는 번호입니다. 첫 번째가 0이고 그 다음 아이콘은 1이 되는 순서로 콤보 상자가 들어갈 위치를 세어보면 4가 됩니다.(SEPARATOR, 즉 한 칸 띄운 것도 순서에 포함됩니다.)

이렇게 4와 위치를 얻을 CRect 변수 rect를 두 번째 인자로 설정하면 rect 안에 콤보 상자가 들어갈 아이콘의 위치 영역이 얻어집니다. 이렇게 영역 위치를 얻은 다음 해당 위치를 콤보 상자의 가로 크기에 맞게 넓혀줍니다. 이렇게 넓혀 놓아야 콤보 상자가 정확한 크기로 들어갈 수 있기 때문입니다. 영역은 CToolBar의 SetButtonInfo라는 함수를 이용하여 넓힐 수 있습니다.

SetButtonInfo 함수는 영역을 넓히는 함수가 아니라 버튼의 속성을 설정할 때 사용하는 함수입니다. 이 함수의 첫 번째 인자는 속성을 설정할 버튼 위치 번호이며, 두 번째 인자는 새로 설정할 버튼의 ID, 세 번째 인자는 버튼의 속성, 네 번째 인자는 버튼 크기입니다. 이때 버튼의 속성은 TBBS_SEPARATOR 공백을 띄우는 속성이 됩니다. 이렇게 속성을 설정한 뒤에 가로 영역의 크기를 설정해주면 해당 영역만큼 공백으로 띄어주게 됩니다. 즉 버튼을 한 칸 띄움 속성으로 바꾸고 띄어지는 영역의 크기를 300으로 한 것입니다. 이 방법을 설정한 항목이 그림 1-6-11의 ③ 항목입니다. 이렇게 되면 ID_COMBOBOX 영역이 가로 300 형태가 되고, ① 항목에서 만든 콤보 상자의 가로 크기가 300이므로 이 영역에 콤보 상자가 들어갈 수 있는 것입니다. 이제 공백으로 띄운 영역에 콤보 상자를 위치시키면 됩니다. 콤보 상자를 부모 윈도 위치에 설정할 때 사용하는 함수는 SetWindowPos입니다. 이 함수를 사용하여 툴바의 공백 위치에 콤보 상자를 삽입한 형태가 그림 1-6-11의 ④ 항목입니다.

그림 1-6-11과 같은 내용을 툴바 외부에서 할 수도 있으나 일반적으로는 툴바 클래스 안에 설정합니다. 이렇게 설정하기 위해서는 CToolBar를 상속받은 자신만의 툴바를 만들고 이 툴바 안에 특정 함수를 만들어서 이 함수가 호출되면 콤보 상자가 설정될 수 있도록 하는 것이 좋은 방법입니다.

class CComboToolbar:public CToolBar{

:

void MakeComboWnd();//이 함수가 호출되면 콤보 상자가 만들어진다.

}

위의 예에서와 같이 CToolBar를 상속받은 CComboToolBar라는 클래스를 만들고 MakeComboWnd라는 함수를 만든 다음, 이 함수 안에 그림 1-6-11과 같은 내용을 코딩한 후에 CComboToolBar라는 클래스를 상속받은 변수를 선언하고 Create 함수를 이용하여 툴바를 만들고 MakComboWnd라는 함수를 호출하여 툴바 안에 콤보 상자를 붙이면 됩니다.

(2) 콤보 상자 이벤트 얻기

콤보 상자를 설정하고 난 후에 콤보 상자에서 발생하는 이벤트를 얻어야 합니다. 그냥 콤보 상자를 설정하였다 하여도 이 콤보 상자에서 발생하는 이벤트를 얻지 못하면 설정한 의미가 없습니다. 예를 들어 콤보 상자에서 특정 항목을 선택하였을 때나 콤보 상자의 에디터 창에서 문자를 입력하고 Enter 키를 눌렀을 경우 등등의 이벤트를 얻어야 하는 것입니다. 이런 이벤트를 얻는 방법은 다양합니다. 기본적으로 생각할 수 있는 방법은 부모 윈도에서 ON_CBN_ 메시지 매핑 매크로를 이용하여 이벤트를 얻는 것입니다. 이 메시지에 의해 발생되는 함수를 만들어서 콤보 상자 밖에서 이벤트를 얻는 방법도 좋지만, 필자는 이 방법보다는 콤보 상자를 상속받은 새로운 클래스를 만들어 그 클래스 안에서 이벤트를 얻은 다음 부모 윈도에 메시지로 날려주는 방법을 이용합니다.

그림 1-6-12는 콤보 상자를 상속받은 새로운 클래스 안에서 콤보 상자의 이벤트를 부모 윈도에 알려주는 예의 형태입니다.

그림 1-6-12는 콤보 상자의 에디터 창에서 데이터를 입력하고, Enter 키를 눌렀을 경우 해당 데이터를 콤보 상자의 리스트에 데이터를 삽입하고, 현재 자신이 속해 있는 부모 윈도의 부모 윈도(툴바가 설정되어 있는 프레임 윈도를 의미합니다.)에 자신의 아이디를 WM_COMMAND를 이용하여 보내주는 예를 보여준 것입니다. 또한 콤보 상자에서 특정 항목이 선택되었을 때도 WM_COMMAND를 보내줍니다. 이렇게 하면 메인프레임에서 툴바의 특정 버튼을 클릭했을 때와 똑같은 형태가 되는 것입니다.

보통 콤보 상자에 항목 아이콘을 만들고 해당 아이콘이 선택되었을 때의 함수를 ClassWizard를 가지고 만들어서 이 항목이 선택되었을 때의 내용을 작성할 수가 있었습니다. 이때 콤보 상자에서 특정 항목이 선택되면 자신의 ID를 wParam에 설정하고 WM_COMMAND라는 메시지를 보내주게 됩니다. 이것을 MFC에서는 ON_COMMAND라는 메시지 매핑을 이용하여 각 함수로 분할합니다. 그렇기 때문에 콤보 상자에서 이벤트가 발생되었을 때 자신의 부모 윈도의 부모 윈도에 WM_COMMAND를 보내어 ClassWizrd에서 콤보 상자로 대치되어 있는 아이콘 항목 ID를 선택하였을 때 발생되는 함수를 만들면, 그 함수는 콤보 상자의 에디터 창에서 문자열을 입력하고 Enter 키를 눌렀을 때와 특정 항목이 선택되었을 때 수행되는 것입니다.

그림 1-6-12의 ① 항목은 콤보 상자가 설정되어 있는 툴바에 자식 윈도로 콤보 상자를 설정한 항목입니다. 이렇게 콤보 상자를 설정할 때 이 콤보 상자 또한 CComboBox를 상속받은 또 하나의 클래스입니다. 이 클래스의 PreTranslateMessage 안에서 Enter 키를 눌렀을 때 이벤트를 얻은 다음 입력된 데이터를 그대로 콤보 상자 리스트 안에 넣고 SendMessage를 이용하여 WM_COMMAND를 설정한 것입니다. 이때 m_id는 자신의 아이디가 설정된 변수입니다. 또한 CMyCombo(CComboBox를 상속받은 또 하나의 클래스)에서 선택 항목이 변경되었을 때의 함수를 만든 다음 이 함수에서도 같은 방법으로 SendMessage를 보낸 것입니다. 이 방법은 콤보 상자에서 발생한 이벤트를 툴바 버튼이 눌려졌을 때와 같은 형태로 프레임 윈도에 보낸 예입니다. 이 밖에도 SendMessage를 이용하여 콤보 상자에서 필요한 메시지를 프레임 윈도에 보내고 프레임 윈도에서는 ON_MESSAGE 매크로를 이용하여 이벤트가 발생되었을 때의 메시지를 얻을 수 있습니다.

콤보 상자가 있는 툴바 예제 ExComboToolBar

ExComboToolBar는 지금까지 설명한 방법으로 툴바에 콤보 상자를 붙이고 콤보 상자의 에디터 창에서 문자를 입력하고 Enter 키를 누르면 현재 입력된 데이터가 메시지 상자로 출력되고 콤보 상자 리스트 안에 설정되며, 콤보 상자에서 항목 선택이 바뀌면 바뀐 데이터가 메시지 상자로 출력되는 예제입니다. ExComboToolBar의 전체 형태는 그림 1-6-13과 같습니다.

그림 1-6-13의 ⓐ 항목이 ExComboToolBar의 출력 형태입니다. 툴바 안에 콤보 상자가 있으며, 이 콤보 상자에 입력한 문자열이 메시지 상자에 출력되고, 또한 콤보 상자에서 특정 항목을 선택하였을 때 해당 문자열이 메시지 상자로 출력되는 형태입니다.

본 예제의 구동 형태는 매우 간단하나 다소 복잡한 형태로 제작됩니다. 본 예제를 만드는 방법은 다음과 같습니다.

(1) 프로젝트 만들기

ExComboToolBar는 SDI 형태의 프로젝트로 만듭니다. 이렇게 만든 다음 ProjectWorkspace 윈도에서 ResourceView 항목을 선택한 다음 Toolbar 항목에서 IDR_MAINFRAME 항목을 선택합니다. 이렇게 선택한 툴바에서 콤보 상자가 들어갈 장소를 만듭니다. 그림 1-6-14는 툴바 자원 IDR_MAINFRAME에 콤보 상자가 들어갈 위치를 만든 형태입니다.

그림 1-6-14에서 보면 네 번째 자리에 아무 그림도 없는 아이콘을 만들고 이 아이콘의 이름을 ID_COMBOBOX로 설정하였습니다. 이렇게 만든 다음, 콤보 상자를 설치하지 않은 상태에서 ClassWizard를 이용하여 ID_COMBOBOX를 선택하여  COMMAND 이벤트 메시지가 발생되었을 때의 함수를 만들면 해당 버튼이 클릭되었을 때 그 함수가 수행됩니다. 이때 내부적으로는 툴바의 아이콘이 클릭되었을 때 WM_COMMAND 메시지와 함께 wParam에 자신의 아이디, 즉 ID_COMBOBOX가 설정됩니다. 이 부분을 먼저 기억하고 있어야 합니다.

여기까지 프로젝트를 만들고 콤보 상자를 설정하기 위한 기본 형태는 끝났습니다.

(2) 콤보 상자 클래스 만들기

이제 툴바에 설정할 콤보 상자 클래스를 만들겠습니다. ClassWizard를 실행하고 Add Class 버튼을 클릭하여 New Class 대화 상자를 출력한 다음, Base class를 CComboBox로 설정하고 클래스명을 CMyCombo로 하여 클래스를 만듭니다.

그림 1-6-15는 CMyCombo 클래스를 만드는 화면입니다.

위와 같이 클래스를 만든 다음 ClassWizard를 실행시키고 CMyCombo에서 PreTranslateMessage 메시지에 의해 수행되는 함수를 만든 다음, 이 함수의 에디터 창에서 Enter 키를 눌렀을 때 입력된 데이터가 콤보 리스트에 설정되면 부모 윈도의 부모 윈도, 즉 프레임 윈도에 WM_COMMAND와 자신의 ID, 즉 그림 1-6-14의 ② 항목에 설정된 ID를 wParam으로 보냅니다. 보내는 방법은 그림 1-6-12의 ② 항목과 같습니다.

ClassWizard를 이용하여 CMyCombo에서 CBN_SELCHANGE 메시지가 발생될 때 수행되는 함수를 만들고, 그림 1-6-12의 ③ 항목처럼 코딩합니다. 이제 CMyCombo 클래스에 자신의 ID를 설정하는 멤버를 만들고, 이 멤버에 값을 설정하는 함수를 만듭니다. 먼저 헤더부에 다음과 같이 기록합니다.

int m_id;

위와 같이 m_id를 설정한 후에 이 변수에 값을 설정하는 SetId 함수를 만듭니다. 함수는 다음과 같습니다.

void CMyCombo::SetId(int id)

{

m_id=id;

}

위와 같이 함수를 만든 이유는 SendMessage 함수를 이용하여 WM_COMMAND와 자신의 id를 wParam에 설정할 때 이 ID값을 이용하기 위해서입니다. 이렇게 함으로써 툴바에 붙일 콤보 상자가 완성되었습니다.

(3) 툴바 클래스 만들기

콤보 상자를 설정하기 위해서 CToolBar를 상속받은 CMyToolBar 클래스를 만듭니다. ClassWizard를 이용하여 CToolBar를 상속받은 CMyToolBar를 만들 수는 없습니다. 이럴 경우에는 가장 비슷한 클래스로 먼저 만든 후에 클래스명을 CToolBar로 바꾸어주면 됩니다. ClassWizard를 이용하여 CToolBarCtrl을 상속받은 CMyToolBar를 만들고, 이렇게 만들어진 CMyToolBar의 헤더 파일과 소스 파일에 CToolBarCtrl을 모두 CToolBar로 바꾸어줍니다. 이렇게 하면 CToolBar를 상속받은 CMyToolBar가 만들어집니다. CMyToolBar를 만든 다음 먼저 전 항목에서 만든 콤보 상자 클래스(CMyCombo)의 멤버를 설정합니다.

CMyCombo m_comboBox;

헤더부에 위와 같이 설정하고 이 클래스에 콤보 상자를 설정하는 MakeComboBox 함수를 만듭니다. 이 함수는 다음과 같습니다.

//콤보 상자를 삽입하는 함수

//pos : 툴바에 삽입될 아이콘 위치

//toolid : 삽입될 아이콘 ID

//size : 콤보 상자의 가로, 세로 크기

void CMyToolBar::MakeComboBox(int pos,int toolid,CSize size)

{

//콤보 상자 영역 설정

CRect rect(-size.cx,-size.cy,0,0);

//콤보 상자에 ID 설정

m_comboBox.SetId(toolid);

//콤보 상자를 만든다.

m_comboBox.Create(WS_CHILD|CBS_DROPDOWN|

CBS_AUTOHSCROLL

|WS_VSCROLL|CBS_HASSTRINGS,

rect,this,toolid);

//툴바에 삽입될 아이콘 번호와 영역을 얻는다.

GetItemRect(pos,rect);

//버튼 정보를 공백으로 설정하고 가로 크기를 콤보 상자의 가로 길이로

//설정한다.

SetButtonInfo(pos,toolid,TBBS_SEPARATOR,size.cx);

//콤보 상자를 붙인다.

m_comboBox.SetWindowPos(NULL,

rect.left,rect.top,0,0,

SWP_NOZORDER|SWP_NOACTIVATE|

SWP_NOSIZE|SWP_NOCOPYBITS);

//콤보 상자를 화면에 나타낸다.

m_comboBox.ShowWindow(SW_SHOW);

}

이와 같이 하면 콤보 상자가 설정된 CMyToolBar 클래스가 만들어집니다.

(4) 메인프레임 수정

이제 메인프레임을 수정하여 지금까지 만든 CMyToolBar를 메인프레임에 설정합니다. 먼저 헤더부에 m_wndToolBar의 클래스명을 CMyToolBar로 설정합니다.

CMyToolBar    m_wndToolBar;

위와 같이 설정한 후에 m_wndToolBar의 Create 함수가 실행되면 CMyToolBar의 MakeComboBox 함수를 실행하여 콤보 상자를 붙입니다. 다음은 Create 다음에 MakeComboBox 함수를 호출한 형태입니다.

//콤보 상자를 붙인다.

m_wndToolBar.MakeComboBox(4,ID_COMBOBOX,CSize(150,150));

위와 같이 하면 콤보 상자가 설정된 툴바가 붙게 됩니다.

(5) 콤보 상자 이벤트 얻기

콤보 상자의 에디터 창에서 문자를 입력하거나 콤보 상자에서 항목 선택이 바뀌었을 때 수행되는 함수는 콤보 상자의 개념을 가지지 않고 단순하게 콤보 상자 위치의 아이콘이 클릭되었을 때 수행되는 함수를 만들면 됩니다. 이렇게 되는 이유는 전에도 설명하였듯이 콤보 상자에서 항목이 변경되거나 문자열이 입력되었을 때 WM_COM-MAND 메시지와 툴바의 아이콘 ID를 wParam으로 보내주기 때문에 툴바의 아이콘이 클릭되었을 때의 메시지와 같은 메시지가 발생되는 것입니다. 이렇게 만든 함수에서 콤보 상자의 현재 선택된 문자열을 얻고 이것을 메시지 상자로 출력합니다. 먼저 ClassWizard를 이용하여 ID_COMBOBOX에서 COMMAND 메시지가 발생되었을 때의 함수를 만들고 이 함수를 다음과 같이 수정합니다.

void CMainFrame::OnCombobox()

{

//툴바의 콤보 상자에서 현재 위치를 얻는다.

int num=m_wndToolBar.m_comboBox.GetCurSel();

CString data;

//현재 위치의 문자열을 얻는다.

m_wndToolBar.m_comboBox.GetLBText(num,data);

//메시지 상자에 출력

AfxMessageBox(data);

}

위와 같이 하면 ExComboToolbar 예제가 완성됩니다.

예제 ExComboToolBar 소스

(1) CMyCombo 소스

#if !defined(AFX_MYCOMBO_H__66F2AAA8_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

#define AFX_MYCOMBO_H__66F2AAA8_2895_11D2_8B70_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// MyCombo.h : header file

//

//////////////////////////////////////////////////////////////////////////// CMyCombo window

class CMyCombo : public CComboBox

{

// Construction

public:

CMyCombo();

// Attributes

public:

int m_id;

// Operations

public:

void SetId(int id);

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMyCombo)

public:

virtual BOOL PreTranslateMessage(MSG* pMsg);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMyCombo();

// Generated message map functions

protected:

//{{AFX_MSG(CMyCombo)

afx_msg void OnSelchange();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MYCOMBO_H__66F2AAA8_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

// MyCombo.cpp : implementation file

//

#include “stdafx.h”

#include “ExComboToolbar.h”

#include “MyCombo.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CMyCombo

CMyCombo::CMyCombo()

{

}

CMyCombo::~CMyCombo()

{

}

BEGIN_MESSAGE_MAP(CMyCombo, CComboBox)

//{{AFX_MSG_MAP(CMyCombo)

ON_CONTROL_REFLECT(CBN_SELCHANGE, OnSelchange)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CMyCombo message handlers

void CMyCombo::SetId(int id)

{

m_id=id;

BOOL CMyCombo::PreTranslateMessage(MSG* pMsg)

{

// TODO: Add your specialized code here and/or call the base class

if(pMsg->message==WM_KEYDOWN && pMsg->wParam==VK_RETURN)

{

CString data;

GetWindowText(data);

AddString(data);

int num=FindString(0,data);

SetCurSel(num);

::SendMessage(GetParent()->GetParent()->m_hWnd, WM_COMMAND,m_id,0);

}

return CComboBox::PreTranslateMessage(pMsg);

}

void CMyCombo::OnSelchange()

{

::SendMessage(GetParent()->GetParent()->m_hWnd,WM_COMMAND,m_id,0);

}

(2) CMyToolBar 소스

#if !defined(AFX_MYTOOLBAR_H__66F2AAA7_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

#define AFX_MYTOOLBAR_H__66F2AAA7_2895_11D2_8B70_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// MyToolBar.h : header file

//

//////////////////////////////////////////////////////////////////////////// CMyToolBar window

#include “MyCombo.h”

class CMyToolBar : public CToolBar

{

// Construction

public:

CMyToolBar();

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMyToolBar)

//}}AFX_VIRTUAL

// Implementation

public:

void MakeComboBox(int pos,int toolid,CSize size);

CMyCombo m_comboBox;

virtual ~CMyToolBar();

// Generated message map functions

protected:

//{{AFX_MSG(CMyToolBar)

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MYTOOLBAR_H__66F2AAA7_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

// MyToolBar.cpp : implementation file

//

#include “stdafx.h”

#include “ExComboToolbar.h”

#include “MyToolBar.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMyToolBar

CMyToolBar::CMyToolBar()

{

}

CMyToolBar::~CMyToolBar()

{

}

BEGIN_MESSAGE_MAP(CMyToolBar, CToolBar)

//{{AFX_MSG_MAP(CMyToolBar)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////

// CMyToolBar message handlers

//콤보 상자를 삽입하는 함수

//pos : 툴바에 삽입될 아이콘 위치

//toolid : 삽입될 아이콘 ID

//size : 콤보 상자 가로, 세로 크기

void CMyToolBar::MakeComboBox(int pos,int toolid,CSize size)

{

//콤보 상자의 영역 설정

CRect rect(-size.cx,-size.cy,0,0);

//콤보 상자에 ID 설정

m_comboBox.SetId(toolid);

//콤보 상자를 만든다.

m_comboBox.Create(WS_CHILD|CBS_DROPDOWN|CBS_AUTOHSCROLL

|WS_VSCROLL|CBS_HASSTRINGS,

rect,this,toolid);

//툴바에서 삽입될 아이콘 번호와 영역을 얻는다.

GetItemRect(pos,rect);

//버튼 정보를 공백으로 설정하고 가로 크기를 콤보 상자의 가로 길이로

//설정한다.

SetButtonInfo(pos,toolid,TBBS_SEPARATOR,size.cx);

//콤보 상자를 붙인다.

m_comboBox.SetWindowPos(NULL,

rect.left,rect.top,0,0,

SWP_NOZORDER|SWP_NOACTIVATE|

SWP_NOSIZE|SWP_NOCOPYBITS);

//콤보 상자를 화면에 나타낸다.

m_comboBox.ShowWindow(SW_SHOW);

}

(3) CMainFrame 소스

// MainFrm.h : interface of the CMainFrame class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__66F2AA9B_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

#define AFX_MAINFRM_H__66F2AA9B_2895_11D2_8B70_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

#include “MytoolBar.h”

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CMyToolBar    m_wndToolBar;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnCombobox();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__66F2AA9B_2895_11D2_8B70_006097AD9DDC__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include “stdafx.h”

#include “ExComboToolbar.h”

#include “MainFrm.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_COMMAND(ID_COMBOBOX, OnCombobox)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

//////////////////////////////////////////////////////////////////////////// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |

CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

//콤보 상자를 붙인다.

m_wndToolBar.MakeComboBox(4,ID_COMBOBOX,CSize(150,150));

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

// TODO: Delete these three lines if you don’t want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

//////////////////////////////////////////////////////////////////////////// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CMainFrame message handlers

void CMainFrame::OnCombobox()

{

//툴바의 콤보 상자에서 현재 위치를 얻는다.

int num=m_wndToolBar.m_comboBox.GetCurSel();

CString data;

//현재 위치의 문자열을 얻는다.

m_wndToolBar.m_comboBox.GetLBText(num,data);

//메시지 상자에 출력

AfxMessageBox(data);

}

비트맵을 사용하여 툴바 만들기

지금까지는 툴바를 만들 때 LoadToolBar 함수를 사용하였습니다. 이것은 툴바 자원을 로드한 형태입니다. 툴바를 만들 때 비트맵을 이용하여 툴바를 만들 수도 있습니다. SDK로 툴바를 만들 경우에는 비트맵을 이용합니다. 그러나 MFC에서는 툴바 자원 항목을 두고 이 툴바 자원을 그대로 로드하였습니다. 때로는 비트맵을 로드하여 툴바에 설정할 때도 있습니다. 즉 사용자 툴바를 만들 경우입니다. 어떤 애플리케이션 프로그램에서는 사용자 툴바라는 항목이 있어서 현재 메뉴 항목 중에 자신이 필요한 부분의 항목을 설정하여 새롭게 툴바를 만드는 경우가 있습니다. 이때 비트맵을 이용해야 합니다. 비트맵은 자원에 있는 것을 로드하기도 하지만 직접 제작할 수도 있습니다. 그렇기 때문에 필요시에 툴바를 만들 수가 있다는 의미가 됩니다. 비트맵을 로드하여 툴바를 만들 때는 먼저 툴바를 만듭니다. CreateEx 함수를 이용하여 툴바를 만든 다음 LoadToolBar 함수 대신에 LoadBitmap 함수를 사용합니다. 이 함수는 자원에 있는 비트맵을 로드하는 함수입니다. 예를 들어서 자원에 IDB_TOOLBARBITMAP이라는 비트맵이 있을 경우 이 비트맵을 로드하여 툴바를 만드는 방법은 다음과 같습니다.

m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |

WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC);

m_wndToolBar.LoadBitmap(IDB_TOOLBARBITMAP);

위와 같이 하면 툴바가 만들어지고 여기에 비트맵이 설정됩니다. LoadToolBar 함수를 사용하면 바로 버튼이 설정되지만 LoadBitmap을 사용하면 바로 버튼이 설정되지 않습니다. 위와 같이 하면 비트맵이 툴바 윈도에 설정될 뿐 버튼에 대한 정보가 없기 때문에 아무런 버튼도 형성되지 않는 것입니다. 이곳에 버튼을 설정하기 위해서는 먼저 경계 영역을 설정해야 합니다. 경계 영역을 설정할 때는 CControlBar::- SetBorders 함수를 사용합니다. 이 함수는 다음과 같습니다.

void SetBorders( int cxLeft, int cyTop, int cxRight, int cyBottom );

cxLeft는 좌측 경계 두께이며 cyTop은 상단 경계, cxRight는 우측 경계, cyBottom은 하단부 경계입니다. 이 경계의 값만큼 버튼과 윈도 경계 사이가 형성됩니다. 위에서 만든 m_wndToolBar에서 경계를 각각 1로 설정하면 다음과 같습니다.

m_wndToolBar.SetBorders(1,1,1,1);

위와 같이 설정하면 윈도가 버튼 크기보다 각각 1씩 큰 영역이 됩니다. 이렇게 설정한 후에 버튼의 크기를 설정합니다. 버튼의 크기를 설정할 때의 함수는 SetSizes입니다. 이 함수는 다음과 같습니다.

void SetSizes( SIZE sizeButton, SIZE sizeImage );

sizeButton은 버튼의 크기이며, sizeImage는 버튼에 설정될 비트맵의 크기입니다. 이때 주의할 점은 버튼의 크기가 sizeImage보다 가로는 7, 세로는 6이 더 커야 한다는 것입니다. 그렇게 설정하지 않으면 버튼이 설정되지 않습니다. 위의 m_wndToolBar에 설정한 비트맵의 단위 크기가 가로 16, 세로 15일 경우 SetSizes는 다음과 같습니다.

SIZE size={16,15},size1={16+7,15+6};

m_wndToolBar.SetSizes(size1,size);

위처럼 하면 가로 16, 세로 15의 크기의 그림이 가로 33, 세로 21 크기의 버튼 위에 설정됩니다. 버튼 크기가 비트맵 크기보다 최소 7과 6만큼 커야 하는 이유는 비트맵 외에 버튼이 들어갈 때 그려주는 라인 영역이 가로 7, 세로 6 정도의 영역이 필요하기 때문입니다. 이 여유분이 없다면 비트맵 위에 들어간 형태의 버튼을 그려주어야 하는데, 이렇게 되면 버튼 자체가 보기에 매우 좋지 않기 때문입니다. 이와 같이 한 후 SetButtons 함수를 이용하여 버튼 정보와 수를 설정합니다. 이 함수는 다음과 같습니다.

BOOL SetButtons( const UINT* lpIDArray, int nIDCount );

lpIDArray는 버튼 ID 정수 배열이며 nIDCount는 버튼의 수입니다. 예를 들어 일반적인 버튼이 “새 파일”, “파일열기”, 공백, “파일저장”의 아이콘을 로드하기 위해서는 버튼 배열 ID를 다음과 같이 설정합니다.

int buttoninfo[4]={ID_FILE_NEW,ID_FILE_OPEN, ID_SEPARATOR,

ID_FILE_SAVE};

툴바 버튼을 한 칸 띄우기 위해서는 ID_SEPARATOR라는 ID를 사용하면 됩니다. 이렇게 사용한 후 총 네 개의 버튼을 설정할 때 SetButtons 함수를 사용한 예는 다음과 같습니다.

m_wndToolBar.SetButtons(buttoninfo,4);

지금까지의 예에서 비트맵이 어떤 형태로 설정되어 있는지는 설명하지 않았습니다. 비트맵은 버튼 정보를 설정할 때 이 형태대로 가로 16, 세로 15 크기로 설정되어 있어야 합니다. 그림 1-6-16은 본 예에서 사용한 비트맵 형태입니다.

그림 1-6-16에서 보듯이 buttoninfo에 설정된 ID에서 ID_SEPARATOR를 제외한 세 개의 그림을 그대로 설정한 것입니다.

지금까지 한 방법으로 비트맵을 로드하여 툴바를 만들 수가 있습니다. 자원에서 비트맵을 로드하여 툴바를 만든다면 어떤 면에서는 LoadToolBar를 사용하는 것이 편리할 것입니다. 그 이유는 비트맵을 로드하나 툴바를 로드하나 자원에서 로드하므로 정적 형태이기 때문에 별로 이득이 없습니다. 만약 동적으로 툴바를 만들고자 한다면 비트맵을 로드하는 것 대신 비트맵을 설정하는 함수를 사용합니다. 이때 사용하는 함수가 SetBitmap 함수입니다 이 함수는 다음과 같은 형태입니다.

BOOL SetBitmap( HBITMAP hbmImageWell );

LoadBitmap 함수 대신에 위의 함수를 이용하여 비트맵을 설정하고 나머지 부분은 위에서 설명한 내용을 그대로 이용하면 됩니다. SetBitmap을 이용한다는 것은 툴바를 동적으로 만들 수 있다는 의미가 됩니다. 먼저 비트맵을 만든 다음 이 비트맵을 SetBitmap 함수를 이용하여 비트맵을 설정하고, SetBorders와 SetSizes, SetButtons를 사용하여 툴바를 만들면 됩니다.

사용자 정의 툴바 만들기

본 항목에서는 사용자 정의 툴바, 즉 툴바를 동적으로 만드는 방법을 설명합니다. 본 항목부터 이어지는 세 개의 항목은 중급자와 고급자들을 위한 항목입니다. 초급자들은 본 장을 그냥 읽어보기만 하십시오. 또는 그냥 다음 항목으로 뛰어넘어도 관계는 없습니다.

그림 1-6-17은 본 장에서 제작한 ExMkToolBar 예제에서 사용자 툴바를 만드는 대화 상자입니다.

그림 1-6-17과 비슷한 사용자 정의 툴바를 만드는 대화 상자를 다른 애플리케이션 프로그램에서 본 적이 있을 것입니다. 현재 프로그램에 설정되어 있는 항목과 그에 해당하는 비트맵이 좌측 리스트 상자에 있고, 이 상자에서 선택한 비트맵들이 우측 리스트 상자에 들어가고, 우측 리스트 상자에 설정된 비트맵을 이용하여 툴바가 만들어지는 형태입니다. 일반적으로 자주 사용하는 항목은 이렇게 툴바로 만들어 사용하는 옵션을 제공하여 주는데, 이렇게 사용자 정의 툴바를 만드는 부분은 바로 SetBitmap을 이용하여 툴바 윈도에 비트맵을 설정하여 만드는 방법을 이용합니다. 그렇다면 툴바에 설정할 비트맵이 필요한데, 이 비트맵은 본 3장에서 설명한 비트맵을 응용하면 됩니다.

예를 들어서 동적으로 툴바를 만들어 보겠습니다. 그림 1-6-17과 같은 비트맵들이 있고 이 비트맵은 툴바 버튼에 이용됩니다.

이때 그림 1-6-17의 ①과 ②, ③ 항목을 이용하여 새로운 툴바를 만들 경우에 새로운 비트맵을 만든 다음 이 비트맵에 BitBlt를 이용하여 그림 1-6-18의 ①, ②, ③ 비트맵을 설정합니다. 먼저 세 개의 비트맵을 설정할 비트맵을 만듭니다.

CBitmap m_userBitmap;

CDC *pDC=GetDC();

m_usrBitmap.CreateCompatibleBitmap(pDC,16*3,15);

위와 같이 하면 가로 16, 세로 15인 세 개의 비트맵을 설정할 비트맵이 설정됩니다. 이렇게 한 후에 화면 캡처할 때 사용한 방법으로 비트맵과 호환되는 DC를 설정합니다. 이렇게 DC를 만든 다음 이 DC에 위에서 만든 m_userBitmap을 설정합니다.

CDC memDC;

memDC.CreateCompatibleDC(pDC);

memDC.SelectObject(&m_userBitmap);

위와 같이 하여 memDC에 그림을 그리면 이것이 모두 m_userBitmap에 설정되는 것입니다. 이제 memdDC에 비트맵을 전송하기 위해서 그림 1-6-18의 전체 비트맵을 DC에 설정합니다.

CDC srcDC;

srcDC.SelectObject(&m_srcBitmap);

m_srcBitmap은 LoadBitmap이나 또 다른 방법으로 이미 비트맵 정보를 가지고 있어야 합니다. 비트맵을 가지고 있지 않으면 그림 1-6-17의 좌측에 비트맵을 설정할 수도 없습니다. 즉 툴바에서나 자원에서 비트맵을 로드하여 m_srcBitmap에 설정합니다.

이제 그림 1-6-18의 ① 비트맵을 m_userBitmap에 설정해 보겠습니다.

memDC.BitBlt(0,0,16,15,&srcDC,0,0,SRCCOPY);

위와 같이 하면 그림 1-6-18 비트맵의 첫 번째 비트맵에서 가로 16, 세로 16만큼 memDC에 설정됩니다. 이와 같은 방법으로 ② 비트맵을 설정하는 것은 다음과 같습니다.

memDC.BitBlt(1쪻16,0,16,15,&srcDC,3쪻16,0,SRCCOPY);

위와 같이 하면 memDC의 두 번째 위치, 즉 가로 16 위치와 세로 0 위치에 그림 1-6-18의 비트맵의 네 번째 위치, 즉 3쪻16의 위치와 세로는 0 위치의 내용을 가로 16, 세로 15만큼 읽서서 출력하는 것입니다. 이와 같은 방법으로 ③번 비트맵을 설정하는 것은 다음과 같습니다.

memDC.BitBlt(2*16,0,16,15,&srcDC,6．16,0,SRCCOPY);

위와 같이 하면 memDC에 설정된 m_usrBitmap에 그림 1-6-18의 ①, ②, ③의 그림들이 순서적으로 들어가게 됩니다. 이렇게 한 후 m_usrBitmap을 툴바에 설정합니다. 설정하는 예는 다음과 같습니다.

m_wndUserBar->SetBitmap(m_usrBitmap);

m_wndUserBar->SetBorders(1,1,1,1);

m_wndUserBar->SetSizes(size1,size);

m_wndUserBar->SetButtons(userid,3);

위의 예에서 size1과 size의 값은 전에 설명했습니다. userid는 정수형 배열로 그림 1-6-18의 ①, ②, ③의 ID를 설정한 것입니다. 직접 프로그램을 작성할 때는 그림 1-6-17의 우측 윈도에 설정한 툴바 버튼에 해당하는 ID를 저장했다가 이 값을 userid에 설정합니다. 지금까지 설명한 방법으로 동적인 비트맵을 만들 수가 있습니다.

사용자 정의 툴바 만들기 예제 ExMkToolbar

ExMkToolbar 예제는 전 항목에서 설명한 방법으로 사용자 정의 툴바를 만드는 예제입니다. 이 예제를 만드는 방법은 다음과 같습니다.

(1) 프로젝트 만들기

본 예제는 SDI 형태로 제작됩니다. 이렇게 하여 프로젝트를 만든 후 툴바에 로드할 비트맵을 만듭니다. 이 비트맵 그림은 그림 1-6-18의 비트맵과 같습니다. 이 비트맵은 프로젝트를 만들면 보통 RES 디렉토리 Toolbar.bmp에 설정되어 있습니다. 이 비트맵을 다시 Bitmap 자원으로 로드한 것입니다. 이렇게 로드한 비트맵으로 메인 툴바를 만듭니다. CMainFrame OnCreate 함수에서 LoadToolBar 함수를 제거하고 다음과 같은 내용을 설정하여 비트맵으로 툴바를 로드합니다.

UINT buttoninfo[6]={ID_FILE_NEW,ID_FILE_OPEN,

ID_FILE_SAVE,ID_SEPARATOR,

ID_EDIT_CUT,ID_EDIT_COPY};

SIZE size={16,15},size1={16+7,15+6};

//비트맵을 로드한다.

m_wndToolBar.LoadBitmap(IDB_TOOLBARBITMAP);

//경계 설정

m_wndToolBar.SetBorders(1,1,1,1);

//비트맵 버튼과 비트맵의 크기를 설정한다.

m_wndToolBar.SetSizes(size1,size);

//버튼 정보와 버튼수를 설정한다.

m_wndToolBar.SetButtons(buttoninfo,6);

이와 같이 하고 EnableDocking 함수를 사용하여 도킹을 가능하게 하는 내용들은 전에 설명한 방법과 같습니다.

(2) 사용자 정의 윈도 설정

사용자 정의 툴바를 만들기 위해서 CToolBar 클래스의 변수인 m_wndUserBar를 헤더에 다음과 같이 정의합니다.

//사용자 정의 툴바 윈도

CToolBar *m_wndUserBar;

위와 같이 정의하고 사용자 정의 툴바 윈도에 설정할 비트맵 클래스의 변수를 또한 헤더에 정의합니다.

//사용자 정의 툴바에 설정할 비트맵

CBitmap m_usrBitmap;

위와 같이 정의한 후에 플러그 하나를 설정해야 합니다. 본 예제에서는 대화 상자에서 설정한 비트맵을 계속적으로 툴바에 설정하게 됩니다. 그렇기 때문에 윈도를 소멸시켰다가 다시 생성하는 것을 반복해야 합니다. 처음에는 윈도가 만들어지지 않았으므로 소멸할 필요가 없었습니다. 즉 처음에는 윈도를 없애지 않고 새롭게 생성하고, 이미 윈도가 생성되어 있으면 이것을 DestroyWindow를 이용하여 윈도를 사라지게 합니다. 이렇게 하기 위해서는 현재 사용자 정의 툴바가 있으면 TRUE, 그렇지 않으면 FALSE로 설정되는 플러그를 만들어야 합니다. 이 플러그를 헤더에 다음과 같이 정의합니다.

BOOL m_bUserBar;

위와 같이 정의한 후에 OnCrate 함수 하단부에 m_bUserBar값을 FALSE로 설정합니다.

(3) 대화 상자 만들기

그림 1-6-17과 같은 대화 상자 폼을 만듭니다. 두 개의 리스트 상자는 CListCtrl 클래스형입니다. 이 두 개의 리스트 상자와 삽입 및 삭제 버튼을 대화 상자 폼에 그림 1-6-17과 같이 설정합니다. 이렇게 대화 상자 폼을 만든 후에 이 대화 상자와 연결할 클래스를 CDialog를 상속받은 CMkToolBarDlg로 만듭니다. 이렇게 만든 후에 OnInitDialog 함수에서 좌측 리스트 상자에 아이콘과 문자열을 설정합니다. 리스트 상자에 데이터를 넣는 방법은 생략하겠습니다. 전 장에서 설명한 방법들을 그대로 이용하면 됩니다.

//리스트 컨트롤에 설정할 문자열

char 쪻menuname[8]={“새파일”,”열기”,”저장”,”잘라내기”,”복사”,”붙여넣기”,

“인쇄”,”정보”};

//리스트 컨트롤에 설정된 툴바 비트맵의 ID

int idnumber[8]={ID_FILE_NEW,ID_FILE_OPEN,ID_FILE_SAVE,

ID_EDIT_CUT,

ID_EDIT_COPY,ID_EDIT_PASTE,ID_FILE_PRINT,ID_APP_ABOUT};

//리스트 컨트롤에 설정할 문자열

char 쪻idnum[8]={“ID_FILE_NEW”,

“ID_FILE_OPEN”,

“ID_FILE_SAVE”,

“ID_EDIT_CUT”,

“ID_EDIT_COPY”,

“ID_EDIT_PASTE”,

“ID_FILE_PRINT”,

“ID_APP_ABOUT”};

BOOL CMkToolBarDlg::OnInitDialog()

{

CDialog::OnInitDialog();

//이미지 리스트를 만들고 이곳에 비트맵을 설정한다.

m_pImageList= new CImageList;

m_pImageList->Create(16,15,ILC_COLOR4,8,3);

CBitmap cBit;

//툴바 비트맵을 로드하여 이미지 저장통에 설정한다.

cBit.LoadBitmap(IDB_TOOLBARBITMAP);

m_pImageList->Add(&cBit,RGB(0,0,255));

//이미지 저장통에 설정한 이미지가 여덟 개임을 설정한다.

m_pImageList->SetImageCount(8);

m_listDes.SetImageList(m_pImageList,LVSIL_NORMAL);

m_listDes.SetImageList(m_pImageList,LVSIL_SMALL);

m_listSrc.SetImageList(m_pImageList,LVSIL_NORMAL);

m_listSrc.SetImageList(m_pImageList,LVSIL_SMALL);

//컬럼 만들기

LV_COLUMN lvcolumn;

char 쪻listcolumn[2]={“툴바 항목”,”메뉴ID”};

int width[2]={150,150};

for (int i=0; i<2; i++)

{

lvcolumn.mask = LVCF_FMT | LVCF_SUBITEM |

LVCF_TEXT | LVCF_WIDTH;

lvcolumn.fmt = LVCFMT_LEFT;

lvcolumn.pszText = listcolumn[i];

lvcolumn.iSubItem = i;

lvcolumn.cx = width[i];

m_listSrc.InsertColumn(i, &lvcolumn);

m_listDes.InsertColumn(i, &lvcolumn);

}

//데이터 넣기

LV_ITEM lvitem;

int curitem;

for (i= 0; i< 8; i++)

for (int j = 0; j < 2; j++)

{

lvitem.mask = (j==0)?LVIF_TEXT|

LVIF_IMAGE:LVIF_TEXT;

lvitem.iItem = (j==0)?i:curitem;

lvitem.iSubItem = j;

if(j==0)

lvitem.iImage=i;

switch(j)

{

case 0:

lvitem.pszText = menuname[i];

curitem=m_listSrc.InsertItem(&lvitem);

break;

case 1:

lvitem.pszText= idnum[i];

m_listSrc.SetItem(&lvitem);

break;

}

}

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

위와 같이 하면 좌측 리스트 컨트롤에 툴바 아이콘과 메뉴 ID명이 설정됩니다. 이때 우측 리스트 컨트롤에 설정되는 툴바의 ID와 툴바 위치 번호, 리스트 컨트롤에 설정된 툴바의 수를 저장할 변수를 대화 상자 헤더에 설정합니다.

//툴바 수

int toolbarnum;

//툴바 ID

UINT toolbarid[50];

//툴바의 위치 번호

이제 => 화살표 버튼을 클릭하여 좌측 리스트 상자에서 선택된 항목을 우측 리스트 상자에 넣고, <= 버튼을 클릭하여 우측 리스트 상자에서 선택된 항목을 삭제합니다.

//==> 버튼 클릭시

void CMkToolBarDlg::OnAdd()

{

LV_ITEM lvitem;

if(srcdatanum<0)

return;

int num=m_listDes.GetItemCount();

for (int j = 0; j < 2; j++)

{

lvitem.mask = (j==0)?LVIF_TEXT|LVIF_IMAGE:LVIF_TEXT;

lvitem.iItem = num;

lvitem.iSubItem = j;

if(j==0)

lvitem.iImage=srcdatanum;

switch(j)

{

case 0:

lvitem.pszText = menuname[srcdatanum];

m_listDes.InsertItem(&lvitem);

break;

case 1:

lvitem.pszText= idnum[srcdatanum];

m_listDes.SetItem(&lvitem);

break;

}

}

}

//<== 버튼 클릭시

void CMkToolBarDlg::OnDel()

{

if(desdatanum<0)

return;

m_listDes.DeleteItem(desdatanum);

}

위와 같이 한 후에 OK 버튼을 클릭하여 우측 리스트 컨트롤에 설정된 문자열을 확인하여서 해당 ID를 toolbarid에, 비트맵 위치를 toolbarpos에 저장합니다.

void CMkToolBarDlg::OnOK()

{

//리스트 컨트롤 안의 아이템수를 num에 저장

int num=m_listDes.GetItemCount();

CString data;

int nid;

toolbarnum=num;

//num만큼 반복하여 문자열을 얻고 이 문자열에

//해당하는 번호를 얻은 다음

//toolbarid와 toolbarpos에 설정한다.

for(int i=0;i<num;i++)

{

data=m_listDes.GetItemText(i,0);

nid=SeekId(data);

toolbarid[i]=idnumber[nid];

toolbarpos[i]=nid;

}

CDialog::OnOK();

}

위와 같이 하면 toolbarid에는 현재 선택된 버튼의 ID 리스트가, toolbarpos에는 비트맵의 위치 번호가 저장됩니다. SeekId 함수는 문자열을 넣으면 OninitDialog 위에 선언한 menuname과 비교하여 문자열이 같으면 해당 배열 번호를 리턴하는 함수입니다. 이 함수는 다음과 같습니다.

//data 문자열을 넣으면 menuname 배열에, 같은

//문자열이 있으면 해당 문자열을 넣는다.

int CMkToolBarDlg::SeekId(LPCSTR data)

{

for(int i=0;i<8;i++)

if(strcmp(menuname[i],data)==0)

return i;

return -1;

}

(4) 대화 상자 실행

메뉴에 “툴바 만들기” 항목을 만들고 이 항목을 클릭하면 CMkToolBarDlg가 실행됩니다. 이렇게 실행한 후에 OK 버튼을 클릭하면 toolbarpos와 toolbarid를 이용하여 새롭게 툴바가 만들어집니다. 이 내용은 다음과 같습니다.

#include “MkToolBarDlg.h”

void CMainFrame::OnMaketoolbar()

{

//대화 상자를 실행한다.

CMkToolBarDlg 쪻pDlg = new CMkToolBarDlg;

if(pDlg->DoModal()==IDOK)

{

//이미 사용자 툴바 윈도가 만들어져 있으면

if(m_bUserBar)

{

//툴바를 메인 윈도에서 플로팅시킨다.

//윈도를 소멸시키기 위해서는 플로팅을

//해야 함.

FloatControlBar(m_wndUserBar,CPoint(0,0));

m_wndUserBar->ShowWindow(SW_HIDE);

m_wndUserBar->DestroyWindow();

m_wndUserBar=NULL;

//사용자 비트맵을 지운다.

m_usrBitmap.DeleteObject();

}

//툴바 윈도를 생성한다.

m_wndUserBar= new CToolBar;

//윈도를 만든다.

m_wndUserBar->CreateEx(this, TBSTYLE_FLAT, WS_CHILD

| WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC);

SIZE size={16,15},size1={16+7,15+6};

int bitsize;

CBitmap cBit;

CDC 쪻pDC,memDC,srcDC;

//툴바 비트맵을 로드한다.

cBit.LoadBitmap(IDB_TOOLBARBITMAP);

pDC=GetDC();

//DC 두 개를 만든다.

memDC.CreateCompatibleDC(pDC);

srcDC.CreateCompatibleDC(pDC);

//src DC에 툴바 비트맵을 설정

srcDC.SelectObject(&cBit);

//비트맵 사이즈는 대화 상자에서 설정한 아이콘수쪻16이 됨.

bitsize=pDlg->toolbarnum쪻16;

//사용자 비트맵을 만든다.

m_usrBitmap.CreateCompatibleBitmap(pDC,bitsize,15);

//사용자 비트맵을 DC에 설정

memDC.SelectObject(&m_usrBitmap);

//대화 상자에서 선택한 비트맵들을 모두

//memDC에 출력하여 m_usrBitmap을 만든다.

for(int i=0;i<pDlg->toolbarnum;i++)

{

memDC.BitBlt(i쪻16,0,16,15,&srcDC,

pDlg->toolbarpos[i]쪻16,0,SRCCOPY);

}

ReleaseDC(pDC);

//사용자 툴바에 비트맵을 설정

m_wndUserBar->SetBitmap(m_usrBitmap);

m_wndUserBar->SetBorders(1,1,1,1);

m_wndUserBar->SetSizes(size1,size);

m_wndUserBar->SetButtons(pDlg->toolbarid,

pDlg->toolbarnum);

//도킹이 가능하게 하고

m_wndUserBar->EnableDocking(CBRS_ALIGN_ANY);

//툴바를 도킹시킨다.

DockControlBar(m_wndUserBar);

//윈도가 만들어졌으므로 m_bUserBar는 TRUE

m_bUserBar=TRUE;

}

delete pDlg;

}

예제 ExMkToolbar 소스

(1) 메인프레임 소스

// MainFrm.h : interface of the CMainFrame class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__42FA4ADE_2935_11D2_A0DA_006097AEB8A7__INCLUDED_)

#define AFX_MAINFRM_H__42FA4ADE_2935_11D2_A0DA_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

BOOL m_bUserBar;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

//사용자 정의 툴바 윈도

CToolBar 쪻m_wndUserBar;

//사용자 정의 툴바에 설정할 비트맵

CBitmap m_usrBitmap;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnMaketoolbar();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__42FA4ADE_2935_11D2_A0DA_006097AEB8A7__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include “stdafx.h”

#include “ExMkToolbar.h”

#include “MainFrm.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_COMMAND(ID_MAKETOOLBAR, OnMaketoolbar)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

//////////////////////////////////////////////////////////////////////////// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

UINT

buttoninfo[6]={ID_FILE_NEW,ID_FILE_OPEN,ID_FILE_SAVE,ID_SEPARATOR,ID_EDIT_CUT,ID_EDIT_COPY};

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC);

SIZE size={16,15},size1={16+7,15+6};

//비트맵을 로드한다.

m_wndToolBar.LoadBitmap(IDB_TOOLBARBITMAP);

//경계 설정

m_wndToolBar.SetBorders(1,1,1,1);

//비트맵 버튼과 비트맵의 크기를 설정한다.

m_wndToolBar.SetSizes(size1,size);

//버튼 정보와 버튼수를 설정한다.

m_wndToolBar.SetButtons(buttoninfo,6);

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

m_bUserBar=FALSE;

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

//////////////////////////////////////////////////////////////////////////// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////// CMainFrame message handlers

#include “MkToolBarDlg.h”

void CMainFrame::OnMaketoolbar()

{

//대화 상자를 실행한다.

CMkToolBarDlg 쪻pDlg = new CMkToolBarDlg;

if(pDlg->DoModal()==IDOK)

{

//이미 사용자 툴바 윈도가 만들어져 있으면

if(m_bUserBar)

{

//툴바를 메인 윈도에서 플로팅시킨다.

//윈도를 소멸시키기 위해서는 플로팅을

//해야 함.

FloatControlBar(m_wndUserBar,CPoint(0,0));

m_wndUserBar->ShowWindow(SW_HIDE);

m_wndUserBar->DestroyWindow();

m_wndUserBar=NULL;

//사용자 비트맵을 지운다

m_usrBitmap.DeleteObject();

}

//툴바 윈도를 생성한다.

m_wndUserBar= new CToolBar;

//윈도를 만든다.

m_wndUserBar->CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE

| CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC);

SIZE size={16,15},size1={16+7,15+6};

int bitsize;

CBitmap cBit;

CDC 쪻pDC,memDC,srcDC;

//툴바 비트맵을 로드한다.

cBit.LoadBitmap(IDB_TOOLBARBITMAP);

pDC=GetDC();

//DC 두 개를 만든다.

memDC.CreateCompatibleDC(pDC);

srcDC.CreateCompatibleDC(pDC);

//src DC에 툴바 비트맵을 설정

srcDC.SelectObject(&cBit);

//비트맵 사이즈는 대화 상자에서 설정한 아이콘수쪻16이 됨.

bitsize=pDlg->toolbarnum쪻16;

//사용자 비트맵을 만든다.

m_usrBitmap.CreateCompatibleBitmap(pDC,bitsize,15);

//사용자 비트맵을 DC에 설정

memDC.SelectObject(&m_usrBitmap);

//대화 상자에서 선택한 비트맵들을 모두

//memDC에 출력하여 m_usrBitmap을 만든다.

for(int i=0;i<pDlg->toolbarnum;i++)

{

memDC.BitBlt(i쪻16,0,16,15,&srcDC,pDlg-> toolbarpos[i]쪻16,

0,SRCCOPY);

}

ReleaseDC(pDC);

//사용자 툴바에 비트맵을 설정

m_wndUserBar->SetBitmap(m_usrBitmap);

m_wndUserBar->SetBorders(1,1,1,1);

m_wndUserBar->SetSizes(size1,size);

m_wndUserBar->SetButtons(pDlg->toolbarid,pDlg-> toolbarnum);

//도킹이 가능하게 하고

m_wndUserBar->EnableDocking(CBRS_ALIGN_ANY);

//툴바를 도킹시킨다.

DockControlBar(m_wndUserBar);

//윈도가 만들어졌으므로 m_bUserBar는 TRUE

m_bUserBar=TRUE;

}

delete pDlg;

}

(2) 대화 상자 CMkToolBar 소스

#if

!defined(AFX_MKTOOLBARDLG_H__42FA4AEA_2935_11D2_A0DA_006097AEB8A7__INCLUDED_)

#define AFX_MKTOOLBARDLG_H__42FA4AEA_2935_11D2_A0DA_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// MkToolBarDlg.h : header file

//

//////////////////////////////////////////////////////////////////////////// CMkToolBarDlg dialog

class CMkToolBarDlg : public CDialog

{

// Construction

public:

CMkToolBarDlg(CWnd쪻 pParent = NULL);   // standard constructor

int srcdatanum;

int desdatanum;

// Dialog Data

//{{AFX_DATA(CMkToolBarDlg)

enum { IDD = IDD_MAKETOOLBAR };

CListCtrl m_listDes;

CListCtrl m_listSrc;

//}}AFX_DATA

CImageList 쪻m_pImageList;

CBitmap m_desBitmap;

//툴바 수

int toolbarnum;

//툴바 ID

UINT toolbarid[50];

//툴바의 위치 번호

UINT toolbarpos[50];

int SeekId(LPCSTR data);

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMkToolBarDlg)

protected:

virtual void DoDataExchange(CDataExchange쪻 pDX);   // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CMkToolBarDlg)

virtual BOOL OnInitDialog();

afx_msg void OnItemchangedSrclist(NMHDR쪻 pNMHDR, LRESULT쪻 pResult);

afx_msg void OnAdd();

afx_msg void OnItemchangedDeslist(NMHDR쪻 pNMHDR, LRESULT쪻 pResult);

afx_msg void OnDel();

virtual void OnOK();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately

// before the previous line.

#endif //

!defined(AFX_MKTOOLBARDLG_H__42FA4AEA_2935_11D2_A0DA_006097AEB8A7__INCLUDED_)

// MkToolBarDlg.cpp : implementation file

//

#include “stdafx.h”

#include “ExMkToolbar.h”

#include “MkToolBarDlg.h”

#include <string.h>

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CMkToolBarDlg dialog

CMkToolBarDlg::CMkToolBarDlg(CWnd쪻 pParent /쪻=NULL쪻/)

: CDialog(CMkToolBarDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CMkToolBarDlg)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

srcdatanum=-1;

desdatanum=-1;

}

void CMkToolBarDlg::DoDataExchange(CDataExchange쪻 pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMkToolBarDlg)

DDX_Control(pDX, IDC_DESLIST, m_listDes);

DDX_Control(pDX, IDC_SRCLIST, m_listSrc);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMkToolBarDlg, CDialog)

//{{AFX_MSG_MAP(CMkToolBarDlg)

ON_NOTIFY(LVN_ITEMCHANGED, IDC_SRCLIST, OnItemchangedSrclist)

ON_BN_CLICKED(IDC_ADD, OnAdd)

ON_NOTIFY(LVN_ITEMCHANGED, IDC_DESLIST, OnItemchangedDeslist)

ON_BN_CLICKED(IDC_DEL, OnDel)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CMkToolBarDlg message handlers

char 쪻menuname[8]={“새파일”,”열기”,”저장”,”잘라내기”,”복사”,”붙여넣기”,”인쇄”,”정보”};

int idnumber[8]={ID_FILE_NEW,ID_FILE_OPEN,ID_FILE_SAVE,

ID_EDIT_CUT,ID_EDIT_COPY,ID_EDIT_PASTE,ID_FILE_PRINT,ID_APP_ABOUT};

char 쪻idnum[8]={“ID_FILE_NEW”,

“ID_FILE_OPEN”,

“ID_FILE_SAVE”,

“ID_EDIT_CUT”,

“ID_EDIT_COPY”,

“ID_EDIT_PASTE”,

“ID_FILE_PRINT”,

“ID_APP_ABOUT”};

BOOL CMkToolBarDlg::OnInitDialog()

{

CDialog::OnInitDialog();

//이미지 리스트를 만들고 이곳에 비트맵을 설정한다.

m_pImageList= new CImageList;

m_pImageList->Create(16,15,ILC_COLOR4,8,3);

CBitmap cBit;

cBit.LoadBitmap(IDB_TOOLBARBITMAP);

m_pImageList->Add(&cBit,RGB(0,0,255));

m_pImageList->SetImageCount(8);

m_listDes.SetImageList(m_pImageList,LVSIL_NORMAL);

m_listDes.SetImageList(m_pImageList,LVSIL_SMALL);

m_listSrc.SetImageList(m_pImageList,LVSIL_NORMAL);

m_listSrc.SetImageList(m_pImageList,LVSIL_SMALL);

//컬럼 만들기

LV_COLUMN lvcolumn;

char 쪻listcolumn[2]={“툴바 항목”,”메뉴ID”};

int width[2]={150,150};

for (int i=0; i<2; i++)

{

lvcolumn.mask = LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH;

lvcolumn.fmt = LVCFMT_LEFT;

lvcolumn.pszText = listcolumn[i];

lvcolumn.iSubItem = i;

lvcolumn.cx = width[i];

m_listSrc.InsertColumn(i, &lvcolumn);

m_listDes.InsertColumn(i, &lvcolumn);

}

//데이터 넣기

LV_ITEM lvitem;

int curitem;

for (i= 0; i< 8; i++)

for (int j = 0; j < 2; j++)

{

lvitem.mask =

(j==0)?LVIF_TEXT|LVIF_IMAGE:LVIF_TEXT;

lvitem.iItem = (j==0)?i:curitem;

lvitem.iSubItem = j;

if(j==0)

lvitem.iImage=i;

switch(j)

{

case 0:

lvitem.pszText = menuname[i];

curitem=m_listSrc.InsertItem(&lvitem);

break;

case 1:

lvitem.pszText= idnum[i];

m_listSrc.SetItem(&lvitem);

break;

}

}

return TRUE;  // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

void CMkToolBarDlg::OnItemchangedSrclist(NMHDR쪻 pNMHDR, LRESULT쪻 pResult)

{

NM_LISTVIEW쪻 pNMListView = (NM_LISTVIEW쪻)pNMHDR;

// TODO: Add your control notification handler code here

srcdatanum = pNMListView->iItem;

쪻pResult = 0;

}

//==> 버튼 클릭시

void CMkToolBarDlg::OnAdd()

{

LV_ITEM lvitem;

if(srcdatanum<0)

return;

int num=m_listDes.GetItemCount();

for (int j = 0; j < 2; j++)

{

lvitem.mask = (j==0)?LVIF_TEXT|LVIF_IMAGE:LVIF_TEXT;

lvitem.iItem = num;

lvitem.iSubItem = j;

if(j==0)

lvitem.iImage=srcdatanum;

switch(j)

{

case 0:

lvitem.pszText = menuname[srcdatanum];

m_listDes.InsertItem(&lvitem);

break;

case 1:

lvitem.pszText= idnum[srcdatanum];

m_listDes.SetItem(&lvitem);

break;

}

}

}

void CMkToolBarDlg::OnItemchangedDeslist(NMHDR쪻 pNMHDR, LRESULT쪻 pResult)

{

NM_LISTVIEW쪻 pNMListView = (NM_LISTVIEW쪻)pNMHDR;

desdatanum = pNMListView->iItem;

쪻pResult = 0;

}

//<== 버튼 클릭시

void CMkToolBarDlg::OnDel()

{

if(desdatanum<0)

return;

m_listDes.DeleteItem(desdatanum);

}

//data 문자열을 넣으면 menuname 배열에, 같은

//문자열이 있으면 해당 문자열을 넣는다.

int CMkToolBarDlg::SeekId(LPCSTR data)

{

for(int i=0;i<8;i++)

if(strcmp(menuname[i],data)==0)

return i;

return -1;

}

void CMkToolBarDlg::OnOK()

{

//리스트 컨트롤 안의 아이템수를 num에 저장

int num=m_listDes.GetItemCount();

CString data;

int nid;

toolbarnum=num;

//num만큼 반복하여 문자열을 얻고 이 문자열에

//해당하는 번호를 얻은 다음

//toolbarid와 toolbarpos에 설정한다.

for(int i=0;i<num;i++)

{

data=m_listDes.GetItemText(i,0);

nid=SeekId(data);

toolbarid[i]=idnumber[nid];

toolbarpos[i]=nid;

}

CDialog::OnOK();

}

상태바는 메인프레임 하단부에 위치하여 메뉴에서 특정 항목을 선택하면 해당 항목의 도움말이 출력되는 형태의 도구바를 의미합니다. AppWizard를 이용하여 프로젝트를 만들면 기본적인 상태바가 만들어져 상태바 우측으로 세 개의 분할 항목이 있으며, 이 항목은 각각 Caps Lock, NumLock, ScrollLock 키가 눌려져 있는가를 알려줍니다. 먼저 이 상태바가 어떻게 만들어지는가를 분석해 보겠습니다.

상태바 만들기

상태바를 컨트롤하는 클래스는 CStatusBar입니다. AppWizard로 프로젝트를 만들면 CMainFrame 클래스 헤더에 다음과 같이 설정된 것을 볼 수 있을 것입니다.

CStatusBar  m_wndStatusBar;

m_wndStatusBar는 CStatusBar를 상속받은 멤버 변수입니다. 이 멤버 변수의 Create 함수를 이용하여 상태바를 만들 수 있습니다. 메인프레임의 OnCreate 함수를 보면 다음과 같은 항목이 이미 있을 것입니다.

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

이것은 상태바를 만들고 상태바에 지시자 항목을 설정한다는 의미입니다. 이 두 가지 작업이 실패하면 상태바 만들기를 실패하였다는 내용이 디버거 윈도에 출력되고 -1을 리턴하게 됩니다. m_wndStatusBar의 Crate 함수는 다음과 같은 형태입니다.

BOOL Create( CWnd쪻 pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );

첫 번째 인자는 부모 윈도 핸들이며 두 번째 인자는 윈도 스타일입니다. 상태바도 도구바이기 때문에 CBRS_ 계열을 이용할 수 있습니다. 보통 이용하는 스타일은 CBRS_BOTTOM, CBRS_LEFT 등등입니다. 일반적으로 상태바는 CBRS_- BOTTOM으로 사용하는 경우가 대부분입니다. 이렇게 사용하는 것이 가장 편리하기 때문입니다. 세 번째 인자는 상태바 아이디입니다. 이 ID는 기본적으로 AFX_IDW_STATUS_BAR로 설정됩니다. 이 아이디를 바꾸면 상태바 창에 메뉴의 특정 항목 설명문이 출력되지 않습니다. 또 다른 용도의 상태를 만들려면 nID를 바꾸면 됩니다. AppWizard로 만들어진 기본 상태바에서는 단순하게 부모 윈도의 클래스 포인터만 넘겨주고 나머지는 기본적으로 설정된 값을 사용합니다. 이렇게 하고 난 후에 지시자를 선택합니다.

지시자를 설정한다는 것은 상태바 하단부에 분할 영역을 만들어서 정보를 출력한다는 의미입니다. 그림 1-6-19에서 상태바 하단부에 기본적으로 세 개의 영역으로 나누어져 있는 것을 볼 수 있습니다.

메인프레임에서 상태바를 만들고 SetIndicators 함수를 수행하면 그림 1-6-19의 ① 항목과 같이 지시자에 의해서 세 개의 항목이 분할됩니다. SetIndicator 함수는  indicator라는 정수형 배열을 인자로 사용하며, 분할될 영역의 수를 두 번째 인자로 사용합니다. 이 정수형 배열은 메인프레임에 그림 1-6-19의 ②와 같이 정의되어 있습니다. 이 안에 설정된 ID_INDICATOR_ 계열의 값들은 이미 String Table에 설정된 값으로써 그림 1-6-19의 ②와 같이 설정하면 Lock 계열의 키가 눌려짐을 알 수가 있습니다.

상태바 지시자 항목 바꾸기

일반 애플리케이션 프로그램에서는 상태바를 이용하여 다양한 정보를 알려줍니다. 현재 좌표 및 키 입력 상태, 또는 여러 정보 등 다양한 형태로 설정되어 있습니다. 이렇게 상태바의 지시자 항목을 바꾸고자 할 경우에는 그림 1-6-19의 indicator 배열에서 값을 바꾸면 됩니다. ExStatusBar 예제는 상태바에 새로운 지시자 항목을 만들고 이 항목에 문자를 출력하는 예제입니다. 이 예제를 만들어 가면서 지시자 항목을 바꾸는 방법에 대해서 설명하겠습니다.

ExStatusBar는 SDI 형태로 만들어졌습니다. 이렇게 만든 후에 먼저 지시자 항목에 설정할 ID를 만듭니다. 이 ID를 만드는 방법은 그림 1-6-20과 같습니다.

그림 1-6-20의 ① 항목과 같이 ResourceView에서 우측 마우스 버튼을 클릭하면 팝업 메뉴가 설정되고, 이때 Resource Symbols 항목을 선택하면 그림 1-6-20의 ② 항목과 같은 대화 상자가 나타납니다. 이 대화 상자에서 New 버튼을 클릭하고 새로 ID를 설정합니다. 이렇게 ID를 설정한 후에 ResourceView 항목에서 String Table을 선택하고, 그림 1-6-20의 ③ 항목처럼 새로 설정한 ID에 문자열을 부과시키면 상태바 지시자에 설정할 ID가 만들어집니다. 이렇게 ID를 만든 후에 메인프레임의 indicator를 다음과 같이 변경합니다.

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_MYWORD, //새로 만든 ID

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

위와 같이 한 후에 컴파일하고 실행시키면 새로운 지시자 항목이 설정됩니다. 그림 1-6-21은 새로운 지시자 항목을 설정하고 컴파일하여 실행한 ExStatusBar의 출력 결과입니다.

상태바 지시자 항목에 문자열 출력하기

상태바 특정 지시자 항목에 문자열을 넣고자 할 때는 두 개의 함수를 이용합니다. 위에서 만든 ID_INDICATOR_MYWORD라는 항목에 문자열을 넣는 예를 만들면서 설명하겠습니다. 먼저 ID_INDICATOR_MYWORD라는 항목의 순서 번호를 얻습니다. 상태바에서 특정 지시자 항목의 순서 번호를 얻으려면 CommandToIndex 함수를 이용하면 됩니다. 다음은 ID_INDICATOR_MYWORD의 순서 번호를 알고자 할 때의 예입니다.

int index=m_wndStatusBar.CommandToIndex(ID_INDICATOR_MYWORD);

위와 같이 하면 index에 번호가 설정됩니다. 이 번호와 CStatusBar의 멤버 함수인 SetPaneText를 이용하여 지시자 항목에 문자열을 넣을 수 있습니다.

CString text=”안녕하세요”;

m_wndStatusBar.SetPaneText(index,text);

SetPanText는 상태바의 특정 항목에 문자열을 설정하는 함수입니다. 이때 index는 위에서 CommandToIndex에 의해 얻어진 순서 번호입니다. 위와 같이 하면 상태바의 지시자 항목에 문자열을 출력할 수 있습니다.

ExStatusBar 예제에서는 메뉴에서 “상태바에 글자넣기” 항목을 만들고 이 항목을 선택하면 상태바의 ID_INDICATOR_MYWORD 항목에 “안녕하세요”라는 문장을 출력하는 예제가 만들어져 있습니다. 지금까지 설명한 부분 외에 더 부과될 내용이 없으므로 소스는 생략하겠습니다. 제공되는 CD를 참조하기 바랍니다.

상태바에 윈도 설정하기

상태바에 에디터 상자나 버튼 프로그래스바 같은 것을 설정한 프로그램들을 가끔 볼 때가 있습니다. 상태바에 윈도를 설정하는 방법은 간단하고 기본적인 개념, 즉 “윈도에는 어떤 자식 윈도든 삽입할 수 있다.”는 것을 기억하면 됩니다. 상태바 또한 CWnd를 상속받은 윈도입니다. 이 윈도 위에 또 다른 윈도를 만들 수 있습니다.

보통 상태바에 프로그래스바 윈도를 많이 붙이는 경우가 있습니다. 프로그래스바 윈도를 붙여놓고 파일을 오픈할 때 파일의 정보를 분석하는 동안 진행 상태를 상태바에서 출력하는 경우 등을 본 적이 있을 것입니다. 이때 프로그래스바를 어떻게 상태바에 붙이는가? 결론은 상태바를 부모 윈도로 Create 함수를 실행하는 것입니다. 이렇게 하면 바로 상태바가 부모 윈도에 붙게 됩니다. 다음은 상태바 위에 프로그래스바를 붙이는 예입니다.

CProgressCtrl m_progressCtrl;

:

CRect rect= CRect(400,3,600,18);

m_progressCtrl.Create(WS_VISIBLE|WS_CHILD,rect,&m_wndStatusBar,500);

m_progressCtrl.SetRange(0,30);

m_progressCtrl.SetPos(5);

m_wndStatusBar라는 상태바를 부모 윈도로 설정하고 상태바의 400, 3 위치에 가로 600, 세로 18의 크기의 프로그래스바를 설정한 형태가 Create 함수입니다. 이렇게 설정하고 상태바의 구간을 30으로 놓고 현재 위치를 5 위치로 설정한 것입니다. 이렇게 하면 상태바에 프로그래스바를 설정할 수 있습니다. 버튼이나 기타 컨트롤도 이런 방법으로 상태바 위에 설정할 수 있습니다.

상태바 안에 자식 윈도로 여러 윈도를 설정하였을 때 주의할 점은 윈도 크기가 변할 때입니다. 윈도 크기가 변하면 이에 맞게 자식 윈도도 변해야 합니다. 윈도 크기가 가로 600 미만일 경우에 위와 같이 하면 프로그래스바의 일부분은 감추어지게 됩니다. 이럴 경우 MoveWindow 함수를 이용하여 자식 윈도의 위치를 재조정해야 합니다. 윈도 크기가 변할 경우에 WM_SIZE라는 메시지가 발생됩니다. 이 메시지가 발생될 때 상태바의 크기가 변경되었을 것이므로 변경된 상태에서 상태바에 있는 자식 윈도들도 크기가 변경되어야 합니다. 상태바의 지시자 윈도는 상태바의 크기가 변경될 때마다 지시자 윈도의 위치가 바뀝니다. 이런 방법으로 상태바에 설정한 자식 윈도의 크기를 바꾸어주면 됩니다.

ExStatusBar2는 상태바에 자식 윈도로 프로그래스바를 설정한 예제입니다. 위에서 설명한 방법을 그대로 이용하였습니다. 먼저 헤더 부분에 CProgressCtrl형의 멤버인 m_progressCtrl을 설정하고 OnCreate 함수에서 상태바를 만든 후에 이 상태바에 프로그래스바를 위의 방법대로 설정한 것입니다. 이렇게 상태바를 설정하고 메뉴에서 프로그래스바 항목을 선택하면 프로그래스바의 위치가 0.1초당 한 단위씩 증가하는 예입니다. 그림 1-6-22는 ExStatusBar2의 전체 구성도입니다.

그림 1-6-22와 같이 메뉴 항목에 “프로그래스바” 항목을 설정하고 헤더부에는 ① 항목과 같이 프로그래스바 윈도 클래스와 BOOL형으로 현재 프로그래스바의 활성 유무를 설정하는 플러그를 m_bProgress로 만듭니다. 이렇게 만든 다음 지금까지 설명한 방법으로 프로그래스바를 상태바 위에 설정합니다. 이렇게 설정한 항목이 그림 1-6-22의 ② 항목입니다.

메뉴에서 “프로그래스바” 항목을 클릭하여 현재 프로그래스바가 진행이 되고 있다면 타이머를 해제하고, 진행되지 않고 있다면 가동시킵니다. 진행 유무는 m_bProgress의 값을 참조합니다. 이 값은 메뉴를 선택할 때마다 토글되는데, 1이면 진행, 0이면 멈춤을 의미합니다. 그림 1-6-22의 ③ 항목이 이 내용입니다. 타이머가 가동되면 그림 1-6-22의 ④ 항목이 수행됩니다. 타이머에서는 프로그래스바의 현재 위치에서 한 칸 증가시키고 최대값, 즉 30이 되었을 때는 다시 0으로 설정한 다음 이 위치를 프로그래스바에 적용시킨 형태입니다. 이렇게 함으로써 ExStatusBar2 예제가 완결됩니다.

다음은 본 예제의 CMainFrame의 소스 부분입니다. 본 예제는 메인프레임에서만 코딩하였습니다.

// MainFrm.h : interface of the CMainFrame class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__62E9DA7E_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

#define AFX_MAINFRM_H__62E9DA7E_2921_11D2_A0D9_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

BOOL m_bProgress;

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

CProgressCtrl m_progressCtrl;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnProgressDo();

afx_msg void OnUpdateProgressDo(CCmdUI쪻 pCmdUI);

afx_msg void OnTimer(UINT nIDEvent);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__62E9DA7E_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include “stdafx.h”

#include “ExStatusBar2.h”

#include “MainFrm.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_COMMAND(ID_PROGRESS_DO, OnProgressDo)

ON_UPDATE_COMMAND_UI(ID_PROGRESS_DO, OnUpdateProgressDo)

ON_WM_TIMER()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

//////////////////////////////////////////////////////////////////////////// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |

CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

// TODO: Delete these three lines if you don’t want the toolbar to

//  be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

//프로그래스바를 상태바에 등록한다.

CRect rect= CRect(400,3,600,18);

m_progressCtrl.Create(WS_VISIBLE|WS_CHILD,rect,&m_wndStatusBar,500);

m_progressCtrl.SetRange(0,30);

m_progressCtrl.SetPos(5);

m_bProgress=FALSE;

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

//////////////////////////////////////////////////////////////////////////// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////// CMainFrame message handlers

void CMainFrame::OnProgressDo()

{

//현재 프로그래스바가 진행되면 타이머를 종료

if(m_bProgress)

KillTimer(1);

//진행되지 않으면 타이머 시작

else

SetTimer(1,100,NULL);

//m_bProgress를 토글시킨다.

m_bProgress=1-m_bProgress;

}

void CMainFrame::OnUpdateProgressDo(CCmdUI쪻 pCmdUI)

{

pCmdUI->SetCheck(m_bProgress);

}

//타이머 발생시

void CMainFrame::OnTimer(UINT nIDEvent)

{

//현재 위치를 얻고

int pos=m_progressCtrl.GetPos();

//한 칸 증가시킨다.

pos++;

//현재 위치가 30이면 도로 0으로 설정

if(pos==30)

pos=0;

//프로그래스바의 위치를 pos로 설정

m_progressCtrl.SetPos(pos);

CFrameWnd::OnTimer(nIDEvent);

}

DialogBar는 툴바와 같은 형태로 대화 상자 폼을 가지고 있는 도구바입니다. 툴바는 버튼만 가지고 있고 상태바는 현재 상태를 출력하는 기능을 합니다. DialogBar는 대화 상자 폼을 가지고 있기 때문에 대화 상자 폼에 설정할 수 있는 모든 컨트롤을 포함한 형태의 바가 되는 것입니다. 이 DialogBar의 많은 기능은 툴바와 비슷합니다. 다만 툴바와 달리 DialogBar에서는 이벤트를 얻고자 할 때 이벤트 메시지를 직접 만들어 주어야 합니다.

DialogBar 만들기

DialogBar는 CDialogBar 클래스를 이용하여 만듭니다. CDialogBar 또한 CWnd를 상속받았으며 ,Create 함수를 이용하여 윈도를 만듭니다. DialogBar를 만들기 위해서는 먼저 대화 상자 폼을 만들어야 합니다. DialogBar를 설명하기 위해 하나의 예제를 만들어 보겠습니다. 본 예제는 SDI 형태로 만들었으며 예제명은 ExDialogBar입니다. 프로젝트를 만든 후에 대화 상자 폼을 그림 1-6-22와 같이 만듭니다.

대화 상자를 만들 경우에는 그림 1-6-23과 같이 폼을 만든 후에 ClassWizard를 실행시켜서 클래스와 연결합니다. 그러나 DialogBar에서는 ClassWizard를 이용하여 연결할 수가 없습니다. 그림 1-6-23과 같이 만든 다음 CDialogBar의 Create 함수를 이용하여 이 멤버와 연결합니다. 먼저 메인프레임 헤더에 다음과 같이 CDialogBar의 형인 멤버 변수를 설정합니다.

//다이얼로그바

CDialogBar m_wndDialogBar;

위와 같이 만든 후에 Create 함수를 이용하여 DialogBar를 만듭니다. 이 내용은 다음과 같습니다.

m_wndDialogBar.Create(this,IDD_DIALOGBAR,CBRS_LEFT,2002);

CDialogBar의 Create 함수는 첫 번째 인자로는 부모 윈도 클래스 포인터를 받으며, 두 번째 인자로는 대화 상자 폼 ID를 설정합니다. 이때 설정되는 ID는 그림 1-6-22와 같이 만든 ID입니다. 세 번째 인자는 대화 상자가 도킹될 때 어느 위치에 도킹될 것인가를 설정하는 스타일 플러그입니다. CBRS_LEFT라고 설정하였기 때문에 대화 상자는 메인프레임 좌측에 붙게 됩니다. 네 번째 인자는 이 대화 상자의 ID입니다. 예제에서는 숫자를 사용하였으나 일반적인 애플리케이션 프로그램을 작성할 때에는 Resource ID에 ID를 설정하여 놓고 이 ID를 사용합니다. 위와 같이 하면 DialogBar가 만들어지는데, 이것을 툴바처럼 EnabeleDocking 함수를 이용하여 도킹과 플로팅이 가능하게 합니다.

//도킹이 가능하도록 한다.

m_wndDialogBar.EnableDocking(CBRS_ALIGN_ANY);

위와 같이 한 후 프레임 또한 EnableDocking 함수를 이용하여 도킹이 가능하도록 한 다음(일반적으로 메인프레임의 EnableDocking 함수는 프로젝트를 만들면 자동으로 설정됩니다.) DialogBar를 도킹시킵니다. 이 내용은 다음과 같습니다.

DockControlBar(&m_wndDialogBar);

위와 같이 하면 DialogBar가 만들어지게 됩니다.

DialogBar의 자식 윈도 컨트롤

DialogBar 안에 있는 자식 윈도들은 대화 상자처럼 클래스 위저드를 이용하는 방법으로 핸들링할 수 없으며, 또한 이벤트를 얻을 수 없습니다. 이렇기 때문에 DialogBar에 있는 자식 윈도 포인터를 얻으려면 GetDlgItem을 이용하여야 합니다. 만일 그림 1-6-23의 ① 항목인 에디터 상자에 문자열을 넣고자 한다면 다음과 같이 GetDlgItem을 이용하여 윈도 클래스 포인터를 얻은 다음 CEdit 멤버 함수나 CWnd 함수를 이용하여 문자열을 에디터 상자에 넣을 수 있습니다.

//에디터 상자에 데이터를 넣는다.

CString data;

data=”이것은 하나의 텍스트 대화 상자입니다.”;

m_wndDialogBar.GetDlgItem(IDC_EDIT1)->SetWindowText(data);

위와 같이 하면 에디터 상자에 “이것은 하나의 텍스트 대화 상자입니다.”라는 문장이 에디터 상자에 들어갈 것입니다. CWnd 함수를 사용하면 위와 같이 GetDlgItem 함수에서 바로 멤버 함수를 호출할 수 있으나, CWnd를 상속받은 하부 클래스일 경우에는 일반적으로 해당 윈도 클래스를 선언하고 형 캐스팅 연산을 하여 해당 윈도 포인터를 얻을 수 있습니다. 그림 1-6-23의 ②와 같은 리스트 상자에 데이터를 넣고자 할 때는 CListBox의 멤버 함수를 사용해야 합니다. 이럴 경우 다음과 같이 하면 해당 윈도를 CListBox의 포인터로 받을 수 있습니다.

CListBox 쪻pListBox;

pListBox=(CListBox 쪻)m_wndDialogBar.GetDlgItem(IDC_LIST1);

for(int i=0;i<10;i++)

{

data.Format(“데이터 %d”,i);

pListBox->AddString(data);

}

pListBox를 포인터로 선언한 후에 DialogBar의 IDC_LIST1을 GetDlgItem으로 하여 CWnd로 받은 다음 이것을 (CListBox 쪻)로 전환하여 pListBox에 설정합니다. 이 포인터를 이용하여 CListBox 함수에 데이터를 넣는 것입니다. 지금까지 설명한 방법으로 DialogBar의 자식 윈도들을 핸들링할 수 있습니다.

DialogBar에서 이벤트 얻기

DialogBar 안에 있는 자식 윈도들에서 이벤트가 발생하였을 때 수행하는 함수를 클래스 위저드로는 만들 수가 없습니다. 그렇기 때문에 직접 코딩으로 이벤트 함수를 만들어야 합니다. 자식 윈도 이벤트를 얻는 방법은 본 Part의 2장에서 설명하였습니다. 즉 ON_BN_ 계열, ON_EN_ 계열, ON_CBN_ 계열, ON_LBN_ 계열로 메시지 이벤트를 얻어서 수행하는 것입니다. 그림 1-6-23에서 ③ 항목의 버튼을 클릭했을 때 수행하는 함수를 만들고자 한다면 먼저 헤더에 버튼을 클릭했을 때 수행하는 함수를 다음과 같이 선언합니다.

afx_msg void OnBarButtonClick(); //버튼이 클릭되었을 때 수행하는 함수

다음 버튼이 눌려졌을 때의  메시지 매핑 매크로 ON_BN_CLICKED를 선언합니다.

ON_BN_CLICKED(IDC_BUTTON,OnBarButtonClick)

위 내용을 BEGIN_MESSAGE_MAP 매크로 안에 설정하는 것은 2장에서 설명하였습니다. 이렇게 하고 OnBarButtonClick 함수를 만들면 버튼이 클릭되었을 때 OnBarButtonClick 함수가 수행됩니다.

void CMainFrame::OnBarButtonClick()

{

AfxMessageBox(“대화 상자 버튼 클릭”);

}

이와 같은 방법으로 DialogBar의 자식 윈도에서 이벤트를 얻을 수가 있습니다.

ExDialogBar 소스

ExDialogBar의 리스트 상자에서 항목이 선택되었을 때의 이벤트 내용은 설명하지 않았습니다. 리스트 상자에서 항목 선택이 변경되었을 때 새로 선택된 항목이 메시지 상자에 문자열로 출력되는 내용은 본 예제에 추가했으며, 체크 버튼과 라디오 버튼을 클릭하였을 때도 OnBarButtonClick 함수가 수행되도록 하였습니다. 이 내용은 소스를 보고 분석하기 바랍니다. 본 예제는 메인프레임에서만 코딩하였습니다. 나머지 부분은 제공되는 CD를 참조하세요.

// MainFrm.h : interface of the CMainFrame class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__62E9D9F9_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

#define AFX_MAINFRM_H__62E9D9F9_2921_11D2_A0D9_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

//다이얼로그바

CDialogBar m_wndDialogBar;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

// NOTE - the ClassWizard will add and remove member

// functions here.

//    DO NOT EDIT what you see in these blocks of

//    generated code!

//}}AFX_MSG

//버튼이 클릭되었을 때

afx_msg void OnBarButtonClick();

//리스트 상자에서 선택 항목 변경시

afx_msg void OnListBoxChange();

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MAINFRM_H__62E9D9F9_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include “stdafx.h”

#include “ExDialogBar.h”

#include “MainFrm.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

// NOTE - the ClassWizard will add and remove mapping

// macros here.

//    DO NOT EDIT what you see in these blocks of

//    generated code !

ON_WM_CREATE()

//}}AFX_MSG_MAP

ON_BN_CLICKED(IDC_BUTTON,OnBarButtonClick)

ON_BN_CLICKED(IDC_CHECK,OnBarButtonClick)

ON_BN_CLICKED(IDC_RADIO,OnBarButtonClick)

ON_LBN_SELCHANGE(IDC_LIST1,OnListBoxChange)

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

//////////////////////////////////////////////////////////////////////////// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |

CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(“Failed to create status bar\n”);

return -1;      // fail to create

}

//다이얼로그를 만든다.

m_wndDialogBar.Create(this,IDD_DIALOGBAR,CBRS_LEFT,2002);

// TODO: Delete these three lines if you don’t want the toolbar to

//  be dockable

//도킹이 가능하도록 한다.

m_wndDialogBar.EnableDocking(CBRS_ALIGN_ANY);

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

DockControlBar(&m_wndDialogBar);

//에디터 상자에 데이터를 넣는다.

CString data;

data=”이것은 하나의 텍스트 대화 상자입니다.”;

m_wndDialogBar.GetDlgItem(IDC_EDIT1)->SetWindowText(data);

CListBox 쪻pListBox;

pListBox=(CListBox 쪻)m_wndDialogBar.GetDlgItem(IDC_LIST1);

for(int i=0;i<10;i++)

{

data.Format(“데이터 %d”,i);

pListBox->AddString(data);

}

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return TRUE;

}

//////////////////////////////////////////////////////////////////////////// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////

// CMainFrame message handlers

//버튼이 눌려졌을 때

void CMainFrame::OnBarButtonClick()

{

AfxMessageBox(“대화 상자 버튼 클릭”);

}

//리스트 상자에서 선택 항목이 바뀌었을 경우

void CMainFrame::OnListBoxChange()

{

CListBox 쪻pListBox;

pListBox=(CListBox 쪻)m_wndDialogBar.GetDlgItem(IDC_LIST1);

int num=pListBox->GetCurSel();

CString data;

pListBox->GetText(num,data);

AfxMessageBox(data);

}

ReBar는 툴바와 다이얼로그바의 중간 형태의 도구바로 생각할 수 있습니다. 인터넷 익스플로러 같은 프로그램의 상단부에 위치한 도구바 형태를 ReBar라고 합니다. ReBar는 6.0에서 새롭게 추가된 도구바입니다. ReBar는 도구바에 버튼과 여러 컨트롤을 설정할 수 있으며, 도킹과 플로팅은 되지 않는 형태의 도구바입니다. 이 ReBar에 대해서는 예제를 하나 만들어가면서 이해하는 것이 좋을 것 같습니다.

ReBar 만들기

AppWizard로 프로젝트를 만들 때 ExReBar는 SDI 형태로 제작합니다. AppWizard Step4에서 Rebar를 선택하면 Rebar 형태의 프로그램을 제작할 수 있습니다. 그림 1-6-24는 AppWizard의 Step4에서 Rebar 형태의 도구바를 선택한 화면입니다.

AppWizard Step4에서 그림 1-6-24와 같이 ReBar를 선택하고 프로젝트를 만든 다음 컴파일하고 실행하면 그림 1-6-25와 같은 화면이 출력됩니다.

그림 1-6-25와 같이 ReBar를 설정하면 툴바와 DialogBar 두 개가 설정됩니다. 그리고 ReBar는 툴바와 DialogBar를 포함한 하나의 바가 됩니다. ReBar는 자신이 도구바이기보다 자신 안에 또 다른 도구바를 설정한 형태입니다. 메인프레임의 OnCreate 함수를 보면 다음과 같이 되어 있습니다.

//툴바를 만든다.

if (!m_wndToolBar.CreateEx(this) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(“Failed to create toolbar\n”);

return -1;      // fail to create

}

//다이얼로그바를 만든다.

if (!m_wndDlgBar.Create(this, IDR_MAINFRAME,

CBRS_ALIGN_TOP, AFX_IDW_DIALOGBAR))

{

TRACE0(“Failed to create dialogbar\n”);

return -1;  // fail to create

}

//ReBar를 만들고 이곳에 툴바와 다이얼로그바를 설정한다.

if (!m_wndReBar.Create(this) ||

!m_wndReBar.AddBar(&m_wndToolBar) ||

!m_wndReBar.AddBar(&m_wndDlgBar))

{

TRACE0(“Failed to create rebar\n”);

return -1;      // fail to create

}

위의 소스를 보면 알 수 있듯이 먼저 툴바와 DialogBar를 만듭니다. 이렇게 만든 다음 ReBar를 만들고 이 Rebar에 AddBar 함수를 이용하여 툴바와 DialogBar를 설정하여 두 개의 도구바를 하나의 도구바 형태로 만듭니다. 이렇게 만든 ReBar는 분리되지는 않지만 안에 있는 도구바들의 위치 바꿈이 가능합니다.

ReBar 안의 DialogBar에 항목 설정

ReBar는 DialogBar와 툴바를 하나의 바 안에 흡수시킨 점 외에 나머지의 모든 기능은 툴바와 DialogBar에 넘겨줍니다. ExReBar에서 보면 DialogBar에서 로드하는 대화 상자 폼을 IDR_MAINFRAME으로 설정하였습니다. 이 대화 상자 폼에 여러분이 원하는 형태로 컨트롤을 설정할 수 있습니다. 이렇게 컨트롤을 설정하고 이 컨트롤의 이벤트를 얻거나 윈도 클래스 포인터를 얻는 방법은 전 항목을 참조하면 됩니다.

ExReBar 예제에서는 그림 1-6-26과 같이 버튼을 두 개 설정하였습니다.

이렇게 버튼을 설정한 후에 ON_BN_CLICKED 메시지 매크로에 의해서 OnReBarButClick 함수와 연결하고, 이 함수에서는 버튼이 클릭되었을 때 버튼 클릭 메시지를 메시지 상자에 설정하였습니다. 이 내용은 다음과 같습니다.

void CMainFrame::OnReBarButClick()

{

AfxMessageBox(“ReBar에서 버튼 클릭됨“);

}

이와 같이 하면 ExReBar 예제가 완성됩니다. ExReBar는 지금까지 설명한 툴바와 DialogBar 내용을 포함시킨 부분이므로 지금까지의 내용을 참조하면 ReBar를 쉽게 제작할 수 있습니다. 본 예제의 소스는 지금까지 설명한 내용 외는 더 이상의 추가 부분이 없으므로 생략하겠습니다. 제공되는 CD를 참조하기 바랍니다.

본 항목은 중·고급자를 위한 부분입니다. 본 항목에서는 복잡한 다이얼로그바를 제작하는 방법에 대해서 설명하고 도구바가 어떻게 만들어지는가의 내부에 대해서 설명합니다.

예제 ExDBarWnd

그림 1-6-27은 ExDBarWnd의 출력 형태입니다.

ExDBarWnd는 ProjectWorkspace와 흡사한 형태입니다. 그러나 본 예제는 리사이즈가 되지 않습니다. 되지 않는 것이 아니라 내부적으로는 리사이즈가 가능하나 외부의 윈도가 움직이지 않는다는 것입니다. 그림 1-6-27 형태의 다이얼로그바를 만드는 부분은 지금까지의 내용을 이해하면 약간 잔손이 많이 가는 차이만 있을 뿐 만들 수는 있을 것입니다.

다이얼로그바에 리스트 컨트롤을 설정하고 트리 컨트롤을 GetDlgItem을 이용하여 받은 다음, 데이터를 넣어 탭 컨트롤을 설정하고 GetDlgItem을 이용하여 데이터를 넣으면 가능할 것입니다. 본 예제에서는 CDialogBar를 상속받은 CMyDialogBar를 만든 다음 이 클래스 안에 트리 컨트롤 멤버 포인터와 탭 컨트롤 멤버 포인터를 얻어서 이곳에 데이터를 설정하였습니다. 이렇게 설정하는 방법에 대해서는 ExDBarWnd의 소스를 분석해 보면 알 수 있습니다.

ExDBarWnd에서 다소 궁금한 부분이 있다면 트리 컨트롤과 탭 컨트롤 이벤트를 어떻게 받는가입니다. ON_ 계열의 메시지 매핑 매크로에는 트리 컨트롤과 탭 컨트롤의 이벤트를 받는 부분이 없기 때문입니다.

이때에는 메인프레임에서 OnNotify 함수를 만들어 이곳에서 이벤트 메시지를 얻으면 됩니다. OnNotify는 WM_NOTIFY 메시지에 의해서 발생되는 함수입니다. WM_NOTIFY 메시지는 lParam에 NMHDR 구조체를 포함한 구조체의 포인터를 넘겨줍니다. 또한 wParam에는 컨트롤의 ID가 설정됩니다. 이렇기 때문에 이 함수의 해당 컨트롤 아이디일 경우 lParam의 구조체에서 이벤트 정보를 얻은 다음 내용을 처리하면 됩니다. 예를 들어서 현재 얻으려는 이벤트가 트리 컨트롤이고 이 트리 컨트롤 ID가 IDC_TREE일 경우, 먼저 lParam의 값을 NMHDR의 포인터로 받은 다음 이벤트 메시지를 얻습니다. 이 내용은 다음과 같습니다.

BOOL CMainFrame::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT쪻 pResult)

{

// TODO: Add your specialized code here and/or call the base class

LPNMHDR pnm=(LPNMHDR)lParam;

if(wParam==IDC_TREE && pnm->code==TVN_SELCHANGED)

{

:

}

위와 같이 하면 ID가 IDC_TREE인 트리 컨트롤에서 현재 항목이 변경되었다는 이벤트 메시지가 나옵니다. 항목이 변경되었을 때의 정보는 트리 이벤트 구조체 중 NM_TREEVIEW 구조체 형태로 설정되어 lParam으로 보내줍니다. 따라서 lParam을 다시 NM_TREEVIEW 포인터로 받고 변경된 항목을 얻어낼 수 있습니다.

BOOL CMainFrame::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT쪻 pResult)

{

// TODO: Add your specialized code here and/or call the base class

LPNMHDR pnm=(LPNMHDR)lParam;

if(wParam==IDC_TREE && pnm->code==TVN_SELCHANGED)

{

HTREEITEM hCurItem;

NM_TREEVIEW쪻 pNMTreeView = (NM_TREEVIEW쪻)pnm;

hCurItem=pNMTreeView->itemNew.hItem;

if(hCurItem)

{

//hCurItem이 새로 설정된 아이템임

}

return TRUE;

}

return CFrameWnd::OnNotify(wParam, lParam, pResult);

}

위와 같이 NM_TREEVIEW의 포인터 형태로 pNMTreeView를 설정하고 pnm을 다시 NM_TREEVIEW 형태로 전환하면 새로 설정된 아이템을 얻을 수가 있습니다. 트리 컨트롤뿐만 아니라 많은 컨트롤들은 위와 같이 OnNotify 함수를 이용하면 됩니다.

ExDBarWnd 예제는 다이얼로그바 내부가 리사이징됩니다. 즉 내부에서는 크기가 바뀌나 외부에서는 바뀌지 않습니다. 이렇게 하는 방법은 대화 상자 폼의 Style에서 Border 항목을 Resizing으로 설정하면 됩니다. 이 경우 바 자체의 크기는 변화되지 않는 반면에 내부에 있는 다이얼로그 폼의 크기는 변경됩니다.

컨트롤바의 내부

우리가 보기에 도킹바를 만들기 위해서는 한 개의 윈도가 메인프레임에 붙었다가 떨어지는 것 같이 보이나, 내부적으로 보면 CDockBar 클래스와 CMiniDock- FrameWnd가 함께 결합되어 메인프레임에 붙을 경우에는 CDockBar가 활성화되고 도킹되면 CMiniDockFrameWnd가 활성화되는 두 개의 윈도를 함께 사용하였습니다. 예제 ExDBarWnd에서 다이얼로그바 크기를 변경해 보면 이 두 개가 함께 존재한다는 의미를 알 수 있을 것입니다.

실제 MFC 소스에서 보면 컨트롤바를 포함하는 윈도는 CMiniDockFrameWnd로 설정되어 있습니다. 소스의 AfxPriv.h를 열어 보면 다음과 같은 항목이 보입니다.

class CMiniDockFrameWnd : public CMiniFrameWnd

{

DECLARE_DYNCREATE(CMiniDockFrameWnd)

public:

// Construction

CMiniDockFrameWnd();

virtual BOOL Create(CWnd쪻 pParent, DWORD dwBarStyle);

// Operations

virtual void RecalcLayout(BOOL bNotify = TRUE);

// Implementation

public:

CDockBar m_wndDockBar;//CDockBar를 상속받은 멤버

//{{AFX_MSG(CMiniFrameWnd)

afx_msg void OnClose();

afx_msg void OnNcLButtonDown(UINT nHitTest, CPoint point);

afx_msg void OnNcLButtonDblClk(UINT nHitTest, CPoint point);

//}}AFX_MSG

afx_msg int OnMouseActivate(CWnd쪻 pDesktopWnd, UINT nHitTest, UINT message);

DECLARE_MESSAGE_MAP()

};

위의 클래스를 보듯이 CMiniDockFrameWnd 안에는 m_wndDockBar라는 CDockBar 클래스가 내장되어 있습니다. 컨트롤바의 외부 윈도는 CMiniDockFrameWnd라고 볼 수 있고, 내부에는 CDockBar라는 클래스 멤버가 내장되어 있는 형태입니다. 컨트롤바의 외부에는 CMiniDockFrameWnd가 있고 그 내부에는 CDocBar가 있습니다. 이렇게 되어 있는 형태라는 것을 좀더 자세히 볼 수 있는 부분이 CMiniDockFrameWnd의 Create 함수입니다.

BOOL CMiniDockFrameWnd::Create(CWnd쪻 pParent, DWORD dwBarStyle)

{

// set m_bInRecalcLayout to avoid flashing during creation

// RecalcLayout will be called once something is docked

m_bInRecalcLayout = TRUE;

//CMiniDockFrameWnd의 스타일

DWORD dwStyle = WS_POPUP|WS_CAPTION|WS_SYSMENU

|MFS_MOVEFRAME|

MFS_4THICKFRAME|

MFS_SYNCACTIVE|MFS_BLOCKSYSMENU|

FWS_SNAPTOBARS;

: 중간 생략

//CDockBar를 만든다.

if (!m_wndDockBar.Create(pParent, WS_CHILD | WS_VISIBLE |

dwStyle,AFX_IDW_DOCKBAR_FLOAT))

{

m_bInRecalcLayout = FALSE;

return FALSE;

}

// CDockBar의 부모 윈도를 CMiniDockFrameWnd로 설정한다.

m_wndDockBar.SetParent(this);

m_bInRecalcLayout = FALSE;

return TRUE;

}

본 함수는 소스 BARDOCK.cpp에 설정되어 있습니다. 쉽게 생각해서, 도구바 내부의 항목들은 CDockBar에 설정되어 있으며, 외부에는 CMiniDockFrameWnd가 설정되어 있습니다. Project workspace와 같이 크기가 변하는 윈도를 만들 경우 외부에서 ControlBar를 상속받아서 만들면 내부에 있는 이 두 함수에서 걸리기 때문에 만들어지지 않습니다. 또한 윈도를 MoveWindow 함수나 SetWindowPos 함수를 사용하여 변경하였다 하더라도 위 두 개의 클래스와 CDockContext 클래스에서 다시 자동적으로 변경시키기 때문에 만들기가 힘들어집니다.

본 항목의 목적은 이 세 개의 클래스 멤버 함수들의 기능과 이 함수들의 소스 파일은 무엇인가를 알려주는 데 있습니다. 지금까지의 결과를 보면 리사이징이 되는 다이얼로그바를 만들기 위해서는 가장 기본적인 방법으로 CFrameWnd 형태의 윈도를 받아서 먼저 하나의 윈도를 만들고, 이 윈도의 NC를 마우스 버튼이 클릭되었을 때 도킹하기 위해서는 윈도 스타일을 WS_CLIPSIBLINGS 형태로 만들고, 플로팅되었을 때는 일반적 형태로 만들고, 도킹되었을 때는 현재 어떤 위치에 도킹이 되었는가를 확인한 다음 메인프레임과 경계가 없는 부분에서는 마우스가 리사이징이 되는 형태로 만드는 등 모든 내용을 하나하나 해주어야 합니다. 그러나 좀더 편리한 방법이 있다면 나만의 CMiniDockFrameWnd와 CDockBar를 만들어서 사용해야 합니다. 즉 MFC 소스에 있는 CMiniDockFramWnd와 CDockBar의 멤버 함수를 알고 필요한 부분을 복사하여 클래스를 만들고 수정할 부분은 수정하면 된다는 의미입니다.

다음은 이 두 개의 클래스 멤버 함수와 필자가 생각하는 기능, 그리고 이 함수가 있는 소스 파일들입니다.

BOOL CDockBar::Create(CWnd쪻 pParentWnd, DWORD dwStyle, UINT nID)

파일명 : BARDOCK.CPP

내   용 : CDockBar 윈도를 만든다. 이 함수는 위의 CMiniDockFrameWnd의 Create에서 호출됨.

void CDockBar::DockControlBar(CControlBar쪻 pBar, LPCRECT lpRect)

파일명 : BARDOCK.CPP

내   용 : 메인프레임에서 호출한 DockControlBar가 수행되면 CFrameWnd:: DockContrlBar의 첫 번째 인자가 pBar로, 그리고 현재 컨트롤바의 크기가 lpRect로 설정되면서 도킹된다.

CSize CDockBar::CalcFixedLayout(BOOL bStretch, BOOL bHorz)

파일명 : BARDOCK.CPP

CDockBar의 크기를 계산하여 설정해 준다. bStretch는 확장 형태. 이 값이 TRUE이면 확장 bHorz는 수평으로 늘어나는 플러그

LRESULT CDockBar::OnSizeParent(WPARAM wParam, LPARAM lParam)

파일명 : BARDOCK.CPP

부모 윈도의 크기나 위치가 바뀌었을 경우

void CDockBar::OnWindowPosChanging(LPWINDOWPOS lpWndPos)

파일명 : BARDOCK.CPP

윈도의 위치가 바뀌었을 때 수행되는 함수

CControlBar쪻 CDockBar::GetDockedControlBar(int nPos) const

도킹되어 있는 바를 CControlBar 클래스 포인터로 넘겨준다.

int CDockBar::Insert(CControlBar쪻 pBarIns, CRect rect, CPoint ptMid)

파일명 : BARDOCK.CPP

새로운 컨트롤바를 rect 영역에 삽입한다.

int CMiniDockFrameWnd::OnMouseActivate(CWnd쪻 pDesktopWnd, UINT nHitTest, UINT message)

파일명 : BARDOCK.CPP

마우스가 현재 활성화되었을 때 리사이징 플러그를 무시하는 함수. 이 부분에서 무시하는 부분을 제거하면 마우스가 리사이징 형태로 바뀐다.

BOOL CDialogBar::Create(CWnd쪻 pParentWnd, LPCTSTR lpszTemplateName,

UINT nStyle, UINT nID)

파일명 : BARDLG.CPP

이 함수의 내부를 보면 다이얼로그바의 윈도 스타일을 알 수 있으며, 다이얼로그바와 MiniDockFrameWnd와 CDockBar의 연관 관계를 알 수가 있다.