「関数電卓」のコマンド版(Windows のデスクトップアプリケーション版)にグラフ描画機能を追加して「関数のグラフ」と同等のことができるようにしました。まだ変数の計算で「読み取りアクセス違反」が発生するので動作はできていません。
描画領域を追加したのでその部分を掲載します。
#include <windows.h> #include <string> #include <tchar.h> #include <map> #include "ProgInpStr.h" #include "ProgExp.h" #include "ClassGraphEnv.h" // 使い方 // exit で終了します。 // list で変数の一覧を表示します。 // 変数の定義は、 // 変数名 = 式 // で行います。 // 変数名は、英字またはアンダースコアで始まる必要があります。 // 変数の値を表示するには、 // 変数名 // と入力します。 // 式の評価は、 // 式 // と入力します。 // 式は、四則演算や関数呼び出しなどが可能です。 // グラフの描画は、 // graph (t, x, y) (t, x, y) ... // と入力します。 // ここで、t はパラメーター名、x は x 座標の変数名、y は y 座標の変数名です。 // 色の設定は、 // color (r, g, b) (r, g, b) ... // と入力します。 // ここで、r, g, b は RGB の値です。 // グラフの範囲は、 // range (t_from, t_to, t_step) // と入力します。 // ここで、t_from は開始値、t_to は終了値、t_step はステップ幅です。 #define ID_INPUT 1001 #define ID_OUTPUT 1002 #define ID_DRAWWINDOW 1003 // https://qiita.com/tsuchinokoman/items/869a30e02e6ddb5786b3 からコードを引用 std::wstring StringToWstring(const std::string& str); std::string WstringToString(const std::wstring& wstr); class ConOutput { // コンソール風出力クラス private: HWND hOutput; // 出力先のウィンドウハンドル void print(const std::string& str) { // 出力先のウィンドウに文字列を追加 std::wstring wstr = StringToWstring(str); int len = GetWindowTextLength(hOutput); SendMessage(hOutput, EM_SETSEL, len, len); SendMessage(hOutput, EM_REPLACESEL, 0, (LPARAM)wstr.c_str()); } public: ConOutput(HWND hOutput) { // コンストラクタで出力先のウィンドウハンドルを設定 this->hOutput = hOutput; } ConOutput& operator<<(const std::string& str) { // 出力演算子オーバーロード print(str); return *this; } }; HWND hInput, hOutput; HWND hDrawWindow; WNDPROC oldEditProc = nullptr; WNDPROC oldDrawProc = nullptr; using Env = std::map<std::string, ProgExp>; Env variables; ClassGraphEnv graphEnv; // グラフ環境のインスタンス bool all_number_string(std::vector<std::vector<ProgExp>>& exp_list_list) { // 各要素が数値かどうかを調べる for (std::vector<ProgExp> exp_list : exp_list_list) { for (ProgExp exp : exp_list) { if (exp.getType() != "var" || !exp.getName().is_number_string()) { return false; // 数値でない要素があれば false } } } return true; // 全ての要素が数値であれば true } bool all_varname_string(std::vector<std::vector<ProgExp>>& exp_list_list) { // 各要素が変数名かどうかを調べる for (std::vector<ProgExp> exp_list : exp_list_list) { for (ProgExp exp : exp_list) { if (exp.getType() != "var" || !exp.getName().is_varname_string()) { return false; // 変数名でない要素があれば false } } } return true; // 全ての要素が変数名であれば true } ControlGraph* pControl = nullptr; // グラフ描画用のコントロール bool process_line(const std::wstring& wline) { ConOutput co_cout(hOutput); // 出力オブジェクト ConOutput co_cerr(hOutput); // エラー出力オブジェクト std::string s_endl = "\r\n"; // 改行文字列 std::string line = WstringToString(wline); if (line.find("exit") == 0) { // "exit" コマンドが入力された場合、アプリケーションを終了 return false; } if (line.find("list") == 0) { // 変数リストの表示 if (variables.empty()) { co_cout << "No variables defined." << s_endl; } else { co_cout << "Defined variables:" << s_endl; for (const auto& var : variables) { co_cout << var.first << " = " << var.second.mprint(0).GetString() << s_endl; } } return true; } if (line.find("graph ") == 0) { // グラフ描画の処理 // graph (t1, x1, y1) (t2, x2, y2) ... // tn: パラメーター名 // xn: x座標の変数名 // yn: y座標の変数名 line = line.substr(6); // "graph " を除去 ProgInpStr inputStr(line); std::vector<std::vector<ProgExp>> graph_list; for (;;) { std::vector<ProgExp> exp_list = inputStr.get_exp_list(); if(exp_list.empty()) { break; // 入力が終了したらループを抜ける } graph_list.push_back(exp_list); } if (!all_varname_string(graph_list)) { co_cerr << "Error: Each graph element must be a variable name." << s_endl; return true; } for (std::vector<ProgExp> exp_list : graph_list) { if (exp_list.size() != 3) { co_cerr << "Error: Invalid graph format. Expected: graph (t, x, y)...." << s_endl; return true; } } graphEnv.set_graph_list(graph_list); pControl->setEnv(graphEnv); // グラフ環境をコントロールに設定 graphEnv.setDrawAll(pControl); // グラフ描画ウィンドウを設定 return true; } if (line.find("color ") == 0) { // 色の設定 // color (r1, g1, b1) (r2, g2, b2) ... line = line.substr(6); // "color " を除去 ProgInpStr inputStr(line); std::vector<std::vector<ProgExp>> color_list; for (;;) { std::vector<ProgExp> exp_list = inputStr.get_exp_list(); if (exp_list.empty()) { break; // 入力が終了したらループを抜ける } color_list.push_back(exp_list); } if (!all_number_string(color_list)) { co_cerr << "Error: Each color element must be a number." << s_endl; return true; } for (std::vector<ProgExp> exp_list : color_list) { if (exp_list.size() != 3) { co_cerr << "Error: Invalid color format. Expected: range (r, g, b)...." << s_endl; return true; } } graphEnv.set_color_list(color_list); return true; } if (line.find("view ") == 0) { // 表示範囲の設定 // view (x1, y1) (x2, y2) line = line.substr(5); // "view " を除去 ProgInpStr inputStr(line); std::vector<std::vector<ProgExp>> view_list; //view_list.clear(); // 既存のリストをクリア for (;;) { std::vector<ProgExp> exp_list = inputStr.get_exp_list(); if (exp_list.empty()) { break; // 入力が終了したらループを抜ける } view_list.push_back(exp_list); } if(view_list.size() != 2 || view_list[0].size() != 2 || view_list[1].size() != 2) { co_cerr << "Error: Invalid view format. Expected: view (x1, y1) (x2, y2)." << s_endl; return true; } // 各要素が数値かどうかを調べる if (!all_number_string(view_list)) { co_cerr << "Error: All coordinates must be numbers." << s_endl; return true; } double x1 = view_list[0][0].getName().ToDouble(); double y1 = view_list[0][1].getName().ToDouble(); double x2 = view_list[1][0].getName().ToDouble(); double y2 = view_list[1][1].getName().ToDouble(); if (x1 >= x2 || y1 >= y2) { co_cerr << "Error: Invalid view coordinates. Ensure x1 < x2 and y1 < y2." << s_endl; return true; } // グラフ環境の表示範囲を設定 graphEnv.set_graph_range(x1, y1, x2, y2); return true; } if (line.find("range ") == 0) { // パラメーターの範囲と増分の設定 // range name = (from, to, step) // name: パラメーター名 // from: 開始値 // to: 終了値 // step: 増分 // range (from1, to1, step1) (from1, to1, step1) ... line = line.substr(6); // "range " を除去 ProgInpStr inputStr(line); std::vector<std::vector<ProgExp>> range_list; for (;;) { std::vector<ProgExp> exp_list = inputStr.get_exp_list(); if (exp_list.empty()) { break; // 入力が終了したらループを抜ける } range_list.push_back(exp_list); } // 各要素が数値かどうかを調べる if (!all_number_string(range_list)) { co_cerr << "Error: Each range element must be a number." << s_endl; return true; } for (std::vector<ProgExp> exp_list : range_list) { if (exp_list.size() != 3) { co_cerr << "Error: Invalid range format. Expected: range (from, to, step)...." << s_endl; return true; } } if (range_list.size() != 1) { co_cerr << "Error: Invalid range format. Expected: range (from, to, step)." << s_endl; return true; } // 今のところは1つの範囲のみをサポート double from = range_list[0][0].getName().ToDouble(); double to = range_list[0][1].getName().ToDouble(); double step = range_list[0][2].getName().ToDouble(); if (step <= 0) { co_cerr << "Error: Step must be greater than zero." << s_endl; return true; } // パラメーターの範囲と増分を設定 graphEnv.set_param_interval(from, to, step); graphEnv.set_range_list(range_list); return true; } ProgInpStr inputStr(line); ProgExp exp = inputStr.get_asn(); if (exp.is_error()) { co_cerr << "Error: " << exp.get_error_message().GetString() << s_endl; return true; } if (exp.getType() == "var") { // 変数の定義を表示 std::string varName = exp.getName().GetString(); if (isalpha(varName[0]) || varName[0] == '_') { if (variables.find(varName) == variables.end()) { co_cerr << "Error: Variable '" << varName << "' is not defined." << s_endl; return true; } // 変数の値を表示 ProgExp pointExp = variables[varName]; co_cout << varName << " = " << pointExp.mprint(0).GetString() << s_endl; // グラフ描画用の環境に変数定義を追加 graphEnv.add_exp(line); return true; } } else if (exp.getType() == "=") { // 変数を定義 std::string varName = exp.get_left().getName().GetString(); ProgExp right_exp = exp.get_right(); variables[varName] = right_exp; return true; } // 式の評価 ProgDefMap vardefs; for (const auto& var : variables) { vardefs.Add(var.first, var.second); } ExpNum result = exp.value(vardefs); if (result.is_error()) { co_cerr << "Error: " << result.get_error_message().GetString() << s_endl; } else { co_cout << "Result: " << result.Print().GetString() << s_endl; } return true; } const int MAX_INPUT_LENGTH = 1000; std::wstring prompt = L"> "; // サブクラス用プロシージャ LRESULT CALLBACK EditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_KEYDOWN && wParam == VK_RETURN) { wchar_t buffer[MAX_INPUT_LENGTH]; DWORD start, end; SendMessage(hInput, EM_GETSEL, (WPARAM)&start, (LPARAM)&end); // カーソル位置取得 int currentLine = (int)SendMessage(hInput, EM_LINEFROMCHAR, (WPARAM)start, 0); // 最初の WORD に、バッファのサイズを入れる(重要!) *((WORD*)buffer) = sizeof(buffer) / sizeof(TCHAR); // EM_GETLINE を使って指定行を取得 int copied = (int)SendMessage(hInput, EM_GETLINE, (WPARAM)currentLine, (LPARAM)buffer); // buffer に copied 文字分のテキストが格納される(終端の \0 は自動で付かないので注意) buffer[copied] = _T('\0'); // 明示的に終端すること int lineCount = (int)SendMessage(hInput, EM_GETLINECOUNT, 0, 0); if (currentLine < lineCount - 1) { // 次の行がある場合は、最後に移動する int len = GetWindowTextLength(hInput); SendMessage(hInput, EM_SETSEL, len, len); } else { // 最後の行の場合は、プロンプトを表示する int len = GetWindowTextLength(hInput); SendMessage(hInput, EM_SETSEL, len, len); SendMessage(hInput, EM_REPLACESEL, 0, (LPARAM)(L"\r\n" + prompt).c_str()); } // 行の処理 std::wstring line = buffer; line = line.substr(prompt.size()); // プロンプト部分を除去 if(!process_line(line)) { // "exit" コマンドが入力された場合、アプリケーションを終了 PostQuitMessage(0); } return 0; // エンターキーの既定動作(ビープ音など)を抑制 } if (msg == WM_CHAR && wParam == VK_RETURN) { return 0; // エンターキーの既定動作(ビープ音など)を抑制 } return CallWindowProc(oldEditProc, hwnd, msg, wParam, lParam); } LRESULT CALLBACK DrawProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: pControl->OnPaint(); return 0; // Paint message handled } return CallWindowProc(oldDrawProc, hwnd, msg, wParam, lParam); } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: // 出力用(読み取り専用、複数行) hOutput = CreateWindow(L"EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_READONLY | WS_VSCROLL, 10, 10, 300, 300, hwnd, (HMENU)ID_OUTPUT, NULL, NULL); // 入力用 hInput = CreateWindow(L"EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | WS_VSCROLL, 10, 320, 300, 210, hwnd, (HMENU)ID_INPUT, NULL, NULL); // 描画用ウィンドウを作成 hDrawWindow = CreateWindow(L"STATIC", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 320, 10, 520, 520, hwnd, (HMENU)ID_DRAWWINDOW, NULL, NULL); pControl = new ControlGraph(hDrawWindow); // グラフ描画用のコントロールを初期化 //pControl = std::make_shared<ControlGraph>(hDrawWindow); // グラフ描画用のコントロールを初期化 SetWindowText(hInput, prompt.c_str()); { int len = GetWindowTextLength(hInput); SendMessage(hInput, EM_SETSEL, len, len); } SetFocus(hInput); // サブクラス化 oldEditProc = (WNDPROC)SetWindowLongPtr(hInput, GWLP_WNDPROC, (LONG_PTR)EditProc); oldDrawProc = (WNDPROC)SetWindowLongPtr(hDrawWindow, GWLP_WNDPROC, (LONG_PTR)DrawProc); break; case WM_COMMAND: if (LOWORD(wParam) == ID_INPUT && HIWORD(wParam) == EN_CHANGE) { // Enterキーを検出するためにEN_CHANGEではなくWM_KEYDOWNを使う } break; case WM_KEYDOWN: break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { const wchar_t CLASS_NAME[] = L"MyConsoleWindow"; WNDCLASS wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); RegisterClass(&wc); HWND hwnd = CreateWindowEx( 0, CLASS_NAME, L"GUI Console", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 868, 578, NULL, NULL, hInstance, NULL ); ShowWindow(hwnd, nCmdShow); MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } // https://qiita.com/tsuchinokoman/items/869a30e02e6ddb5786b3 からコードを引用 std::wstring StringToWstring(const std::string& str) { std::wstring ret; //一度目の呼び出しは文字列数を知るため auto result = MultiByteToWideChar(CP_UTF8, 0, str.c_str(),//入力文字列 str.length(), nullptr, 0); ret.resize(result);//確保する //二度目の呼び出しは変換 result = MultiByteToWideChar(CP_UTF8, 0, str.c_str(),//入力文字列 str.length(), ret.data(), ret.size()); return ret; } // https://qiita.com/tsuchinokoman/items/869a30e02e6ddb5786b3 からコードを引用 std::string WstringToString(const std::wstring& wstr) { std::string ret; //一度目の呼び出しは文字列数を知るため auto result = WideCharToMultiByte( CP_ACP, 0, wstr.c_str(),//入力文字列 wstr.length(), nullptr, 0, nullptr, nullptr); ret.resize(result);//確保する //二度目の呼び出しは変換 result = WideCharToMultiByte( CP_ACP, 0, wstr.c_str(),//入力文字列 wstr.length(), ret.data(), ret.size(), nullptr, nullptr); return ret; }


