6.메뉴 비트맵 엑셀레이터

본장은 메뉴와 비트멥 엑셀레이터에 대한 내용을 설명합니다. 비트맵은 GDI에 속하는 항목이면서 리소스를 이용하는 항목이기에 본장에 삽입하였습니다. 지금까지는 윈도우에 그래픽을 출력하는 기본적인 방법과 메시지 처리에 대해서 설명을 하였습니다. 이제 윈도우에서 어떤 항목을 선택하는 메뉴와 단축키를 이용해서 항목을 설정하는 엑셀레이터에 대하여 설명하겠습니다. 본장을 통해서 윈도우에 메뉴을 설정하고 다양한 그림을 출력하는 방법을 배우시게 될것입니다.

리소스에 대하여

본장부터 4장은 리소스를 이용합니다. 그렇다면 이 리소스(Resource)는 무엇인가 ? 한마디로 요약하자면 윈도우 프로그램에 필요한 여러 자원들을 리소스라고 합니다. 이 리소스에는 메뉴,비트맵,엑셀레이터,대화상자,아이콘,커서,문자열테이블등을 말합니다. GUI 하에서 프로그래밍을 할 때 위에 열거한 것들은 프로그래밍에서 구동되는 모듈이기보다 프로그램이 사용자와 좀더 쉽게 접근할수 있든 부속품 같은 것들입니다. 보통 이런 리소스는 rc라는 확장자를 가진 스크립트 파일로 제작합니다. 간단한 예를 하나든다면 메뉴를 만들고자 할 경우

MYMENU MENU PRELOAD DISCARDABLE

BEGIN

POPUP "File"

BEGIN

MENUITEM "New",                ID_FILE_NEW

MENUITEM "Open..",            ID_FILE_OPEN

MENUITEM "Save",               ID_FILE_SAVE

MENUITEM "Save As...",                 ID_FILE_SAVE_AS

END

POPUP "Help"

BEGIN

MENUITEM "About test...",              ID_APP_ABOUT

END

END

위와 같은 스크립트를 RC라는 확장자를 가진 파일에 기록하고 프로젝트에 삽입하면 됩니다. 위와같은 메뉴를 삽입하게되면 팝업메뉴로 "File"과 "Help□ 가설정되고 "File"의 서브 메뉴로 "New","Open","Save","Save As" 가 □Help" 메뉴에는 □About test"라는 항목이 설정됩니다.

위와 같이 하였다고 자동적으로 이메뉴가 프로그램에 척 달라 붙는 것이 절대 아닙니다. 리소스에 만들어진 것은 출력하고자 하는 데이터 형태만 구축된것이지 이것이 바로 프로그램과 연결되지 않습니다. 자원을 프로그램에 로드할경우에는 Load계열의 함수를 이용합니다. 메뉴를 로드하고자 한다면 LoadMenu 아이콘을 로드하고자 한다면 LoadIcon 비트맵을 로드하고자 한다면 LoadBitmap 등등 로드계열의 함수를 이용하여 로드할수 있으며 이런 함수들은 H계열의 형의 값을 리턴합니다. H란 핸들의 약자이기 때문에 리턴하는 것은 각 로드된 자원의 핸들러가 되는 것입니다. 예를 들어 LoadIcon에 의해서 로드되는 것은 HCION형의 핸들러 이며 LoadBitmap에 의해서 로드되는 것은 HBITMAP 핸들러 인것입니다.

위의 리소스 스크립트 소스를 보면 첫줄에  MYMENU MENU 라는 것이 있을것입니다. 이의미는 메뉴인데 이메뉴의 ID가 MYMENU라는 의미입니다. 모든 리소스는 자신의 ID를 가지고 있습니다. 또한 그렇게 설정해 주어야 합니다. 그렇게 함으로써 Load계열함수는 이 ID에 의해서 리소스를 로드하기 때문입니다. 예를 든다면 윈의 메뉴 스크립트의 ID는 MYMENU이므로 다음과 같이 설정할수 있습니다.

HMENU hMenu;

hMenu=LoadMenu(hInstance,"MYMENU");

Load계열의 함수는 대부분이 다음과 같은 형태입니다.

Load[자원이름](HINSTANCE hInstance , 리소스ID);

여기에서 우리는 하나는 알고 갑시다. 자원을 자신의 프로그램에 로드하고자 할경우에는 Load계열을 사용하며 Load계열을 사용하여 리턴되는 것은 H계열의 핸들러이라는 것을 기억해 주시기 바랍니다.

(표1) 자원을 로드하는 함수 리스트

자원 (Resource)	함수	리턴 핸들러
메뉴	LoadMenu(hInstance,"Menu");	HMENU hMenu
아이콘	LoadIcon(hInstance,"Icon");	HICON hIcon
비트맵	LoadBitmap(hInstance,"Bitmap");	HBITMAP hBitmap
엑셀레이터	LoadAccelerators(hInstance,"ACCERLATE");	HACCEL hAccel;
마우스커서	LoadCursor(hInstance,"Cur");	HCURSOR hCursor;

프로젝트에 리소스 삽입하기

프로젝트에 리소스를 삽입하기 위해서는 rc파일을 만들고 이 파일에 스크립트 언어식으로 리소스를 설정해야 한다고 설명하였습니다. 이렇게 되면 프로그래밍 언어 외에도 리소스 스크립트를 배워야 한다는 문제가 생깁니다. 그러나 이런문제는 컴파일러가 Visual 툴을 제공함으로써 쉽게 해결됩니다. Visual C++에서는 자원을 만드는 리소스 에디터를 기본적으로 제공하고 있습니다. 본항목에서는 Visual C++를 이용하여 리소스를 만드는 방법에 대해서 설명하겠습니다. 윈도우즈에서 볼란드 C++과 같은 다른 윈도우즈 컴파일러를 사용하시는 분들은 본장을 공부하기 대신에 각 컴파일러에서 제공하는 리소스 에디터 사용법을 습득하시기 바랍니다.

프로젝트를 만들고 난후에 파일메뉴에서 New항목을 설정하면 그림 1과 같은 화면이 나옵니다.

(그림 1) File 메뉴에서 New 항목을 선택하였을 때

카드철 메뉴에서 Files를 선택하시고 Resource Script를 선택하신후에 Add to project가 체크되어 있는 것을 확인한다음 만들 파일명을 입력합니다. 예를 들어 ExMenu란 프로젝트를 만들었다면 보통 rc파일은 프로젝트명과 같은 형식으로 설정하는 것이 좋습니다. 그림 1에서는 ExMenu.rc로 리소스 스크립트 파일을 설정하였습니다.

위와 같이 설정하면 그림 2와 같이 Program Workspace Window에 Resource View 카드철이 설정되고 ExMenu resource라는 항목이 생성됩니다.

(그림 2) Program Workspace Window에서 Resource View

이상태가 바로 ExMenu.rc와 resource.h 두 개가 생성된 상태입니다. ExMenu.rc는 스크립트 파일 과 이스크립트 의 헤더이 resource.h가 생성됩니다. 앞으로 프로그램밍을 할 경우 resource.h는 자주 수정을 하게 될것입니다. 그러나 ExMenu.rc는 파일 자체를 수정하는 경우가 거히 없습니다. Visual C++에서 제공하는 툴로 간단하게 제작할수 있기 때문입니다.

1.메뉴

메뉴 만들기

메뉴를 만들기 위해서는 Ctrl+2키를 누르거나 Resource 메뉴바에서

항목을 클릭하시면 됩니다. Resource 도구바가 윈도우에 나타나 있지 않을 경우 메뉴와 툴바 사이의 빈공간에 마우스 커서를 위치한후에 우측 마우스 버튼을 클릭하게 되면 그림 3과 같이 여러 도구바 리스트가 나타납니다. 이곳에서 Resource를 선택하시면 화면에 Resource 도구바가 나타날것입니다.

(그림 3) 도구바 리스트 메뉴

Ctrl+2키나 또는 Resource 도구바에서 New Menu항목을 클릭하여 메뉴를 만들게 되면  IDR_MENU1이라는 메뉴가 만들어 집니다. SDK를 이용할경우에는 IDR_MENU1이라는 정의형보다는 문자열을 사용하는 것이 편리합니다. 예를 든다면 "MENU"라든지 또는 "MYMENU"라는 식으로 설정하는 것이 좋다는 이야기입니다. 이유는 LoadMenu의 두 번째 인자가 문자열을 받기 때문입니다.

