티스토리 뷰


키보드 입력 (p77)

* WM_CHAR

도스(콘솔) 환경에서는 사용자의 입력을 요구하는 함수들이 입력이 완료될 때까지 절대로 리턴하지 않으며 이들 함수가 실행되면 프로그램이 잠시 중단된다.
윈도우즈 환경은 멀티 태스킹 환경으로 사용자가 언제 자신에게 관심을 가질지 예측할 수 없다. 사용자가 관심을 보이는 프로그램만 CPU 시간을 받으며 나머지 프로그램은 대기해야 한다.

키보드 입력이 발생하면 윈도우즈는 포커스를 가진 프로그램에게 키보드 메시지(WM_CHAR, WM_KEYDOWN)를 보내며 프로그램은 이 메시지를 받아 입력을 처리한다.

Focus  입력 초점. 키보드 입력을 받아들일 수 있는 상태를 뜻한다. 포커스를 가진 상태란 활성화되어 있고 현재 사용자가 쓰고 있다는 뜻으로 한 번에 오직 하나의 프로그램만 활성화된다. 시스템에 키보드는 하나뿐이며 키보드를 사용하는 주체인 사용자도 오직 한 명뿐이기 때문이다.

* str 버퍼의 길이는 256으로 선언되어 최대 255개의 문자열을 저장할 수 있다. 그 이상을 입력하면 메모리의 뒤쪽이 파괴되므로 프로그램의 안정성을 보장할 수 없다.

WM_CHAR 메시지에서 str 문자 배열은 사용자가 입력한 문자들을 모은다. 이는 키보드 입력이 발생하는 시점과 문자열을 출력해야 할 시점이 분리되어 있기 때문이다. 키보드 입력 시점은 WM_CHAR(키보드 입력)가 발생했을 때인데 이때 문자열을 조립하기만 하고 출력은 WM_PAINT에서 처리한다. 이는 언커버되었을 때 바로 복구 가능하도록 하기 위해서이다. 즉, 출력에 필요한 모든 정보는 따로 저장해 두고 WM_PAINT에서 일괄적으로 출력해야 한다.

WM_CHAR 메시지는 입력된 문자 코드를 wParam으로 전달하며 이 값을 읽어 어떤 문자키를 눌렀는지 알아낸다. lParam에는 비트별로 정보가 담겨있으며 필요한 정보를 참조하여 사용한다.
사용자 삽입 이미지

len = strlen(str);
str[len]=(TCHAR)wParam;
str[len+1]=0;
wParam으로 전달된 문자 코드를 str 문자 배열에 계속 쌓아간다. 하지만 문자열을 출력하는 일은 WM_PAINT에서 하므로 화면에 출력되지는 않는다. 따라서 키보드 입력마다 강제로 WM_PAINT 메시지를 발생시켜야 하는데 이 때는 InvalidateRect 함수를 호출한다.


* 무효 영역

WM_PAINT 메시지는 윈도우가 다시 그려져야 할 필요가 있을 때마다(원래 모습과 다른 모습을 가졌을 때) 호출되는데 이는 무효영역(Invalid Region)이 있다는 뜻이다. 윈도우의 일부가 지워졌을 때 지워진 작업 영역을 무효영역으로 만들어 윈도우가 다시 그려지도록 한다. 프로그램 내부에서 윈도우 모습을 변경시켰을 때 변경된 부분을 다시 그리도록 강제로 무효화해야 하며 이때 쓰이는 함수가 InvalidateRect이다.

BOOL InvlidateRect(HWND hWnd, CONST RECT *lpRect, BOOL bErase);
윈도우의 작업영역을 무효화하여 운영체제로 하여금 WM_PAINT 메시지를 해당 윈도우로 보내도록 한다.
hWnd 다시 그려져야 할 윈도우의 핸들
자신이 아닌 다른 윈도우를 무효화 할 수도 있다.
lpRect 무효화할 사각 영역. NULL 이면 윈도우의 전 영역이 무효화된다.
속도를 높이기 위해 최소한의 영역만 계산하여 필요한 부분만 무효화한다.
bErase 무효화하기 전 배경을 모두 지운 후(TRUE) 다시 그릴지 지우지 않고(FALSE) 그릴지 지정한다.


* WM_KEYDOWN

WM_CHAR 메시지는 문자만 입력받는 메시지이다. 문자키 이외의 키는 WM_KEYDOWN 메시지를 사용해야 한다. 단 Alt키, 윈도우 키, 한영 전환키 등 몇 가지는 제외된다. 이 때 wParam으로 문자 코드가 아닌 가상 키코드(Virtual Key Code at MSDN)가 전달된다. 이는 시스템에 장착된 키보드의 종류에 상관없이 키를 입력받기 위해 만들어진 범용적인 코드값이다.

