컨트롤 - 푸시 버튼
컨트롤이란?
컨트롤도 하나의 윈도우 입니다. 윈도우의 일정 영역을 차지하기도 하여 자신의 고유 메시지를 처리할 수 있는 능력을 가지고 있지요. 보통은 대화상자의 차일드 윈도우로 존재하고, 컨트롤은 윈도우즈가 제공하기 때문에 윈도우 클래스 등록을 따로 할 필요 없이, 미리 정의되어있는 윈도우 클래스를 불러다 쓰면 됩니다.
윈도우 클래스 |
컨트롤 |
button |
버튼, 체크, 라디오 |
static |
텍스트 |
scrollbar |
스크롤 바 |
edit |
에디트 |
listbox |
리스트 박스 |
combobox |
콤보 박스 |
이 윈도우 클래스들은 시스템 부팅시에 운영체제에 의해 등록되므로 윈도우 클래스를 따로 등록할 필요 없이
CreateWindow 함수의 첫 번째 인수로 클래스 이름만 주면 해당 컨트롤을 만들 수 있습니다.
----------------------------------------------------------------------------------------------------------------------
1. 버튼 만들기
컨트롤은 윈도우이기는 하지만 홀로 사용될 수 없으며 반드시 부모 윈도우의 차일드로 존재해야 합니다. 차일드 컨트롤은 보통 부모 윈도우가 만들어질 때, 즉 WM_CREATE 메시지가 발생했을 때 만들어지고, 컨트롤도 하나의 윈도우이므로 CreateWindow 함수를 호출하여 만들어야합니다.
CreateWindow 함수의 사용법은 대강 알고 있으니, 함수 소개가 아닌 코드로 바로 넘어가도록 하겠습니다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { switch (iMessage) { case WM_CREATE: CreateWindow(TEXT("button"), TEXT("ClickMe"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 20, 20, 100, 25, hWnd, (HMENU)0, g_hInst, NULL); CreateWindow(TEXT("button"), TEXT("MeTwo"), 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, TEXT("FirstButton"), TEXT("BUTTON"), MB_OK); break; case 1: MessageBox(hWnd, TEXT("SecondButton"), TEXT("BUTTON"), MB_OK); break; } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return (DefWindowProc(hWnd, iMessage, wParam, lParam)); }
CreateWindow 함수의 컨트롤 스타일을 지정해주는 인자를 제외한 나머지는 대충 어떤 역할을 하는지 감이 잡히셨을겁니다.
그럼 천천히 코드를 분석해 보도록 합시다.
CreateWindow(TEXT("button"), TEXT("ClickMe"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 20, 20, 100, 25, hWnd, (HMENU)0, g_hInst, NULL);
1. "button"
CreateWindow 함수의 첫 번째 인수는 만들고자 하는 윈도우의 윈도우 클래스입니다. 위에서 말씀드렸다 싶이 컨트롤은 운영체제에서 제공함으로 따로 등록할 필요가 없고, 이 인수에 만들고자 하는 컨트롤의 윈도우 클래스를 적어주면 됩니다.
2. "ClickMe"
두 번째 인수는 윈도우 타이틀 바에 표시되는 윈도우 제목인데요, 컨트롤에 따라 캡션의 위치가 달라집니다. 윈도우라면 타이틀 바에 나타나지만, 버튼은 버튼 위에,
체크, 라디오 버튼은 그 오른쪽에 나타나게 됩니다. 리스트 박스나 스크롤 바처럼 캡션이 필요 없는 컨트롤의 경우에는 두 번째 인수를 NULL로 지정해 주시면 됩니다.
3. 스타일
세 번째 인수는 윈도우의 속성입니다. 컨트롤은 차일드 윈도우이므로 WS_CHILD 스타일은 필수이고, WS_VISIBLE을 적어주어야 ShowWindow 함수를 호출하지 않아도 화면에 나타납니다. 이 두 개의 값은 고정입니다! 그다음은 컨트롤에 따른 고유한 스타일을 추가로 지정하는데 버튼의 경우에는 접두어가 BS_로 시작되고, 버튼의 종류는 아래와 같습니다.
스타일 |
설명 |
BS_PUSHBUTTON |
푸시 버튼 |
BS_DEFPUSHBUTTON |
디폴트 푸시 버튼 |
BS_CHECKBOX |
체크 박스 |
BS_3STATE |
3가지 상태를 가지는 체크 박스 |
BS_AUTOCHECKBOX |
자동 체크 박스 |
BS_AUTO3STATE |
3가지 상태를 가지는 자동 체크 박스 |
BS_RADIOBUTTON |
라디오 버튼 |
BS_AUTORADIOBUTTON |
자동 라디오 버튼 |
BS_GROUPBOX |
그룹 박스 |
4. 위치
위치는 생략하겠습니다.
5. 부모 윈도우
8번째 인수는 컨트롤의 부모 윈도우를 지정해 줍니다. 컨트롤은 차일드이므로 반드시 부모 윈도우가 존재해야 하므로 메인 윈도우의 핸들인 hWnd를 적어줌으로써 부모 윈도우를 지정해주었습니다. 그래서 여기서 만들어진 버튼들은 hWnd 윈도우의 작업영역을 기준으로 한 좌표에서 생성되며 무슨 일이 생기면 hWnd에게 통지 메시지를 보내고, hWnd가 파괴될 때 같이 파괴됩니다.
차일드 윈도우는 항상 부모 윈도우의 위쪽에 나타나며, 부모가 최소화되거나 숨겨지면 자식도 같이 숨겨지며 부모가 파괴되면 자식도 같이 파괴되는 특성이 있습니다.
메인 윈도우를 생성할 때는 부모 윈도우를 NULL로 지정하는데, 이건 부모가 없다는 뜻이 아니라 데스크탑을 부모로 한다는 뜻입니다.
6. ID
CreateWindow의 9번째 인수는 윈도우에서 사용할 메뉴의 핸들입니다. 하지만 차일드 컨트롤은 메뉴를 가지지 않으므로 이 인수를 컨트롤의 ID 지정 용도로 사용합니다.
컨트롤의 ID는 컨트롤간의 구분을 위해 사용하는 것이므로 같은 부모를 가진 차일드 컨트롤이 중복된 값을 가지지 않는 한 자유롭게 지정할 수 있습니다.
여기에서는 0과 1을 사용하여 구분했는데, 원래 이 인수가 메뉴를 지정하므로 (HMENU)로 캐스팅해야 합니다.
7. Instance handle
WinMain의 첫 번째 인수로 전달받은 hInstance의 사본인 g_hInst를 넘겨주면 됩니다.
컨트롤을 생성한 후에 CreateWindow 함수는 생성된 차일드 컨트롤의 윈도우 핸들을 리턴하는데 핸들이 필요할 경우 별도의 변수에 핸들값을 저장해 두고, 필요할 때 사용하시면 됩니다.
----------------------------------------------------------------------------------------------------------------------
2. 부모와의 통신
다음은 버튼이 눌러질 경우의 처리를 해보도록 합시다.
컨트롤은 버튼을 클릭했다거나 에디트에 문자열을 입력했다거나 할 경우 부모 윈도우로 통지 메시지를 보내 어떤 사건이 발생했는지를 알립니다.
부모 윈도우는 차일드 컨트롤이 보내는 통지 메시지를 받아 적절한 처리를 해야 하며, 버튼을 클릭할 경우 WM_COMMAND 메시지를 부모 윈도우에게
보내며 이때 전달되는 정보는 다음과 같습니다.
인수 |
설명 |
HIWORD (wParam) |
통지코드 |
LOWORD(wParam) |
컨트롤의 ID |
lParam |
메시지를 보낸 차일드 윈도우의 윈도우 핸들 |
컨트롤의 ID는 CreateWindow의 아홉 번째 인수에서 지정한 정수값이며 어떤 컨트롤이 통지 메시지를 보냈는지를 알려줍니다.
통지코드는 차일드 컨트롤이 왜 메시지를 보냈는가를 나타내는 값이구요.
버튼 같은 경우에는 통지 코드가 항상 사용자가 자신을 클릭했다는 의미의 BN_CLICKED이므로 컨트롤이 버튼일 경우에는 검사할 필요가 없지만, 통지코드가 여러 개인 컨트롤은 이 값을 검사해 보아야 합니다.
부모 윈도우는 WM_COMMAND에서 LOWORD(wParam)값을 조사하여 어떤 컨트롤이 눌러졌는지에 따라 적절한 처리를 합니다.
다른 컨트롤은 HIWORD(wParam)으로 전달되는 통지 코드도 봐야 하지만 버튼이나 메뉴, 엑셀러레이터는 선택이나 클릭 이상의 사건이 없으므로 굳이 통지 코드를 볼 필요는 없습니다.
case WM_COMMAND: switch (LOWORD(wParam)) { case 0: MessageBox(hWnd, TEXT("FirstButton"), TEXT("BUTTON"), MB_OK); break; case 1: MessageBox(hWnd, TEXT("SecondButton"), TEXT("BUTTON"), MB_OK); break; } return 0;
이 코드를 말로 풀어보면 "첫 번째 버튼이 클릭 되면 FirstButton을 보여주고, 두 번째 버튼이 클릭 되면 SecondButton을 보여줘라!"입니다.
다른 컨트롤의 경우도 통지 메시지를 처리하는 방법은 버튼의 경우와 동일하지만 컨트롤에 따라 전달될 수 있는 통지 메시지의 종류가 다르므로 각 통지 메시지에 대해서는 컨트롤별로 따로 공부가 필요합니다.
WM_COMMAND 메시지는 컨트롤의 통지 메시지뿐만 아니라 메뉴, 엑셀러레이터 등 여러 가지 명령을 처리하는 중요한 역할을 합니다.
그런데 만약 컨트롤의 ID나 메뉴 ID 또는 엑셀러레이터의 ID가 중복된다면 어떻게 될까요?
코드가 정상적으로 작동하지 않겠죠? 위의 세 가지 값은 모두 LOWORD를 통해 전달되므로 0~65535까지의 범위에서 중복되지 않는 ID를 가져야만 합니다!
WM_COMMAND 메시지 처리 루틴은 일반적으로 다음과 같습니다.
case WM_COMMAND: switch (LOWORD(wParam)) { case 메뉴1: 처리; break; case 메뉴1: 처리; break; case 엑셀러레이터처리1: 처리; break; case 컨트롤1: switch (HIWORD(wParam)) { case 통지코드1: 처리; break; case 통지코드2: 처리; break; } break; } return 0;
컨트롤의 ID에 따라 1차 분기를 하고 통지 코드에 따라 2차 분기를 하고 있습니다. 누가 왜 메시지를 보냈는가에 따라 처리가 달라지는 것이죠.
사용자로부터 많은 명령을 받는 프로그램일수록 WM_COMMAND 메세지 처리는 복잡해지게 된답니다...