아무것 도 설정되어 있지 않는 메뉴에서 항목을 입력하고자 한다면 Alt+Enter키를 누르면  됩니다. 이때 화면에 Properties 대화상자가 나타납니다. 이 대화상자에는 두 개의 카드철 형태로 되어 있습니다. Genear형태와 Extended Styles형태입니다.

Extended Style형태는 사용하지 않습니다. General 형태만 설명하겠습니다.

일반적인 항목(General)

그림 4는 Alt+Enter키를 눌렀을 때 나타나는 Properties의 대화상자입니다.

(그림 4) Properties 대화상자

ID항목은 메뉴의 ID를 의미합니다. 여러분이 원하는대로 ID를 설정할 수가 있습니다. 보통 ID를 설정할 경우 ID_[메뉴명] 으로 설정하는 것이 좋습니다. 일관적이기 때문에 이해하기가 쉽기 때문입니다. Caption이란 메뉴에 출력될 글자입니다. 이글자에서 앞에 '&□키를 삽입하게 되면 메뉴에 "New" 형태도 출력되고 메뉴가 활성화 되어 있는 상태에서 '&□ 다음의 문자키를 누르게 되면 메뉴를 선택한것과 같는 이벤트가 발생되게 됩니다. "\t"문자는 메뉴에서 탭크기만큼 띄우라는 의미입니다.

그림 4에서 Ctrl+N을 사입한 것은 이메뉴를 선택할 때 단축키로 Ctrl+N를 누른것과 같다는 의미입니다. 단축키를 이용하는 방법은 뒤에 엑셀레이터에서 다시 설명하겠습니다.

Separator은 현재 메뉴 항목이 그냥 분리되는 형태로 라인이 그어진 형태라는 것입니다. 그림 5에서 Save As와  Print Setup 하단부에 라인만 있는것처럼 라인을 설정하고자 할경우에는 Separator를 선택합니다.

(그림 5) Separator 이 삽입된 메뉴

Popup 이란 현재 메뉴가 팝업 메뉴이며 이메뉴에 부메뉴가 설정되어 있다는 의미입니다. 그림 6에서 popup 메뉴는 Popup를 설정하게 한것입니다. 이렇게 하면 popup 에 부메뉴를 설정할수 있습니다.

(그림 6) Popup 설정 형태

메뉴 예제 MenuEx 에 메뉴 자원 등록하기

MenuEx는 위에서 설명한 방법으로 메뉴늘 만들고 이것을 프로그램에 삽입하는 예제입니다. 표2 은 MenuEx에서 만든 메뉴와 그에 해당하는 ID들입니다.

팝업메뉴	메뉴명	ID
File	&New\tCtrl+N	ID_FILENEW
	&Open...\tCtrl+O	ID_FILEOPEN
	&Save\tCtrl+S	ID_FILESAVE
	Save &As...	ID_FILESAVEAS
	E&xit	ID_FILEEXIT
Edit	&Undo\tCtrl+Z	ID_EDITUNDO
	Cu&t\tCtrl+X	ID_EDITCUT
	&Copy\tCtrl+C	ID_EDITCOPY
	&Paste\tCtrl+V	ID_EDITPASTE
View	&Toolbar	ID_VIEWTOOLBAR
	&Status Bar	ID_VIEWSTATUSBAR

(표2) MenuEx에 설정된 메뉴 형태

위와 같이하였다면 ID_FILENEW와 ID_FILEOPEN이라는 값들을 정의 해주어야 합니다. Resource.h에 위의 내용을 만들고 나면 다음과 같이 자동으로 정의되어 있을것입니다.

#define ID_FILENEW                      40001

#define ID_FILEOPEN                     40006

#define ID_FILESAVE                     40007

#define ID_FILESAVEAS                   40008

#define ID_FILEEXIT                     40009

#define ID_EDITUNDO                     40010

#define ID_EDITCUT                      40011

#define ID_EDITCOPY                     40012

#define ID_EDITPASTE                    40013

#define ID_VIEWTOOLBAR                  40014

#define ID_VIEWSTATUSBAR                40015

위의 값들은 리소스 에디터에서 임으적으로 설정해 준값입니다. 여러분이 사용하기 편리하도록 원하는 값으로 수정하여도 무방합니다. 이정의 값은 메뉴가 선택되었을 때 WM_COMMAND와 함께  넘겨주는 LOWORD(wParam) 에 설정되어 오기 때문에 어떤 메뉴를 선택하였는가를 확인하는데 매우 중요한 값들이 됩니다.

메뉴 메시지

메뉴에서 특정 항목을 선택하였을 경우 WM_COMMAND 메시지가 발생됩니다.

이메시와 함께 wParam 에는 다음과 같은 정보가 옵니다.

LOWORD(wParam) : 메뉴에 해당하는 ID

HIWORD(wParam) : 0

lParam : 0

WM_COMMAND는 메뉴 뿐만 아니라 차일드 윈도우에서도 발생합니다. 위의 W메뉴에서 WM_COMMAND가 발생되었을경우에는 HIWORD(wParam)에는 0 lParam에는 0이 설정됩니다. 이메뉴항목은 메뉴에만 해당됩니다. 뒤에 설명할 차일드윈도우에서 WM_COMMAND에서는  HIWORD(WPARAM) 와 lParam에 값이 설정되어서 옵니다.

프로그램에서 설정된 메뉴가 아닌 윈도우에서 기본적으로 제공하는 메뉴가 있습니다. 이 메뉴는  WS_SYSMENU스타일을 윈도우를 만들 때 설정하였다면 나타나는 메뉴입니다. 이메뉴를 선택했을때의 정보를 얻고자 한다면 WM_SYSCOMMAND를 메시지를 받으면 됩니다. WM_SYSCOMMAND와 함께 들어오는 wParam과 lParam에는 다음과 같은 값들이 넘어옵니다.

LOWORD(wParam): 메뉴에 해당하는 ID

HIWORD(wParam): 마우스 Y좌표

HIWORD(lParam) :  마우스 X좌표

WM_SYSCOMMAND는 그림 7과 같은 형태입니다. 각항목에 대한 ID들은 다음과 같습니다.

(그림 7) 시스템 메뉴

SC_MOVE : 이동

SC_SIZE : 크기 변경

SC_MINIMIZE : 아이콘 표시

SC_MAXIMIZE : 전체 화면 표시

SC_CLOSE : 닫기

SC_RESTORE: 화면 복귀

이외에 SC_VSCROLL ,SC_HSCROLL, SC_ARRANGE ,SC_TASKLIST등이 들어 옵니다.

표2와 같은 메뉴를 설정한 MenuEx에서 각 메뉴에 해당하는 이벤트를 만들고자 한다면 다음과 같이 할수 있습니다.

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_FILENEW:

//File메뉴에서 New항목 선택시

break;

case ID_EIDTCOPY:

//Edit 메뉴에서 Copy항목을 선택시

break;

:

}

break;

case WM_SYSCOMMAND:

switch(LOWROD(wParam))

{

case SC_MOVE :

//시스템 메뉴에서 이동을 선택하였을 때

break;

}

메뉴 설정하기

리소스에서 메뉴를 만들고 난후에 이메뉴를 윈도우에 설정하고자 한다면 WinMain에서 wndclass 맴버 변수인 lpszMenuName 에 메뉴 자원 ID를 설정하여 주면 됩니다. MenuEx에서 Menu명을 □MENU" 로 설정하였습니다. 이 값을 설정하고자 할 때 다음과 같이 합니다.

wndclass.lpszMenuName  = "MENU" ;

이와 같은 방법 외에서 메뉴를 로드하여 설정하는 방법이 있습니다. 위에서 자원을 얻고자 한다면 LoadMenu라는 함수를 이용한다고 하였습니다.

HMENU hMenu;

hMenu=LoadMenu(hInstance,"MENU");

SetMenu(hwnd,hMenu);

위의 내용은 WndProc에서 WM_CREATE 메시지가 발생된 후에 기록하거나 또는 WinMain에서 CreateWindow를 실행한 후에 설정해 주면 됩니다.

메뉴와 서브 메뉴 얻기

설정된 메뉴를 얻고자 할 경우 사용되는 함수는 GetMenu입니다.

HMENU GetMenu(

HWND hWnd // 윈도우 핸들

);

