Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

シンプルアナログ時計 Part1

スポンサードリンク



Windows APIを使ってC言語でアナログ時計を書いてみました。
もっときれいな時計を作りたい!ちゃんとオブジェクト指向したいんやーという方はこちら

Javaでのシンプル実装はこちら

実行画面


Windows APIのGDIのみを使った簡単なシンプルなアナログ時計です!
ただし、GDIだけでなんの工夫もないのでガタガタな絵面の時計になっちゃっています(笑)
ちなみにGDIにはガタガタを修正するアンチエイリアスの機能が使えないため、
滑らかにしたいと思うとDirectXを直接的か間接的に使わないとダメです。
C#やJavaを使うとその辺は気にしなくてよくなりますが、C++で書こうとすると
外部のライブラリを使わない限りこの程度の実装でも結構大変なコードになってしまいます。
あくまでこのサンプルはWindows API入門用ということで…

この記事はだいぶ前に書きましたが、2014/10/02頃のMicrosoftのWindows10の発表が出て
ひょっとしたらMFCやWindows APIを直に使うことはほとんどなくなるかもしれません。
今でもそうですが……。
ちなみに空のWindowsプロジェクトを作ってコピペすれば動きますよ!

ソースコード

#include <Windows.h>
#include <math.h>

const int ID_MYTIMER = 1000; // Timer ID
const int ClientWidth = 180;
const int ClientHeight = 180;
const int ClockSize = 180;

const double Pi = 3.141592;
const double OneDegree = Pi / 180; // 1度 = π/180 (ラジアン表現)
const int AngPerHour = 30; // 1時間あたりの角度
const int AngPerMin = 6; // 1分あたりの角度

TCHAR dbgmsg[100];

