본문 바로가기

API

컨트롤 - 체크 박스

컨트롤이란?


컨트롤도 하나의 윈도우 입니다. 윈도우의 일정 영역을 차지하기도 하여 자신의 고유 메시지를 처리할 수 있는 능력을 가지고 있지요. 보통은 대화상자의 차일드 윈도우로 존재하고, 컨트롤은 윈도우즈가 제공하기 때문에 윈도우 클래스 등록을 따로 할 필요 없이, 미리 정의되어있는 윈도우 클래스를 불러다 쓰면 됩니다.


윈도우 클래스

컨트롤 

button 

버튼, 체크, 라디오 

static 

텍스트 

scrollbar 

스크롤 바 

edit 

에디트 

listbox 

리스트 박스 

combobox 

콤보 박스 


이 윈도우 클래스들은 시스템 부팅시에 운영체제에 의해 등록되므로 윈도우 클래스를 따로 등록할 필요 없이

CreateWindow 함수의 첫 번째 인수로 클래스 이름만 주면 해당 컨트롤을 만들 수 있습니다.


----------------------------------------------------------------------------------------------------------------------


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

다음은 윈도우즈의 폴더 옵션에 있는 체크 박스입니다.



위와 같은 체크 박스 또한 차일드 윈도우이며, 체크 마크를 표시하는 조그만 사각형과 체크 박스의 의미를 설명하는 짧은 문자열로 구성되어 있습니다.

체크 박스를 만드는 방법도 푸쉬 버튼을 만드는 방법과 동일한데요, 한 가지 다른 점이 있다면 바로 '스타일'입니다.

클래스 이름은 푸쉬 버튼과 같은 "button"을 사용하지만, 스타일은 BS_PUSHBUTTON 대신 4가지 종류의 체크 박스를 사용합니다.


일단 체크 박스의 스타일은 두 가지 상태를 가지는 체크 박스(BS_CHECKBOX)와 세 가지 상태를 가지는 체크 박스(BS_3STATE)로 구분됩니다.

두 가지 상태를 가지는 체크 박스는 선택/비선택 둘 중 하나의 상태를 가지지만 세 가지 상태를 가지는 체크 박스는 

선택/비선택 외에도 Grayed(알 수 없음, 결정할 수 없음)라는 제3의 상태를 가집니다.


또한, 동작 방법에 따라 자동 체크 박스와 수동 체크 박스로 나누어지는데 수동 체크 박스는 선택/비선택 상태를 부모 윈도우가 직접 변경해야 하며 자동 체크 박스는 스스로 체크 상태를 변경합니다.

(체크 박스의 상태는 사용자가 마우스 버튼으로 체크 박스를 클릭할 때 변경되는데, 자동 체크 박스는 별도의 코드 없이 스스로 체크 상태를 토글하지만 수동 체크 박스는 통지 코드가 전달될 때 체크 박스의 상태와 프로그램의 여러 가지 상태를 보고 직접 토글시켜야 합니다.)

자동 체크 박스는 별도의 코드 없이 체크 상태를 토글 할 수 있다는 장점이 있지만, 체크 상태를 변경할 때 좀 더 복잡한 조건을 점검하기에는 어울리지 않습니다.

상황에 따라 사용하는 체크 박스가 달라지는데, 보통 체크 박스의 상태가 변경될 때마다 어떤 처리를 해야 하고 체크 조건이 복잡한 경우에는 수동 체크 박스를 사용하고, 그 외에는 자동 체크 박스를 사용합니다.




위의 폴더 옵션을 보시면 '알려진 파일 형식의 파일 확장명 숨기기'라는 체크 박스가 있습니다. 만약 이 옵션을 체크한다면 옵션이 체크된 후에 바로 파일 확장명이 숨겨져야 합니다.

체크 상태에 따라 파일 확장명을 숨기는 복잡한 작업을 해야 하는데, 이는 프로그램의 여러 가지 상태를 보고 직접 토글할 수 있는 수동 체크 박스에 더 적합하겠죠?

자동 체크 박스를 사용할 수도 있습니다만, 자동은 스스로 체크 상태를 토글하므로 프로그램이 별도의 제어를 하기 어렵습니다.

위와 같이 복잡한 처리를 거칠 필요가 없으면 수동이 아닌 자동 체크 박스를 사용합니다.(대개의 경우는 자동 체크 박스를 쓰는 것이 훨씬 편하고 보편적입니다.)