숫자 및 영문자의 가상 키코드는 아스키 코드와 같으며 매크로 상수는 정의되어 있지 않으므로 문자 상수와 wParam을 바로 비교하면 안된다. 영문자의 경우 대문자 코드와 일치되어 있으므로 반드시 대문자와 비교해야 한다.
if (wParam == 'Z')
가상 키코드는 앞으로 만들어질 키보드까지 고려하여 만든 범용적인 코드이다. 윈도우즈를 키보드 구성이 완전히 다른 시스템으로 이식하더라도 키코드를 그대로 쓸 수 있도록 하기 위한 배려이다.

방향키로 문자를 움직인다. 키보드에 자동 반복 기능이 있으므로 계속 누르고 있으면 이 메시지가 반복적으로 발생한다. 스페이스바로 문자를 토글한다.
방향키 제어에서 x, y좌표가 음수 일 경우 윈도우 밖으로 나가버린다. 반대로 이동시 다시 돌아온다.
Question! 대각선 움직임에 대한 구현은 모르겠다. 동시에 누를 경우 대각선으로 움직이나 하나가 눌린 상태에서 다른 키의 인식은 어찌하나? OR연산자로 묶을 경우 한 경우는 인식이 되나 나머지 세경우가 같은 case라 인식되버린다.

키보드 입력시 발생 메시지는 WM_KEYDOWN, WM_CHAR, WM_KEYUP 세 가지다. 하지만 키보드로부터 전달되는 메시지는 WM_KEYDOWN, WM_KEYUP 두 가지뿐이다. WM_CHAR 메시지는 WM_KEYDOWN에 의해 추가로 발생되는 메시지이며 메시지 루프에서 인위적으로 생성된다.
while(GetMessage(&Message,0,0,0)) {
	TranslateMessage(&Message);
	DispatchMessage(&Message);
}
1. 메시지 루프에서 GetMessage는 메시지 큐에서 메시지를 꺼낸 후 이 메시지를 TranslateMessage 함수로 넘긴다.
2. TranslateMessage 함수는 전달된 메시지가 WM_KEYDOWN인지 키가 문자인지 검사한다.
3. 조건이 맞다면 WM_CHAR 메시지를 추가로 발생시킨다.(문자입력이 아닌 경우 아무 일도 하지 않는다.)
4. 이 메시지는 DispatchMessage 함수에 의해 WndProc으로 보내진다.

TranslateMessage 함수는 키보드로부터 문자키 입력 메시지인 WM_CHAR를 만들어내기 위해 존재한다. 없다면 위 프로그램에서 문자가 출력되지 않는다.(입력 인식도 안될것이다.)
TranslateMessage가 하는 일은 단순한 것 같지만 내부적으로 꽤 많은 계산을 한다. 어떤 키가 문자키인지 키보드 디바이스 드라이버에 의해 결정된다. 문자 입력후 종합 판단해 최종 입력된 문자를 선택해 WM_CHAR 메시지를 보낸다.
A키를 누름
1. 한글모드?
1.Y. Shift?
1.Y.Y 'ㄲ'
1.Y.N 'ㄱ'
1.N. CapsLock?
1.N.N. Shift?
1.N.N.N 'a'
1.N.N.Y 'A'
1.N.Y. Shift?
1.N.Y.N 'A'
1.N.Y.Y 'a'

키보드 메시지 외 WM_SYSKEYDOWN, WM_SYSKEYUP, WM_SYSCHAR 등의 시스템 키보드 메시지도 있다. SYS가 붙는 메시지는 모두 Alt키와 함께 눌려지는 키보드 메시지이며 시스템 내부적인 용도로 사용되므로 응용프로그램에서 처리하는 경우는 별로 없다. 이 메시지를 처리한 후 그냥 리턴해서는 안되며 반드시 DefWindowProc으로 보내줘야 한다. 그렇지 않으면 Alt키와 함께 동작하는 모든 키보드 조합이 먹통이 되어 버린다.


마우스 입력 (p91)

마우스 입력에 관한 메시지
버튼 누름 놓음 더블클릭
좌측 WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK
우측 WM_RBUTTONDOWN WM_RBUTTONUP WM_RBUTTONDBLCLK
중앙 WM_MBUTTONDOWN WM_MBUTTONUP WM_MBUTTONDBLCLK
MS 마우스에서 X버튼을 지원하기 위한 WM_XBUTTON 메시지가 세 개 더 있다.

