第339章 時計を作る 色をカスタマイズ可能にする


今回は、背景色と文字色をユーザーが自由に設定できるようにします。 また、終了時の位置を次回起動時に再現できるようにもします。また、日付も表示 するようにします。



背景色と文字色を変更できるように改良します。日付と曜日も表示するようにしました。



色が変ると雰囲気も変ります。次回起動時には最後に設定した色が反映されます。



右クリックするとメニューが出ます。



では、プログラムを見てみましょう。

// clock03.rcの一部

/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

MYMENU MENU 
BEGIN
    POPUP "ダミーです"
    BEGIN
        POPUP "ファイル(&F)"
        BEGIN
            MENUITEM "終了(&X)",                    IDM_END
        END
        POPUP "オプション(&O)"
        BEGIN
            MENUITEM "背景色(&B)",                  IDM_BACKGROUND
            MENUITEM "文字色(&T)",                  IDM_TEXT
        END
    END
END
リソーススクリプトの一部です。普通のメニューリソースですが、第1階層がダミーとなっています。第44章を参照してみてください。
// clock03.cpp

#define hParentKey HKEY_CURRENT_USER
#define lpszSubKey "Software\\Kumei\\Clock"

#define CLOCK_WIDTH 250        //時計全体のウィンドウ幅
#define CLOCK_HEIGHT 60        //高さ
#define MYTIMER 1            //タイマーID
#include 
#include "resource.h"

typedef struct MYDATA {
    COLORREF cr_bg;
    COLORREF cr_txt;
    int x;
    int y;
} INIDATA;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
HFONT SetMyFont(LPCTSTR, int);
BOOL GetMyColor(HWND, COLORREF *);
void GetInitialSettings(INIDATA *);
BOOL GetDataDWORD(char *, DWORD *);
BOOL SetInitialSettings(INIDATA);
BOOL SetDataDWORD(char *, DWORD);

char szClassName[] = "clock03";    //ウィンドウクラス
char szAppName[] = "猫クロック"; //アプリケーション名
char szBuf[64], szBuf2[64]; //時刻表示用
HINSTANCE hInst;
設定を記録するためのレジストリーキー等を最初にdefineしています。

また、MYDATAという構造体をtypedefしています。以後、レジストリに 格納するデータ類はこの構造体のメンバにまとめておくことにします。