GetMenu함수를 실행시키면 현재 윈도우에 연결된 메뉴가 리턴됩니다.

HMENU hMenu;

hMenu=GetMenu(hwnd);

GetMenu함수는 메뉴전체를 받습니다. 만일 우리가 설정한 메뉴중 서브 메뉴 일부분을 받고자 한다면 GetSubMenu 함수를 사용하면 됩니다. 예를 들어서 표2와 같은 메뉴에서 첫 번째 팝업 메뉴를 얻고자 한다면 다음과 같이 할수 있습니다.

HMENU hMenu;

hMenu=GetSubMenu(hMenu,0);

두 번째 인자 0은 첫 번째 서브메뉴를 가르키는 것입니다. Edit메뉴를 얻고자 한다면 1의 값을 넣어주면 됩니다.

프로그램에서 때로는 시스템 메뉴를 수정하거나 시스템 메뉴의 정보를 얻기위해서 시스템 메뉴를 얻을때가 있습니다. 이때 사용하는 함수가 GetSystemMenu입니다.

HMENU GetSystemMenu(

HWND hWnd, // 윈도우 핸들

BOOL bRevert // 리셋 플러그

);

bRevert가 FALSE이면 현재 윈도우의 시스템메뉴를 복사하면 이것을 수정할수 있습니다. TRUE이면 윈도우의 기본 시스템 메뉴를 얻게 됩니다.

시스템 메뉴를 얻고자 한다면 다음과 같이 할수 있습니다.

HMENU hSysMenu;

hSysMenu=GetSystemMenu(hwnd,FALSE);

메뉴 에 항목 추가하기

메뉴에 새로운 항목을 추가하고자 한다면 AppendMenu 함수를 사용하면 됩니다.

BOOL AppendMenu(

HMENU hMenu, //메뉴 핸들러

UINT uFlags, //메뉴 아이템 플러그

UINT uIDNewItem, // 삽입할 항목의 ID

LPCTSTR lpNewItem //삽입할 항목 문자열이나 또는 정보

);

hMenu는 새로운 항목을 추가할 메뉴의 핸들이며 uFlags는 메뉴 아이템 플러그입니다. MF_STRING일경우에는 문자열이며 MF_BITMAP일경우에는 비트맵입니다. 직접 메뉴를 그리고자한다면 MF_OWNERDRAW 플러그를 사용하면 됩니다.

예를 들어서 시스템 메뉴를 얻고 이시스템 메뉴에 새로운 항목을 추가하고자 한다면 다음과 같이 할수 있습니다.

//시스템 메뉴를 얻는다.

hSysMenu=GetSystemMenu(hwnd,FALSE);

//시스템 메뉴에 "TEST"라는 항목을 추가하면

//id를 100으로 설정한다.

AppendMenu(hSysMenu,MF_STRING,100,"TEST");

Floating 팝업 메뉴

Floating 팝업 메뉴란 어떤 이벤트 발생시 화면에 메뉴가 나타나는 형태를 의미합니다. 보통 일반적으로 우측 마우스 버튼을 클릭하거나 좌측 마우스 버튼을 클릭하였을 때 마우스 위치에 메뉴가 나타나는 것을 Floating 팝업 메뉴라고 합니다.

이메뉴를 출력하고자 할경우에는 TrackPopupMenu 함수를 이용하면 됩니다.

BOOL TrackPopupMenu(

HMENU hMenu, // 출력할 메뉴

UINT uFlags, // 출력위치에서 출력형태 플러그

int x, // 출력할 x좌표

int y, // 출력할 y좌표

int nReserved, // 0값

HWND hWnd, // 윈도우 핸들

CONST RECT *prcRect //무시 영역

);

hMenu는 구동시킬 메뉴 핸들러 이며 uFlags는 출력의 플러그입니다. 이 플러그는 다음과 같은 값을 가질수 있습니다.

TPM_CENTERALIGN : 출력 좌표값의 중앙으로 정렬

TPM_LEFTALIGN  :출력 좌표 좌측으로 정렬

TPM_RIGHTALIGN :출력 좌표 우측으로 정렬

TPM_LEFTBUTTON  : 좌측 마우스 버튼이 눌려 졌을 때 수행

TPM_RIGHTBUTTON  :우측 마우스 버튼이 눌려 졌을 때 수행

보통 nReserve와 prcRect는 사용하지 않습니다. 출력할 좌표  x,y는 클라이언트 영역에 해당됩니다.

우측 마우스 버튼을 눌렀을 경우 특정 팝업 메뉴를 출력시키는 예제는 다음과 같습니다.

case WM_RBUTTONDOWN:

//마우스 좌표를 얻는다.

point.x= LOWORD(lParam);

point.y= HIWORD(lParam);

//좌표를 클라이언트 좌표로 전환

ClientToScreen(hwnd,&point);

//Floating 메뉴 출력

TrackPopupMenu(hSubMenu,TPM_LEFTALIGN,point.x,point.y,

0,hwnd,NULL);

WM_CONTEXTMENU

WM_CONTEXTMENU메세지는 우측 마우스 버튼을 타이틀같은  클라이언트 영역을 제외한 부분에 클릭하였을 때 발생됩니다. 보통 타이틀 바에서 우측 마우스 버튼을 클릭하면 시스템 메뉴가 나타나는데 이것은 WM_CONTEXTMENU가 발생되고 DefWindowProc에서 이메시지를 보고 시스템 메뉴를 로드하여 출력하기 때문입니다. Visual C++에서 도구바와 메뉴 사이에서 우측 마우스 버튼을 누르게 되면 각종 도구마 리스트가 메뉴 형태로 보이는 것을 보신적이 있을것입니다. 이것들의 대부분이 WM_CONTEXTMENU에 의해서 만들어진것입니다. WM_CONTEXTMENU메시지에 의해서 특정 메뉴를 만드는 예는 다음과 같습니다.

case WM_CONTEXTMENU:

//현재 마우스 위치를 알고

GetCursorPos(&point);

//그위치에 Floating메뉴를 출력한다.

TrackPopupMenu(hSysMenu,0,point.x,point.y,0,hwnd,NULL);

return 0;

WM_CONTEXTMENU에서 return 0을 하지않고 break; 문을 사용하였다고 한다면 프로그램에서 기존에 내장된 시스템 메뉴와 현재 설정된 메뉴가 함께 교차해서 나타납니다. 내장된 시스템 메뉴를 사용하지 않기 위해서는 return 0;을 해주어서 DefWindowProc를 수행하지 못하도록 해야 합니다.

메뉴 문자열 얻기

현재 선택된 메뉴의 문자열을 얻고자 할 경우 GetMenuString 함수를 사용합니다.

int GetMenuString(

HMENU hMenu, // 메뉴 핸들러

UINT uIDItem, // 메뉴 ID

LPTSTR lpString, // 메뉴 문자열을 저장할 버퍼

int nMaxCount, //버퍼의 최대크기

UINT uFlag // 메뉴 플러그

);

uFlag는 MF_BYCOMMAND  와 MF_BYPOSITION   둘중에 한값을 지정할수 있는데 MF_BYCOMMAND일경우에는 메뉴 ID에 의해서 값을 얻으라는 의미이며 MF_BYPOSITION이면 0부터 시작하는 순서대로 하여 메뉴를 얻으라는 이야기입니다. MF_BYPOSITION으로 설정하였을 경우 첫 번째 메뉴가 0 이고 그다음부터 1,2,3 으로 증가되는 메뉴 순서번호를 uIDItem에 넣어야 하며 MF_BYCOMMAND일경우에는 단순하게 메뉴의 ID를 넣으면 됩니다.

GetMenuString 함수가 성공적으로 수행되면 lpString에 설정된 문자열 크기를 리턴합니다. 만일 0이 리턴되면 해당되는 메뉴가 없다는 의미입니다.

예를 하나 들어 보겠습니다. WM_COMMAND 메시지에 의해서 메뉴 선택이 발생되었을 때 이메뉴의 문자열을 얻는 방법은 다음과 같습니다.

case WM_COMMAND:

//메뉴 문자열을 얻는다.

//만일 hMenu즉 현재 메뉴에서 문자열을 얻고자 하는데

// 문자열이 없다면

if(GetMenuString(hMenu,LOWORD(wParam),

temp,80,MF_BYCOMMAND)==0)