마우스 메시지는 lParam의 상위 워드에 마우스가 사용된 y좌표, 하위 워드에 x좌표를 가지며 좌표값을 검출하기 위해 HIWORD, LOWORD 등의 매크로 함수를 사용한다.

듀얼 모니터의 경우 오른쪽 모니터 입장에서 좌표값이 음수가 될 수 있다. 이 경우 음수 좌표가 전달 될 수 있는데 반드시 (int)(short)형으로 다시 한 번 더 캐스팅하여 부호를 제대로 변환해야 한다.

wParam에는 마우스 버튼의 상태와 키보드 조합 키(Shift, Ctrl)의 상태가 전달된다. 조합키 상태는 비트 연산을 통해 알아내며 이 값을 참조하면 Shift 클릭, 좌우 동시 누름 등의 조건을 검출할 수 있다.
설명
MK_CONTROL Ctrl 키가 눌러져 있다.
MK_LBUTTON 마우스 왼쪽 버튼이 눌러져 있다.
MK_RBUTTON 마우스 오른쪽 버튼이 눌러져 있다.
MK_MBUTTON 마우스 중앙 버튼이 눌러져 있다.
MK_SHIFT Shift 키가 눌러져 있다.

마우스가 이동할 때마다 전달되는 WM_MOUSEMOVE 메시지도 있다. 이는 lParam에 마우스 커서의 위치가 전달되며 wParam에 조합키 상태가 전달된다.
휠 마우스에 의한 WM_MOUSEWHEEL 메시지도 있다.

메시지 추가 정보로 전달되는 wParam, lParam은 둘 다 32비트 크기를 가지므로 총 64비트의 정보를 전달할 수 있다. 32비트 인수의 상하위 워드나 바이트를 잘라 여러 인수를 합쳐 전달하며 메시지를 받는 쪽에서 다음과 정의된 매크로로 개별 정보를 분리한다.
DWORD로부터 상, 하위 워드 추출
#define LOWORD(l)	((WORD)(I))
#define HIWORD(l)	((WORD)(((DWORD)(I) >> 16 & 0xFFFF))

WORD로부터 상, 하위 바이트 추출
#define LOBYTE(w)	((BYTE)(w))
#define HIBYTE(w)	((BYTE)(((WORD)(w) >> 8) & 0xFF))

16비트값으로 32비트값을 조립
두 개의 바이트로부터 16비트값을 만든다.
#define MAKEWORD(a,b)	((WORD)(((BYTE)(a))|((WORD)((BYTE)(b)))<<8))
두 개의 16비트 값으로 32비트값을 만든다.
#define MAKELONG(a,b)	((LONG)(((WORD)(a))|((DWORD)((WORD)(b)))<<16))
MAKELONG과 똑같이 정의된 MAKEWPARAM, MAKELPARAM 매크로가 정의되어 메시지를 직접 보내고자 할 때 파라미터 조립에 사용된다. 예를 들어 (123,98)좌표를 lParam에 실어 보내려면 MAKELPARAM(123,98) 매크로를 사용한다.

책을 보지 않고도 그대로 복원할 수 있어야 한다. 헉.. ㅡ"ㅡ;;;;;


GetDC함수를 호출하여 DC핸들을 발급받아야 한다.
그림 복원을 위해 화면 전체를 비트맵으로 저장하든가 연결리스트를 사용해 마우스의 움직임을 일일이 보관해야 한다. 이전의 WM_PAINT 메시지 발생시 복원하는 방법을 사용했지만 여기서는 이전에 그려진 그림을 기억하는 방법이 있어야 하므로 현재 복원은 하지 못한다.


더블클릭으로 화면을 지우는 코드 만들기.
case WM_LBUTTONDBLCLK:
	InvalidateRect(hWnd, NULL, TRUE);
	return 0;
InvalidateRect 함수를 호출하여 작업 영역 전체를 무효화 하면 WM_PAINT 메시지를 처리하지 않으므로 무효영역이 생기면 DefWindowProc이 WM_PAINT 메시지를 처리하여 배경색으로 윈도우를지운다.

WM_LBUTTONDOWN 메시지는 WndClass.style에서의 설정이 필요하다. 그렇지 않으면 WM_LBUTTONDOWN 메시지와 WM_LBUTTONUP 메시지가 교대로 두 번 발생할 뿐이다. 이는 더블 클릭을 검출하는 데 그만큼 실행시간의 감소가 요구되며 어떤 프로그램은 더블클릭보다 WM_LBUTTONDOWN을 두 번 받길 원할 수도 있기 때문이다. 트리플 클릭의 경우도 더블클릭을 지원하게 되면 검출이 어렵다.
WndClass.style=CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

