API-컨트롤 총정리


컨트롤의 정의
 
컨트롤(Control)이란 사용자와의 인터페이스를 이루는 도구이다. 인터페이스를 이룬다는 말은 사용자로부터 명령을 받아들이고 출력 결과를 보여준다는 뜻이다. 프로그램은 실행중에 끊임없이 사용자와 통신을 하는데 컨트롤을 통해 명령과 정보를 받아들이고 또한 컨트롤을 통해 실행 결과를 사용자에게 보고한다.

윈도우즈 3.1부터 지원하는 컨트롤에는 버튼, 에디트, 체크, 라디오, 리스트 박스, 콤보 박스, 스크롤 바, 스태틱 등 여섯가지에 불과하지만 윈도우즈 95/98부터는 사용할 수 있는 컨트롤의 수가 대폭 증가되었으며 현재는 OCX 컨트롤까지 사용할 수 있도록 되어 컨트롤의 수는 거의 무한대에 이른다. 인터넷을 뒤져보면 사용목적에 부합하는 공개된 컨트롤들이 많이 있으며 사용방법은 갈수록 쉬워지고 있다.

그러나 다행스럽게도 컨트롤의 종류가 많다 해서 공부해야 할 내용이 많아지는 것은 아니다. 왜냐하면 사용방법이 대체로 다 비슷비슷하며 컨트롤에 대한 개념만 있으면 상식적인 수준에서 이해할 수 있도록 만들어지기 때문이다.

컨트롤도 하나의 윈도우이다. 화면상의 일정한 영역을 차지하며 자신의 고유 메시지를 처리할 수 있는 능력을 가지고 있다. 그렇다고 해서 진짜 윈도우처럼 타이틀 바나 경계선을 가지고 독립적으로 사용되는 것은 아니며 보통 대화상자의 차일드 윈도우로 존재한다.

윈도우를 만들 때는 WNDCLASS형의 구조체를 정의한 후 RegisterClass 함수를 사용하여 등록한 후 CreateWindow 함수를 호출하여 윈도우를 만들어야 한다. 그러나 컨트롤은 윈도우즈가 운영체제 차원에서 제공해주기 때문에 윈도우 클래스를 만들어 사용할 필요없이 윈도우즈에 미리 정의되어 있는 클래스를 사용하기만 하면 된다. 미리 정의된 윈도우 클래스는 다음과 같다.


윈도우 클래스 컨트롤
button 버튼, 체크, 라디오
static 텍스트
scrollbar 스크롤 바
edit 에디트
listbox 리스트 박스
combobox 콤보 박스

윈도우 클래스를 따로 만들 필요없이 CreateWindow 함수의 첫번째 인수로 미리 정의된 윈도우 클래스를 주면 해당 컨트롤을 만들 수 있다. 잠시 후 실습을 해 보기로 하자.


컨트롤은 윈도우이다.!!!!
 

 
컨트롤이 윈도우와 동격이라는 말은 초보자가 선뜻 이해하기 힘들지만 분명히 윈도우의 일종이 틀림없다. 일단 화면상의 사각 영역을 차지한다는 점에 있어서 윈도우와 같은데 메인 윈도우의 차일드로 존재하며 타이틀 바가 없기 때문에 사용자가 직접 이동시킬 수 없을 뿐이다. 또한 윈도우처럼 스타일을 가지며 만들때 지정한 스타일에 따라 모양이나 기능이 달라진다. 컨트롤이 윈도우라는 결정적인 증거는 스스로 메시지를 처리할 수 있는 능력이 있다는 점이다. 버튼은 마우스를 누르면 쑥 들어가는 모양으로 바뀌고 에디트는 키보드 입력을 받으면 문자열을 조립하여 보여준다. 또한 WM_PAINT 메시지를 처리하기 때문에 스스로 자신을 복구(Repaint)할 수 있고 부모 윈도우가 메시지를 보내면 그 메시지를 처리한다. 예를 들어 체크 박스는 BM_SETCHECK 메시지를 받으면 wParam값에 따라 자신의 체크 상태를 스스로 변경시킨다.

 버튼이나 에디트를 만들 때 사용한 함수가 CreateWindow 함수라는 점만 봐도 역시 컨트롤은 윈도우이다. CreateWindow 외에 윈도우에 적용할 수 있는 함수는 대부분 컨트롤에도 적용할 수 있다. 과연 그런지 예제를 하나 만들어 보자. EdtWnd라는 이름으로 예제를 만들었다.


[1] 버튼만들기
 

1)버튼 컨트롤 만들기

예) 컨트롤중에 제일 간단하고 익숙한 버튼 컨트롤을 만들어 보자

 
이 책에서는 lpszClass 문자열을 프로젝트명으로 정의하여 사용하고 있지만 이 프로젝트의 경우 Button이라는 클래스명은 윈도우즈가 미리 사용하고 있기 때문에 사용할 수 없다. 그래서 윈도우 클래스를 MyButton이라는 이름으로 만들었다. WndProc은 다음과 같다.
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
	switch(iMessage) {
	case WM_CREATE:
		CreateWindow("button","Click Me",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
			20,20,100,25,hWnd,(HMENU)0,g_hInst,NULL);
		CreateWindow("button","Me Two",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
			20,50,100,25,hWnd,(HMENU)1,g_hInst,NULL);
		return 0;
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case 0:
			MessageBox(hWnd,"First Button Clicked","Button",MB_OK);
			break;
		case 1:
			MessageBox(hWnd,"Second Button Clicked","Button",MB_OK);
			break;
		}
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}


