第54章 簡単なグラフ


今回は、簡単なグラフ表示のプログラムを考えます。



まず、左のようなダイアログボックスに各教科の得点を 入力します。そうすると、

このようなグラフが表示される、というプログラムです。 今までの知識のみで、比較的簡単に作れると思います。 また、このグラフはウィンドウの大きさに応じて 伸縮します。

この手のグラフを作るとき意外と面倒なのが 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

次にソースファイルです。

// 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; //描画スイッチ

#define STRICTはなくても良いのですが、 これを定義しておくと型チェックをより厳しく実行されます。 また、マクロを使いますのでwindowsx.hも忘れずにインクルードして下さい。 また、各教科の得点を表す文字列、各教科の得点などを グローバル変数にしておきました。

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)); }

ここは、いつもと殆ど同じです。クラスメニュー"MYMENU"を 登録し忘れないようにして下さい。

//ウィンドウの生成 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; }

ここはいつもと同じです。

//ウィンドウプロシージャ 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; }

WM_PAINTメッセージがきたら、自作関数の DrawGr関数を呼んでグラフを描画させます。

また、メニューから「データ入力」が選択されたときは、 (IDM_DATA)ダイアログボックスを呼び出します。 この関数が終了したら(データを入力し終わった) InvalidateRect関数でクライアント領域全体を描き直します。 プログラムの仕組みそのものは単純です。

//ダイアログプロシージャ 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; } }

ダイアログボックスが呼ばれるとすぐに(WM_INITDIALOG) 各教科の得点を入力するエジットボックスに前回の得点を 表示します。初回は何も表示されません。 前回の得点を表示したくないときは、このメッセージの時 何もしなければ良いでしょう。

OKボタンが押されたら入力された得点を整数に変換して point配列に代入します。そして描画スイッチを1にします。 そのあとEndDialog関数でダイアログボックスを終了します。 この関数の第2引数をIDOKにしましたが、このプログラムでは 特にDialogBox関数の戻り値は使わないので何でも良いです。

キャンセルボタンが押されたときは何もせずに ダイアログボックスを終了します。

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; }

次に、グラフを描画する自作関数ですが、ここではWindowsのプログラムの 知識はほとんど使っていません。

描画スイッチ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
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。