//시스템 메뉴에서 문자열을 얻는다.

GetMenuString(hSysMenu,LOWORD(wParam),

temp,80,MF_BYCOMMAND);

MessageBox(hwnd,temp,"선택메뉴",MB_OK );

break;

위의 예는 메뉴를 선택하였을 때 선택된 메뉴를 MessageBox에 출력하는 것입니다. MessageBox는 간단한 대화상자로써 어떤 메시지를 출력할 때 사용하는 함수입니다. MessageBox의 기본 형은 다음과 같습니다.

int MessageBox(

HWND hWnd, // 윈도우 핸들

LPCTSTR lpText, // 출력할 문자열

LPCTSTR lpCaption, // 타이틀 바에 출력할 문자열

UINT uType // 메시지 박스의 스타일

);

uType의 값은 다음과 같습니다.

MB_ABORTRETRYIGNORE : Abort,Retry,Ignore  형태

MB_OK : OK 버튼만 있는 형태

MB_OKCANCEL : OK와 CANCEL 버튼이 있는 형태

MB_RETRYCANCEL  : Retry버튼과 CANCEL 버튼이 있는 형태

MB_YESNO : YES와 NO버튼이 있는 형태

MB_YESNOCANCEL : YES와 NO, CANCEL이 있는 형태

메시지 박스가 수행된후에 버튼을 눌러서 또는 Enter이나 ESC키를 눌러서 종료되면 해당되는 메시지를 리턴합니다. 이리턴값들은 다음과 같이 정의 되어 있습니다.

IDABORT  : Abort 버튼 클릭

IDCANCEL : CANCEL 버튼 클릭

IDIGNORE : Ignore 버튼 클릭

IDNO : NO 버튼 클릭

IDOK : OK 버튼 클릭

IDRETRY : Retry 버튼 클릭

IDYES : Yes 버튼 클릭

메뉴 스타일 바꾸기

에디터나 워드프로세서 에 사용되는 메뉴들 중에 어떤 메뉴는 비활성형태로 되어있다가 특정항목을 선택하면 메뉴가 다시 활성화 되는 경우가 있고 어떤 메뉴는 그메뉴를 선택하게 되면 메뉴 앞에 체크 표시가 붙는 경우가 있습니다. 프로그램에서 특정 메뉴에대한 스타일을 바꿀 때 이때 사용하는 함수들이 CheckMenuItem과 EnableMenuItem 등입니다. CheckMenuItem은 특정 메뉴를 체크 형태로 설정하거나 또는 체크 형태를 해제할 때 사용합니다.

DWORD CheckMenuItem(

HMENU hmenu, // 메뉴 핸들

UINT uIDCheckItem, // 메뉴 ID

UINT uCheck // 체크 플러그

);

예를 들어서 표2의 메뉴에서 ID_FILENEW를 체크하거나 또는 체크를 해제할경우에 다음과 같이 할수 있습니다.

//체크를 해제할때

CheckMenuItem(hMenu,ID_FILENEW,MF_UNCHEDKED);

//체크를 설정할 때

CheckMenuItem(hMenu,ID_FILENEW,MF_CHEDKED);

EnableMenuItem은 메뉴를 활성화 하거나 또는 메뉴를 비활성화 할 때 사용하는 함수입니다.

BOOL EnableMenuItem(

HMENU hMenu, // 메뉴 핸들러

UINT uIDEnableItem, // 메뉴  ID

UINT uEnable // 활성 비활성 플러그

);

다음은 EnbaleMenuItem을 사용한 예입니다.

//메뉴를 비활성화

EnableMenuItem(hMenu,ID_FILENEW,MF_CRAYED);

//메뉴를 활성화

EnableMenuItem(hMenu,ID_FILENEW,MF_ENABLED);

프로그램에서 메뉴만들기

리소스를 이용하지 않고 메뉴를 만들 수도 있습니다. 메뉴를 만들고자 할경우에는 CreateMenu함수를 이용하여 메뉴를 만든다음 AppendMenu를 이용하여 메뉴를 삽입하면 됩니다.  표2와 같은 메뉴를 만들기 위해서는 HMENU 핸들이 두 개가 필요합니다. 그이유는 먼저 서브메뉴를 하나만든다음 이것을 설정할 메뉴에 붙이고 다시 또 서브메뉴를 만든다음 이것을 설정할 메뉴에 붙이는 방식을 취해야 하기 때문입니다.

HMENU hMenu; //만들고자 하는 메뉴

HMENU sMenu;// 임시로 설정할 서브메뉸

hMenu=CreateMenu();

sMenu=CreateMenu();

//sMenu에 File메뉴의 부메뉴인 "New","Open","Save","Save As"를 등록한다.

:

//sMenu를 hMenu에 등록한다.

AppendMenu(hMenu,MF_POPUP,(UINT)sMenu,"&File");

sMenu=CreateMenu();

//sMenu에 Edit 메뉴의 부메뉴인 "Undo","Copy","Paster","Cut"를 등록한다

:

//sMenu를 hMenu에 등록한다.

AppendMenu(hMenu,MF_POPUP,(UINT)sMenu,"&Edit");

실제로 표2에 해당하는 메뉴를 직접 프로그래밍 코딩으로 입력한다면 다음과 같을 것입니다.

HMENU hMenu; //만들고자 하는 메뉴

HMENU sMenu;// 임시로 설정할 서브메뉸

hMenu=CreateMenu();

sMenu=CreateMenu();

AppendMenu(sMenu,MF_STRING,ID_FILENEW,"&New\tCtrl+N");

AppendMenu(sMenu,MF_STRING,ID_FILEOPEN,"&Open\tCtrl+O");

AppendMenu(sMenu,MF_STRING,ID_FILESAVE,"&Save\tCtrl+S");

AppendMenu(sMenu,MF_STRING,ID_FILESAVEAS,"Save &As\tCtrl+A");

//sMenu를 hMenu에 등록한다.

AppendMenu(hMenu,MF_POPUP,(UINT)sMenu,"&File");

:

//같은 방식으로 Edit메뉴를 붙인다.

실제 프로그래밍에서는 프로그램에서 메뉴를 직접 만들어 붙이는 방법을 거히 사용하지 않습니다. 그이유는 매우 많은 양의 코딩을 해야 하고 프로그램 소스가 복잡해 지는 이유가 있기 때문입니다. 다만 메뉴 프로그램에서 메뉴를 어떻게 만드는가를 이해할수 있도록 하기 위해서입니다.

때로는 이방법으로 메뉴를 만들경우도 있습니다. 리소스 자원으로 등록된 것은 한번 설정하면 그메뉴 형태로 나타납니다.(물론 수정이 가능하지만) 저작도구 같은 프로그램을 제작한다면 이프로그램에 의해서 설정된 유동적인 메뉴 형태를 로드하여 화면에 출력할경우가 있습니다. 이럴경우에는 리소스를 이용하지 않고 직접 메뉴를 설정해야 하기 때문에 이방법을 사용하기도 합니다.

메뉴 항목 수정하기

메뉴에 새로운 데이터를 삽입하거나 또는 현재 메뉴를 삭제하거나  메뉴를 수정하거나 할경우가 있습니다. 이럴경우에는 표3의 함수 들을 이용합니다.

(표3) 메뉴 항목 수정 함수 리스트

함수명	내용
AppendMenu( hMenu, uFlags, uID, lpData );	새로운 메뉴를 설정한다.
InsertMenu( hMenu,pos, uFlags, uID, lpData );	pos에 항목을 삽입한다.
DeleteMenu(hMenu,pos,uFlags);	pos위치의 항목 삭제
ModifyMenu(hMenu,pos, uFlags, uID, lpData );	pos 항목 수정
RemoveMenu(hmenu,pos,uFlgs);	pos항목 삭제

RemoveMenu와 DeleteMenu의 차이는 팝업 메뉴를 항목을 없애는가의 차이입니다. DeleteMenu는 팝업메뉴 항목이  사라지나 RemoveMenu는 사라지지 않습니다.

위의 함수를 이용하여 메뉴를 수정하였다면 메뉴를 다시 출력해주어야 합니다. 메뉴를 다시 출력할때는 DrawMenuBar라는 함수를 사용합니다.

BOOL DrawMenuBar(

HWND hWnd //윈도우 핸들

);