WM_CREATE 메시지와 WM_COMMAND 두 개의 메시지를 처리하고 있는데 자세한 분석은 잠시 후에 해 보기로 하고 일단 실행부터 시켜 보자. 화면에 두 개의 버튼이 배치되어 있으며 이 버튼을 누르면 메시지 박스가 나타날 것이다




버튼 만들기와 만들어진 버튼이 눌러졌을 때의 처리 방법을 보인 것이다.


2)CreateWindow()함수 분석

컨트롤은 윈도우이기는 하지만 홀로 사용될 수 없으며 반드시 부모 윈도우의 차일드로 존재해야 한다. 
컨트를은 보통 메인 윈도우가 만들어질 때 즉, WM_CREATE 메시지가 발생했을 때 만들어 준다. 컨트롤도 하나의 윈도우이므로 CreateWindow 함수를 호출하여 만들었다.

CreateWindow("button","Click Me",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 20,20,100,25,hWnd,(HMENU)0,g_hInst,NULL); 
 


이 함수 호출문에서 각 인수가 가지는 의미를 하나씩 순서대로 짚어 보도록 하자.

"button"
CreateWindow 함수의 첫번째 인수는 만들고자 하는 윈도우의 윈도우 클래스이다. 컨트롤은 윈도우즈에 의해 미리 윈도우 클래스가 정의되어 있으므로 별도의 윈도우 클래스를 정의할 필요없이 인수에 만들고자 하는 컨트롤의 윈도우 클래스명을 적어주면 된다. "button" 윈도우 클래스를 지정했으므로 버튼이 만들어질 것이다.


"Click Me"
두번째 인수는 윈도우의 타이틀 바에 나타날 윈도우의 제목이되 컨트롤에 따라 캡션이 나타날 위치가 달라진다. 보통 윈도우라면 타이틀 바에 캡션이 나타나지만 버튼은 버튼위에 캡션이 나타나며 체크, 라디오 버튼은 그 왼쪽에 캡션이 나타난다. 리스트 박스나 스크롤 바처럼 캡션이 필요없는 컨트롤은 NULL을 지정하면 된다.

스타일
 
세번째 인수는 윈도우의 속성값이다. 컨트롤의 경우 예외없이 차일드 윈도우이므로 WS_CHILD 스타일은 반드시 주어야 한다. 또한 WS_VISIBLE 스타일을 주어야 ShowWindow 함수를 호출하지 않아도 컨트롤이 화면에 나타나게 되므로 컨트롤의 경우 이 두 스타일값은 거의 예외없이 지정해 주어야 한다. 그 외 컨트롤에 따른 스타일값을 추가로 지정해 주는데 버튼의 경우는 다음과 같은 스타일값을 사용하여 버튼의 종류를 지정한다. 버튼의 스타일 값은 BS_ 접두어로 시작된다.
 
스타일 속성
BS_PUSHBUTTON 푸시 버튼
BS_DEFPUSHBUTTON 디폴트 푸시 버튼
BS_CHECKBOX 체크 박스
BS_3STATE 3가지 상태를 가지는 체크 박스
BS_AUTOCEHCKBOX 자동 체크 박스
BS_AUTO3STATE 3가지 상태를 가지는 자동 체크 박스
BS_RADIOBUTTON 라디오 버튼
BS_GROUPBOX 그룹 박스


좀 이상하게 보이겠지만 체크박스나 라디오 버튼도 일종의 버튼이며 스타일만 다를 뿐이다. 우리가 만들고자 하는 버튼은 푸시 버튼이므로 BS_PUSHBUTTON 스타일을 지정해 주었다.


위치
4번째부터 7번째 인수까지는 윈도우의 위치와 크기를 지정한다. 컨트롤의 경우는 부모 윈도우의 작업 영역을 기준으로 한 좌표가 사용된다. 이 예제의 경우 부모 윈도우의 작업 영역 좌상단에서 (20,20)에 버튼이 위치하며 폭은 100, 높이는 25픽셀이다.
 

부모 윈도우
8번째 인수는 컨트롤의 부모 윈도우를 지정하는데 컨트롤은 차일드이므로 반드시 부모 윈도우가 있어야 한다. 예제에서는 메인 윈도우의 핸들인 hWnd를 적어줌으로써 부모 윈도우를 지정해 주었다. 그래서 여기서 만들어진 버튼 컨트롤은 hWnd 윈도우의 작업 영역을 기준으로 한 좌표에 생성되며 무슨 일이 생기면 hWnd에게 통지 메시지를 보내고 또한 hWnd가 파괴될 때 같이 파괴된다.
 

ID
CreateWindow의 9번째 인수는 윈도우에서 사용할 메뉴의 핸들이다. 단 차일드 컨트롤의 경우는 메뉴를 가지지 않으므로 이 인수를 컨트롤의 ID를 지정하는 용도로 사용한다. 한 인수가 경우에 따라 다른 의미를 가지는 것이 다소 이상하게 보이겠으나 윈도우즈 API 함수에서는 이런 일들이 종종 있다. 컨트롤의 ID는 컨트롤간의 구분을 위해 사용하는 것이므로 한 부모 아래의 컨트롤끼리 중복되지 않는 ID를 가지기만 하면 된다. 이 예제에서는 두 버튼에 각각 ID 0, ID 1을 주어 구분하고 있는데 (HMENU)형으로 캐스팅해 주어야 한다. 간단한 예제라 0,1의 상수를 직접 사용했지만 사용되는 컨트롤이 많다면 #define으로 매크로 상수를 정의해 쓰는 것이 좋다.
 
