さて、今回からよくある時計のプログラムを作ってみます。
今までの知識を総動員して作ってみましょう。
まずは、外観を考えてみましょう。アナログとデジタルの両方を
表示できるようにしてみます。構想ができたらラフスケッチを描いてみます。
こ、これはずいぶん雑なスケッチですね・・・
左側にアナログ時計があるのはわかると思います。
ウィンドウを非矩形にしています。タイトルバーなどもありません。
とりあえず、今回は矩形のウィンドウに時刻を表示するようにだけしてみます。
ウィンドウスタイルはWS_POPUPにします。
さて、このプログラムを終了するにはどのようにすればよいのでしょうか。 今回は簡単のため、右クリックすると終了するようにしておきます。
また、タイトルバーがないのでウィンドウの移動が困ります。クライアント領域を ドラッグしてウィンドウを移動させる方法は昔からいくつかの方法が知られています。 今回は最も簡単な方法を使います。
では、プログラムを見てみましょう。
// clock01.cpp
#define CLOCK_WIDTH 250 //時計全体のウィンドウ幅
#define CLOCK_HEIGHT 60 //高さ
#define MYTIMER 1 //タイマーID
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitApp(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
HFONT SetMyFont(LPCTSTR, int);
char szClassName[] = "clock01"; //ウィンドウクラス
char szAppName[] = "猫クロック"; //アプリケーション名
char szBuf[64]; //時刻表示用
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,
"猫でもわかる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;
}
CreateWindow関数の第3引数がWS_POPUPである点に注意してください。
これで、タイトルバーのないウィンドウを作ります。
//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{
int id, wx, wy, x, y;
SYSTEMTIME st;
PAINTSTRUCT ps;
HDC hdc;
HFONT hFont;
SIZE s;
switch (msg) {
case WM_CREATE:
SetTimer(hWnd, MYTIMER, 500, NULL);
wx = GetSystemMetrics(SM_CXSCREEN);
wy = GetSystemMetrics(SM_CYSCREEN);
x = (wx - CLOCK_WIDTH) / 2;
y = (wy - CLOCK_HEIGHT) / 2;
MoveWindow(hWnd, x, y, CLOCK_WIDTH, CLOCK_HEIGHT, TRUE);
break;
case WM_RBUTTONDOWN:
SendMessage(hWnd, WM_CLOSE, 0, 0);
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);
InvalidateRect(hWnd, NULL, TRUE);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
Rectangle(hdc, 0, 0, CLOCK_WIDTH, CLOCK_HEIGHT);
hFont = SetMyFont("MS ゴシック", 40);
SelectObject(hdc, hFont);
GetTextExtentPoint32(hdc, szBuf, (int)strlen(szBuf), &s);
x = (CLOCK_WIDTH - s.cx) / 2;
y = (CLOCK_HEIGHT - s.cy) / 2;
TextOut(hdc, x, y, szBuf, (int)strlen(szBuf));
DeleteObject(hFont);
EndPaint(hWnd, &ps);
break;
case WM_CLOSE:
id = MessageBox(hWnd,
"終了してもよろしいですか",
"確認",
MB_YESNO | MB_ICONQUESTION);
if (id == IDYES)
DestroyWindow(hWnd);
break;
case WM_DESTROY:
KillTimer(hWnd, MYTIMER);
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, msg, wp, lp));
}
return 0;
}
メインウィンドウのプロシージャです。WM_CREATEメッセージが来たら、タイマーをセットして、ウィンドウを画面の中心に移動します。
GetSystemMetrics関数についてはすでに、第67章で解説しています。
WM_RBUTTONDOWNメッセージが来たらプログラムを終了します。
WM_LBUTTONDOWNメッセージが来たら、自分自身にWM_NCLBUTTONDOWNメッセージを送ります。 そもそも、このメッセージはマウスカーソルが非クライアント領域にある時、左クリックをすると 送られるものです。
LRESULT CALLBACK WindowProc( HWND hwnd, // ウィンドウハンドル UINT uMsg, // WM_NCLBUTTONDOWN WPARAM wParam, // ヒットテスト値 LPARAM lParam // カーソル位置 );さて、WM_NCLBUTTONDOWNを送る時にwParamをHTCAPTIONにするとシステムは、タイトルバーが クリックされたと勘違い(??)します。これで、クライアント領域をドラッグしてウィンドウを 移動させることができます。
WM_TIMERメッセージが来たら、現在時刻を取得して体裁を整えてszBufに書き込みます。 そして、クライアント領域全体を無効領域にします。
WM_PAINTメッセージが来たら、ウィンドウの外枠をデフォルトのペンで描画します。
SetMyFont関数で論理フォントを作成し、TextOut関数でszBufを描画します。 この時、書き込み位置をGetTextExtentPoint32関数を利用して求めています。 GetTextExtentPoint32関数についてはすでに第45章で解説してあります。
WM_DESTROYメッセージが来たらタイマを破棄します。
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;
}
論理フォントを作成します。フォントについては第27章を参照してください。さて、これで原始的な時計の原型ができました。
左の図が、今回の完成品です。ドラッグして位置を移動できます。右クリックすると終了します。
背景色を変えたり、文字色を変えることができるように改造してみてください。
Update 28/Dec/2002 By Y.Kumei