まず、左のようなダイアログボックスに各教科の得点を
入力します。そうすると、
このようなグラフが表示される、というプログラムです。
今までの知識のみで、比較的簡単に作れると思います。
また、このグラフはウィンドウの大きさに応じて
伸縮します。
この手のグラフを作るとき意外と面倒なのが
y軸方向の座標とグラフの関係です。
プログラミング上では、y座標が大きくなれば
画面上では下方に進みます。
通常見かける座標とは上下方向の進み方が反対になっています。
今回は、素直に自前で計算してグラフを作ってみました。
まずは、リソーススクリプトを見て下さい。
リソースエジタで作る人は関係ありませんが
各コントロールのIDなどはこれを参照して下さい。
当然ですが自前でリソーススクリプトを書く人は
windows.hと***.h(自前のヘッダーファイル)
をインクルードするのを忘れないで下さい。
次にソースファイルです。// gr01.rc ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END MENUITEM "データ入力(&D)", IDM_DATA END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 127, 93 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "データ入力" FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "OK",IDOK,79,49,31,14 PUSHBUTTON "キャンセル",IDCANCEL,79,72,31,14 LTEXT "国語:",IDC_STATIC,7,11,18,8 LTEXT "算数:",IDC_STATIC,7,31,18,8 LTEXT "理科:",IDC_STATIC,7,51,18,8 LTEXT "社会:",IDC_STATIC,7,73,18,8 EDITTEXT IDC_KOKUGO,39,7,31,15,ES_AUTOHSCROLL EDITTEXT IDC_SANSU,39,28,31,15,ES_AUTOHSCROLL EDITTEXT IDC_RIKA,39,49,31,15,ES_AUTOHSCROLL EDITTEXT IDC_SHAKAI,39,70,31,15,ES_AUTOHSCROLL END
#define STRICTはなくても良いのですが、 これを定義しておくと型チェックをより厳しく実行されます。 また、マクロを使いますのでwindowsx.hも忘れずにインクルードして下さい。 また、各教科の得点を表す文字列、各教科の得点などを グローバル変数にしておきました。// gr01.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void DrawGr(HWND, HDC); char szClassName[] = "gr01"; //ウィンドウクラス char kokugo[4], sansu[4], rika[4], shakai[4]; //各教科の得点文字列 int point[4];//各教科の得点 enum {koku, san, ri, sha}; int sw; //描画スイッチ
ここは、いつもと殆ど同じです。クラスメニュー"MYMENU"を 登録し忘れないようにして下さい。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!hPrevInst) { if (!InitApp(hCurInst)) return FALSE; } if (!InitInstance(hCurInst, nCmdShow)) { return FALSE; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; return (RegisterClass(&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; }
WM_PAINTメッセージがきたら、自作関数の DrawGr関数を呼んでグラフを描画させます。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; HINSTANCE hInst; HDC hdc; PAINTSTRUCT ps; switch (msg) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); DrawGr(hWnd, hdc); EndPaint(hWnd, &ps); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0L, 0L); break; case IDM_DATA: hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); InvalidateRect(hWnd, NULL, TRUE); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }
また、メニューから「データ入力」が選択されたときは、 (IDM_DATA)ダイアログボックスを呼び出します。 この関数が終了したら(データを入力し終わった) InvalidateRect関数でクライアント領域全体を描き直します。 プログラムの仕組みそのものは単純です。
ダイアログボックスが呼ばれるとすぐに(WM_INITDIALOG) 各教科の得点を入力するエジットボックスに前回の得点を 表示します。初回は何も表示されません。 前回の得点を表示したくないときは、このメッセージの時 何もしなければ良いでしょう。//ダイアログプロシージャ LRESULT CALLBACK MyDlgProc(HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: Edit_SetText(GetDlgItem(hDlgWnd, IDC_KOKUGO), kokugo); Edit_SetText(GetDlgItem(hDlgWnd, IDC_SANSU), sansu); Edit_SetText(GetDlgItem(hDlgWnd, IDC_RIKA), rika); Edit_SetText(GetDlgItem(hDlgWnd, IDC_SHAKAI), shakai); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(GetDlgItem(hDlgWnd, IDC_KOKUGO), kokugo, sizeof(kokugo)); Edit_GetText(GetDlgItem(hDlgWnd, IDC_SANSU), sansu, sizeof(sansu)); Edit_GetText(GetDlgItem(hDlgWnd, IDC_RIKA), rika, sizeof(rika)); Edit_GetText(GetDlgItem(hDlgWnd, IDC_SHAKAI), shakai, sizeof(shakai)); point[koku] = atoi(kokugo); point[san] = atoi(sansu); point[ri] = atoi(rika); point[sha] = atoi(shakai); sw = 1; EndDialog(hDlgWnd, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlgWnd, IDCANCEL); return TRUE; default: return FALSE; } default: return FALSE; } }
OKボタンが押されたら入力された得点を整数に変換して point配列に代入します。そして描画スイッチを1にします。 そのあとEndDialog関数でダイアログボックスを終了します。 この関数の第2引数をIDOKにしましたが、このプログラムでは 特にDialogBox関数の戻り値は使わないので何でも良いです。
キャンセルボタンが押されたときは何もせずに ダイアログボックスを終了します。
次に、グラフを描画する自作関数ですが、ここではWindowsのプログラムの 知識はほとんど使っていません。void DrawGr(HWND hWnd, HDC hdc) { RECT rc; HBRUSH hBrush; int y_unit, x_unit, i, space_x, space_y, gr_haba; if (sw == 0) return; GetClientRect(hWnd, &rc); space_x = rc.right / 10; space_y = rc.bottom / 10; gr_haba = rc.right / 12; y_unit = (rc.bottom - (2 * space_y)) / 10; x_unit = (rc.right - 2 * space_x) / 5; //軸を描く MoveToEx(hdc, space_x, rc.bottom - space_y, NULL); LineTo(hdc, rc.right - space_x, rc.bottom - space_y); MoveToEx(hdc, space_x, rc.bottom -space_y, NULL); LineTo(hdc, space_x, space_y); //Y軸の目盛り for (i = 0; i <=10; i++) { MoveToEx(hdc, space_x, rc.bottom - space_y - y_unit * i, NULL); LineTo(hdc, space_x - space_x / 8, rc.bottom - space_y - y_unit * i); } //X軸の目盛り for (i = 1; i <= 4; i++) { MoveToEx(hdc, space_x + x_unit * i, rc.bottom - (space_y - space_y / 8), NULL); LineTo(hdc, space_x + x_unit * i, rc.bottom - space_y); } //0点と100点 TextOut(hdc, 0, rc.bottom - space_y, "0", 1); TextOut(hdc, 0, rc.bottom - space_y - 11 * y_unit, "100", 3); // TextOut(hdc, space_x + x_unit * 1 - gr_haba / 2, rc.bottom - space_y + 4, "国語", 4); TextOut(hdc, space_x + x_unit * 2 - gr_haba / 2, rc.bottom - space_y + 4, "算数", 4); TextOut(hdc, space_x + x_unit * 3 - gr_haba / 2, rc.bottom - space_y + 4, "理科", 4); TextOut(hdc, space_x + x_unit * 4 - gr_haba / 2, rc.bottom - space_y + 4, "社会", 4); //各教科の得点グラフ hBrush = CreateSolidBrush(RGB(255, 0, 0)); SelectObject(hdc, hBrush); for (i = 1; i <= 4; i++) { Rectangle(hdc, space_x + x_unit * i - gr_haba /2, rc.bottom - space_y, space_x + x_unit * i + gr_haba/2, rc.bottom - space_y - point[i-1] * y_unit / 10); } DeleteObject(hBrush); return; }
描画スイッチswが0時は何もせずに戻ります。 それ以外の時時はまずクライアント領域の大きさを計測します。 そして、xy方向のスペース(グラフと前後左右の隙間)を クライアント領域の幅、高さの10分の1としました。 すなわち、グラフのX軸の長さにspace_xの2倍をたしたものが クライアント領域の幅になります。
同じようにY軸の長さにspace_yの2倍をたしたものがクライアント 領域の高さになります。
棒グラフの幅はクライアント領域の幅の12分の1としました。
また、y_unitは得点10点分の長さです。 x_unitは1教科分の幅です。4教科ありますがX軸の長さの4分の1 を1教科の幅とするとぎりぎりすぎて、見栄えが悪いので5分の1に してあります。
次にXY軸を描きます。 この時実際に描いてみるとわかるのですがいろいろ錯覚が起こります。 (筆者だけかもしれない。)X軸方向は問題がないのですが、Y軸方向については 勘違いしやすくなります。
あとは、目盛りをつけて、棒グラフそのものは長方形で 描画します。 最後の方のRectangle関数の中身をよーく見て下さい。 得点が高いほど第5引数が小さくなります。
今回は、座標系はデフォルトのままで素直にプログラムを
書いてみました。実はこの座標系はいろいろ変更ができます。
これに関してはまた後の章で解説する予定です。
[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]
Update Jul/07/1997 By Y.Kumei