タイマー
そして時は動き出す
前回、デッドタイムを使った処理方法を説明しました
これで、無限ループのような処理も可能になります
しかし、これだけではアニメーションなどを作成することはできません
アニメーションを実現するには、CPUの速度に依存することのないよう
ある程度の時間を待機させるような処理が必要なのです
実際に、500 Mhz 以上の速度を持つインテルのCPUならば
前回の方法で、塗りつぶされた矩形が黒から赤色にフェードするプログラムを実行すると
一瞬で作業が終わってしまい、何が起こったのかわからないでしょう
そこで、PeekMessage() とは違った方法でこれを実現させなければなりません
このような動作はタイマーによる、時間差処理が好ましいと思います
タイマーとは、ある一定の指定した時間が経過すると
メッセージキューにメッセージをポストするという便利な機能です
これらの作業は一見非同期のように思われますが、タイマは入出力装置です
つまり、処理方法はキーボードやマウスと同じ方法でできるのです
指定時間がたつとタイマーはキューにメッセージをポストしますが、それは割込みではありません
その前に他のメッセージが存在していれば、当然そのメッセージが先行して処理されます
一般に、タイマーを使うには SetTimer() ファンクションを使います
UINT SetTimer(
HWND hWnd , UINT nIDEvent ,
UINT uElapse ,
TIMERPROC lpTimerFunc
);
hWnd には関連付けるウィンドウのハンドルを指定します
この値を NULL にするとウィンドウには関連付けられないタイマーが作成されます
nIDEvent は 0 以外の値を指定し、タイマーのIDを指定します
このIDは複数のタイマーを同時に扱う時に重要になります
hWnd が NULL の場合、このパラメータは無視されます
uElapse は、時間をミリ秒単位(1/1000秒)で指定します
この時間が経過するたびに、メッセージを生成します
lpTimerFunc はコールバック関数のポインタを指定します
これは、メッセージキューにメッセージをポストするのではなく
指定時間が経過するごとに、指定関数を実行させるという処理に使います
メッセージキューにポストさせたい場合は、この値を NULL に指定します
戻り値は関数が成功すると、新しいタイマIDが返され、失敗すると 0 が返ります
タイマーは、指定した時間が経過すると WM_TIMER メッセージを発行します
lpTimerFunc が NULL の場合は、メッセージキューにポストされます
//タイマーのコールバック関数の利用法は後記
WM_TIMER は WPARAM にタイマIDを
LPARAM には、コールバック関数へのアドレスが格納されています
lpTimerFunc に NULL を指定している場合は 0 になります
このメッセージを処理した場合は 0 を返します
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
RECT rctDimension;
static BOOL blRight = TRUE;
static int x = 0;
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
SetTimer(hwnd , 1 , 1 , NULL);
return 0;
case WM_TIMER:
GetClientRect(hwnd , &rctDimension);
if (x + 100 >= rctDimension.right) blRight = FALSE;
else if (x <= 0) blRight = TRUE;
if (blRight) x++;
else x--;
InvalidateRect(hwnd , NULL , TRUE);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd , &ps);
SelectObject(hdc , GetStockObject(BLACK_BRUSH));
Ellipse(hdc , x , 50 , x + 100 , 150);
EndPaint(hwnd , &ps);
return 0;
}
return DefWindowProc(hwnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow ) {
HWND hwnd;
MSG msg;
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return -1;
hwnd = CreateWindow(
TEXT("KITTY") , TEXT("Kitty on your lap") ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hwnd == NULL) return -1;
while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
return msg.wParam;
}
このプログラムを実行すると、黒く塗りつぶされた楕円が左右に移動しつづけます
クライアントエリアの右端にたどり着くと、左に移動し
左端につくと、再び右にインクリメントしはじめ、この動作を繰り返します
重要なことがいくつかあります
まず、SetTimer() 関数で1ミリ秒単位でメッセージを発行するようにしていますが
実際に1秒間に1000回 WM_TIMER が実行されるわけではありません
タイマーには 分解能 という限界周期が定められており
この分解能以上の周期でメッセージを発行することはできないのです
この周期は 9x と NT で異なり、 9x の場合は55ミリ秒、NT は10ミリ秒です
つまり、9x は1秒間に最大で18回、 NT は100回までしか WM_TIMER を発生させられないのです
さらに、タイマーは 1000 ミリ秒に設定したからといって
必ず1秒後に WM_TIMER が実行されるとは限りません
WM_TIMER が発生した時、他のメッセージが処理中であれば
その間 WM_TIMER はキューの中で実行を待つことになります
また、コールバック関数を使って指定関数を呼び出すことも可能です
この場合は、指定時間が経過するとWindowsが関数を呼び出します
SetTimer() 関数の第四引数には TIMERPROC 型の
コールバック関数のポインタを指定することで次の型になっています
VOID CALLBACK TimerProc(
HWND hwnd , UINT uMsg ,
UINT idEvent , DWORD dwTime
);
外部から呼び出される関数のため CALLBACK を宣言します
hwnd には関連付けられているウィンドウのハンドル
uMsg にはメッセージが入りますが、この関数が処理するメッセージは
常に WM_TIMER メッセージなので、このメッセージが入っています
idEvent には、タイマーの ID が
dwTime は Windows が起動してからの経過時間がミリ秒単位で入っています
#include <windows.h>
VOID CALLBACK TimerProc(HWND hwnd , UINT uMsg ,UINT idEvent , DWORD dwTime) {
HDC hdc;
static int iCount = 0;
TCHAR strCount[64];
iCount++;
hdc = GetDC(hwnd);
wsprintf(strCount , "%d" , iCount);
TextOut(hdc , 10 , 10 , strCount , lstrlen(strCount));
ReleaseDC(hwnd , hdc);
}
LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
SetTimer(hwnd , 1 , 100 , TimerProc);
return 0;
}
return DefWindowProc(hwnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow ) {
HWND hwnd;
MSG msg;
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return -1;
hwnd = CreateWindow(
TEXT("KITTY") , TEXT("Kitty on your lap") ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hwnd == NULL) return -1;
while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
return msg.wParam;
}
タイマーがメッセージを発行するごとにカウントをインクリメントし
カウントの値を描画するプログラムです
SetTimer() 関数の第四引数に、コールバック関数のポインタを指定しています
WM_TIMER が発行されると、Windows はこのポインタを呼び出します
タイマーをKill
SetTimer() によってタイマーを発生させると
指定時間ごとに WM_TIMER が発生しファンクションを実行します
しかし、プログラムが起動してから終了するまで必要となるのは
時計などの一部のアプリケーションだけであり
通常は数サイクル実行されると、タイマーが不要になったりするでしょう
タイマーが不要になった場合は KillTimer() 関数を使います
この関数を使うことによって、タイマーは停止します
BOOL KillTimer(HWND hWnd , UINT uIDEvent);
hWnd には、タイマーに関連付けられているウィンドウのハンドルを指定します
これは SetTimer() の第一引数で設定した値と同じでなければなりません
uIDEvent には、タイマーの ID を指定します
uIDEvent は、SetTimer() の第二引数で設定した値と同じでなければいけませんが
ウィンドウのハンドルを NULL に設定していた場合はIDがありません
この時は SetTimer() の戻り値を指定 しなければいけません
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
static int iCount = 0;
static TCHAR strCount[64];
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
SetTimer(hwnd , 1 , 100 , NULL);
return 0;
case WM_TIMER:
iCount++;
if(iCount == 50) KillTimer(hwnd , 1);
InvalidateRect(hwnd , NULL , TRUE);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd , &ps);
wsprintf(strCount , "%d" , iCount);
TextOut(hdc , 10 , 10 , strCount , lstrlen(strCount));
EndPaint(hwnd , &ps);
}
return DefWindowProc(hwnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow ) {
HWND hwnd;
MSG msg;
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return -1;
hwnd = CreateWindow(
TEXT("KITTY") , TEXT("Kitty on your lap") ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hwnd == NULL) return -1;
while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
return msg.wParam;
}
このプログラムは、タイマーが WM_TIMER を発行するごとにカウントをインクリメントし
それをウィンドウに描画する動作を繰り返すプログラムですが
カウントが50に達すると KillTimer() によってタイマーを破棄します
複数のタイマー実行
これまでは、単一のタイマーを作成して実行していました
しかし、タイマーは複数作成することもできます
この場合、発行されたメッセージは WPARAM パラメータに
タイマーのIDが入力されているため、これで判断します
または、コールバック関数をそれぞれに用意して実行することもできます
複数のタイマーを使う場合、一般的には ID を定数にして
switch などを用いてメッセージと同様に処理すると見やすくなります
#include <windows.h>
#define TM_COUNT1 1
#define TM_COUNT2 2
LRESULT CALLBACK WndProc(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
HDC hdc;
PAINTSTRUCT ps;
RECT rctSize;
static int iCount1 = 0 , iCount2 = 0;
static TCHAR strCount[64];
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
SetTimer(hwnd , TM_COUNT1 , 100 , NULL);
SetTimer(hwnd , TM_COUNT2 , 500 , NULL);
return 0;
case WM_TIMER:
switch(wp) {
case TM_COUNT1:
iCount1++;
break;
case TM_COUNT2:
iCount2++;
break;
}
InvalidateRect(hwnd , NULL , TRUE);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd , &ps);
wsprintf(strCount , "TM_COUNT1 = %d\nTM_COUNT2 = %d" ,
iCount1 , iCount2
);
GetClientRect(hwnd , &rctSize);
DrawText(hdc , strCount , -1 , &rctSize , DT_LEFT);
EndPaint(hwnd , &ps);
}
return DefWindowProc(hwnd , msg , wp , lp);
}
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
PSTR lpCmdLine , int nCmdShow ) {
HWND hwnd;
MSG msg;
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL , IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL , IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = TEXT("KITTY");
if (!RegisterClass(&winc)) return -1;
hwnd = CreateWindow(
TEXT("KITTY") , TEXT("Kitty on your lap") ,
WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
CW_USEDEFAULT , CW_USEDEFAULT ,
CW_USEDEFAULT , CW_USEDEFAULT ,
NULL , NULL , hInstance , NULL
);
if (hwnd == NULL) return -1;
while(GetMessage(&msg , NULL , 0 , 0)) DispatchMessage(&msg);
return msg.wParam;
}
異なるスピードでカウントをインクリメントしていくプログラムです
TM_COUNT1 は 100 ミリ秒ごとにインクリメントが実行され
TM_COUNT2 は 500 ミリ秒ごとにインクリメントされます
SetTimer()
UINT SetTimer(
HWND hWnd , UINT nIDEvent ,
UINT uElapse ,
TIMERPROC lpTimerFunc
);
タイマーを指定したタイムアウト値で作成します
hWnd - 関連付けるウィンドウのハンドル。関連付けない場合は NULL を指定します
nIDEvent - 0 以外でタイマーの ID を指定します。hWnd が NULL ならば無視します
uElapse - タイムアウト値をミリ秒単位で指定します
lpTimerFunc - 時間が経過すると呼び出す TIMERPROC 型関数へのポインタ
戻り値 - 新しく設定した ID
KillTimer()
BOOL KillTimer(HWND hWnd , UINT uIDEvent);
指定したタイマーを破棄します
hWnd - タイマーに関連付けられているウィンドウへのハンドル
uIDEvent - タイマーのID
戻り値 - 成功すると 0 以外。失敗すると 0
TimerProc
VOID CALLBACK TimerProc(
HWND hwnd , UINT uMsg ,
UINT idEvent , DWORD dwTime
);
アプリケーション定義のコールバック関数です
WM_TIMER を処理します
hwnd - タイマーに関連付けられているウィンドウへのハンドルが入ります
uMsg - WM_TIMER が入ります
idEvent - タイマーのIDが入ります
dwTime - Windows が起動してからの経過時間がミリ秒単位で入ります