DrawMenuBar함수는 인자로 HMENU가 설정되는 것이 아니고 hWnd입니다. 즉 현재 윈도우에 설정되어 있는 메뉴를 다시 그리게 합니다. Floating 메뉴를 수정하였을경우에는 이함수를 사용할 필요는 없습니다. Floating 메뉴는 선택시에 언제나 메뉴를 다시 그리기 때문입니다.

메뉴 예제 MenuEx

MenuEx는 우측 마우스 버튼을 클릭하면 설정된 메뉴중 첫 번째 서브 메뉴가 Floating메뉴로 설정되고 타이틀바 같은 비클라이언트 영역에서 우측 마우스 버튼을 클릭하면 □test"라는 메뉴가 첨가된 Floating메뉴가 나타나고 각메뉴를 클릭하였을 때 메뉴면을 메시지 박스로 출력하는 예제입니다. 지금까지 설명한 메뉴에대한 내용을 그대로 적용한것입니다.

(프로그램 소스)

//메뉴 예제

//MenuEx

#include <windows.h>

#include "resource.h"

HINSTANCE hInst;

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

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "MenuEx" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

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

wndclass.lpszMenuName  = "MENU" ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

//hInstance값을 hInst에 설정

hInst=hInstance;

hwnd = CreateWindow (szAppName,

"메뉴예제:MenuEx",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

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

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

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

{

HDC         hdc ;

PAINTSTRUCT ps ;

static HMENU hMenu;

static HMENU hSubMenu;

static HMENU hSysMenu;

char temp[80];

POINT point;

switch (iMsg)

{

case WM_CREATE :

//자원에서 MENU메뉴를 로드한다.

hMenu=LoadMenu(hInst,"MENU");

//MENU메뉴에서 첫번째 서브메뉴를 얻는다.

hSubMenu=GetSubMenu(hMenu,0);

//시스템 메뉴를 얻는다.

hSysMenu=GetSystemMenu(hwnd,FALSE);

//시스템 메뉴에 "TEST"라는 항목을 추가하면

//id를 100으로 설정한다.

AppendMenu(hSysMenu,MF_STRING,100,"TEST");

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_RBUTTONDOWN:

//마우스 좌표를 얻는다.

point.x= LOWORD(lParam);

point.y= HIWORD(lParam);

//좌표를 클라이언트 좌표로 전환

ClientToScreen(hwnd,&point);

//Floating 메뉴 출력

TrackPopupMenu(hSubMenu,TPM_LEFTALIGN,point.x,point.y,

0,hwnd,NULL);

break;

case WM_CONTEXTMENU:

//현재 마우스 위치를 알고

GetCursorPos(&point);

//그위치에 Floating메뉴를 출력한다.

TrackPopupMenu(hSysMenu,0,point.x,point.y,0,hwnd,NULL);

return 0;

case WM_COMMAND:

//메뉴 문자열을 얻는다.

//만일 hMenu즉 현재 메뉴에서 문자열을 얻고자 하는데

// 문자열이 없다면

if(GetMenuString(hMenu,LOWORD(wParam),temp,80,MF_BYCOMMAND)==0)

//시스템 메뉴에서 문자열을 얻는다.

GetMenuString(hSysMenu,LOWORD(wParam),temp,80,MF_BYCOMMAND);

//메시지 출력

MessageBox(hwnd,temp,"선택메뉴",MB_OK );

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

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

}

(프로그램 소스끝)

(그림 8)MenuEx출력 결과

2.비트맵 , 아이콘,문자테이블, 엑셀레이터

비트맵을 이용하여 화면에 그림을 출력하는 부분은 GID항목에 해당하나 다양한 방법을 이용하기 때문에 본책에서는 2부분에 나누어 설명됩니다. 본장에서는 자원을 이용하여 비트맵을 로드하고 이것을 화면에 출력하는 방법이 설명되며 뒤의 전문가 프로그래밍에서는 장치독립적인 Dib를 이용하여 비트맵을 출력하는 방법을 설명합니다. 장치독립적인 Dib란 쉽게 말해서 bmp파일에서 비트맵을 로드하여 출력하는 것을 의미합니다.

비트맵 만들기

비트맵을 만들기 위해서는 rc파일을 프로젝트에 등록한후에 Ctrl+5 키를 누르거나 또는 Resource 도구바에서 비트맵 설정 버튼을 클릭함으로써 만들 수 있습니다. 이와 같이 하여 새로운 비트맵을 만들게 되면 아무것도 그려지지 않는 하얀색의 비트맵 이 만들어지고 Grphisc도구바와 Color도구바를 이용하여 그림을 그릴수 있습니다. 그림 9은 새로운 비트맵을 만든다음 이비트맵에 그림을 그린것입니다.

(그림 9) 새로운 비트맵 만들기

그림 9는 실제 그린 것이 아니고 다른 그래픽 에디터에서 그림을 클립보드로 복사한다음 이것을 새로운 비트맵을 만든곳에 복사한것입니다. 실제로 비트맵을 저장할 경우 이것을 일일이 손으로 그리는것보다 그래픽에디터에서 그린다음 이것을 복사하는 것이 편리합니다. Visual C++ 4.x이전에는 16칼라만 지원되었으나 이제 비트맵은 256까지 지원됩니다. 새로운 비트맵을 만들고 이것을 256칼라로 셋팅하고자 한다면 Alt+Enter키를 눌러서 속성을 변환시켜줄 필요성이있습니다.

(그림 10) 비트맵 속성보기

Alt+Enter키를 누르게 되면 그림 10과 같은 화면이 나타납니다. 이곳에는 그림의 ID와 저장되는 파일명 그리고 Color의 수값들이 나옵니다. Colors가 256이므로 이그림은 256색으로 그려진 그림입니다. File name이란 이그림이 저장되는 파일명을 의미합니다. 그림 10에서는 File name가 bitmap1.bmp로 설정되어 있는데 이파일은 현재 프로젝트 디렉토리의 RES 서브 디렉토리에 위치하게 됩니다. 프로그램이 컴파일 되고 링크되면 bitmpa1.bmp파일이 필요 없게 됩니다. 프로그램은 리소스 자원을 프로그램의 특정위치에 설정하기 때문에 완결된 프로그램 은 자원의 정보가 필요없기 때문입니다.

비트맵 로드하기

비트맵을 로드하기 위해서는 LoadBitmap를 사용한다고 위에서 설명하였습니다. 이함수를 이용하여 위의 IDB_BITMAP1을 로드하는 방법은 다음과 같습니다.

hBitmap=LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance,IDB_BITMAP1);

첫 번째 인자에 hInstance를 넣어야 하는데 이값을 얻기 위해서 전장에서는 Winmain에서 넘겨주는 hInstance를 전역변수 hInst에 받았습니다. 이방법외에 프로시저에서 hInstance를 얻고자 한다면 lParam를 이용하면 됩니다.

((LPCREATESTRUCT)lParam)->hInstance 하면 현재 윈도우의 hInstance를

얻을수 있습니다.

비트맵 출력하기

비트맵을 출력하고자 할경우에는 절차가 약간 복잡합니다. 문자를 출력하듯이 바로 DC에 출력하는 것이 아니라 특정 DC를 만든다음 이 DC에 비트맵을 설정하고 이 DC를 현재 클라이언트 DC에 전송해야 합니다. 즉 다음과 같은 로직으로 해야합니다.

HDC memdc;

//현재 DC와 호환적인 메모리 DC를 만든다

memdc=CreateCompatibleDC(hdc);

//메모리 DC에 비트맵을 설정한다.

SelectObject(memdc,hBitmap);

//메모리 DC를 화면 DC에 전송한다.

BitBlt(hdc,0,0,320,320,memdc,0,0,SRCCOPY);

//메모리 DC를 제거한다.

DeleteDC(memdc);

위의 예를 자세히 설명하자면 CreateCompatibelDC란 특정 DC를 만드는데 인자로 넘겨주는 hdc와 호환성이 있는 DC를 만드는것입니다. 이렇게 DC를 만드는 이유는 DC가 화면만 해당되지 않고 프린터 출력 또한 DC로 얻기 때문입니다. 프린터와 화면은 그 DC의 성격이 다르기 때문에 비트맵을 출력하기 위한 어떤 DC를 만든다면 이거은 출력할 DC의 성격과 같은 형으로 만들어야 하기 때문입니다.

이렇게 하면 memdc는 설정된 hdc와 같은 형태의 DC가 됩니다. 여기에 LoadBitmap함수에 의해 로드된 hBitmap를 설정합니다. 이렇게 설정한후 BitBlt함수를 이용하여 memdc를 hdc에 전송합니다. BitBlt함수는  형은 다음과 같습니다.

BOOL BitBlt(

HDC hdcDest, // 출력할 DC핸들

int nXDest, // 출력할 DC의 x위치

int nYDest, // 출력할 DC의 y위치

int nWidth, // 출력할 내용의 가로크기

int nHeight, // 출력할 내용의 세로크기

HDC hdcSrc, // hdcDest에 전송할 DC

int nXSrc, // 전송할 DC의 시작 X위치

int nYSrc, // 전송할 DC의 시작 Y위치

DWORD dwRop // ROP코드

);

비트맵이 설정된  hdcSrc를  출력할 화면 hdcDest에 보낼 때 hdcSrc의 시작 위치정보가 nXSrc와 nYSrc이며 출력할 hdcDest의 x,y와 가로크기 세로크기가 nXDest와 nYDest, nWidth와 nHeight입니다. dwRop란 hdcSrc와 hdcDest와의 화면 결합을 어떻게 할것인가를 정의한것입니다. 표4는 이 값들의 리스트입니다.

SRCCOPY라고 설정을 하면 hdcDest의 화면에 hdcSrc를 그대로 출력하는 것입니다. 만일 두 개를 XOR의 형태로 출력하고자 한다면 SRCINVERT를 사용하시면 됩니다. 칼라 그래픽에서 이연산은 매우 다양하게 쓰입니다. 예를 들어서 현재 화면의 (0,0)의 색이 100이라는 값을 가지고 있고 전송할 hdcSrc의 첫값이 200이라고 할 경우 그리고 이것을 NOTSRCERASE라는 형태로  출력하고자 한다면 다음과 같은 형태가 됩니다.

dwRop	부울 함수	ROP코드
BLACKNESS	0	0x000042
NOTSRCERASE	~(Src | Des)	0x1100A6
NOTSRCCOPY	~Src	0x330008
SRCERASE	Src & ~Des	0x440328
DSTINVERT	~Des	0x550009
PATINVERT	Pattern ^ Des	0x5A0049
SRCINVERT	Src ^ Des	0x660046
SRCAND	Src & Des	0x8800C6
MERGEPAINT	~Src | Des	0xBB0226
MERGECOPY	Pattern & Src	0xC00CA
SRCCOPY	Src	0xCC0020
SRCPAINT	Src | Des	0xEE0086
PATCOPY	Pattern	0xF00021
PATPAINT	Pattern | ~Src | Des	0xFB0A09
WHITENESS	1	0xFF0062

(표4) dwRop값의 리스트

01100100 //화면 (0,0)의 값 100

11001000 //전송할 DC의 (0,0)값 200

------------------ OR 연산 (|)

11101100

----------------- ~ 연산

00010011 //실제 출려하는 값 19

즉 비트맵의 200이라는 색의 값을 화면(0,0)의 좌표에 있는 100이라는 색의 값에

NOTSRCERASE 모드로 전송하고자 한다면 출력되는 색의 값은 19가되는 것입니다. 게임프로그램을 만들 때 배경화면과 지우지 않고 현재 화면을 출력하고자 할 경우 ROP 코드를 이용한 BitBlt함수를 많이 이용합니다. BitBlt함수와 같은 이 기능이면서 출력하는 내용을 확대 축소할수 있는 함수가 있습니다. 이함수는 StretchBlt입니다.

BOOL StretchBlt(

HDC hdcDest, // 출력 DC핸들

int nXOriginDest, // 출력 좌표 x

int nYOriginDest, // 출력 좌표 y

int nWidthDest, // 가로 크기

int nHeightDest, // 세로 크기

HDC hdcSrc, // hdcDest에 전송할 DC

int nXOriginSrc, // x시작 좌표

int nYOriginSrc, // y시작 좌표

int nWidthSrc, // 가로 크기

int nHeightSrc, // 세로 크기

DWORD dwRop // ROP 코드

);

StretchBlt는 hdcDest에 전송할 hdcSrc의 nXOriginSrc와 nYOrginSrc 그리고 nWidthSrc,nHeightSrc의 영역을 nXOrignDest,nYOriginDes,nWidthDest,nHeightDest영역에 크기에 맞게 축소 또는 확대하여 출력하는 함수입니다. 이함수를 이용하면 현재 비트맵을 원하는 크기로 확대 축소 하여 출력할 수가 있습니다.

비트맵에서 패턴 얻기

비트맵에서 이비트맵의 패턴을 브러쉬로 얻는 방법이 있습니다. 이렇게 비트맵에서 브러쉬로 패텬을 얻은다음 이브러쉬로 박스나 또는 원을 그릴수 있습니다.

비트맵에서 패턴을 얻는 함수는 CreatePatternBrush입니다.

HBRUSH CreatePatternBrush(

HBITMAP hbmp // 비트맵 핸들

);

예를 들어서 비트맵으로부터 패턴을 얻고 이것을 배경화면으로 쓰자고 한다면 다음과 같이 할수 있습니다.

//비트맵을 로드한다.

hBitmap=LoadBitmap(hInstance,IDB_BITMAP2);

//비트맵으로 부터 패턴을 얻는다.

hBrush=CreatePatternBrush(hBitmap);

hinst=hInstance;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (hInstance, IDR_ICON) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

//패턴을 배경색으로 설정

wndclass.hbrBackground = hBrush;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (hInstance, IDR_ICON) ;

DC를 비트맵으로 전환하기

DC를 비트맵으로 전환하는 방법은 매우 간단합니다. 비트맵을 만든다음에 이비트맵을 DC에 설정하고 그리고 설정된 DC에 그래픽을 출력하면 바로 비트맵에 설정하는 것과 같습니다. 다음은 DC에 문자를 출력하고 이것을 바로 비트맵으로 받는 형태입니다.

HBITMAP hBitmap;

HDC memDC;

hBitmap=CreateBitmap(100,100,1,24,NULL);

memDC= CreateCompatibleDC(hdc);

SelectObject(memDC,hBitmap);

TextOut(memDC,0,0,"this is test",12);

memDC는 현재 DC와 호환성 있는 DC이며 이 DC에 hBitmap를 설정한후에

□this is test"라는 문자를 출력하면 hBitmap에는 □this is test□라는 문자가 그려진 비트맵으로 설정됩니다. CreateBitmap는 아무것도 들어있지않는 비공백의 비트맵을 만듭니다. CreateBitmap함수는 다음과 같습니다.

HBITMAP CreateBitmap(

int nWidth, // 비트맵 가로크기

int nHeight, // 비트맵 세로크기

UINT cPlanes, // 비트맵 플랜

UINT cBitsPerPel, //  픽셀당 비트수

CONST VOID *lpvBits // 칼라 팔레트 데이터

);

nWidth와 nHeight는 만들 비트맵의 가로와 세로 크기이며 cPlanes와 cBitsPerPel은 비트맵의 칼라수를 의미합니다. 둘중에 한 개를 1로 설정하고 나머지를 변화시키면 칼라수를 만들 수 있습니다. 예제에서는 cPlanes를 1로 설정하고 24비트를 설정하였으므로 TRUE칼라가 됩니다. 이값이 8비트라면 256칼라이며 4비트이면 16칼라 1비트이면 모노가 됩니다. lpvBits는 현재 비트맵에서 사용하는 칼라의 정보를 의미합니다. NULL로 설정할 경우 시스템에서 제공하는 칼라값을 사용하게 됩니다.

칼라 데이터를 설정하는 방법에 대해서는 비트맵 파일을 출력하는 부분에서 자세히 설명할것입니다.

아이콘 만들기

아이콘을 만드는 방식은 비트맵과 비슷합니다. Ctrl+4나 또는 Resource도구바에서 new Icon항목을 클릭하여 아무것도 그려지지 않는 아이콘을 설정할수 있습니다. 이렇게 설정한 아이콘은 두가지 형태로 설정됩니다.

그림11 은 새로운 아이콘을 만든 형태입니다.

(그림 11) 새로운 아이콘 화면

아이콘을 만들게 되면 화면 상단부에 Device라는 항목에 콤보박스가 나타나고 이콤보박으세는 그림 11처럼 Standard와 Small 두 개의 항목이 설정됩니다. 아이콘을 그리고자 한다염 일반적으로 Standard와 Small두개를 모두 그립니다.  윈도우 탐색기나 또는 다른 방법으로 프로그램을 구종시킬 때 이 프로그램의 성격을 표현하기 위해 아이콘이 화면에 나타납니다. 또한 프로그램이 구동되면 윈도우의 작업표시줄에 프로그램이름과 함께 작은 아이콘이 설정됩니다. 윈도우 탐색기에는 큰형태의 아이콘으로도 출력될수도 있고 또한 작은 형태의 아이콘으로도 출력될수도 있습니다. 즉 아이콘은 보통형태 즉 32x32형태와 작은 형태 16x16형태가 교차적으로 사용되기 때문에 아이콘을 그릴때는 두 개를 한꺼번에 그려서 등록하는 것이 좋습니다.

윈도우에 아이콘 등록과 화면에 출력

윈도우에 아이콘을 등록할때는 WNDCLASSEX  wndclass 의 맴버 변수인 hIcon 에 LoadIcon함수를 사용하여 등록하면 됩니다.

//아이콘을 등록한다.

wndclass.hIcon         = LoadIcon (hInstance, IDR_ICON) ;

때때로 아이콘을 DC에 출력할경우가 있습니다. 아이콘은 비트맵과 달리 바로 DC에 출력할수 있는 함수가 있습니다. 이함수가 DrawIcon입니다. IDR_ICON이라는 아이콘 자원을 로드하여 이것을 화면에 출력하고자 한다면 다음과 같이 하면 됩니다.

HICON hIcon;

hIcon=LoadIcon(((LPCREATESTRUCT)lParam)->hInstance,IDR_ICON);

//아이콘을 출력한다.

DrawIcon(hdc,0,0,hIcon);

문자열 테이블

프로그램에서 자주 사용하는 문자들이 있을 경우 이것을 자원에 저장하여 놓고 자원에서 문자들을 로드할수 있습니다. 문자열을 저장할 리소스는 String Table라는 항목입니다. Ctrl+8 키나 Resource도구바에서 New String Table를 선택하면 새로운 스트링 테이블이 나옵니다. 이곳에 문자열을 넣을수 있습니다. 그림 12는 새로운 String Table를 만들고 이곳에 문자열을 넣은 예입니다.

(그림 12)String Table

문자열은 H계열의 핸들러를 사용하지 않습니다. 문자열은 바로 문자열 버퍼로 얻을수 잇습니다. 예를 들어서 IDS_MYDATA1의 문자열을 얻고자 한다면 다음과 같이 할수 있습니다.

char temp[80];

LoadString(hinst,IDS_MYDATA1,temp,80);

위와 같이하면 temp안에 □죽는날가지 하늘을 우러러□ 라는 문자열이 들어가게 됩니다. IDS_MYDATA1부터 IDS_MYDATA10는 1부터 10의 값으로 정의되어 있습니다. 만일 이 데이터를 순서적으로 모두 출력하고자 한다면 다음과 같이 할수 있습니다.

//IDS_MYDATA1무터 IDS_MYDATA10까지 문자열을

//얻는다.

for(i=IDS_MYDATA1;i<IDS_MYDATA10;i++)

{

LoadString(hinst,i,temp,80);

TextOut(hdc,320,i*20,temp,strlen(temp));

}

for루프의 반복을 통해서 i 는 IDS_MYDATA1부터 IDS_MYDATA10까지 증가되게 됩니다. 이렇게 증가되면서 temp에 로드하고 로드된 데이터가 화면에 출력되게 되는것입니다.

비트맵,아이콘,문자열 예제 BitmapEx

BitmapEx는 비트맵과 아이콘을 화면에 출력하고 특정 비트맵의 패턴을 얻어서 배경 으로 설정한 예제입니다. 지금까지 설명한 모든 내용을 사용한 것입니다.

(프로그램 소스)

//비트맵,아이콘,문자열 예제

//BitmapEx.c

#include <windows.h>

#include "resource.h"

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

HINSTANCE hinst;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "BitmapEx" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

HBITMAP hBitmap;

HBRUSH hBrush;

//비트맵을 로드한다.

hBitmap=LoadBitmap(hInstance,IDB_BITMAP2);

//비트맵으로 부터 패턴을 얻는다.

hBrush=CreatePatternBrush(hBitmap);

hinst=hInstance;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

//아이콘을 등록한다.

wndclass.hIcon         = LoadIcon (hInstance, IDR_ICON) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

//패턴을 배경색으로 설정

wndclass.hbrBackground = hBrush;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (hInstance, IDR_ICON) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"비트맵 예제:BitmapEx",

WS_OVERLAPPEDWINDOW ,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

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

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

DeleteObject(hBrush);

DeleteObject(hBitmap);

return msg.wParam ;

}

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

