자료 저장소

# PeekMessage

메시지 루프에서 제일 중요한 함수는 메시지를 가져오는 GetMessage 함수이다.
이 함수는 스레드 메시지 큐에서 메시지를 가져오는데 메시지가 없으면 새로운 메시지가 전달될 때까지 리턴하지
않는다. 즉 메시지가 들어올 때까지 무한 대기한다. 그래서 윈도우즈 프로그램은 사용자의 입력이 없을 경우
대부분 GetMessage에서 다음 메시지를 기다리고 있다. 이 노는 시간에 다른 프로세스가 CPU를 쓸 수 있도록
양보 하는데 GetMessage의 이런 특성 때문에 멀티 태스킹이 부드럽게 이루어지는 것이다.

GetMessage의 특징 ① 제거한다. ② 대기한다. ③ 양보한다.

이렇게 GetMessage에서 놀고 있는 시간을 데드 타임(dead time)이라고 하는데 사람보다 CPU가 월등히 빠르기
때문에 데드타임의 비율이 꽤 높은 편이다. 이 데드 타임을 잘 활용하면 애니메이션이나 기타 틈틈히 해야 할 일을
다른 작업 시간에 영향을 주지 않고도 할 수 있다.
하지만 문제는 GetMessage 함수가 메시지를 받기 전에는 절대로 리턴하지 않기 때문에 데드 타임을 활용할 수가
없다는 점이다. 이때 바로  PeekMessage 함수를 사용한다.

BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,
UINT wRemoveMsg);
GetMessage 함수와 원형이 거의 비슷한데 wRemoveMsg 인수가 하나 더 추가되어 있고 리턴값의 의미가 다르다.
PeekMessage는 GetMessage의 세 가지 특징과 완전히 반대되는 성질을 가지는데 메시지 큐에서 메시지를 꺼내거나
검사하되 메시지가 없더라도 즉각 리턴한다. 즉, 대기하지 않으며 따라서 양보도 할 줄 모른다. 이때 리턴값이
TRUE 이면 메시지가 있다는 뜻이며 FALSE이면 메시지가 없다는 뜻이다.

wRemoveMsg 플래그는 메시지가 있을 경우 이 메시지를 큐에서 제거할 것인가 아닌가를 지정하는데
PM_REMOVE 이면 GetMessage처럼 메시지를 큐에서 제거하지만 PM_NOREMOVE이면 제거하지 않을 수도 있다.


# 아이들 타임(Idle Time)

응용프로그램이 아무 것도 하지 않고 노는 시간을 아이들 타임 또는 데드 타임이라고 한다.
아이들 타임은 어떠한 메시지로 전달되지 않기 때문에 개발자가 직접 찾아서 활용하는 수밖에 없다.
GetMessage 함수가 메시지를 꺼내지 못하고 있는 상태, 즉 메시지 큐가 텅 비어 있는 상태가 바로 아이들 타임이다.
GetMessage 함수는 메시지가 들어올 때까지 무한 대기하는 특성때문에 이 함수로는 아이들 타임을 얻을 수 없다.
아이들 타임을 활용하려면 읽을 메시지가 없더라도 즉시 리턴하는 PeekMessage 함수를 사용해야 한다.


[PeekMessage()와 아이들 타임을 활용한 백그라운드 작업 예제]
BOOL AllowIdle=TRUE; 
for(;;)
{
if(PeekMessage(&Message,NULL,0,0,PM_REMOVE))
{
if(Message.message==WM_QUIT) // WM_QUIT 메시지 처리
break;
AllowIdle=TRUE; // 메시지가 처리될때 TRUE로 변경
TranslateMessage(&Message);
DispatchMessage(&Message);
}
else
{
if(AllowIdle) // 메시지가 처리될때만 실행
{
OnIdle(); // 백그라운드 작업
AllowIdle=FALSE; // 작업 후 상태 변경
}
WaitMessage(); // 큐에 메시지가 들어올때까지 대기하되 메시지가 없다면
// 다른 프로세스에게 실행 시간을 양보한다.

}
}


# 키 상태 조사하기

WM_KEYDOWN 메시지를 받으면 키가 입력되는 시점을 알수 있으며 이때 wParam을 읽어 어떤 키가 눌러졌는지
조사한다. 키를 계속 누르고 있을 경우 키보드의 반복 입력 기능에 의해 이 메시지가 연속적으로 전달되므로 커서
이동키로 물체를 빠르게 이동시킬 수 있다. 그러나 이 메시지는 키가 입력되었다는 사실을 알릴 뿐이지 키의 현재
상태를 조사하는 것은 아니다.

이때는 직접 키의 상태를 조사해서 적용 해야 하는데 현재 키의 상태, 즉 키가 눌러졌는지 아니면 떨어져 있는지를
조사할 때는 다음 두 함수를 사용한다.

SHORT GetKeyState(int nVirtKey); 
SHORT GetAsyncKeyState(int vKey);
두 함수 모두 조사하고 싶은 가상 키 코드를 인수로 전달받는다. 일반 키일 경우 눌러졌으면
최상위비트(MSB)가 1로 설정되고 그렇지 않으면 0으로 설정된다.
CapsLock같은 토글키는 켜져 있을 경우 최하위 비트(LSB)가 1로 설정되고 그렇지 않으면 0으로 설정된다.

어떤 키가 현재 눌러져 있는가 아닌가를 알고 싶으면 이 함수의 리턴값을 0x8000과 & 연산하여 결과가 0인가
아닌가를 살펴보면 된다. 또는 리턴값이 부호있는 값이므로 음수이면 키가 눌러진 것으로 판단해도 된다.