그러면 체크 박스를 사용하는 예제를 만들어 보도록 합시다. 화면에 사각형을 하나 그리되 체크 박스의 상태에 따라 타원으로도 그릴 수 있도록 할 겁니다.

즉 체크 박스의 체크 상태에 따라 타원 또는 사각형을 그리는 거죠. 또한, 자동 체크 박스의 사용 예를 보여주기 위해 자동 체크 박스가 선택되어 있을 경우 프로그램 종료 전에 메세지 박스를 출력해 보도록 하겠습니다.

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
LPSTR lpszCmdParam, int nCmdShow) {
       ...
       WndClass.hBackground = (HBRUSH)(COLOR_BTNFACE + 1)
       ...
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
	HDC hdc;
	PAINTSTRUCT ps;
	static HWND c1, c2, c3, c4;
	static bool bEllipse = false;
	switch (iMessage) {
	case WM_CREATE:
		c1 = CreateWindow(TEXT("button"), TEXT("Draw Ellipse?"), WS_CHILD | WS_VISIBLE | BS_CHECKBOX, 
                20, 20, 160, 25, hWnd, (HMENU)0, g_hInst, NULL);
		c2 = CreateWindow(TEXT("button"), TEXT("Good Bye Message?"), WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 
                20, 50, 160, 25, hWnd, (HMENU)1, g_hInst, NULL);
		c3 = CreateWindow(TEXT("button"), TEXT("3State"), WS_CHILD | WS_VISIBLE | BS_3STATE,
                20, 80, 160, 25, hWnd, (HMENU)2, g_hInst, NULL);
		c4 = CreateWindow(TEXT("button"), TEXT("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);
				bEllipse = true;
			}
			else {
				SendMessage(c1, BM_SETCHECK, BST_UNCHECKED, 0);
				bEllipse = 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 (bEllipse) {
			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, TEXT("Good bye"), TEXT("Check"), MB_OK);
		}
		PostQuitMessage(0); 
		return 0;
	}
	return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}


WinMain의 윈도우 클래스 등록문이 약간 변경되었는데, 배경 브러쉬를 버튼 색상으로 지정했습니다. COLOR_BTNFACE는 버튼 표면의 색상을 의미하는데, 말 그대로 버튼을 그릴 때 시스팀에 사용하는 색입니다.

만약 변경하지 않았을 경우에는 아래와 같은 상황이 발생하게 됩니다.


컨트롤의 배경색과 윈도우의 배경색이 다르면 보기에 좋지 않으므로 윈도우의 배경 브러쉬를 컨트롤의 색상과 일치시켰습니다.



4가지 종류의 체크 박스를 모두 만들어 보기 위해 c1~c4까지 네 개의 윈도우 핸들 변수를 선언했습니다.

WM_CREATE에서 체크 박스의 스타일을 변경해 가며 4개의 체크 박스를 생성했으며 각 체크 박스의 ID는 0~3까지 주었습니다.

WM_PAINT에서는 bEllipse 변수값에 따라 타원 또는 사각형을 그리도록 하였습니다.


첫 번째 체크 박스(ID 0번)의 체크 상태에 따라 사각형이나 타원이 그려집니다.

체크 박스가 눌러지는 즉시 이 체크 박스는 부모 윈도우로 BN_CLICKED 통지 메세지를 보내고, 부모 윈도우는 0번 컨트롤인 Draw Ellipse 체크 박스가 눌러질 때마다 체크 박스의 상태를 조사해서 

체크 상태를 토글하고 bEllipse변수의 값도 같이 토글시킵니다. 그리고 InvalidateRect 함수를 호출하여 화면을 다시 그려 변경된 도형을 출력하였습니다.


두 번째 체크 박스는 선택 상태를 변경하는 시점에서는 아무런 동작도 하지 않지만 이 체크 박스가 선택되어 있으면 프로그램 종료 전에 메세지 박스를 출력합니다.

체크 상태를 토글하는데 별다른 조건이 없이 부모가 개입할 필요가 없으므로 자동 체크 박스로 만들었습니다.

세 번째, 네 번째 체크 박스는 세 가지 상태를 가지는 체크 박스의 모양을 살펴보기 위해 만들어만 놓았으며 기능은 가지고 있지 않습니다.



컨트롤의 메세지-------------------------------------


통지 메시지란 컨트롤이 자신에게 어떤 변화가 있을 때마다 부모 윈도우로 보내는 메시지를 뜻합니다.

예를 들어 체크 박스의 경우 사용자가 마우스로 클릭할 때마다 부모 윈도우로 BN_CLICKED 메시지를 보냅니다.

컨트롤이 부모 윈도우로 보내는 통지 메시지와는 달리 부모 윈도우가 체크 박스의 현재 상태를 알아보거나, 상태를 변경하고자 할 때도 차일드 윈도우로 메시지를 보내기도 한답니다.


(위의 사진처럼 부모 윈도우에서 컨트롤로 보내는 메시지는 명령이며, 컨트롤에서 부모 윈도우에게로 보내는 메시지는 보고 메시지입니다.)


메시지의 종류는 컨트롤마다 다르지만, 부모 윈도우가 체크 박스로 보낼 수 있는 메시지에는 다음 두 가지가 있습니다.


 메시지

 설명 

 BM_GETCHECK

 체크 박스가 현재 체크되어 있는 상태인지를 조사하며 wParam, lParam은 사용하지 않는다.

 체크상태는 리턴값으로 돌려진다

 BM_SETCHECK

 체크 박스의 체크 상태를 변경하며 wParam에 변경할 체크 상태를 지정한다.


그리고 BM_GETCHECK에 의해 리턴되는 값, 또는 BM_SETCHECK에 의해 설정되는 체크 박스의 상태에는 다음 세 가지가 있습니다.


상수 

 값 

의미 

 BST_UNCHECKED

 0 

 현재 체크되어 있지 않다.

 BST_CHECKED

 1 

 현재 체크되어 있다. 

 BST_INDETERMINATE

 2

 체크도 아니고 안 체크도 아닌 상태


ID 0의 체크 박스가 클릭되었을 때를 처리하는 코드를 봅시다. 체크 박스가 클릭되면 BM_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);
				bEllipse = true;
			}
			else {
				SendMessage(c1, BM_SETCHECK, BST_UNCHECKED, 0);
				bEllipse = false;
			}
			InvalidateRect(hWnd, NULL, TRUE);
			break;