{

HDC         hdc ,memdc;

PAINTSTRUCT ps ;

static HBITMAP hBitmap;

static HICON hIcon;

char temp[80];

int i;

switch (iMsg)

{

case WM_CREATE :

hBitmap=LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance,IDB_BITMAP1);

hIcon=LoadIcon(((LPCREATESTRUCT)lParam)->hInstance,IDR_ICON);

return 0 ;

case WM_SIZE:

break;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//현재 DC와 호환적인 메모리 DC를 만든다

memdc=CreateCompatibleDC(hdc);

//메모리 DC에 비트맵을 설정한다.

SelectObject(memdc,hBitmap);

//메모리 DC를 화면 DC에 전송한다.

BitBlt(hdc,0,0,320,320,memdc,0,0,SRCCOPY);

//메모리 DC를 제거한다.

DeleteDC(memdc);

//IDS_MYDATA1무터 IDS_MYDATA10까지 문자열을

//얻는다.

for(i=IDS_MYDATA1;i<IDS_MYDATA10;i++)

{

LoadString(hinst,i,temp,80);

TextOut(hdc,320,i*20,temp,strlen(temp));

}

//아이콘을 출력한다.

DrawIcon(hdc,0,0,hIcon);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

DeleteObject(hBitmap);

PostQuitMessage (0) ;

return 0 ;

}

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

}