const TCHAR szClassName[] = TEXT("WinClock03");
const TCHAR WndTitle[] = TEXT("Clock");
static TCHAR szTime[64];

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
LRESULT MyTimerProc(HWND, UINT, WPARAM, LPARAM, int*, int*, int*);
void MyPaintProc(HWND, HFONT, HPEN, int*, int*, int*);
void CreateScale(HDC, POINT*, POINT*, POINT*, const int, const double);
void CreateClockHand(HDC, POINT*, const int, const double, const double);
HFONT MyCreateFont(const int, DWORD, LPCTSTR);


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;
    // --- MessageLoop
    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
    {
        if (bRet == -1)
        {
            MessageBox(NULL, TEXT("GetMessage Error!!"),
                TEXT("Erorr"), MB_OK);
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

// WindowClassの登録
ATOM InitApp(HINSTANCE hInst)
{
    WNDCLASSEX wc = { 0 };
    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 = szClassName;
    wc.hIconSm = (HICON)LoadImage(
        NULL,
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON,
        0,
        0,
        LR_DEFAULTSIZE | LR_SHARED
        );

    return RegisterClassEx(&wc);
}

// Windowの生成
BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
{
    HWND hWnd;
    DWORD dwStyle = WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX;

    // クライアントのサイズを指定サイズにする。
    RECT rc = { 0, 0, ClientWidth, ClientHeight };
    AdjustWindowRectEx(&rc, dwStyle, FALSE, 0);
    int nWidth = rc.right - rc.left;
    int nHeight = rc.bottom - rc.top;

    hWnd = CreateWindow(
        szClassName,
        WndTitle,
        dwStyle,
        CW_USEDEFAULT, // X-Axis
        CW_USEDEFAULT, // Y-Axis
        nWidth,      // Width
        nHeight,     // Hegiht
        NULL,
        NULL,
        hInst,
        NULL
        );
    if (!hWnd) return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

// WindowProcedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
    LRESULT lRet = 0;
    static int sec, min, hour;
    static HFONT hFont1;
    static HPEN hPen1;

    switch (msg)
    {
    case WM_CREATE:
        hPen1 = CreatePen(PS_SOLID, 1, RGB(1, 1, 255));
        hFont1 = MyCreateFont(12, SHIFTJIS_CHARSET, TEXT("MS ゴシック"));
        SetTimer(hWnd, ID_MYTIMER, 500, NULL);
        break;
    case WM_DESTROY:
        DeleteObject(hPen1);
        DeleteObject(hFont1);
        PostQuitMessage(0);
        break;
    case WM_TIMER:
        lRet = MyTimerProc(hWnd, msg, wp, lp, &sec, &min, &hour);
        break;
    case WM_PAINT:
        MyPaintProc(hWnd, hFont1, hPen1, &sec, &min, &hour);
    default:
        return(DefWindowProc(hWnd, msg, wp, lp));
    }
    return lRet;
}

LRESULT MyTimerProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp,
    int* sec, int* min, int* hour)
{
    SYSTEMTIME st;

    if (wp != ID_MYTIMER) return(DefWindowProc(hWnd, msg, wp, lp));
    GetLocalTime(&st);
    *sec = st.wSecond;
    *min = st.wMinute;
    *hour = st.wHour;
    InvalidateRect(hWnd, NULL, TRUE); // Update Client Area
    return 0;
}

void MyPaintProc(HWND hWnd, HFONT hFont1, HPEN hPen1, int* sec, int* min, int* hour)
{
    PAINTSTRUCT ps;
    POINT center;
    POINT indexFrom, indexTo;
    POINT idxPrecFrom, idxPrecTo;
    const int MaxAngle = 360;

    RECT rc;
    TCHAR strTime[10];

    // 時刻初期化
    SYSTEMTIME st;
    GetLocalTime(&st);
    *sec = st.wSecond;
    *min = st.wMinute;
    *hour = st.wHour;

    HDC hDC = BeginPaint(hWnd, &ps);

    SelectObject(hDC, hPen1);
    // 円弧を描く
    Ellipse(
        hDC,
        0, // Left X
        0, // Top Y
        ClockSize, // Right X
        ClockSize  // Bottom Y
        );
    center.x = ClockSize / 2;
    center.y = ClockSize / 2;
    // 時間単位の目盛り描画
    CreateScale(hDC, &center, &indexFrom, &indexTo, AngPerHour, 0.25);
    // 分単位の目盛り描画
    int angle1 = 0;
    double angle2 = 0.0;
    // --- Sec
    angle1 = 90 - *sec * 6; angle2 = Pi / 24.0;
    CreateClockHand(hDC, &center, angle1, 2.4, angle2);
    // --- Min
    angle1 = 90 - *min * 6 - *sec / 10; angle2 = Pi / 20.0;
    CreateClockHand(hDC, &center, angle1, 2.5, angle2);
    // --- Hour
    angle1 = 90 - *hour * 30 - *min / 2; angle2 = Pi / 16.0;
    CreateClockHand(hDC, &center, angle1, 3.0, angle2);

    rc.left = ClientWidth / 2 - ClockSize / 6;
    rc.top = ClientHeight - ClockSize / 2 + 20;
    rc.right = ClientWidth / 2 + ClockSize / 6;
    rc.bottom = ClientHeight - ClockSize / 2 + 40;

    SelectObject(hDC, hFont1);
    wsprintf(strTime, TEXT("%02d:%02d:%02d"), *hour, *min, *sec);

    DrawText(hDC, strTime, -1, &rc, DT_CENTER);

    EndPaint(hWnd, &ps);
    return;
}

void CreateScale(HDC hDC, POINT* pCenter, POINT *pFrom, POINT *pTo,
    const int inc, const double len)
{
    for (int iangle = 0; iangle < 360; iangle += inc)
    {
        // 目盛り(X座標)
        pFrom->x = (int)(pCenter->x + (ClockSize / 2) * cos(iangle * OneDegree));
        pTo->x = (int)(pCenter->x + (ClockSize / (2 + len)) * cos(iangle * OneDegree));
        // 目盛り(Y座標)
        pFrom->y = (int)(pCenter->y + (ClockSize / 2) * sin(iangle * OneDegree));
        pTo->y = (int)(pCenter->y + (ClockSize / (2 + len)) * sin(iangle * OneDegree));
        // 外側から内側に描画
        MoveToEx(hDC, pFrom->x, pFrom->y, NULL);
        LineTo(hDC, pTo->x, pTo->y);
    }
}

void CreateClockHand(HDC hDC, POINT* pCenter,
    const int angle1, const double len, const double angle2)
{
    const int Len1 = 20;
    POINT pt[5]; // 時・分・秒針(菱形状の4角形)を描くために
    // 時計の中心と針の先端とそれ以外の二つの頂点の座標(計4つ)
    // Index 0:中心, 1:右側, 2:先端, 3:左側, 4:中心(戻り)
    pt[0].x = pt[4].x = (int)(pCenter->x);
    pt[0].y = pt[4].y = (int)(pCenter->y);
    pt[1].x = (int)(pCenter->x + Len1 * cos(angle1 * OneDegree + angle2));
    pt[1].y = (int)(pCenter->y - Len1 * sin(angle1 * OneDegree + angle2));
    pt[2].x = (int)(pCenter->x + (ClockSize / len) * cos(angle1 * OneDegree));
    pt[2].y = (int)(pCenter->y - (ClockSize / len) * sin(angle1 * OneDegree));
    pt[3].x = (int)(pCenter->x + Len1 * cos(angle1 * OneDegree - angle2));
    pt[3].y = (int)(pCenter->y - Len1 * sin(angle1 * OneDegree - angle2));

    Polyline(hDC, pt, 5);
}

HFONT MyCreateFont(const int nHeight, DWORD dwCharSet, LPCTSTR lpName)
{
    return
        CreateFont(
        nHeight,
        0,
        0,
        0,
        FW_DONTCARE,
        FALSE,
        FALSE,
        FALSE,
        dwCharSet,
        OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        DEFAULT_PITCH | FF_DONTCARE,
        lpName
        );
}