それでは最後に実際にグラフを作ったり、色を変更したりするコードをGraphMakerProc.hに書き込んでゆきます。
またこと元のコメントと追加の「//解説:」を見てください。またBCC2ECCの変更部分は赤にしておきます。
【GraphMakerProc.h】
//////////////////////////////////////////
// GraphMakerProc.h
// Copyright (c) 12/14/2022 by ECCSkelton
//////////////////////////////////////////
/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CMyWnd::OnInit(WPARAM wParam, LPARAM lParam) {
//PICTUREBOXの初期化
m_PicBox.Init(m_hWnd, IDC_GRAPH); //ダイアログコントロールIDC_GRAPHにラップ
RECT rec; //m_PicBoxのクライアントエリアサイズを取得
GetClientRect(m_PicBox.m_hWnd, &rec);
m_GraphW = rec.right - rec.left; //m_PicBoxクライアントエリアの幅(523)
m_GraphH = rec.bottom - rec.top; //m_PicBoxクライアントエリアの高さ(334)
ClearGraph(); //背景を黒、軸を反対色(白)で初期化する(ユーザー定義関数)
m_PicBox.Color(12); //描画色を初期的に明るい緑色にする
m_FrCol = GetCol(); //初期描画色を記録
//解説:カスタムコントロール"PICTUREBOX"のメンバー変数m_PicBoxをCPICBOXクラスでラップし、必要な値を記録するとともに初期化します。
//コンボボックスの初期化
WCHAR expStr1[11] = {0x0079, 0x0020, 0x003D, 0x0020, 0x0061, 0x0078, 0x0020, 0x002B, 0x0020, 0x0062, 0x0000};
WCHAR expStr2[21] = {0x0079, 0x0020, 0x003D, 0x0020, 0x0061, 0x0078, 0x00B2, 0x0020, 0x002B, 0x0020, 0x0062, 0x0078, 0x0020, 0x002B, 0x0020, 0x0063, 0x0000};
WCHAR expStr3[23] = {0x0079, 0x0020, 0x003D, 0x0020, 0x0061, 0x0078, 0x00B3, 0x0020, 0x002B, 0x0020, 0x0062, 0x0078, 0x00B2, 0x0020, 0x002B, 0x0020, 0x0063, 0x0078, 0x0020, 0x002B, 0x0020, 0x0064, 0x0000};
WCHAR expStr4[17] = {0x0079, 0x0020, 0x003D, 0x0020, 0x002B, 0x002D, 0x221A, 0x0028, 0x0061, 0x0078, 0x00B2, 0x0020, 0x002D, 0x0020, 0x0062, 0x0029, 0x0000};
//解説:ここが噂の式(↓)のUnicode文字列をコード入力しているところです。
SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L""); //未選択用-解説:これが結構重要です。
//一次方程式(linear equation)
SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr1);
// SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = ax + b");
//二次方程式(quadratic equation)
SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr2);
// SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = ax^2 + bx + c");
//三次方程式(cubic equation)
SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr3);
// SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = ax^3 + bx^2 + cx + d");
//円の方程式(equation of a circle)
SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)expStr4);
// SendItemMsg(IDC_COMBOBOX, CB_ADDSTRING, 0, (LPARAM)L"y = +-√(ax^2 - b)");
SendItemMsg(IDC_COMBOBOX, CB_SETCURSEL, 0, (LPARAM)0);
//解説:もともとはSJISで保存しても文字化けが起こらない式を使っていましたが、味気なくてねぇ。
//a - dの項入力エディットボックスをdisabledにする
SendItemMsg(IDC_EDITA, EM_SETLIMITTEXT, 5, 0);
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), FALSE);
SendItemMsg(IDC_EDITB, EM_SETLIMITTEXT, 5, 0);
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), FALSE);
SendItemMsg(IDC_EDITC, EM_SETLIMITTEXT, 5, 0);
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
SendItemMsg(IDC_EDITD, EM_SETLIMITTEXT, 5, 0);
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
//解説:値入力用のエディットボックスの入力文字数を制限し、不活性化します。
return TRUE;
}
bool CMyWnd::OnNotify(WPARAM wParam, LPARAM lParam) {
if(((NMHDR*)lParam)->code == WM_RBUTTONDOWN) {
//ポップアップメニューを読みこむ
HMENU hMenu = LoadMenuW(m_hInstance, L"IDM_POPUP");
HMENU hPopupMenu = GetSubMenu(hMenu, 0);
//ポップアップメニューの表示
POINT pt;
GetCursorPos(&pt);
TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON,
pt.x, pt.y, 0, m_hWnd, NULL);
//メニューリソースの開放
DestroyMenu(hMenu);
//解説:このブログを書いていて、ロードした(メモリーを確保した)メニューを開放していないことを発見!直ちに修正しました。(最近こういうボケが多いなぁ。)なお、この右クリックポップアップメニューのコードは定番でBCCMakerから借りてきました。
}
return TRUE;
}
bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {
if(MessageBoxW(m_hWnd, L"終了しますか", L"終了確認",
MB_YESNO | MB_ICONINFORMATION) == IDYES)
//処理をするとDestroyWindow、PostQuitMessageが呼ばれる
return TRUE;
else
//そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
return FALSE;
}
//ダイアログベースの場合はこれが必要
bool CMyWnd::OnDestroy(WPARAM wPram, LPARAM lParam) {
PostQuitMessage(0);
return TRUE;
}
/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CMyWnd::OnClear() {
SetCol(m_BkCol); //前景色から背景色へ変更
ClearGraph(); //背景を背景色で塗りつぶす
SetCol(m_FrCol); //前景色へ戻す
return TRUE;
}
//解説:これは手書きで追加した画面をクリアする処理です。処理はコメントのとおりです。
bool CMyWnd::OnBackCol() {
m_PicBox.Color(-1); //カラーダイアログを使って色選択を行う(キャンセルの場合は0 == 黒)
m_BkCol = GetCol(); //新設定色を記録
OnClear(); //消去して再描画
if(m_Selection) //式(グラフ)が選択されていれば再描画
OnShowgraph();
return TRUE;
}
//解説:これは手書きで追加した背景色設定処理です。処理はコメントのとおりです。
bool CMyWnd::OnFrontCol() {
m_PicBox.Color(-1); //カラーダイアログを使って色選択を行う(キャンセルの場合は0 == 黒)
m_FrCol = GetCol(); //新設定色を記録
if(m_Selection) //式(グラフ)が選択されていれば再描画
OnShowgraph();
return TRUE;
}
//解説:これは手書きで追加した前景色設定処理です。処理はコメントのとおりです。
bool CMyWnd::OnCombobox(WPARAM wParam) {
if(HIWORD(wParam) == CBN_SELCHANGE) {
//解説:コンボボックスの選択に変更があった時だけ処理をします。
m_Selection = SendItemMsg(IDC_COMBOBOX, CB_GETCURSEL, 0, 0);
//解説:新しい選択を取得します。
switch(m_Selection) {
case 0: //未選択状態(全ての項入力ボックスを不能にする)
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), FALSE);
SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), FALSE);
SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"");
break;
//解説:未選択の場合はすべての項入力ボックスをブランクにし、入力不能にします。
case 1: //一次方程式
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"1");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"0");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"");
break;
case 2: //二次方程式
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"0.01");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"0");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), TRUE);
SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"-144");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"");
break;
case 3: //三次方程式
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"0.001");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"0");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), TRUE);
SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"0");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), TRUE);
SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"0");
break;
case 4: //円の方程式
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITA), TRUE);
SendItemMsg(IDC_EDITA, WM_SETTEXT, 0, (LPARAM)L"1");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITB), TRUE);
SendItemMsg(IDC_EDITB, WM_SETTEXT, 0, (LPARAM)L"6400");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITC), FALSE);
SendItemMsg(IDC_EDITC, WM_SETTEXT, 0, (LPARAM)L"0");
EnableWindow(GetDlgItem(m_hWnd, IDC_EDITD), FALSE);
SendItemMsg(IDC_EDITD, WM_SETTEXT, 0, (LPARAM)L"0");
break;
//解説:式を選択した場合は関連のある項だけ入力を許し、(そのままグラフが描けるように)入力ボックスにサンプルの値を入れています。
}
return TRUE;
}
return FALSE;
}
bool CMyWnd::OnShowgraph() {
//解説:ここがグラフを描画する処理です。
//変数宣言
WCHAR str[6];
double a, b, c, d; //項の値(倍精度)
static int sx, sy, ex, ey; //始点と終点
//解説:最初始点と終点はPOINTでメンバー変数にしようと考えたのですが、CANVASがintで処理するので変更しました。
//aとbの項目データを読み取る
//解説:グラフ描画では必ずaとbを使うのでこれらは無条件に取得します。
SendItemMsg(IDC_EDITA, WM_GETTEXT, 6, (LPARAM)str);
//解説:入力最大文字数が5文字なので、NULL終端を入れて6文字までを取得します。
if(!*str) {
MessageBoxW(m_hWnd, L"項aが未入力です", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
//解説:ブランクの場合のエラー処理です。
}
a = stod(str);
//解説:取得された文字列を倍精度実数に変更するstod関数を使い、IDC_EDITAエディットボックスに対応したaに代入します。
if(a == 0) {
MessageBoxW(m_hWnd, L"項aがゼロです", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
//解説:aがゼロだと話にならないのでaだけエラー処理を行います。
}
SendItemMsg(IDC_EDITB, WM_GETTEXT, 6, (LPARAM)str);
if(!*str) {
MessageBoxW(m_hWnd, L"項bが未入力です", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
b = stod(str);
//解説:IDC_EDITBも同じように処理します。
//m_PicBoxクライアントエリアを左から右へ描画
for(int i = 0, sx = ex = 0; i < m_GraphW; i++) {
//解説:描画するm_PicBoxのクライアントエリアを左から右にかけて整数X座標を動かします。これに対応する整数Y座標を取得して、「前回の座標と、今回の座標を線で描画」します。(注:「点で描画」してもよいのですが、Y座標が大きく異なると連続性がなくなるので線にしました。)
double x = (double)(ex - m_GraphW / 2);
//解説:描画するm_PicBoxのクライアントエリア整数X座標を倍精度実数の仮想X座標に変換します。
double y;
ex = i;
//解説:倍精度実数の仮想Y座標変数を宣言します。また「今回の整数X座標ex」にiを代入します。
switch(m_Selection) {
//解説:選択式(m_Selection)に応じてグラフを描画します。
case 0:
MessageBoxW(m_hWnd, L"式が選択されていません", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
//解説:未選択の場合のエラー処理です。
case 1:
//一次方程式グラフ描画
y = a * x + b; //方程式計算
ey = (int)-y + m_GraphH / 2; //上下反転
//解説:仮想座標では上、右へ大きくなるのですが、m_PicBoxの整数座標は下、右へ大きくなるので反転する必要があります。
if(!sx)
sy = ey;
m_PicBox.Line(sx, sy, ex, ey); //始点-終点線の描画
break;
//解説:指定した式のとおりに倍精度の仮想座標で計算を行い、それをm_PicBoxの整数座標に変換して「前回終点の始点と今回終点間の線」を描画します。
case 2:
//二次方程式グラフ描画
SendItemMsg(IDC_EDITC, WM_GETTEXT, 6, (LPARAM)str);
if(!*str) {
MessageBoxW(m_hWnd, L"項cが未入力です", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
c = stod(str);
//解説:二次方程式ではcまで使います。
y = a * pow(x, 2.0) + b * x + c; //方程式計算
//解説:べき乗計算にはpow(底、指数)関数を使います。
ey = (int)-y + m_GraphH / 2; //上下反転
if(!i)
sy = ey;
m_PicBox.Line(sx, sy, ex, ey); //始点-終点線の描画
break;
case 3:
//三次方程式グラフ描画
SendItemMsg(IDC_EDITC, WM_GETTEXT, 6, (LPARAM)str);
if(!*str) {
MessageBoxW(m_hWnd, L"項cが未入力です", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
c = stod(str);
SendItemMsg(IDC_EDITD, WM_GETTEXT, 6, (LPARAM)str);
if(!*str) {
MessageBoxW(m_hWnd, L"項dが未入力です", L"エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
d = stod(str);
//解説:三次方程式ではcに加え、dまで使います。
y = a * pow(x, 3.0) + b * pow(x, 2.0) + c * x + d; //方程式計算
//解説:pow(底、指数)関数が大活躍!
ey = (int)-y + m_GraphH / 2; //上下反転
if(!ex)
sy = ey;
m_PicBox.Line(sx, sy, ex, ey); //始点-終点線の描画
break;
case 4:
//円の方程式グラフ描画(+と-の解を一緒に描画)-解説:式から中心は常に(0, 0)になります。
double rad = sqrt(b);
//解説:最初に半径(radius)radを求めておきます。
if(x >= -rad && x <= rad) {
y = sqrt(abs(a * pow(x, 2.0) - b)); //方程式計算
//解説:squrt()は平方根を求める関数です。
ey = -(int)y + m_GraphH / 2; //上下反転(y)
if(x == -rad)
sy = ey;
//解説:+と-の解を一緒に描画します
m_PicBox.Line(sx, sy, ex, ey); //上弦の描画
m_PicBox.Line(sx, sy - sy * 2 + m_GraphH,
ex, ey - ey * 2 + m_GraphH); //下弦の描画
}
break;
}
sx = ex; //今回終点を次回の始点にして一つXを進める
sy = ey; //今回終点を次回の始点にして一つYを進める
}
return TRUE;
}
bool CMyWnd::OnIdok() {
SendMsg(WM_CLOSE, 0, 0);
//解説:定番のWM_CLOSEメッセージの発信です。
return TRUE;
}
//ユーザー定義関数
COLORREF CMyWnd::GetCol() {
return m_PicBox.m_Color;
//解説:CPICBOXはCANVASを持っており、そのメンバー変数m_Colorの値を返します。
}
void CMyWnd::SetCol(COLORREF col) {
m_PicBox.m_Color = col;
//解説:CPICBOXのCANVASのメンバー変数m_Colorの値を設定します。
}
void CMyWnd::ClearGraph() {
m_PicBox.BrSelection(1);
m_PicBox.Clear();
COLORREF ContraCol = m_BkCol ^ 0x00FFFFFF; //背景色(COLORREF-下3バイトがRGB)の排他的論理和を取得
SetCol(ContraCol);
m_PicBox.Line(m_GraphW / 2, 0, m_GraphW / 2, m_GraphH);
m_PicBox.Line(0, m_GraphH / 2, m_GraphW, m_GraphH / 2);
SetCol(m_FrCol);
}
//解説:これらは共通の処理をユーザー定義関数にしたものです。ピクチャーボックス(の中のCANVAS)は簡単な16色選択ができますが、それ以外は色選択ダイアログを使って色指定もできます。色情報は内部的にCOLORREF変数(m_Color)で持っているのでそれを取得したり、設定したりすることをC#のようにGet、Setで行っています。また画面消去(ClearGraph関数)は選択色(m_Color)で塗りつぶすだけではなく、前景色で「背景色の反対色(ContraCol)」でx、y座標軸を描画し、最後に記録した前景色に戻します。
///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
次回は作成したECCSkeltonコードをコンパイルして動作を確認します。