(프로그램 소스끝)

(그림 13) BitmapEx출력 결과

BitmapEx를 컴파일 하면 warning가 나올것입니다. 이문제는 자원의 ID는 숫자로 등록하고 Load할경우에는 문자열을 설정하기 때문에 발생되는 warning입니다. 크게 걱정하지 않으셔도 되믄데 waring가 보기 싫으시면 ID를 모두 문자열로 하시면 됩니다.

비트맵 메뉴

지금 까지는 메뉴를 문자로만 출력하였습니다. 본항목에서는 메뉴 항목을 비트맵으로 출력하는 방법을 알아보겠습니다.  메뉴를 그림으로 하고자 할경우에는 자원에 메뉴를 등록하는 것이아니고 메뉴를 프로그램에서 작성해야합니다. 본장의 프로그램에서 메뉴 만들기 항목에서 설명한 방법으로 메뉴를 설정해야 합니다.

메뉴에 도시할 그림들을 비트맵으로 자원에 등록한다음 이그림들을 로드합니다.

//메뉴에 등록할 비트맵들

static HBITMAP hFileNew,hFileMenu,hFileOpen,hCopy,hEditMenu;

//비트맵을 로드한다.

hFileNew=LoadBitmap(hInstance,"NEW");

hFileMenu=LoadBitmap(hInstance,"FILEMENU");

hFileOpen=LoadBitmap(hInstance,"OPEN");

hEditMenu=LoadBitmap(hInstance,"EDITMENU");

hCopy=LoadBitmap(hInstance,"COPY");

윈도우에 설정할 메뉴를 만듭니다.

hMenu=CreateMenu();

프로그램에서 메뉴 만들기 항목에서 설명하였듯이 메뉴를 만들경우에는 두 개의 메뉴 핸들러가 필요합니다. 즉 서브메뉴를 만들 핸들러와 실제 출력될 메뉴 핸들러인것입니다. hMenu는 실제 출력될 메뉴 핸들러입니다. 이제 서브 메뉴 핸들러를 한다 만듭니다.

hSubMenu=CreateMenu();

AppendMenu 함수의 두 번째 인자가 MF_BITMAP 이면 비트맵으로 메뉴를 설정할수 있습니다. 다음과 같이 하여 hSubMenu에 로드한 비트맵을 메뉴로 설정합니다.

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

