(←クリックするとアマゾンに行きます)

第3日では、タイマーの使い方を理解します。

タイマーを作動させるには、SetTimer関数を使います。

不要になったらKillTimer関数で破棄します。

指定した間隔ごとに、WM_TIMERメッセージが来ます。(p103-)


さて、もう少し詳しくみてみると、

UINT_PTR SetTimer(
  HWND hWnd,              // ウィンドウのハンドル
  UINT_PTR nIDEvent,      // タイマの識別子
  UINT uElapse,           // タイムアウト値
  TIMERPROC lpTimerFunc   // タイマのプロシージャ
);
lpTimerFuncをNULLに指定した場合、WM_TIMERメッセージはhWndで指定した ウィンドウのプロシージャにやって来ます。

nIDEventには、タイマーを識別する番号を割り振ります。

uElapseには、タイマーメッセージをどのくらいの間隔で発行してもらうかを ミリ秒単位で指定します。

通常は、lpTimerFuncをNULLにして使いますが、これを自分で指定するには、 lpTimerFunc関数は、次のような形をしていなくてはいけません。

VOID CALLBACK TimerProc(
  HWND hwnd,         // ウィンドウのハンドル
  UINT uMsg,         // WM_TIMER メッセージ
  UINT_PTR idEvent,  // タイマの識別子
  DWORD dwTime       // 現在のシステム時刻
);
まずは、lpTimerFuncをNULLにした例を見てみましょう。
// timera.cpp

#define ID_MYTIMER 100

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

char szClassName[] = "timera";    //ウィンドウクラス


int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    
    if (!InitApp(hCurInst))
        return FALSE;
    if (!InitInstance(hCurInst, nCmdShow)) 
        return FALSE;
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            break;
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

//ウィンドウ・クラスの登録

ATOM InitApp(HINSTANCE hInst)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;    //プロシージャ名
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;//インスタンス
    wc.hIcon = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hCursor = (HCURSOR)LoadImage(NULL,
        MAKEINTRESOURCE(IDC_ARROW),
        IMAGE_CURSOR,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;    //メニュー名
    wc.lpszClassName = (LPCSTR)szClassName;
    wc.hIconSm = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);

    return (RegisterClassEx(&wc));
}

//ウィンドウの生成

BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindow(szClassName,
            "猫でもわかるタイマ", //タイトルバーにこの名前が表示されます
            WS_OVERLAPPEDWINDOW, //ウィンドウの種類
            CW_USEDEFAULT,    //X座標
            CW_USEDEFAULT,    //Y座標
            CW_USEDEFAULT,    //幅
            CW_USEDEFAULT,    //高さ
            NULL, //親ウィンドウのハンドル、親を作るときはNULL
            NULL, //メニューハンドル、クラスメニューを使うときはNULL
            hInst, //インスタンスハンドル
            NULL);
    if (!hWnd)
        return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