9번째 인수는 이 윈도우를 만드는 인스턴스의 핸들이며 10번째 인수는 MDI에서 사용하는 구조체인데 일단 무시하도록 하자. CreateWindow 함수를 두번 호출하여 두 개의 버튼 컨트롤을 만들었다. 컨트롤을 생성한 후에 CreateWindow 함수는 생성된 차일드 컨트롤의 윈도우 핸들을 리턴해 주는데 핸들이 필요할 경우 별도의 변수에 핸들값을 저장해 주면 된다. 이 예제는 버튼의 윈도우 핸들을 쓰지 않으므로 리턴값을 무시하였다.



4.WM_COMMAND 사용
 

다음은 버튼이 눌러질 경우의 처리를 해 주어야 한다. 컨트롤들은 자신에게 무슨 일이 일어났을 때, 예를 들어 버튼을 클릭했다거나 에디트에 문자열을 입력했다거나 할 경우 부모 윈도우로 통지 메시지(Notification Message)를 보내준다. 부모 윈도우는 차일드 컨트롤이 보내주는 통지 메시지를 받아 적절한 처리를 해 준다. 버튼을 클릭할 경우 WM_COMMAND 메시지를 부모 윈도우에게 보내며 이때 전달되는 정보는 다음과 같다.
 

인수 설명
HIWORD(wParam) 통지코드
LOWORD(wParam) 컨트롤의 ID
lParam 메시지를 보낸 차일드 윈도우의 윈도우 핸들


컨트롤의 ID는 CreateWindow의 아홉번째 인수에서 지정한 정수값이며 어떤 컨트롤이 통지 메시지를 보냈는지를 알려준다. 통지코드는 차일드 컨트롤이 왜 메시지를 보냈는가를 나타내는 값이다. 버튼의 경우 통지코드는 항상 사용자가 자신을 클릭했다는 의미의 BN_CLICKED이므로 이 값은 특별히 검사해볼 필요가 없지만 통지 코드가 여러 개인 컨트롤은 이 값을 검사해 보아야 한다. 부모 윈도우는 WM_COMMAND에서 LOWORD(wParam)값을 조사하여 어떤 컨트롤이 눌러졌는지에 따라 적절한 처리를 한다. 이 예제의 경우는 두 버튼에 대해 각각 다른 메시지 박스만을 보여주었다.

	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case 0:
			MessageBox(hWnd,"First Button Clicked","Button",MB_OK);
			break;
		case 1:
			MessageBox(hWnd,"Second Button Clicked","Button",MB_OK);
			break;
		}
		return 0;

 이 코드의 의미를 말로 풀어보면 "첫번재 버튼이 클릭되었으면 First Button Clicked 메시지를 보여주고 두번째 버튼이 클릭되었으면 Second Button Clicked 메시지를 보여준다"이다. 다른 컨트롤의 경우도 통지 메시지를 처리하는 방법은 버튼의 경우와 동일하되 컨트롤에 따라 전달될 수 있는 통지 메시지의 종류가 다르므로 각 통지 메시지에 대해서는 컨트롤별로 따로 공부를 해야 한다.

 
WM_COMMAND 메시지는 컨트롤의 통지 메시지뿐만 아니라 메뉴 항목, 액셀러레이터 등의 명령을 처리하는 중요한 일을 한다. 이름 그대로 버튼을 누르거나, 메뉴를 선택하거나 액셀러레이터를 누르는 등 사용자로부터의 명령이 될만한 것들을 모두 처리한다. 이때 컨트롤의 ID, 메뉴 ID, 액셀러레이터 ID 등은 모두 LOWORD(wParam)으로 전달되므로 이 세가지 명령들끼리는 0~65535까지의 범위에서 상호 중복되지 않는 ID를 가져야 한다. 또한 그중에서도 컨트롤의 통지 메시지는 통지 코드에 따라 처리를 달리해야 한다. 그래서 WM_COMMAND 메시지의 일반적인 모양은 다음과 같다.
case WM_COMMAND:
	switch(LOWORD(wParam) {		// ID에 따른 분기
	case 메뉴1:처리1;break;
	case 메뉴2:처리2;break;
	case 액셀러레이터1:처리3;break;
	case 컨트롤1:
		switch(HIWORD(wParam)) {	// 통지 코드에 따른 분기
		case 통지코드1:처리1;break;
		case 통지코드2:처리2;break;
		...........
		}
		break;
	}
return 0; 

사용자로부터 명령을 많이 받아들이는 프로그램일수록 WM_COMMAND 메시지 처리는 더욱 복잡해진다.


[2] 체크 박스 만들기

1)체크박스 정의 

푸쉬 버튼은 주로 사용자로부터 명령을 받아들이기 위해 사용하는데 비해 체크 박스는 참, 거짓의 진위적인 선택을 입력받을 때 주로 사용되며 윈도우즈의 곳곳에서 볼 수 있다. 



체크 박스를 만드는 방법도 푸쉬버튼을 만드는 방법과 동일하다.
클래스 이름도 푸쉬 버튼과 같은 "button"을 사용하되 스타일에
BS_PUSHBUTTON 대신 체크 박스 스타일을 지정해 주는 것만 다르다. 지정해 주는 스타일에 따라 4가지 종류의 체크 박스가 있는데 앞절에서 제시한 버튼의 스타일을 참고하기 바란다.

 자동 체크 박스수동 체크 박스로 나누어지는데 수동 체크 박스는 선택/비선택 상태를 사용자가 직접 설정해 주어야 하며 자동 체크 박스는 윈도우즈가 체크 박스의 상태를 자동으로 설정해 준다. 체크 박스의 상태는 사용자가 마우스 버튼으로 체크 박스를 클릭할 때 변경되는데 자동 체크 박스의 경우는 별도의 코드를 작성하지 않아도 되지만 수동 체크 박스의 경우는 체크 박스의 통지 코드가 입력될 때 체크 박스의 상태를 보고 토글시켜주어야 한다.

 자동 체크 박스는 별도의 코드를 작성하지 않아도 상태를 자동으로 토글할 수 있는 장점이 있기는 하지만 반면 체크 박스가 변경되는 시점에서 특정한 처리를 할 수 없다는 단점이 있다. 어떤 종류의 체크 박스를 사용할 것인가는 경우에 따라 다르다. 체크 박스의 상태가 변경될 때마다 어떤 처리를 해 주어야 할 경우라면 수동 체크 박스를 사용하고 필요할 때 상태를 조사하기만 하면 될 경우라면 자동 체크 박스를 사용하면 된다.