AppendMenu(hSubMenu,MF_BITMAP ,ID_NEW,(PSTR)(LONG)hFileNew);

AppendMenu(hSubMenu,MF_BITMAP ,ID_OPEN,(PSTR)(LONG)hFileOpen);

위와 같이 하면 hSubMenu에는 비트맵의 두 개의 메뉴가 설정됩니다. 이 서브메뉴를 hMenu에 등록합니다. 등록할때도 마찬가지로 비트맵 형태로 합니다.

//hMenu에 등록한다.

AppendMenu(hMenu,MF_BITMAP|MF_POPUP,(int)hSubMenu,

(PSTR)(LONG)hFileMenu);

위와 같은 방법으로 계속적으로 비트맵 메뉴를 설정할수 있습니다.

비트맵 메뉴 예제 BmpMenu

BmpMenu는 비트맵 메뉴를 설정한 예제입니다. 이예제에서는 CreateWindow의

9번째 인자에 설정하는 방법으로 메뉴를 설정하였습니다.

hwnd = CreateWindow (szAppName,

"비트맵메뉴:BmpMenu",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

hMenu, //메뉴를 이곳에 설정

hInstance,

NULL) ;

(프로그램 소스)

//비트맵 메뉴

//BmpMenu.c

#include <windows.h>

#include "resource.h"

#define ID_NEW 1

#define ID_OPEN 2

#define ID_COPY 3

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "BmpMenu" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

HMENU hMenu,hSubMenu;

//메뉴에 등록할 비트맵들

static HBITMAP hFileNew,hFileMenu,hFileOpen,hCopy,hEditMenu;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

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

wndclass.lpszMenuName  = NULL;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

//비트맵을 로드한다.

hFileNew=LoadBitmap(hInstance,"NEW");

hFileMenu=LoadBitmap(hInstance,"FILEMENU");

hFileOpen=LoadBitmap(hInstance,"OPEN");

hEditMenu=LoadBitmap(hInstance,"EDITMENU");

hCopy=LoadBitmap(hInstance,"COPY");

hMenu=CreateMenu();

hSubMenu=CreateMenu();

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

AppendMenu(hSubMenu,MF_BITMAP ,ID_NEW,(PSTR)(LONG)hFileNew);

AppendMenu(hSubMenu,MF_BITMAP ,ID_OPEN,(PSTR)(LONG)hFileOpen);

//hMenu에 등록한다.

AppendMenu(hMenu,MF_BITMAP |MF_POPUP,(int)hSubMenu,(PSTR)(LONG)hFileMenu);

hSubMenu=CreateMenu();

AppendMenu(hSubMenu,MF_BITMAP ,ID_COPY,(PSTR)(LONG)hCopy);

AppendMenu(hMenu,MF_BITMAP |MF_POPUP,(int)hSubMenu,(PSTR)(LONG)hEditMenu);

hwnd = CreateWindow (szAppName,

"비트맵메뉴:BmpMenu",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

hMenu, //메뉴를 이곳에 설정

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

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

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

DeleteObject(hFileNew);

DeleteObject(hFileMenu);

DeleteObject(hFileOpen);

DeleteObject(hEditMenu);

DeleteObject(hCopy);

return msg.wParam ;

}

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

{

HDC         hdc ;

PAINTSTRUCT ps ;

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

EndPaint (hwnd, &ps) ;

return 0 ;

break;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_NEW:

MessageBox(hwnd,"새로운 파일","선택메뉴",MB_OK );

break;

case ID_OPEN:

MessageBox(hwnd,"파일 열기","선택메뉴",MB_OK );

break;

case ID_COPY:

MessageBox(hwnd,"복사하기","선택메뉴",MB_OK );

break;

}

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

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

}

(프로그램 소스끝)

(그림 14) BmpMenu출력 결과

엑셀레이터 만들기

엑셀레이터란 쉽게 말해서 핫키 저장통이라고 볼수 있습니다. 보통 일반 어플리 케이션 프로그램에서 Ctrl+O를 누르면 파일 오픈 메뉴가 실행되고 Ctrl+S를 누르면 파일 저장 항목이 실행됩니다. 여기에서 Ctrl+O나 Ctrl+S는 엑셀레이터에 저장된 킷값들입니다. 이키값들을 저장하고 프로그램에 등록하면 파일메뉴를 선택한것과 같은 방법으로 사용할수 있습니다. 엑셀레이터 메뉴를 만들기 위해서는  Visual C++에서 Ctrl+7키를 누르거나 또는 Resource 도구바에서 New Accelerator 항목을 클릭하면 됩니다. 그림 15는 새로운 엑셀레이터를 만든 화면입니다.

(그림 16) 엑셀레이터 설정화면

엑셀레이터에 항목을 추가하기 위해서는 Alt+Enter키를 눌러 Properties대화상자를 그림 16처럼 출력한후에 ID에는 기존의 메뉴와 같은 값을 설정하고 Key에는 단축키값을 설정합니다. 그림 16 Modifiers 에 Ctrl키를 체크하고 Key값은 N으로 그리고 ID는 메뉴항목의 ID_FILENEW로 설정하였습니다. 이렇게 되면 Ctrl+N키를 누르면  메뉴에서 ID_FILENEW를 실행한것과 같은 형태로 실행됩니다.

엑셀레이터 설정하기

그림 16과 같이 엑셀레이터를 리소스에 등록한후 프로그램에 설정할때는 다음과 같이 합니다.

HACCEL hAccel;

//엑셀레이터를 로드한다.

hAccel=LoadAccelerators(hInstance,"ACCERLATE");

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

{

//엑셀레이터에서 처리가 먼저 이루어진후

if(!TranslateAccelerator(hwnd,hAccel,&msg))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

}

LoadAccelerators함수는 자원에서 엑셀레이터를 로드하는 함수입니다. 이함수를 실행하여 hAccel에 핸들러를 받은다음 while루프안에 TranslateAccelerator함수를 삽입하여 메시지 루프에서 먼저 엑셀레이터에서 설정된 키가 눌려 졌는가를 확인하고 단축키가 눌려졌을경우엔는 바로 단축키에 해당하는 ID를 LOWORD(wParam)에 설정하고 WM_COMMAND를 보내게 됩니다.

만일 엑셀레이터에 등록한 단축키에 해당하는 이벤트가 아닐경우에는 TranslateAccelerator 함수는 0을 리턴하게 되고 따라서 기존의 TranslateMessage 메시지와 DispatchMessage 메시지를 수행합니다.

메뉴와 연결되지 않는 엑셀레이터를 만들어도 단축키는 가동됩니다. 즉 메뉴와 상관없는 ID로 단축키를 엑셀레이터에 저장하였다 하더라도 위의 설정방법으로 저장을 시키면 WM_COMMAND와 함께 엑셀레이터에 등록된 ID가 LOWORD(wParam)에 발생됩니다.

엑셀레이터 예제 ExAccerlate

ExAccerlate는 기존 메뉴중 일부분을 단축키로 등록한 예제입니다.

(프로그램 소스)

//엑셀레이터 예제

//ExAccerlate

#include <windows.h>

#include "resource.h"

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExAccerlate" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

HACCEL hAccel;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

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

wndclass.lpszMenuName  = "MENU" ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

//엑셀레이터를 로드한다.

hAccel=LoadAccelerators(hInstance,"ACCERLATE");

hwnd = CreateWindow (szAppName,

"엑셀레이터 예제:ExAccerlate",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

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

{

//엑셀레이터에서 처리가 먼저 이루어진후

if(!TranslateAccelerator(hwnd,hAccel,&msg))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

}

return msg.wParam ;

}

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

{

HDC         hdc ;

PAINTSTRUCT ps ;

switch (iMsg)

{

case WM_CREATE :

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

case WM_COMMAND:

switch(LOWORD(wParam))

{

case ID_FILENEW:

MessageBox(hwnd,"새로운 파일","선택메뉴",MB_OK );

break;

case ID_FILEOPEN:

MessageBox(hwnd,"기존파일 열기","선택메뉴",MB_OK);

break;

case ID_FILESAVE:

MessageBox(hwnd,"파일저장","선택메뉴",MB_OK);

break;

}

break;

}

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

}

(프로그램 소스끝)

(그림 17) ExAccerlate출력 결과