レジストリに書き込んだり読み出したりする関数がいくつか追加となりました。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    
    hInst = hCurInst;

    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;
}
インスタンスハンドルをグローバル変数hInstにコピーしています。 後にメニューハンドルを取得する時に必要となるからです。
//ウィンドウ・クラスの登録

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,
            "猫でもわかるWindowsプログラミング", //タイトルバーにこの名前が表示されます
            WS_POPUP, //ウィンドウの種類
            0,    //X座標
            0,    //Y座標
            CLOCK_WIDTH,    //幅
            CLOCK_HEIGHT,    //高さ
            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, x, y;
    SYSTEMTIME st;
    PAINTSTRUCT ps;
    HDC hdc;
    HFONT hFont;
    SIZE s;
    HRGN hRgn, hRgn1, hRgn2, hRound1Rgn, hRound2Rgn, hRectRgn;
    HBRUSH hBrush;
    HMENU hMenu, hSubMenu;
    POINT pt;
    COLORREF cr;
    static INIDATA inidata;
    RECT rc;
    char szYobi[8];

    switch (msg) {
        case WM_CREATE:
            GetInitialSettings(&inidata);
            MoveWindow(hWnd,
                inidata.x, 
                inidata.y, 
                CLOCK_WIDTH, 
                CLOCK_HEIGHT, 
                TRUE);
            SetTimer(hWnd, MYTIMER, 500, NULL);

            hRgn = CreateRectRgn(0, 0, 1, 1);
            hRgn1 = CreateRectRgn(0, 0, 1, 1);
            hRgn2 = CreateRectRgn(0, 0,1, 1);

            hRound1Rgn = CreateEllipticRgn(0, 0, CLOCK_HEIGHT, CLOCK_HEIGHT);
            hRectRgn = CreateRectRgn(CLOCK_HEIGHT / 2,
                0, 
                CLOCK_WIDTH - CLOCK_HEIGHT / 2,
                CLOCK_HEIGHT);
            CombineRgn(hRgn1, hRound1Rgn, hRectRgn, RGN_OR);
            hRound2Rgn = CreateEllipticRgn(CLOCK_WIDTH - CLOCK_HEIGHT,
                0,
                CLOCK_WIDTH,
                CLOCK_HEIGHT);
            CombineRgn(hRgn2, hRound2Rgn, hRectRgn, RGN_OR);
            CombineRgn(hRgn, hRgn1, hRgn2, RGN_OR);
            SetWindowRgn(hWnd, hRgn, TRUE);

            DeleteObject(hRound1Rgn);
            DeleteObject(hRound2Rgn);
            DeleteObject(hRectRgn);
            DeleteObject(hRgn1);
            DeleteObject(hRgn2);
            
            break;
        case WM_RBUTTONDOWN:
            pt.x = LOWORD(lp);
            pt.y = HIWORD(lp);
            hMenu = LoadMenu(hInst, "MYMENU");
            hSubMenu = GetSubMenu(hMenu, 0);
            ClientToScreen(hWnd, &pt);
            TrackPopupMenu(hSubMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL);
            DestroyMenu(hMenu);
            break;
        case WM_LBUTTONDOWN:
            PostMessage(hWnd, WM_NCLBUTTONDOWN, (WPARAM)HTCAPTION, lp);
            break;
        case WM_TIMER:
            if (wp != MYTIMER)
                return DefWindowProc(hWnd, msg, wp, lp);
            GetLocalTime(&st);
            wsprintf(szBuf, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);
            switch (st.wDayOfWeek) {
                case 0:
                    strcpy(szYobi, "Sun");
                    break;
                case 1:
                    strcpy(szYobi, "Mon");
                    break;
                case 2:
                    strcpy(szYobi, "Tue");
                    break;
                case 3:
                    strcpy(szYobi, "Wed");
                    break;
                case 4:
                    strcpy(szYobi, "Thu");
                    break;
                case 5:
                    strcpy(szYobi, "Fri");
                    break;
                case 6:
                    strcpy(szYobi, "Sat");
                    break;
            }

            wsprintf(szBuf2, "%d/%02d/%02d(%s)", st.wYear, st.wMonth, st.wDay, szYobi);
            InvalidateRect(hWnd, NULL, TRUE);
            break;
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            hBrush = CreateSolidBrush(inidata.cr_bg);
            SelectObject(hdc, hBrush);
            PatBlt(hdc, 0, 0, CLOCK_WIDTH, CLOCK_HEIGHT, PATCOPY);

            hFont = SetMyFont("MS ゴシック", 32);
            SelectObject(hdc, hFont);
            GetTextExtentPoint32(hdc, szBuf, (int)strlen(szBuf), &s);
            
            x = (CLOCK_WIDTH - s.cx) / 2;
            y = (CLOCK_HEIGHT - s.cy) / 2 + 10;
            
            SetBkMode(hdc, TRANSPARENT);
            SetTextColor(hdc, inidata.cr_txt);
            TextOut(hdc, x, y, szBuf, (int)strlen(szBuf));
            DeleteObject(hFont);

            hFont = SetMyFont("MS ゴシック", 18);
            SelectObject(hdc, hFont);
            GetTextExtentPoint32(hdc, szBuf2, (int)strlen(szBuf2), &s);
            x = (CLOCK_WIDTH - s.cx) / 2;
            TextOut(hdc, x, 4, szBuf2, (int)strlen(szBuf2));
            DeleteObject(hFont);
            DeleteObject(hBrush);
            EndPaint(hWnd, &ps);
            break;
        case WM_COMMAND:
            switch (LOWORD(wp)) {
                case IDM_END:
                    SendMessage(hWnd, WM_CLOSE, 0, 0);
                    break;
                case IDM_BACKGROUND:
                    if (GetMyColor(hWnd, &cr)) {
                        inidata.cr_bg = cr;
                    }
                    break;
                case IDM_TEXT:
                    if (GetMyColor(hWnd, &cr)) {
                        inidata.cr_txt = cr;
                    }
                    break;
            }
            break;
        case WM_CLOSE:
            id = MessageBox(hWnd,
                "終了してもよろしいですか",
                szAppName,
                MB_YESNO | MB_ICONQUESTION);
            if (id == IDYES) {
                GetWindowRect(hWnd, &rc);
                inidata.x = rc.left;
                inidata.y = rc.top;
                DestroyWindow(hWnd);
            }
            break;
        case WM_DESTROY:
            SetInitialSettings(inidata);
            KillTimer(hWnd, MYTIMER);
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, msg, wp, lp));
    }
    return 0;
}
メインウィンドウのプロシージャです。

WM_CREATEメッセージが来たら、自作関数のGetInitialSettingsを呼んで、 inidata構造体にレジストリの値を読み込んでセットしています。

次に早速このデータを利用してMoveWindow関数で、前回終了時のウィンドウ位置に 移動します。

今までは、初期設定は画面の中央にウィンドウが来るようにしていましたが この必要がなくなりました。

WM_RBUTTONDOWNメッセージが来たら、型どおりポップアップメニューを出します。 詳しくは第44章を参照してください。

WM_TIMERメッセージが来たら、現在時刻を取得してszBufに現在時刻を、szBuf2に 現在の日付と曜日を書き込みます。そして、クライアント領域全体を無効領域にします。

WM_PAINTメッセージが来たら、表示内容を描画します。今回は色の設定はinidata構造体 のメンバに従って決めています。また、表示位置も日付が入る分調整しています。

メニューからIDM_ENDが選択されたら、WM_CLOSEメッセージを送ります。

メニューからIDM_BACKGROUNDが選択されたら、GetMyColor関数を呼んでユーザーに 背景色を選択させます。ユーザーがOKボタンを押したなら、選択された色を inidata構造体のメンバに書き込みます。書き込むだけでよいのです。いずれ、WM_TIMER メッセージが発行されて、InvalidateRect関数が実行されるからです。