2) 예제
 
체크 박스를 사용하는 간단한 예제를 만들어 보자.
화면에 사각형을 하나 그리되 체크 박스의 상태에 따라 타원형으로도 그릴 수 있도록 할 것이다. 또한 자동 체크 박스의 사용예를 보이기 위해 자동 체크 박스가 선택되어 있을 경우 프로그램 종료 전에 메시지 상자를 출력해 보이도록 한다. 실용성은 하나도 없는 예제이지만 체크 박스를 만들고 사용하는 방법과 각 체크 박스 종류와 용도를 알 수 있을 것이다. 소스는 다음과 같다.
 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	static HWND c1,c2,c3,c4;
	static BOOL ELLIPSE = FALSE;
	switch(iMessage) {
	case WM_CREATE:
		c1=CreateWindow("button","Draw Ellipse?",WS_CHILD | WS_VISIBLE | 
			BS_CHECKBOX,20,20,160,25,hWnd,(HMENU)0,g_hInst,NULL);
c2=CreateWindow("button","Good bye Message?",WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,20,50,160,25,hWnd,(HMENU)1,g_hInst,NULL);
c3=CreateWindow("button","3State",WS_CHILD | WS_VISIBLE | BS_3STATE, 20,80,160,25,hWnd,(HMENU)2,g_hInst,NULL);
c4=CreateWindow("button","Auto 3State",WS_CHILD | WS_VISIBLE | BS_AUTO3STATE,20,110,160,25,hWnd,(HMENU)3,g_hInst,NULL); return 0;
case WM_COMMAND: switch(LOWORD(wParam)) { case 0: if (SendMessage(c1,BM_GETCHECK,0,0)==BST_UNCHECKED) { SendMessage(c1,BM_SETCHECK,BST_CHECKED,0); ELLIPSE = TRUE; } else { SendMessage(c1,BM_SETCHECK,BST_UNCHECKED,0); ELLIPSE = FALSE; } InvalidateRect(hWnd, NULL, TRUE); break; case 2: if (SendMessage(c3,BM_GETCHECK,0,0)==BST_UNCHECKED) SendMessage(c3,BM_SETCHECK,BST_CHECKED,0); else if (SendMessage(c3,BM_GETCHECK,0,0)==BST_INDETERMINATE) SendMessage(c3,BM_SETCHECK,BST_UNCHECKED,0); else SendMessage(c3,BM_SETCHECK,BST_INDETERMINATE,0); break; } return 0; case WM_PAINT: hdc=BeginPaint(hWnd,&ps); if (ELLIPSE == TRUE) Ellipse(hdc,200,100,400,200); else Rectangle(hdc,200,100,400,200); EndPaint(hWnd, &ps); return 0; case WM_DESTROY: if (SendMessage(c2,BM_GETCHECK,0,0)==BST_CHECKED) MessageBox(hWnd,"Good bye","Check",MB_OK); PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); }


4가지 종류의 체크 박스를 모두 만들어 보기 위해 c1~c4까지 네 개의 윈도우 핸들 변수를 선언하고 WM_CREATE에서 체크 박스의 스타일을 변경해 가며 4개의 체크 박스를 생성하였다. 실행 중의 모습은 다음과 같다.



 첫번째 체크 박스의 선택 상태에 따라 사각형이 그려지기도 하고 타원형이 그려지기도 한다. 두번째 체크 박스는 선택 상태를 변경하는 시점에서는 아무런 동작도 하지 않지만 이 체크 박스가 선택되어 있으면 프로그램 종료 전에 메시지 박스를 출력하도록 하였다. 


메시지 설명
BM_GETCHECK 체크 박스가 현재 체크되어 있는 상태인지를 조사하며 추가정보는 없다.
BM_SETCHECK 체크 박스의 체크 상태를 변경하며 wParam에 변경할 체크 상태를 보내주면 된다.
 BM_GETCHECK에 의해 리턴되는 값, 또는 BM_SETCHECK에 의해 설정되는 체크 박스의 상태는 다음 세가지가 있다.
 

상수 의미
BST_CHECKED 현재 체크되어 있다.
BST_UNCHECKED 현재 체크되어 있지 않다.
BST_INDETERMINATE 체크도 아니고 안체크도 아닌 상태 

