1.키보드 입력 ¶
- 1-1) WM_CHAR 메시지
키보드로부터 입력이 발생했을 경우 윈도우즈는 포커스를 가진 프로그램에게 키보드 메시지(WM_CHAR)를 보내주며 프로그램은 이 메시지를 받아 키보드 입력을 처리한다. 여기서 포커스(Focus)를 가진 프로그램이란 활성화되어 있는 윈도우를 말하며 한번에 오직 하나의 프로그램만 활성화된다. 아무리 여러개의 프로그램이 동시에 실행되는 멀티 태스킹 환경이라 하더라도 활성화될 수 있는 프로그램은 오직 하나밖에 없으며 활성화된 프로그램만 포커스를 가지고 키보드 입력을 받아들일 수 있다. 왜냐하면 시스템에 키보드는 하나뿐이며 키보드를 사용할 수 있는 사용자도 하나뿐이기 때문이다.
~cpp #include <windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); HINSTANCE g_hInst; LPSTR lpszClass="Key"; int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance ,LPSTR lpszCmdParam,int nCmdShow) { HWND hWnd; MSG Message; WNDCLASS WndClass; g_hInst=hInstance; WndClass.cbClsExtra=0; WndClass.cbWndExtra=0; WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); WndClass.hCursor=LoadCursor(NULL,IDC_ARROW); WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION); WndClass.hInstance=hInstance; WndClass.lpfnWndProc=(WNDPROC)WndProc; WndClass.lpszClassName=lpszClass; WndClass.lpszMenuName=NULL; WndClass.style=CS_HREDRAW | CS_VREDRAW; RegisterClass(&WndClass); hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, NULL,(HMENU)NULL,hInstance,NULL); ShowWindow(hWnd,nCmdShow); while(GetMessage(&Message,0,0,0)) { TranslateMessage(&Message); DispatchMessage(&Message); } return Message.wParam; } LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static char str[256]; int len; switch(iMessage) { case WM_CHAR: len = strlen(str); str[len]=(TCHAR)wParam; str[len+1]=0; InvalidateRect(hWnd,NULL,FALSE); return 0; case WM_PAINT: hdc=BeginPaint(hWnd,&ps); TextOut(hdc,100,100,str,strlen(str)); EndPaint(hWnd,&ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); }
소스를 입력한 후 실행해 보자. 키보드에서 키를 누르면 입력한 문자들이 화면 상단에 출력될 것이다.WndProc을 보면 우선 문자열 str이 선언되어 있으며 이 문자열 변수에 사용자가 입력한 문자들을 모은다. 단 이 변수는 WndProc에 선언되어 있는 지역변수이므로 그냥 선언하면 메시지가 발생할 때마다 초기화되기 때문에 static을 붙여 정적변수로 만들어 두어야 한다. 아니면 아예 WinMain 함수 이전에 선언하여 전역 변수로 만들어 두어도 된다.
입력된 문자들을 바로 바로 출력하지 않고 반드시 문자열에 모아 두어야 하는 이유는 키보드 입력이 발생하는 시점과 문자열을 출력해야 할 시점이 분리되어 있기 때문이다. 키보드 입력 시점은 키보드 메시지인 WM_CHAR가 발생했을 때이며 이 메시지에서 문자열을 조립하기만 하고 문자열의 출력은 WM_PAINT에서 처리한다. 물론 WM_CHAR 메시지에서 문자열을 바로 바로 출력하는 것도 가능하기는 하지만 윈도우즈 프로그램은 화면을 다시 그릴 준비를 항상 해 두어야 하며 모든 출력은 WM_PAINT에서 하도록 되어 있다. 그렇지 않으면 출력된 문자들이 지워지면 다시 복구되지 않는다.
WM_CHAR 메시지는 입력된 문자의 아스키 코드를 wParam으로 전달하도록 되어 있으며 우리는 wParam의 값을 읽어 사용자가 어떤 키를 눌렀는지를 알아내게 된다.
다음 코드는 wParam으로 전달된 키 코드를 str 문자배열에 저장한다.
입력된 문자들을 바로 바로 출력하지 않고 반드시 문자열에 모아 두어야 하는 이유는 키보드 입력이 발생하는 시점과 문자열을 출력해야 할 시점이 분리되어 있기 때문이다. 키보드 입력 시점은 키보드 메시지인 WM_CHAR가 발생했을 때이며 이 메시지에서 문자열을 조립하기만 하고 문자열의 출력은 WM_PAINT에서 처리한다. 물론 WM_CHAR 메시지에서 문자열을 바로 바로 출력하는 것도 가능하기는 하지만 윈도우즈 프로그램은 화면을 다시 그릴 준비를 항상 해 두어야 하며 모든 출력은 WM_PAINT에서 하도록 되어 있다. 그렇지 않으면 출력된 문자들이 지워지면 다시 복구되지 않는다.
WM_CHAR 메시지는 입력된 문자의 아스키 코드를 wParam으로 전달하도록 되어 있으며 우리는 wParam의 값을 읽어 사용자가 어떤 키를 눌렀는지를 알아내게 된다.
다음 코드는 wParam으로 전달된 키 코드를 str 문자배열에 저장한다.
~cpp len = strlen(str); str[len]=(TCHAR)wParam; str[len+1]=0;
문자열의 제일 끝 부분에 wParam값을 써 넣고 바로 뒤쪽의 0을 써 넣어 문자열 끝을 표시한다. 키 입력이 있을 때마다 이 동작을 반복함으로써 str 문자 배열에는 입력된 키 값이 차곡 차곡 쌓여갈 것이다.
키보드 메시지에서는 str배열에 문자열을 집어 넣기만 하며 문자열을 화면으로 출력하는 일은 WM_PAINT에서 맡는다. 단 키보드 메시지에 의해 문자열이 다시 입력되더라도 화면상의 변화는 없으므로 WM_PAINT메시지가 발생하지 않는다. 그래서 강제로 WM_PAINT 메시지를 발생시켜 주어야 하는데 이 때는 InvalidateRect 함수를 호출해 주면 된다. WM_CHAR에서 문자열을 조립한 후 InvalidateRect 함수를 호출해 주어 키보드가 입력될 때마다 화면을 다시 그리도록 하였다.
키보드 메시지에서는 str배열에 문자열을 집어 넣기만 하며 문자열을 화면으로 출력하는 일은 WM_PAINT에서 맡는다. 단 키보드 메시지에 의해 문자열이 다시 입력되더라도 화면상의 변화는 없으므로 WM_PAINT메시지가 발생하지 않는다. 그래서 강제로 WM_PAINT 메시지를 발생시켜 주어야 하는데 이 때는 InvalidateRect 함수를 호출해 주면 된다. WM_CHAR에서 문자열을 조립한 후 InvalidateRect 함수를 호출해 주어 키보드가 입력될 때마다 화면을 다시 그리도록 하였다.
- 1-2) WM_KEYDOWN
키보드로부터 문자를 입력받고자 할 경우는 WM_CHAR 메시지를 사용하면 된다는 것을 배웠다. 문자 이외의 키를 입력 받으려면 WM_CHAR 메시지만으로는 입력을 받을 수 없다. 예를 들어 커서 이동키라든가 Ins, Del, PgUp, 펑션키 등의 키는 문자키가 아니기 때문에 WM_CHAR 메시지로는 검출해 낼 수 없다. 이때는 WM_KEYDOWN 메시지를 사용해야 한다.
가상키 코드 / 값 / 키
VK_LBUTTON / 01
VK_RBUTTON / 02
VK_CANCEL / 03 / Ctrl-Break
VK_MBUTTON / 04
VK_BACK / 08 / Backspace
VK_TAB / 09 / Tab
VK_RETURN / 0D / Enter
VK_SHIFT / 10 / Shift
VK_CONTROL / 11 / Ctrl
VK_MENU / 12 / Alt
VK_ESCAPE / 1B / Esc
VK_SPACE / 20 / Space
VK_END / 23 / End
VK_HOME / 24 / Home
VK_LEFT / 25 / 좌측커서 이동키
VK_UP / 26 / 위쪽커서 이동키
VK_RIGHT / 27 / 오른쪽커서 이동키
VK_DOWN / 28 / 아래쪽커서 이동키
VK_NUMLOCK / 90 / Num Lock
VK_SCROLL / 91 / Scroll Lock
VK_LBUTTON / 01
VK_RBUTTON / 02
VK_CANCEL / 03 / Ctrl-Break
VK_MBUTTON / 04
VK_BACK / 08 / Backspace
VK_TAB / 09 / Tab
VK_RETURN / 0D / Enter
VK_SHIFT / 10 / Shift
VK_CONTROL / 11 / Ctrl
VK_MENU / 12 / Alt
VK_ESCAPE / 1B / Esc
VK_SPACE / 20 / Space
VK_END / 23 / End
VK_HOME / 24 / Home
VK_LEFT / 25 / 좌측커서 이동키
VK_UP / 26 / 위쪽커서 이동키
VK_RIGHT / 27 / 오른쪽커서 이동키
VK_DOWN / 28 / 아래쪽커서 이동키
/ 30 ~ 39 / 숫자키 0~9
/ 41 ~ 5A / 영문자 A~Z
VK_F1 ~ VK_F16 / 70 ~ 7F / 펑션키 F1 ~ F16/ 41 ~ 5A / 영문자 A~Z
VK_NUMLOCK / 90 / Num Lock
VK_SCROLL / 91 / Scroll Lock
- 1-3) TranslateMessage
~cpp while(GetMessage(&Message,0,0,0)) { TranslateMessage(&Message); DispatchMessage(&Message); }
GetMessage는 메시지 큐에서 메시지를 꺼내온 후 이 메시지를 TranslateMessage 함수로 넘겨 준다. TranslateMessage 함수는 전달된 메시지가 WM_KEYDOWN인지와 눌려진 키가 문자키인지 검사해 보고 조건이 맞을 경우 WM_CHAR 메시지를 만들어 메시지 큐에 덧붙이는 역할을 한다. 물론 문자 입력이 아닐 경우는 아무 일도 하지 않으며 이 메시지는 DispatchMessage 함수에 의해 WndProc으로 보내진다. 만약 메시지 루프에서 TranslateMessage 함수를 빼 버리면 WM_CHAR 메시지는 절대로 WndProc으로 전달되지 않을 것이다.
2.마우스 입력 ¶
윈도우즈와 같은 GUI운영체제에서는 키보드보다 마우스가 더 많이 사용된다. 윈도우즈의 공식 입력 장치는 키보드이지만 그래픽 툴이나 DTP, CAD 등의 복잡한 프로그램에서는 마우스가 주요 입력 장치로 사용된다. 키보드 입력 처리를 메시지로 하는 것과 마찬가지로 마우스 입력 처리도 메시지를 받아 처리한다. 마우스 입력에 관한 메시지는 다음과 같은 종류가 있다.
버튼 / 누름 / 놓음 / 더블클릭
좌측 / WM_LBUTTONDOWN/ WM_LBUTTONUP / WM_LBUTTONDBLCLK
우측 / WM_RBUTTONDOWN/ WM_RBUTTONUP / WM_RBUTTONDBLCLK
좌측 / WM_LBUTTONDOWN/ WM_LBUTTONUP / WM_LBUTTONDBLCLK
우측 / WM_RBUTTONDOWN/ WM_RBUTTONUP / WM_RBUTTONDBLCLK
마우스 메시지는 lParam의 상위 워드에 마우스 버튼이 눌러진 y좌표, 하위 워드에 x좌표를 가지며 좌표값을 검출해 내기 위해 HIWORD, LOWORD 등의 매크로 함수를 사용한다. 즉 마우스 메시지가 발생한 위치의 좌표는 (LOWORD(lParam), HIWORD(lParam))이 된다.
wParam에는 마우스 버튼의 상태와 키보드 조합 키(Shift, Ctrl)의 상태가 전달된다. 조합키 상태는 다음 값들과 비트 연산을 해보면 알 수 있다.
값 / 설명
MK_CONTROL / Ctrl 키가 눌려져 있다.
MK_LBUTTON / 마우스 왼쪽 버튼이 눌려져 있다.
MK_RBUTTON / 마우스 오른쪽 버튼이 눌려져 있다.
MK_MBUTTON / 마우스 중간 버튼이 눌려져 있다.
MK_SHIFT / Shift 키가 눌려져 있다.
MK_CONTROL / Ctrl 키가 눌려져 있다.
MK_LBUTTON / 마우스 왼쪽 버튼이 눌려져 있다.
MK_RBUTTON / 마우스 오른쪽 버튼이 눌려져 있다.
MK_MBUTTON / 마우스 중간 버튼이 눌려져 있다.
MK_SHIFT / Shift 키가 눌려져 있다.
마우스 키의 누름 메시지 외에 마우스가 이동할 때마다 전달되는 WM_MOUSEMOVE 메시지가 있다. 이 메시지도 다른 마우스 메시지와 마찬가지로 lParam에 마우스 커서의 위치가 전달되며 wParam에 조합키 상태가 전달된다.