안녕하세요. 언제나휴일입니다.
Windows API에서 주기적인 작업을 할 수 있게 타이머를 제공하고 있어요.
타이머를 생성할 때는 SetTimer 함수를 이용하고 해제할 때는 KillTimer를 사용합니다.
UINT_PTR WINAPI SetTimer(HWND hWnd,UINT_PTR nIDEvent,UINT uElapse, TIMERPROC lpTimerFunc);
hWnd: 타이머 메시지를 처리할 윈도우
nIDEvent: 타이머 일련 번호(개발자가 정의)
uElapse: 타이머 메시지를 발생할 주기(1/1000초 단위)
lpTimerFunc: 타이머 메시지를 수행할 함수(NULL 전달하면 hWnd의 콜백 프로시저에 WM_TIMER 메시지 발생
typedef VOID (CALLBACK* TIMERPROC)(HWND, UINT, UINT_PTR, DWORD);
타이머 메시지는 SetTimer를 호출하였을 때 바로 발생하지 않습니다. 세 번째 인자로 전달한 시간이 흘렀을 때 처음으로 타이머 메시지가 발생합니다.
SetTimer 함수를 호출할 때 4번째 인자인 타이머 콜백 프로시저를 전달하지 않고 NULL을 전달할 수도 있습니다.
타이머 콜백 프로시저를 전달하면 주기적으로 타이머 콜백 프로시저가 동작합니다.
NULL을 전달하면 hWnd의 윈도우 콜백 프로시저에 WM_TIMER 메시지가 전달됩니다.
BOOL WINAPI KillTimer(HWND hWnd,UINT_PTR uIDEvent);
hWnd: SetTimer에서 설정한 hWnd
nIDEvent: 타이머 일련 번호, SetTimer에서 전달한 값
두 개의 타이머를 이용하는 간단한 코드를 작성해 봅시다.
첫 번째 타이머는 1초 주기로 발생하고 타이머 콜백 프로시저에서는 현재 시각을 출력하게 합시다.
두 번째 타이머는 1/100초 주기로 발생하고 프로그램 시작한 후 얼마나 흘렀는지 틱(특정 목적으로 계측하기 위한 단위로 여기에서는 1/100초를 1틱) 수를 출력하게 합시다.
먼저 윈도우 생성 메시지 처리에서 두 개의 타이머를 생성합니다. 첫 번째 타이머는 타이머 콜백 프로시저로 전달하고 두 번째 타이머는 NULL을 전달할게요. (둘 다 타이머 콜백 프로시저를 전달하거나 NULL을 전달해도 문제 없어요. 여기에서는 학습 목적으로 두 가지 방법을 모두 사용하는 것입니다.)
#define IDT_CLOCK 1 #define IDT_TICK 2 VOID CALLBACK OnClock(HWND, UINT, UINT_PTR, DWORD); void OnTick(HWND hWnd); void OnCreate(HWND hWnd) { SetTimer(hWnd, IDT_CLOCK, 1000, OnClock); SetTimer(hWnd, IDT_TICK, 1 / 10, NULL); } void OnTimer(HWND hWnd, DWORD tid) { switch (tid) { case IDT_TICK: OnTick(hWnd); return; } } void OnDestroy(HWND hWnd) { KillTimer(hWnd, IDT_CLOCK); KillTimer(hWnd, IDT_TICK); PostQuitMessage(0);//WM_QUIT 메시지를 발급, 메시지 루프 종료 위함 } LRESULT CALLBACK MyWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { switch (iMessage) { case WM_CREATE: OnCreate(hWnd); return 0; case WM_PAINT: OnPaint(hWnd); return 0; case WM_TIMER: OnTimer(hWnd, wParam); return 0; case WM_DESTROY: OnDestroy(hWnd); return 0; } return DefWindowProc(hWnd, iMessage, wParam, lParam); }
1초 주기의 타이머 콜백 프로시저에서는 현재 시각을 얻어옵니다. 출력 내용을 바꾸기 위해 무효화 영역을 발생시키세요.
OnTick 프로시저에서는 ticks를 1 증가 시킵니다. 출력 내용을 바꾸기 위해 무효화 영역을 발생시키세요.
SYSTEMTIME st; int ticks; VOID CALLBACK OnClock(HWND hWnd, UINT iMessage, UINT_PTR tid, DWORD cnt) { GetLocalTime(&st); InvalidateRect(hWnd, 0, 0); } void OnTick(HWND hWnd) { ticks++; InvalidateRect(hWnd, 0, 0); }
다음은 전체 소스 코드입니다.
//Windows API //두 개의 타이머를 사용하는 실습 //첫 번째 타이머: 1초 주기, 시각 출력 //두 번째 타이머: 1/100초 주기, 수행한 틱 수 출력 #include <Windows.h> #define MY_DEF_STYLE CS_HREDRAW | CS_VREDRAW|CS_DBLCLKS LRESULT CALLBACK MyWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); void RegMyWndClass(LPCWSTR cname); void MakeWindow(LPCWSTR cname); void MsgLoop(); INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow) { RegMyWndClass(TEXT("MyWindow")); MakeWindow(TEXT("MyWindow")); MsgLoop(); } void RegMyWndClass(LPCWSTR cname) { //윈도우 클래스 속성 설정 WNDCLASS wndclass = { 0 }; wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.hInstance = GetModuleHandle(0); wndclass.hIcon = LoadIcon(0, IDI_APPLICATION); wndclass.hCursor = LoadCursor(0, IDC_ARROW); wndclass.lpfnWndProc = MyWndProc; wndclass.lpszMenuName = 0; wndclass.lpszClassName = cname; wndclass.style = MY_DEF_STYLE; //윈도우 클래스 등록 RegisterClass(&wndclass); } void MakeWindow(LPCWSTR cname) { //윈도우 개체 생성 HWND hWnd = CreateWindow(cname,//클래스 이름 TEXT("두 개의 타이머 "),//타이틀 명 WS_OVERLAPPEDWINDOW,//윈도우 스타일 100, 30, 700, 600,//Left, Top, Width, Height 0, //부모 윈도우 핸들 0,//메뉴 GetModuleHandle(0),//모듈 핸들 0 //WM_CREATE에 전달할 인자 ); //윈도우 개체 시각화 ShowWindow(hWnd, SW_SHOW); } void MsgLoop() { MSG Message; while (GetMessage(&Message, 0, 0, 0))//응용 큐에서 메시지를 꺼내오기 { DispatchMessage(&Message);//메시지 수행(콜백 가동) } } SYSTEMTIME st; int ticks; VOID CALLBACK OnClock(HWND hWnd, UINT iMessage, UINT_PTR tid, DWORD cnt) { GetLocalTime(&st); InvalidateRect(hWnd, 0, 0); } void OnTick(HWND hWnd) { ticks++; InvalidateRect(hWnd, 0, 0); } void OnDraw(HDC hdc) { WCHAR buf[256]; wsprintf(buf, TEXT("현재 시간 : %d:%d:%d"), st.wHour, st.wMinute, st.wSecond); TextOut(hdc, 10, 10, buf, lstrlen(buf)); wsprintf(buf, TEXT("%8d"), ticks); TextOut(hdc, 10, 40, buf, lstrlen(buf)); } void OnPaint(HWND hWnd) { PAINTSTRUCT ps; BeginPaint(hWnd, &ps); OnDraw(ps.hdc); EndPaint(hWnd, &ps); } #define IDT_CLOCK 1 #define IDT_TICK 2 void OnCreate(HWND hWnd) { SetTimer(hWnd, IDT_CLOCK, 1000, OnClock); SetTimer(hWnd, IDT_TICK, 1 / 10, NULL); } void OnTimer(HWND hWnd, DWORD tid) { switch (tid) { case IDT_TICK: OnTick(hWnd); return; } } void OnDestroy(HWND hWnd) { KillTimer(hWnd, IDT_CLOCK); KillTimer(hWnd, IDT_TICK); PostQuitMessage(0);//WM_QUIT 메시지를 발급, 메시지 루프 종료 위함 } LRESULT CALLBACK MyWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { switch (iMessage) { case WM_CREATE: OnCreate(hWnd); return 0; case WM_PAINT: OnPaint(hWnd); return 0; case WM_TIMER: OnTimer(hWnd, wParam); return 0; case WM_DESTROY: OnDestroy(hWnd); return 0; } return DefWindowProc(hWnd, iMessage, wParam, lParam); }
이 프로그램을 실행시키면 시작 시점에는 현재 시각을 0:0:0으로 출력합니다.
시작하자 마자 현재 시각을 정상적으로 출력하기 위해서는 OnCreate에서 현재 시각을 얻어오는 작업을 수행하여야 합니다.
PostMessage 혹은 SendMessage로 WM_TIMER 메시지를 보내면 윈도우 콜백 프로시저에서 WM_TIMER를 처리합니다.
현재 시각을 출력하기 위한 목적의 타이머는 타이머 콜백 프로시저를 직접 등록하였기 때문에 PostMessage나 SendMessage로 WM_TIMER 메시지를 보내는 것은 현명한 방법이 아닙니다.
오히려 여기에서는 OnCreate 함수에서 GetLocalTime을 호출하는 것이 나은 방법일 수 있어요.
//Windows API //두 개의 타이머를 사용하는 실습 //첫 번째 타이머: 1초 주기, 시각 출력 //두 번째 타이머: 1/100초 주기, 수행한 틱 수 출력 #include <Windows.h> #define MY_DEF_STYLE CS_HREDRAW | CS_VREDRAW|CS_DBLCLKS LRESULT CALLBACK MyWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); void RegMyWndClass(LPCWSTR cname); void MakeWindow(LPCWSTR cname); void MsgLoop(); INT APIENTRY WinMain(HINSTANCE hIns, HINSTANCE hPrev, LPSTR cmd, INT nShow) { RegMyWndClass(TEXT("MyWindow")); MakeWindow(TEXT("MyWindow")); MsgLoop(); } void RegMyWndClass(LPCWSTR cname) { //윈도우 클래스 속성 설정 WNDCLASS wndclass = { 0 }; wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.hInstance = GetModuleHandle(0); wndclass.hIcon = LoadIcon(0, IDI_APPLICATION); wndclass.hCursor = LoadCursor(0, IDC_ARROW); wndclass.lpfnWndProc = MyWndProc; wndclass.lpszMenuName = 0; wndclass.lpszClassName = cname; wndclass.style = MY_DEF_STYLE; //윈도우 클래스 등록 RegisterClass(&wndclass); } void MakeWindow(LPCWSTR cname) { //윈도우 개체 생성 HWND hWnd = CreateWindow(cname,//클래스 이름 TEXT("두 개의 타이머 "),//타이틀 명 WS_OVERLAPPEDWINDOW,//윈도우 스타일 100, 30, 700, 600,//Left, Top, Width, Height 0, //부모 윈도우 핸들 0,//메뉴 GetModuleHandle(0),//모듈 핸들 0 //WM_CREATE에 전달할 인자 ); //윈도우 개체 시각화 ShowWindow(hWnd, SW_SHOW); } void MsgLoop() { MSG Message; while (GetMessage(&Message, 0, 0, 0))//응용 큐에서 메시지를 꺼내오기 { DispatchMessage(&Message);//메시지 수행(콜백 가동) } } SYSTEMTIME st; int ticks; VOID CALLBACK OnClock(HWND hWnd, UINT iMessage, UINT_PTR tid, DWORD cnt) { GetLocalTime(&st); InvalidateRect(hWnd, 0, 0); } void OnTick(HWND hWnd) { ticks++; InvalidateRect(hWnd, 0, 0); } void OnDraw(HDC hdc) { WCHAR buf[256]; wsprintf(buf, TEXT("현재 시간 : %d:%d:%d"), st.wHour, st.wMinute, st.wSecond); TextOut(hdc, 10, 10, buf, lstrlen(buf)); wsprintf(buf, TEXT("%8d"), ticks); TextOut(hdc, 10, 40, buf, lstrlen(buf)); } void OnPaint(HWND hWnd) { PAINTSTRUCT ps; BeginPaint(hWnd, &ps); OnDraw(ps.hdc); EndPaint(hWnd, &ps); } #define IDT_CLOCK 1 #define IDT_TICK 2 void OnCreate(HWND hWnd) { SetTimer(hWnd, IDT_CLOCK, 1000, OnClock); SetTimer(hWnd, IDT_TICK, 1 / 10, NULL); GetLocalTime(&st); } void OnTimer(HWND hWnd, DWORD tid) { switch (tid) { case IDT_TICK: OnTick(hWnd); return; } } void OnDestroy(HWND hWnd) { KillTimer(hWnd, IDT_CLOCK); KillTimer(hWnd, IDT_TICK); PostQuitMessage(0);//WM_QUIT 메시지를 발급, 메시지 루프 종료 위함 } LRESULT CALLBACK MyWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { switch (iMessage) { case WM_CREATE: OnCreate(hWnd); return 0; case WM_PAINT: OnPaint(hWnd); return 0; case WM_TIMER: OnTimer(hWnd, wParam); return 0; case WM_DESTROY: OnDestroy(hWnd); return 0; } return DefWindowProc(hWnd, iMessage, wParam, lParam); }