ID 0의 체크 박스가 클릭되었을 때를 처리하는 코드를 보자. 체크 박스가 클릭되면 BN_CLICKED 통지 메시지가 WM_COMMAND 메시지를 통해 전달되므로 WM_COMMAND 메시지에 이 코드가 작성되어 있다.
case WM_COMMAND:
switch(LOWORD(wParam)) {
case 0:
	if (SendMessage(c1,BM_GETCHECK,0,0)==BST_UNCHECKED) {
		SendMessage(c1,BM_SETCHECK,BST_CHECKED,0);
		ELLIPSE = TRUE;
	}
	else {
		SendMessage(c1,BM_SETCHECK,BST_UNCHECKED,0);
		ELLIPSE = FALSE;
	}
	InvalidateRect(hWnd, NULL, TRUE);
	break;


 부모 윈도우는 먼저 BM_GETCHECK 메시지를 c1체크 박스로 보내 현재 체크 박스의 상태를 조사해 본다. 그리고 그 값이 BST_UNCHECKED 즉 현재 체크되어 있지 않으면 BM_SETCHECKED 메시지를 다시 보내 체크 박스에 체크 표시를 하도록 명령한다. 이때 wParam으로 체크 표시를 하라는 의미의 BST_CHECKED가 전달되었다. 만약 BST_UNCHECKED가 아니면, 즉 현재 체크되어 있으면 체크 표시를 해제하도록 메시지를 보낸다. 또한 조사된 체크 박스의 상태에 따라 ELLIPSE 변수값을 TRUE나 FALSE로 변경하도록 하며 InvalidateRect 함수를 호출하여 WM_PAINT 메시지를 발생시키는데 WM_PAINT 메시지에서는 ELLIPSE 변수의 값에 따라 사각형이나 타원을 그리게 된다. 따라서 사용자가 이 체크 박스를 클릭할 때마다 체크박스의 상태가 토글되며 화면상의 사각형/타원형이 계속 교체된다.


 부모 윈도우와 자식 윈도우는 이런 식으로 메시지를 통해 서로의 상태를 알리거나 변경하도록 지시한다.


 
 ID 2의 체크 박스는 BS_3STATE 스타일로 생성되었으므로 세가지 상태를 가질 수 있으며 수동 체크 박스이기 때문에 ID 0의 체크 박스와 마찬가지로 BN_CLICKED 통지 메시지가 발생했을 때 체크 상태를 변경해 주도록 해 주어야 한다.


 
반면 ID 1과 ID 3의 체크 박스는 자동 체크 박스이기 때문에 이런 코드를 작성하지 않아도 윈도우즈가 알아서 체크 박스의 상태를 변경해 준다. 이렇게 변경된 값은 체크 박스의 상태가 필요할 때만 BM_GETCHECK 메시지를 보내 상태를 알아 보기만 하면 되며 체크 박스값이 변경될 때마다 어떤 처리를 해 줄 필요는 없다. 이 예제의 경우 ID 1의 체크 박스 상태는 종료시에만 사용되므로 종료 직전에만 값을 검사하여 체크되어 있으면 메시지 박스를 출력하고 그렇지 않으면 그냥 프로그램을 종료하면 된다.

	case WM_DESTROY:
		if (SendMessage(c2,BM_GETCHECK,0,0)==BST_CHECKED)
			MessageBox(hWnd,"Good bye","Check",MB_OK);
		PostQuitMessage(0);
		return 0;

ID 0의 수동 체크 박스는 체크 상태가 변경될 때마다 ELLIPSE 변수값을 변경해 주어야 하고 또한 화면을 다시 그려야 하므로 자동 체크 박스 스타일은 부적당하다.



[3] 라디오 버튼

1)정의
라디오 버튼도 일종의 버튼이다.
"button" 클래스에 BS_RADIOBUTTON, BS_AUTORADIOBUTTON 둘 중 하나의 스타일을 지정하면 라디오 버튼이 된다. 체크와 마찬가지로 수동, 자동의 두 가지 종류가 있으며 윈도우즈가 체크 상태를 자동으로 변경해 주는가 그렇지 않은가의 차이가 있다. 체크 버튼은 진위적인 옵션을 개별적으로 입력받는데 비해 라디오 버튼은 여러가지 선택 사항 중 한가지만 선택할 필요가 있을 때 사용한다. 그래서 하나의 선택사항에 대해 여러개의 라디오 버튼들이 그룹을 이루어 사용된다는 특징이 있다. 같은 그룹에 속한 라디오 버튼은 하나만 선택할 수 있다. 사용자에 의해 라디오 버튼이 선택되면 같은 그룹에 속한 다른 모든 라디오 버튼은 선택이 해제된다.

 라디오 버튼의 그룹을 구성하는 방법은 간단하다. 그룹을 이루는 첫번째 라디오 버튼에만 WS_GROUP 스타일을 주고 나머지 라디오 버튼은 WS_GROUP 스타일을 주지 않으면 된다. 어떤 라디오 버튼들이 같은 그룹에 속하는지 나타내기 위해 그룹 박스 컨트롤이 사용된다. 그룹 박스는 BS_GROUPBOX 스타일을 가지는 버튼의 일종이지만 화면으로 보여지기만 할 뿐 사용자의 입력을 받아들이거나 어떤 기능을 가지는 것은 아니다. 단순한 장식일 뿐이다.

2)예제



 
 


[4] 에디트
에디트는 문자열을 직접 입력받고자 할 때 사용하는데 버튼과 마찬가지로 윈도우즈에서 가장 흔하게 볼 수 있는 컨트롤이다. 가로로 길쭉하게 생겼으며 여기에 문자열을 입력할 수 있다. 다음 대화상자에서 문자열이 입력된 흰색 컨트롤이 모두 에디트이다. 워낙 흔해 빠진 컨트롤이라 사용방법에 대해서는 이미 익숙할 것이다


 
 "edit" 윈도우 클래스를 사용하며 생성시에 다음과 같은 스타일을 사용할 수 있다.
물론 이 스타일들은 CreateWindow 함수의 세번째 인수로 지정한다.
 

