MZC4で無料Windowsアプリ開発

時計を作る2

戻る | 前へ | 次へ


今回は、タイマーとデバイスコンテキストを使って別の時計を作ろうと思います。

MyProject_res.rcをRisohEditorで開いて下さい。 これからコマンドIDの「ID_EXIT」と「ID_TEST」を追加します。

「リソースIDの一覧」で「追加...」を選んで下さい。 「リソースIDの追加」ダイアログが表示されますね。 「IDの名前」に「ID_EXIT」と入力して、「自動」ボタンを押して下さい。 次のようになりますね。

[ID_EXITを追加]

OKボタンをクリックします。同様にして「ID_TEST」を追加して下さい。 「リソースIDの一覧」が次のようになりますね。

[リソースIDの一覧]

次は、メニューを編集します。RisohEditorの左側のツリービューで 「RT_MENU」→「1」→「日本語 (日本) (1041)」 を順番に開きます。 「日本語 (日本) (1041)」をクリックして、リソーステキストを次のようにします。

LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT

1 MENU
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "テスト(&T)", ID_TEST
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", ID_EXIT
    }
}

編集が終わったら「コンパイル」して下さい。

さらに、アクセスキーを追加します。アクセスキー (Accelerators)というのは、 特定のキーの組み合わせを入力すると、コマンドが発生する仕組みです。

RisohEditorの「編集」メニューから「追加」→「アクセスキーを追加」を選んで下さい。 「リソースの追加」ダイアログが表示されますので、「リソースの名前」に「1」(いち)を入力して、 OKボタンをクリックして下さい。

[リソースの追加]

名前が1のRT_ACCELERATORが追加されます。

[RT_ACCELERATOR追加]

RT_ACCELERATORの「日本語 (日本) (1041)」をダブルクリックして下さい。 次のような「アクセスキーの編集」ダイアログが表示されます。

[RT_ACCELERATOR編集]

左下の「すべて削除」ボタンをクリックして下さい。中身が消えましたね。 その後、右上の「追加」ボタンをクリックして下さい。「キーの追加」ダイアログが表示されますので、 「キー:」に「"T"」(二重引用符、ティー、二重引用符)と入力して、 「コマンドID:」に「ID_TEST」と入力して下さい。 チェックボックス「CONTROL」にチェックを入れます。

[RT_ACCELERATOR編集2]

OKボタンをクリックします。次のようになりますね。

[RT_ACCELERATOR編集3]

OKボタンをクリックします。 MyProject_res.rcにエクスポートして下さい。これでリソースの編集は終わりです。

MyProject.cppを開いて下さい。 "resource.h"と<cmath>のインクルードを追加して下さい。

#include "targetver.h"
#include "MWindowBase.hpp"
#include "resource.h"
#include <cmath>

<cmath>は、C言語の数学関数を使うためにインクルードします。 アナログ時計を描くには、三角関数が必要だからです。

OnCommandメソッドを次のようにします。

    void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
    {
        switch (id)
        {
        case ID_EXIT:
            DestroyWindow(hwnd);
            break;
        case ID_TEST:
            MsgBoxDx(TEXT("TEST"), MB_ICONINFORMATION);
            break;
        }
    }

MsgCrackでWM_TIMERのテキストをコピーしてMMainWnd内部に貼り付けます。 貼り付けたOnTimerを次のようにします。

    void OnTimer(HWND hwnd, UINT id)
    {
        InvalidateRect(hwnd, NULL, TRUE);
    }

InvalidateRect APIは、再描画を発生させる関数です。 OnCreateメソッドを次のようにします。

    BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
    {
        SetTimer(hwnd, 999, 500, NULL);
        return TRUE;
    }

WindowProcDxにWM_TIMER/OnTimerを登録して下さい。

    virtual LRESULT CALLBACK
    WindowProcDx(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        HANDLE_MSG(hwnd, WM_CREATE, OnCreate);
        HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hwnd, WM_DESTROY, OnDestroy);
        HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
        HANDLE_MSG(hwnd, WM_TIMER, OnTimer);
        default:
            return DefaultProcDx();
        }
    }

これで、0.5秒ごとに再描画されます。 次は、OnPaintメソッドに時計を描画する処理を書きます。

    void DrawClockHand(HDC hDC, INT cx, INT cy, LONG nRadius, double e60thValue)
    {
        const double PI = 3.14159;
        INT x = cx + nRadius * std::cos((PI / 2) - e60thValue * (2 * PI) / 60);
        INT y = cy - nRadius * std::sin((PI / 2) - e60thValue * (2 * PI) / 60);
        MoveToEx(hDC, cx, cy, NULL);
        LineTo(hDC, x, y);
    }

    void OnPaint(HWND hwnd)
    {
        RECT rc;
        GetClientRect(hwnd, &rc);

        PAINTSTRUCT ps;
        if (HDC hDC = BeginPaint(hwnd, &ps))
        {
            FillRect(hDC, &rc, GetStockBrush(GRAY_BRUSH));

            INT cx = (rc.left + rc.right) / 2;
            INT cy = (rc.top + rc.bottom) / 2;

            SIZE siz = SizeFromRectDx(&rc);

            LONG nDiameter;
            if (siz.cx < siz.cy)
                nDiameter = siz.cx;
            else
                nDiameter = siz.cy;

            LONG nRadius = nDiameter / 2;

            SYSTEMTIME st;
            GetLocalTime(&st);

            Ellipse(hDC, cx - nRadius, cy - nRadius, cx + nRadius, cy + nRadius);

            {
                TCHAR sz[64];
                StringCchPrintf(sz, _countof(sz), TEXT("%02u:%02u:%02u"), st.wHour, st.wMinute, st.wSecond);

                SIZE siz;
                GetTextExtentPoint32(hDC, sz, lstrlen(sz), &siz);
                TextOut(hDC, cx - siz.cx / 2, cy + nRadius / 2, sz, lstrlen(sz));
            }

            if (HPEN hPen = CreatePen(PS_SOLID, 8, RGB(255, 0, 0)))
            {
                HPEN hPenOld = SelectPen(hDC, hPen);
                DrawClockHand(hDC, cx, cy, nRadius * 2 / 3, st.wHour * (60 / 12) + st.wMinute / 60.0);
                SelectPen(hDC, hPenOld);
                DeleteObject(hPen);
            }

            if (HPEN hPen = CreatePen(PS_SOLID, 4, RGB(0, 255, 0)))
            {
                HPEN hPenOld = SelectPen(hDC, hPen);
                DrawClockHand(hDC, cx, cy, nRadius * 8 / 9, st.wMinute + st.wSecond / 60.0);
                SelectPen(hDC, hPenOld);
                DeleteObject(hPen);
            }

            SelectPen(hDC, GetStockPen(BLACK_PEN));
            DrawClockHand(hDC, cx, cy, nRadius, st.wSecond);

            EndPaint(hwnd, &ps);
        }
    }

