6. 타이머 입력

안녕하세요. 언제나휴일입니다.

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);
}