스타일 설명
ES_AUTOHSCROLL 수평 스크롤을 지원한다.
ES_AUTOVSCROLL 여러줄 편집시 수직 스크롤을 지원한다.
ES_LEFT 왼쪽 정렬한다.
ES_CENTER 중앙 정렬한다.
ES_RIGHT 오른쪽 정렬한다.
ES_LOWERCASE 소문자로 변환하여 표시한다.
ES_UPPERCASE 대문자로 변환하여 표시한다.
ES_MULTILINE 여러줄을 편집할 수 있도록 한다.
ES_NOHIDESEL 포커스를 잃더라도 선택된 영역을 표시한다.
ES_READONLY 읽기전용으로 만들어 편집을 금지한다.

 지정할 수 있는 스타일이 많은데 각 스타일의 의미에 대해서는 14장에서 자세히 알아볼 것이므로 당장 다 이해할 필요없이 대충 봐 두기만 하면 된다.
 
자신의 변화에 대해 다음과 같은 통지 메시지를 부모 윈도우로 보내준다. 부모 윈도우는 이 메시지를 받았을 때 적절한 처리를 해 주면 된다.

메시지 설명
EN_CHANGE 문자열이 변경되었다.
EN_ERRSPACE 메모리가 부족하다.
EN_HSCROLL 사용자가 수평 스크롤 바를 클릭하였다.
EN_VSCROLL 사용자가 수직 스크롤 바를 클릭하였다.
EN_KILLFOCUS 포커스를 잃었다.
EN_SETFOCUS 포커스를 얻었다.
EN_MAXTEXT 지정한 문자열 길이를 초과하였다.
EN_UPDATE 문자열이 변경되기 직전이다.

 
EN_CHANGE와 EN_UPDATE가 비슷한 것처럼 보이지만 약간 다르다. EN_UPDATE는 문자열이 변경된 후 화면에 출력하기 전에 보내주는 메시지이며 이 메시지가 발생했을 때 사용자는 문자열 길이에 따라 에디트의 폭을 늘리거나 별도의 조치를 취할 수 있다. EN_CHANGE는 문자열이 화면으로 출력되고 난 후 보내지는 메시지이다. 즉 에디트는 문자열이 변경된 후 EN_UPDATE 메시지를 보내고 화면에 그린 후 다시 EN_CHANGE 메시지를 보낸다. 상황과 필요에 따라 둘 중 하나를 선택하여 사용하되 대개의 경우 어떤 메시지를 쓰나 별 차이가 없으며 보통 EN_CHANGE 메시지를 많이 사용한다. 메인 윈도우에서 에디트 컨트롤에게 보내는 메시지도 있지만 이 예제에서는 사용되지 않으므로 다음 기회에 알아보도록 하자.

 에디트 하나를 배치하고 에디트에 입력된 문자열을 메인 윈도우의 타이틀바에 출력하도록 하는 예제를 작성해 보았다.



실행중의 모습은 다음과 같다.


 

에디트는 사용자가 자신을 편집할 때마다 부모 윈도우로 EN_CHANGE 메시지를 보내며 메인 윈도우는 이 메시지를 받을 때마다 에디트의 텍스트를 읽어 자신의 타이틀 바를 갱신하고 있다. EN_CHANGE 통지 메시지 처리 코드에서 에디트에 입력된 텍스트를 읽을 때 GetWindowText 함수를 사용하였는데 보다시피 컨트롤도 윈도우의 일종이기 때문에 윈도우 관리 함수를 모두 사용할 수 있다.

에디트는 최대 32k까지의 문자열을 편집할 수 있으며 여러줄 편집, 블럭선택, 클립보드 지원 기능까지 다양한 기능을 가지고 있는데 비해 사용하기는 비교적 쉬운편에 속한다. 14장에서 에디트에 대한 상세한 프로그래밍 방법을 익혀 보도록 하자.

[5]리스트 박스
리스트 박스는 선택가능한 여러개의 항목들을 나열해 놓고 그중 하나(또는 여러개)를 선택하도록 하는 컨트롤이며 여기서 항목이란 주로 문자열이다. "listbox"라는 윈도우 클래스를 사용하며 다음과 같은 여러가지 스타일이 정의되어 있다.

스타일 설명
LBS_MULTIPLESEL 여러개의 항목을 선택할 수 있도록 한다. 이 스타일을 적용하지 않으면 디폴트로 하나만 선택할 수 있다.
LBS_NOTIFY 사용자가 목록중 하나를 선택했을 때 부모 윈도우로 통지 메시지를 보내도록 한다.
LBS_SORT 추가된 항목들을 자동 정렬하도록 한다.
LBS_OWNERDRAW 문자열이 아닌 비트맵이나 그림을 넣을 수 있도록 한다.
LBS_STANDARD LBS_NOTIFY | LBS_SORT | WS_BORDER

부모 윈도우로 통지 메시지를 보내는 것이 일반적이므로 LBS_NOTIFY 스타일은 거의 필수적으로 선택하는 것이 좋으며 그외 LBS_MULTIPLESEL이나 LBS_SORT 스타일은 필요할 때 선택하면 된다. 

 
부모 윈도우가 리스트 박스를 조작하고자 할 때는 리스트 박스 메시지를 사용한다.

메시지 설명
LB_ADDSTRING 리스트 박스에 항목을 추가한다. lParam으로 추가하고자 하는 문자열의 번지를 넘겨주면 된다.
LB_DELETESTRING 항목을 삭제한다. wParam으로 항목의 번호를 넘겨주며 남은 문자열수를 리턴한다.
LB_GETCURSEL 현재 선택된 항목의 번호(Index)를 조사해준다.
LB_GETTEXT 지정한 항목의 문자열을 읽는다. wParam에 항목 번호, lParam에 문자열 버퍼의 번지를 넘겨주면 버퍼에 문자열을 채워준다.
LB_GETCOUNT 항목의 개수를 조사한다.
LB_SETCURSEL wParam이 지정한 항목을 선택하도록 한다.
 