ややこしいですが、少しずつかみ砕いていきましょう。

DrawClockHandメソッドは、時計の針を書くメソッドです。 三角関数、std::cosとstd::sinを使って針の位置を計算しています。 MoveToExとLineToで実際に針を描いています。

    void DrawClockHand(HDC hDC, INT cx, INT cy, LONG nRadius, double e60thValue)
    {
        const double PI = 3.14159;
        INT x = cx + nRadius * std::cos((PI / 2) - e60thValue * (2 * PI) / 60);
        INT y = cy - nRadius * std::sin((PI / 2) - e60thValue * (2 * PI) / 60);
        MoveToEx(hDC, cx, cy, NULL);
        LineTo(hDC, x, y);
    }

OnPaintメソッドを見てみましょう。

            FillRect(hDC, &rc, GetStockBrush(GRAY_BRUSH));

FillRect APIは、指定された領域を塗りつぶす関数です。 ブラシというものでクライアント領域全体を塗りつぶしています。 GetStockBrush(GRAY_BRUSH)は、グレー(灰色)のストックブラシです。 ストックブラシはDeleteObjectで解放する必要はありません。

            INT cx = (rc.left + rc.right) / 2;
            INT cy = (rc.top + rc.bottom) / 2;

(cx, cy)は、クライアント領域の中心点です。

           SIZE siz = SizeFromRectDx(&rc);

SIZEはサイズを表す構造体です。SizeFromRectDxは、RECTからSIZEを求めるMZC4の関数です。

            LONG nDiameter;
            if (siz.cx < siz.cy)
                nDiameter = siz.cx;
            else
                nDiameter = siz.cy;

            LONG nRadius = nDiameter / 2;

縦と横のうち、短い方を直径nDiameterに設定しています。半径nRadiusは、直径nDiameterの半分です。

            SYSTEMTIME st;
            GetLocalTime(&st);

GetLocalTime APIは、ローカルの日時を取得する関数です。

            Ellipse(hDC, cx - nRadius, cy - nRadius, cx + nRadius, cy + nRadius);

Ellipse APIで円を描いています。

            {
                TCHAR sz[64];
                StringCchPrintf(sz, _countof(sz), TEXT("%02u:%02u:%02u"), st.wHour, st.wMinute, st.wSecond);

                SIZE siz;
                GetTextExtentPoint32(hDC, sz, lstrlen(sz), &siz);
                TextOut(hDC, cx - siz.cx / 2, cy + nRadius / 2, sz, lstrlen(sz));
            }

ここは、デジタル時計を描いています。 GetTextExtentPoint32 APIは、テキストのサイズを取得する関数です。 lstrlen APIは、テキストの長さを取得する関数です。 TextOut APIは、テキストを描画する関数です。

            if (HPEN hPen = CreatePen(PS_SOLID, 8, RGB(255, 0, 0)))
            {
                HPEN hPenOld = SelectPen(hDC, hPen);
                DrawClockHand(hDC, cx, cy, nRadius * 2 / 3, st.wHour * (60 / 12) + st.wMinute / 60.0);
                SelectPen(hDC, hPenOld);
                DeleteObject(hPen);
            }

ここでは、時計の時針を描いています。

            if (HPEN hPen = CreatePen(PS_SOLID, 4, RGB(0, 255, 0)))
            {
                HPEN hPenOld = SelectPen(hDC, hPen);
                DrawClockHand(hDC, cx, cy, nRadius * 8 / 9, st.wMinute + st.wSecond / 60.0);
                SelectPen(hDC, hPenOld);
                DeleteObject(hPen);
            }

ここでは、時計の分針を描いています。

            SelectPen(hDC, GetStockPen(BLACK_PEN));
            DrawClockHand(hDC, cx, cy, nRadius, st.wSecond);

ここでは、時計の秒針を描いています。GetStockPen(BLACK_PEN)は、太さ1の黒いペンです。 ストックペンはDeleteObjectで解放する必要はありません。

ビルドして動作を確認しましょう。

[動作確認]

メニューが日本語になりましたね。Ctrl+Tを押したり、「ファイル」→「テスト」を選ぶと、ID_TESTコマンドが実行されます。

時間に余裕があれば、自由に改造してみて下さい。


© 2018 片山博文MZ. All Rights Reserved.

戻る | 前へ | 次へ

inserted by FC2 system