부모 윈도우는 BM_GETCHECK 메시지를 c1 체크 박스로 보내 현재 체크 박스의 상태를 조사합니다.

그리고 그 값이 BST_UNCHECKED이면(현재 선택되어 있지 않으면) BM_SETCHECK 메시지를 다시 보내 체크 박스에 체크 표시를 하도록 명령합니다.

이때 wParam으로 체크 표시를 하라는 의미의 BST_CHECKED가 전달되고, 반대로 만약 BST_UNCHECKED가 아니면(현재 선택되어 있으면) 체크 표시를 해제하라는 메시지를 보내게 됩니다.

또한, 조사된 체크 박스의 상태에 따라 bEllipse변수값을 true나 false로 변경하며 InvalidateRect 함수를 호출하여 WM_PAINT 메시지를 발생시키는데,

WM_PAINT 메시지에서는 bEllipse변수 값에 따라 사각형이나 타원형을 그리게 됩니다.

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


ID 2의 체크 박스는 BS_3STATE 스타일로 생성되었으므로 세 가지 상태를 가질 수 있으며 AUTO(수동 체크 박스)가 아니므로 BM_CLICKED 통지 메시지가 발생했을 때 부모 윈도우에서 체크 상태를 변경해 줘야 합니다.


ID 1과 ID 3의 체크 박스는 자동 체크 박스이기 때문에 따로 추가코드를 작성하지 않아도 체크 박스가 스스로 자신의 상태를 토글합니다.

즉 체크 박스의 상태가 필요할 때만 BM_SETCHECK 메시지만 보내 상태를 조사하면 되고, 체크 박스값이 변경될 때마다 어떤 처리를 할 필요는 없게 되는 거죠.

이 예제의 경우 ID 1의 체크 박스 상태는 종료 시에만 사용하므로 윈도우가 파괴되기 전(프로그램이 종료되기 전)에 값을 검사하여 체크 박스 상태에 따라

추가 행동을 하게끔 하면 됩니다.


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


'API' 카테고리의 다른 글

컨트롤 - 푸시 버튼  (0) 2018.02.04
그리기 모드(코드)  (0) 2018.01.24
그리기 모드  (0) 2018.01.24
Old의 의미  (0) 2018.01.22
브러쉬 만들기  (0) 2018.01.22