이 외에도 수많은 메시지들이 있는데 자세한 사항은 레퍼런스를 참고하기 바란다. 이 메시지들은 부모 윈도우가 리스트 박스에게 명령을 내리기 위해 사용하며 반대로 리스트 박스에서 어떤 사건이 발생했을 때 부모 윈도우로 통지 메시지를 보낸다.

메시지 설명
LBN_DBLCLK 리스트 박스를 더블클릭하였다.
LBN_ERRSPACE 메모리가 부족하다.
LBN_KILLFOCUS 키보드 포커스를 잃었다.
LBN_SELCANCEL 사용자가 선택을 취소하였다.
LBN_SELCHANGE 사용자에 의해 선택이 변경되었다.
LBN_SETFOCUS 키보드 포커스를 얻었다.
 

예제) 
그럼 리스트 박스를 사용하는 간단한 예제를 만들어 보자.


실행 직후의 모습은 다음과 같다





[6]콤보 박스

 
콤보 박스는 에디트 컨트롤과 리스트 박스를 결합시켜놓은 컨트롤이다. 그래서 목록에 있는 항목중의 하나를 선택할 수도 있고 원하는 항목이 없을 경우 에디트에 직접 항목을 입력해 넣을 수도 있다. 또한 리스트 박스는 항상 열려 있는데 비해 콤보 박스는 필요할 경우만 목록을 열어 선택하므로 화면 공간을 적게 차지한다는 장점도 있다.
"combobox" 윈도우 클래스를 사용하며 세가지 종류가 있는데 스타일로 원하는 종류를 선택한다.

스타일 설명
CBS_SIMPLE 에디트만 가진다.
CBS_DROPDOWN 에디트와 리스트 박스를 가진다.
CBS_DROPDOWNLIST 리스트 박스만 가지며 에디트에 항목을 입력할 수는 없다.

이 외의 스타일이나 메시지들은 에디트와 리스트에 있는 것들과 거의 동일하며 접두어만 조금씩 다르다. 예를 들어 리스트 박스의 LB_ADDSTRING 메시지가 콤보 박스에는 CB_ADDSTRING으로 되어 있다. 자세한 목록은 레퍼런스를 참고하기 바라며 여기서는 간단한 예제 하나만 만들어 보기로 한다.



실행중의 모습은 다음과 같다.


[7].스크롤바

스크롤 바는 지금까지 논한 컨트롤들에 비해서는 비교적 복잡한 컨트롤이다. 여기서는 대충 예제 구경만 하고 자세한 내용은 다음에 알아보는 것이 좋을 것 같다. 
 

 스크롤 바는 "scrollbar"윈도우 클래스로 생성하며 수평 스크롤 바일 경우 SBS_HORZ 스타일을, 수직 스크롤 바일 경우는 SBS_VERT 스타일을 지정한다. 다행히 스타일은 두 가지 종류밖에 없다. 스크롤 바는 범위와 현재 위치값을 가지는데 다음 두 함수로 이 값들을 지정한다.
 

BOOL SetScrollRange( HWND hWnd, int nBar, int nMinPos, int nMaxPos, BOOL bRedraw );
int SetScrollPos( HWND hWnd, int nBar, int nPos, BOOL bRedraw ); 

 
SetScrollRange 함수로 최대값(nMaxPos), 최소값(nMinPos)을 지정하되 첫번째 인수가 스크롤 바의 윈도우 핸들이다.
두번째 인수 nBar는 메인 윈도우에 부착된 스크롤 바 또는 별도의 스크롤 바 컨트롤을 지정하는데 이 값이 SBS_CTL이면 별도의 컨트롤을 지정한다. SetScrollPos는 스크롤 바의 현재 위치를 세번째 인수 nPos로 지정한다.
 다른 컨트롤들은 자신에게 변화가 있을 때 부모 윈도우로 통지 메시지를 보내는데 비해 스크롤 바는 WM_HSCROLL(수평일 경우), WM_VSCROLL(수직일 경우)이라는 별도의 메시지를 부모 윈도우로 보내며 추가 정보는 다음과 같다.

인수 설명
LOWORD(wParam) 스크롤 바 내의 어디를 눌렀는가?
HIWORD(wParam) 현재 위치
lParam 스크롤 바의 윈도우 핸들

LOWORD(wParam)으로 전달되는 값은 사용자의 스크롤 요구사항을 나타내며 가능한 값의 종류는 다음과 같다.
 
설명
SB_LINELEFT 또는 SB_LINEUP 사용자가 왼쪽 화살표 버튼을 눌렀다는 뜻이며 이때는 왼쪽으로 한 단위 스크롤시킨다.
SB_LINERIGHT 또는 SB_LINEDOWN 사용자가 오른쪽 화살표 버튼을 눌렀다는 뜻이며 이때는 오른쪽으로 한 단위 스크롤시킨다.
SB_PAGELEFT 또는 SB_PAGEUP 사용자가 왼쪽 몸통 부분을 눌렀다는 뜻이며 이때는 한페이지 왼쪽으로 스크롤시킨다.
SB_PAGERIGHT 또는 SB_PAGEDOWN 사용자가 오른쪽 몸통 부분을 눌렀다는 뜻이며 이때는 한페이지 오른쪽으로 스크롤시킨다.
SB_THUMBPOSITION 스크롤 박스를 드래그한 후 마우스 버튼을 놓았다.
SB_THUMBTRACK 스크롤 박스를 드래그하고 있는 중이다. 이 메시지는 마우스 버튼을 놓을 때까지 계속 전달된다.