※ 위 두개의 함수의 가장 큰 차이점
- GetKeyState는 메시지가 발생 했을 때의 상황을 조사한다.
- GetAsyncKeyState는 이 메시지가 처리될 때의 상황을 조사한다.


BOOL GetKeyboardState(PBYTE lpKeyState);
모든 가상 키의 상태를 한꺼번에 조사하고 싶을 때 사용, 256바이트의 배열을 할당하여 넘기면
배열에 모든 가상 키의 상태를 조사하며 가상 키 코드를 첨자로 배열 요소를 점검하면 키의 상태를 알수 있다.


[물체 이동 + 가속 (주기적으로 키상태를 조사하는 방식 = 폴링) ]
#define ACC 5
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
staticint x=100,y=100;
staticint delta=1;
BOOL bPress;
RECT rt;
switch (iMessage) {
case WM_ACTIVATEAPP:
if (wParam) {
SetTimer(hWnd,1,50,NULL);
} else {
KillTimer(hWnd,1); // 자신이 활성상태가 아닐 경우 타이머 해제
}
return0;
case WM_TIMER: // 키 상태를 주기적으로 조사하여 이동방향을 결정한다.
bPress=FALSE;
if (GetKeyState(VK_LEFT) & 0x8000) {
x-=(delta/ACC);
bPress=TRUE;
}
if (GetKeyState(VK_RIGHT) & 0x8000) {
x+=(delta/ACC);
bPress=TRUE;
}
if (GetKeyState(VK_UP) & 0x8000) {
y-=(delta/ACC);
bPress=TRUE;
}
if (GetKeyState(VK_DOWN) & 0x8000) {
y+=(delta/ACC);
bPress=TRUE;
}
if (bPress) {
delta=min(delta+1,ACC*10); // 키가 계속 눌릴 경우 delta값을 증가시켜 가속
SetRect(&rt,x-20,y-40,x+20,y+40);
InvalidateRect(hWnd,&rt,TRUE);
} else {
delta=1; // 키가 눌린게 없다면 delta값을 1로 리셋
}
return0;
case WM_PAINT:
hdc=BeginPaint(hWnd,&ps);
TextOut(hdc,x,y,TEXT("#"),1);
EndPaint(hWnd,&ps);
return0;
case WM_DESTROY:
KillTimer(hWnd,1);
PostQuitMessage(0);
return0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

입력 메시지를 받지 않고 자신이 원할 때 정보를 조사하는 방식을 폴링(Polling)이라고 한다.
신호를 기다리는 것이 아니라 주기적으로 상태를 직접 조사하여 사용하는 방식인데 반복적으로 조사해야 하므로
시간을 소모하지만 필요할 때 즉시 조사하므로 결과를 신뢰할 수 있다.


# 트리플 클릭

트리플 클릭을 검출하려면 윈도우 클래스에 CS_DBLCLKS 스타일을 지정하지 말아야 한다.
화면 좌표 범위나 시간은 시스템 설정에 따라 다르지만 보통 4픽셀 내외의 위치를 0.5초보다 짧은 간격으로 눌러야
연속 클릭으로 인정되며 메시지에 기록된 발생 시간과 마우스 커서 위치 정보를 통해 조건 검사를 하면된다.


[트리플 클릭 검출]

DWORD ct[5]; // 왼쪽버튼을 누른 시간 저장할 배열
DWORD cp[5]; // 누른 위치를 저장할 배열
BOOL TestClicks(int n)
{
static DWORD dt=GetDoubleClickTime(); // 더블클릭으로 인정되는 시간을 저장
staticint dx=GetSystemMetrics(SM_CXDOUBLECLK); // 더블클릭으로 인정되는 X축 범위
staticint dy=GetSystemMetrics(SM_CYDOUBLECLK); // 더블클릭으로 인정되는 Y축 범위
int i;

if (n < 2 || n > 5) returnFALSE;
for (i=0;i<n-1;i++) {
if (ct[4-i]-ct[4-i-1] > dt) returnFALSE; // 가장 최근 저장된 배열부터 시간 조건 검사
if (abs(LOWORD(cp[4-i])-LOWORD(cp[4-i-1])) > dx) returnFALSE; // 범위 검사
if (abs(HIWORD(cp[4-i])-HIWORD(cp[4-i-1])) > dy) returnFALSE; // 범위 검사
}
returnTRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
...
switch (iMessage) {
case WM_LBUTTONDOWN:
for (i=0;i<4;i++) {
ct[i]=ct[i+1]; // 메시지가 발생 할때마다 낮은 첨자로 이동
cp[i]=cp[i+1];
}
ct[4]=GetMessageTime(); // 가장 최근 메시지 발생 시간을 저장
cp[4]=GetMessagePos(); // 가장 최근 메시지 발생 좌표를 저장
if (TestClicks(3)) {
MessageBox(hWnd,TEXT("트리플 클릭이 검출되었습니다."),TEXT("알림"),MB_OK);
}
return0;
...
}
방금 발생한 메시지와 이전에 발생한 메시지의 간격이 0.5초 이내여야 하며 이젠 메시지와 그 이전 메시지의
간격도 0.5초 이하 여야 한다. x,y 좌표도 어느쪽이라도 4픽셀 범위를 넘어서는 인정이 안된다.



댓글 로드 중…

최근에 게시된 글