더블클릭 - 일정 시간, 일정 영역에서 연속적으로 눌려야 한다. 떨어진 장소에서 빠른 클릭은 더블클릭으로 인정되지 않는다. 더블 클릭으로 인정할 시간 간격이나 위치 등의 규칙을 자체적으로 만들고 싶다면 플래그를 주지 않고 직접 처리해야 한다.

자유 곡선 예제는 GUI 프로그램의 기본 형태와 메시지를 받아 처리하는 방법을 잘 보여주는 예제이므로 확실히 이해해야 한다

위 매크로들은 모두 작업영역 내의 마우스 메시지이다. 비작업 영역(타이틀 바, 경계선, 메뉴, 스크롤 바 등)에서 발생하는 마우스 메시지들도 있는데 이는 작업영역 메시지의 이름에 NC(Non Client)가 덧붙여진다.(예 WM_NCLBUTTONDOWN) 이 비작업영역 메시지는 시스템이 내부적인 용도로 사용한다.(화면 크기 조절 등) 표준적인 윈도우만 사용한다면 이 메시지를 사용할 일은 무척 드물다.


타이머 (p97)

WM_TIMER  한 번 지정해 놓기만 하면 일정한 시간 간격으로 연속적으로 발생한다. 주기적으로 같은 동작을 반복해야 한다거나 여러 번 나누어 해야 할 일이 있을 때 이 메시지를 이용한다.


WM_CREATE 메시지는 윈도우가 처음 생성될 때 발생하는데 프로그램 시작시 꼭 한 번 초기화해야 할 처리를 한다. 메모리를 할당한다든가 전역 변수에 초기값을 대입하는 등 초기화를 한다.

UNIT SetTimer(HWND hWnd, UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc)
SetTimer(hWnd,1,1000,NULL);
WM_CREATE에서 타이머 번호 1번으로 1초에 한번씩 타이머 메시지를 hWnd로 보내주도록 타이머를 설정되었다. 이제 1초에 한 번씩 hWnd 윈도우에 WM_TIMER 메시지가 전달된다.

WM_TIMER 메시지는 wParam으로 타이머 ID를 전달받으며 lParam으로 타이머 메시지 발생시 호출될 함수의 번지가 전달된다.
hWnd 타이머 메시지를 받을 윈도우
nIDEvent 타이머 번호 - 여러개의 타이머를 사용할 경우 nIDEvent가 겹치지 않도록 한다. 이 번호는 WM_TIMER 메시지에서 타이머를 구분하기 위한 표식으로 사용된다.
uElapese 1/1000초 단위로 타이머 주기를 설정
타이머의 최대 해상도는 95/98에서 0.055초 NT/2000에서 0.01초이다 아무리 짧게 해도 1초에 100회(98에서 18회) 이상 발생하지 않는다.
lpTimerFunc 타이머 메시지가 발생할 때마다 호출될 함수로 사용하지 않을 때는 NULL로 설정한다.
RETURN 타이머를 소유하는 윈도우 없이 타이머가 만들어 졌을 때에 한해 특별하게 사용하는 것으로 거의 사용되지 않는다.
succeeds and the hWnd parameter is NULL : an integer identifying the new timer.
succeeds and the hWnd parameter is not NULL : a nonzero integer
fails : zero

BOOL KillTimer( HWND hWnd, UINT uIDEvent )
타이머는 시스템 전역 자원이므로 더 이상 필요가 없어지면 파괴하는 것이 좋다.
hWnd 이 타이머를 소유한 윈도우 핸들
uIDEvent 파괴할 타이머 ID

처음 프로그램을 실행한 후 1초 정도 경과한 다음 시간이 보인다. 이는 WM_TIMER에서 시간값을 조사한 후 WM_PAINT에서 문자열을 출력하는데 타이머 주기를 1초로 지정했기 때문이다. 따라서 WM_TIMER 메시지를 프로그램 시작 직후에 강제로 발생시켜 시간을 먼저 조사해야 한다.

LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
메시지는 사용자의 동작이나 시스템의 상황 변화에 의해 발생하는 것이 원칙이지만 강제로 메시지가 발생한 것처럼 만들어야 할 때 사용한다. 어떤 종류의 메시지라도 누구에게나 보낼 수 있다.
hWnd 타이머를 소유한 윈도우 핸들
Msg 어떤 종류의 메시지인가
wParam 메시지 추가 정보로 메시지에 따라 의미가 달라진다.
WM_TIMER 메시지는 타이머 ID를 보내도록 되어 있다.
lParam 메시지 추가 정보로 메시지에 따라 의미가 달라진다.
RETURN The return value specifies the result of the message processing;
it depends on the message sent.