수평 스크롤 바를 예로 들어 사용자가 클릭한 부분과 이때 전달되는 메시지를 보면 다음과 같다. 양끝의 버튼을 누르는 것은 비교적 작은 단위(예를 들어 한 줄씩)로 섬세하게 이동하라는 뜻이며 몸통부분을 클릭하는 것은 비교적 큰 단위(예를 들어 한 페이지씩)로 이동하라는 뜻이다.
 


또한 수평 스크롤 바는 왼쪽, 오른쪽으로 스크롤되지만 수직 스크롤 바는 위, 아래로 스크롤되는데 이때 수평의 왼쪽 이동과 수직의 위쪽 이동은 둘 다 값을 감소시키는 점에서 동일하다. 그래서 SB_LINELEFT와 SB_LINEUP은 같은 의미라고 할 수 있다.

다음 예제는 스크롤 바 세 개를 사용하여 R,G,B값을 입력받아 브러시를 만든 후 사각형을 그린다.



[8].스태틱
 
스태틱은 모든 컨트롤을 통틀어 제일 간단한 컨트롤이다. 사용자로부터 입력을 받아들이는 기능은 없고 오로지 문자열을 보여주는 것이 기능의 전부이기 때문이다. 다음은 비주얼 C++ 개발자 스튜디오의 File/Page Setup 대화상자인데 여기서 Header, Footer, Left, Top 등의 문자열이 바로 스태틱 컨트롤이다.
 



주로 에디트나 다른 컨트롤 옆에 위치하며 컨트롤의 용도를 설명해 주는 역할을 한다. 스태틱 컨트롤을 만들 때는 윈도우 클래스를 "static"으로 설정해 주면 된다. 다음은 스태틱 컨트롤을 윈도우에 배치하는 간단한 예제이다.

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
	switch(iMessage) {
	case WM_CREATE:
		CreateWindow("static","Only Text",WS_CHILD | WS_VISIBLE,
			20,20,100,25,hWnd,(HMENU)-1,g_hInst,NULL);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

WM_CREATE에서 스태틱 컨트롤을 만들기만 할 뿐 그 외의 코드는 작성할 필요가 없다. 스태틱 컨트롤은 실행중에 부모 윈도우로 통지 메시지를 보낼 필요가 없기 때문에 ID를 -1로 지정해도 되며 여러 개의 스태택 컨트롤이 있을 경우에도 모두 -1의 같은 ID를 사용해도 상관없다. 스태틱은 그 자리에 있는 자체가 존재의 의미이며 다른 컨트롤과 구분할 필요조차도 없기 때문이다. 실행중의 모습은 다음과 같다.



 문자열 하나만 출력되어 있으며 아무런 기능도 없다. 그럼 스태틱 컨트롤로 출력한 문자열은 TextOut로 출력한 문자열과는 어떻게 다를까하는 의문이 생길것이다. TextOut로 출력한 문자열은 그냥 문자열일 뿐이므로 언제든지 지워질 수가 있고 그래서 WM_PAINT에서 계속 출력해주어야 한다. 반면 스태틱 컨트롤은 스스로 메시지를 처리할 수 있는 윈도우이기 때문에 일단 배치해 놓기만 하면 더 이상 신경쓰지 않아도 된다. 또한 색상, 글꼴 크기 등 운영체제의 세팅이 바뀔 경우 이런 변화에 대해서도 스스로 대처한다는 장점이 있는데 이에 대해서는 다음 기회에 알아 보기로 한다.

 스태택 컨트롤은 일반적으로 문자열이지만 스타일에 따라 사각형 모양이나 아이콘이 될 수도 있다. 잘 사용되지는 않지만 스태틱도 다음과 같은 스타일을 가진다. 아무 스타일도 지정하지 않으면 왼쪽으로 정렬되는 단순한 텍스트이다.
 

타일 설명
SS_LEFT 왼쪽으로 정렬되는 텍스트이며 자동으로 개행된다.
SS_LEFTNOWORDWRAP 왼쪽으로 정렬되는 텍스트이며 자동으로 개행되지 않는다.
SS_CENTER 중앙으로 정렬되는 텍스트이며 자동으로 개행된다.
SS_RIGHT 오른쪽으로 정렬는 텍스트이며 자동으로 개행된다.
SS_SIMPLE 단순한 문자열이며 자동개행되지 않는다.
SS_WHITEFRAME 윈도우의 배경색으로 그려지는 사각형
SS_WHITERECT 윈도우의 배경색으로 그려지는 속이 채워진 사각형
SS_BLACKFRAME 화면 배경색으로 그려지는 사각형
SS_BLACKRECT 화면 배경색으로 그려지는 속이 채워진 사각형
SS_GRAYFRAME 윈도우 프레임 색상으로 그려지는 사각형
SS_GRAYRECT 윈도우 프레임 색상으로 그려지는 속이 채워진 사각형
SS_ICON 대화상자내에서 아이콘 출력
SS_NOPREFIX &문자를 단축키 지정에 사용하지 않고 그대로 출력한다.


이상으로 표준 컨트롤 6가지에 대한 간략한 소개를 마친다. 앞으로 수많은 컨트롤들을 다루게 될텐데 여기서 배운 지식을 바탕으로 하면 나머지 컨트롤들도 쉽게 다룰 수 있을 것이다.
컨트롤을 잘 다루기 위해서는
①스타일
②통지 메시지
③부모 윈도우가 보내는 메시지
④메시지에 의해 사용되는 구조체,
이 네가지에 대해 잘 알아야 하는데 굳이 외울 필요까지는 없다.

필요할 때마다 레퍼런스를 참고할 수도 있고 스타일은 개발툴에 따라 비주얼 편집이 가능하기도 하므로 대충의 이해만 하고 있으면 된다.


출저: www.winapi.com 

이 글을 공유하기

댓글

Designed by JB FACTORY