メニューからIDM_TEXTが選択されたら、やはりGetMyColor関数を呼んでユーザーに 色の選択をさせて、OKボタンを押したなら、選択した色をinidata構造体のメンバに書き込みます。

終了時にDestroyWindow関数が実行される前にメインウィンドウの位置を調べて、inidata構造体の メンバに書き込んでおきます。

終了直前にinidata構造体のメンバの値をレジストリに書き込みます。

HFONT SetMyFont(LPCTSTR face, int h)
{
    HFONT hFont;
    hFont = CreateFont(h,    //フォント高さ
        0,                    //文字幅
        0,                    //テキストの角度
        0,                    //ベースラインとx軸との角度
        FW_REGULAR,            //フォントの重さ(太さ)
        FALSE,                //イタリック体
        FALSE,                //アンダーライン
        FALSE,                //打ち消し線
        SHIFTJIS_CHARSET,    //文字セット
        OUT_DEFAULT_PRECIS,    //出力精度
        CLIP_DEFAULT_PRECIS,//クリッピング精度
        PROOF_QUALITY,        //出力品質
        FIXED_PITCH | FF_MODERN,//ピッチとファミリー
        face);    //書体名
    
    return hFont;
}
この関数に変更はありません。
BOOL GetMyColor(HWND hWnd, COLORREF *lpcr)
{
    CHOOSECOLOR cc;
    static DWORD dwCustColors[16];
    static COLORREF cr = RGB(0, 255, 255);

    cc.lStructSize = sizeof(CHOOSECOLOR);
    cc.hwndOwner = hWnd;
    cc.lpCustColors = dwCustColors;
    cc.rgbResult = cr;
    cc.Flags = CC_RGBINIT;

    if (ChooseColor(&cc)) {
        cr = cc.rgbResult;
        *lpcr = cc.rgbResult;
        return TRUE;
    }

    return FALSE;
}
色の選択ダイアログボックスを出す関数です。

すでに第139章で解説しています。

void GetInitialSettings(INIDATA *lpini)
{
    DWORD dwData;

    if (GetDataDWORD("background-color", &dwData)) {
        lpini->cr_bg = (COLORREF)dwData;
    } else {
        lpini->cr_bg = RGB(0, 255, 255);
    }
    if (GetDataDWORD("text-color", &dwData)) {
        lpini->cr_txt = (COLORREF)dwData;
    } else {
        lpini->cr_txt = RGB(0, 0, 0);
    }
    if (GetDataDWORD("x", &dwData)) {
        lpini->x = (int)dwData;
    } else {
        lpini->x = 0;
    }
    if (GetDataDWORD("y", &dwData)) {
        lpini->y = (int)dwData;
    } else {
        lpini->y = 0;
    }
    return;
}
レジストリから設定を読み出し、inidata構造体に値を書き込む関数です。

実際に読み出すのはGetDataDWORD関数ですが、これが失敗した時 (初めて起動した時など、まだレジストリにデータがない場合)は デフォルトの値を書き込むようにしてあります。

BOOL GetDataDWORD(char *szName, DWORD *dwValue)
{
    HKEY hKey;
    DWORD dwPosition;
    DWORD dwType = REG_DWORD;
    DWORD dwByte = 32;
    RegCreateKeyEx(hParentKey,
        lpszSubKey,
        0,
        "",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        NULL,
        &hKey,
        &dwPosition);
    if (RegQueryValueEx(hKey,
        szName,
        NULL,
        &dwType,
        (BYTE *)dwValue,
        &dwByte) != ERROR_SUCCESS) {
            RegCloseKey(hKey);
            return FALSE;
        }
    RegCloseKey(hKey);
    return TRUE;
}
実際にレジストリからDWORD値を読み出す関数です。 レジストリについてはすでに第129章で解説してあるので参照してください。
BOOL SetInitialSettings(INIDATA inidata)
{
    SetDataDWORD("background-color", inidata.cr_bg);
    SetDataDWORD("text-color", inidata.cr_txt);
    SetDataDWORD("x", inidata.x);
    SetDataDWORD("y", inidata.y);
    return TRUE;
}
レジストリにINIDATA構造体の値を書き込む関数です。 実際の書き込みはSetDataDWORD関数にやらせています。
BOOL SetDataDWORD(char *szName, DWORD dwData)
{
    HKEY hKey;
    DWORD dwPosition;
    
    RegCreateKeyEx(hParentKey,
        lpszSubKey,
        0,
        "",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        NULL,
        &hKey,
        &dwPosition);
    RegSetValueEx(hKey,
        szName,
        0,
        REG_DWORD,
        (CONST BYTE *)&dwData,
        sizeof(DWORD));
    RegCloseKey(hKey);
    return TRUE;
}
レジストリに値を書き込む関数です。 これも第129章を参照してください。

今回も簡単でした。このプログラムに、アナログの時計も表示するよう改良してみてください。 WM_TIMERメッセージが来るたびにクライアント領域の書き直しが起こっているので、比較的 簡単に作れるはずです。


[SDK第4部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 01/Jan/2003 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。