//ウィンドウプロシージャ

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    int id; 
    static int x, y, r, wx, wy;
    HDC hdc;
    PAINTSTRUCT ps;
    static BOOL bDec = FALSE;

    switch (msg) {
        case WM_CREATE:
            SetTimer(hWnd, ID_MYTIMER, 100, NULL);
            break;
        case WM_TIMER:
            if (wp != ID_MYTIMER) {
                return DefWindowProc(hWnd, msg, wp, lp);
            }
            if (bDec) {
                r -= 5;
                if (r <= 0)
                    bDec = FALSE;
            } else {
                r += 5;
                if (r >= wy || r >= wx)
                    bDec = TRUE;
            }
            InvalidateRect(hWnd, NULL, TRUE);
            break;
        case WM_SIZE:
            wx = LOWORD(lp);
            wy = HIWORD(lp);
            x = wx / 2;
            y = wy / 2;
            break;
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            Ellipse(hdc, x - r / 2, y - r / 2, x + r / 2, y + r / 2);
            EndPaint(hWnd, &ps);
            break;
        case WM_CLOSE:
            id = MessageBox(hWnd, "終了してもよろしいですか",
                    "終了確認", MB_YESNO);
            if (id == IDYES) {
                KillTimer(hWnd, ID_MYTIMER);
                DestroyWindow(hWnd);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}

WM_CREATEメッセージが来たら、SetTimer関数を呼んでいます。

プログラム終了時にはKillTimer関数でタイマを破棄しています。

WM_TIMERメッセージは、このウィンドウのプロシージャに来ます。

WM_TIMERメッセージが来たら、描画する円の半径を計算しています。 そして、InvalidateRect関数を呼んでいます。こうすることにより、WM_PAINT メッセージがやって来ます。

さて、このプログラムは、おまけでWM_SIZEメッセージの処理もしています。 WM_SIZEメッセージは、ウィンドウのサイズが変更されたらやって来ます。

また、この時lParam値の下位WORDはクライアント領域の幅、上位WORDは高さ を表しています。

以上をまとめると、

WM_TIMERメッセージが来る→半径を変更する、InvalidateRect関数で無効領域を発生
WM_SIZEメッセージが来る→中心の座標の計算やり直し
WM_PAINTメッセージが来る→円の描画
ということになります。

では、同じ内容のプログラムを書換えてみましょう。

// timerb.cpp

#define ID_MYTIMER 100

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
VOID CALLBACK MyTimer(HWND, UINT, UINT_PTR, DWORD);

char szClassName[] = "timerb";    //ウィンドウクラス
int x, y, r, wx, wy;
BOOL bDec = FALSE;

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    
    if (!InitApp(hCurInst))
        return FALSE;
    if (!InitInstance(hCurInst, nCmdShow)) 
        return FALSE;
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            break;
        } else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

//ウィンドウ・クラスの登録

ATOM InitApp(HINSTANCE hInst)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;    //プロシージャ名
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;//インスタンス
    wc.hIcon = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hCursor = (HCURSOR)LoadImage(NULL,
        MAKEINTRESOURCE(IDC_ARROW),
        IMAGE_CURSOR,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;    //メニュー名
    wc.lpszClassName = (LPCSTR)szClassName;
    wc.hIconSm = (HICON)LoadImage(NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED);

    return (RegisterClassEx(&wc));
}

//ウィンドウの生成

BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;

    hWnd = CreateWindow(szClassName,
            "猫でもわかるタイマ", //タイトルバーにこの名前が表示されます
            WS_OVERLAPPEDWINDOW, //ウィンドウの種類
            CW_USEDEFAULT,    //X座標
            CW_USEDEFAULT,    //Y座標
            CW_USEDEFAULT,    //幅
            CW_USEDEFAULT,    //高さ
            NULL, //親ウィンドウのハンドル、親を作るときはNULL
            NULL, //メニューハンドル、クラスメニューを使うときはNULL
            hInst, //インスタンスハンドル
            NULL);
    if (!hWnd)
        return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

//ウィンドウプロシージャ

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    int id; 
    HDC hdc;
    PAINTSTRUCT ps;

    switch (msg) {
        case WM_CREATE:
            SetTimer(hWnd, ID_MYTIMER, 100, MyTimer);
            break;
        case WM_SIZE:
            wx = LOWORD(lp);
            wy = HIWORD(lp);
            x = wx / 2;
            y = wy / 2;
            break;
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            Ellipse(hdc, x - r / 2, y - r / 2, x + r / 2, y + r / 2);
            EndPaint(hWnd, &ps);
            break;
        case WM_CLOSE:
            id = MessageBox(hWnd, "終了してもよろしいですか",
                    "終了確認", MB_YESNO);
            if (id == IDYES) {
                KillTimer(hWnd, ID_MYTIMER);
                DestroyWindow(hWnd);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}

VOID CALLBACK MyTimer(HWND hWnd, UINT msg, UINT_PTR idEvent, DWORD dwTime)
{
    if (idEvent != ID_MYTIMER)
        return;
    if (bDec) {
        r -= 5;
        if (r <= 0)
            bDec = FALSE;
    } else {
        r += 5;
        if (r >= wy || r >= wx)
            bDec = TRUE;
    }
    InvalidateRect(hWnd, NULL, TRUE);

    return;
}
第3日ではWM_TIMERメッセージの処理の仕方がわかればOKです。
16/Aug/2005 By Y.Kumei
[総合INDEX] [目次] [第2日] [第4日]