InvalidateRect(hWnd,NULL,TRUE);
hWnd 윈도우에서 화면 전체(NULL)를 무효화하여, 화면을 지운 다음(TRUE) 다시 그린다. 때문에 시간이 바뀔 때마다 화면이 깜박인다. 이럴 경우 지우기는 하되 무효영역을 최소화하여 꼭 필요한 부분만 무효화하면 깜빡임을 많이 줄일 수 있다. 이 영역 외부는 지우지도 않고 그리지도 않으며 전혀 신경쓰지 않으므로 속도가 현저히 빨라진다. 이런 처리를 얼마나 깔끔하게 하느냐에 따라 프로그램의 질이 달라진다.

3초 간격으로 3자리수 이내의 덧셈 문제를 출제. 답은 3초 후에 알려준다.


두 개의 타이머

TwoTimer : 시계와 5초 간격 beep

타이머 ID를 각각 다르게 설정한다. 여러 개의 타이머를 설치했을 경우에도 하나의 타이머를 설치했을 때처럼 전달되는 메시지는 WM_TIMER 하나 뿐이다.

Win32 환경에서 만들 수 있는 타이머의 개수에는 제한이 없으나 많이 사용하게 되면 CPU가 타이머를 관리하는데 상당히 애를 먹는다. 16비트 윈도우즈에서는 최대 16개까지 설치할 수 있다.


* 백그라운드 작업

멀티 태스킹 환경에서 프로그램의 제어권을 독점하는 일 없이 꾸준히 실행 중인 프로그램에서 다른 작업이 동시에 이루어진다. CPU를 독점하는 무한 루프를 작성해서는 안되며 반드시 메시지가 전달되었을 때에 한해 필요한 작업을 해야 한다.
초당 20번 주기, 1주기에 1000개의 점을 난수 좌표에 임의 색상으로 출력한다. 그러는 중 마우스 클릭에 의한 원을 출력한다. 만약 for(;;) 로 무한 번 점을 찍으면 WM_PAINT 메시지 처리 구간에서 무한 루프에 빠지므로 다른 메시지를 받을 수 없게된다. 따라서 지속적인 작업은 타이머를 설치한 후 타이머 메시지를 받을 때마다 나누어 해야 한다. 작업은 가급적 잘게 나누어야 반응성이 좋아진다. 더 짧은 시간에 나누어 한다.

임의 색상의 점 출력은 포토샵의 노이즈 기능과 유사한 것 같다. 프로그램을 실행하고 계속 보고 있으면 마치 모래가 점점 쌓이는 듯한 느낌을 받는다. 어떤 그림이나 글자를 출력하고 유사 색상 범위에서 이러한 점을 출력하면 모래에 묻혀 사라지는 듯한 효과를 줄 수 있을 것 같다.



콜백함수

SetTimer의 네 번째 인수에 타이머 함수가 지정되었을 경우 매 시간마다 이 함수가 대신 호출된다.
일반적으로 API함수들은 운영체제가 제공하며 프로그램에서 호출하여 운영체제의 서비스를 이용한다. 반면 콜백함수는 응용 프로그램이 제공하며 운영체제가 필요할 때 호출하는 함수이다.

콜백 함수는 조건이 될 때 시스템이 부르는 함수이므로 원형이 미리 정해져 있다. 이름은 어떻게 붙여도 상관없다.

위의 백그라운드 작업과 동일한 작동을 하지만, WM_TIME 메시지는 다른 메시지가 있을 경우 실행 순서에 밀려 늦게 호출되는 경우가 있지만 콜백함수는 정확한 시간에 호출된다. Win32환경에서 사실상 별 차이가 없다. 정확도를 요하는 작업은 타이머 메시지보다 콜백함수를 이용한다.



일회용 타이머
시간을 지연시켜주는 Sleep함수는 지정된 시간 동안 프로그램을 완전히 정지시켜 버리므로 대기중 다른 작업을 할 수 없다. 언커버되었을 때 복구도 되지 않는다.
일회용 타이머는 타이머 메시지를 처음 받았을 때 해제하는 것으로 일정 시간 후에 특정한 일을 하고 싶을 때 사용한다.


+ bartsesang + 이 글은 김상형 저 윈도우즈 API 정복 (한빛 미디어)의 내용과 예제를 개인적인 연습을 위해 작성된 글입니다. 문제가 된다면 개인적인 용도로만 사용하겠습니다. 학습을 위한 활용, 정보의 공유, 지적, 질문은 환영합니다. + http://bartsesang.tistory.com/ +
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함