2024/01/16(火) [n年前の日記]
#1 [prog] C/C++で設定ダイアログを表示してみたいその2
C/C++ と OpenGL を使ってWindows用のスクリーンセーバを作りたい。そのためには設定ダイアログを表示して、設定値の読み書きしないといけない。
_昨日の実験 で、Windows10 x64 22H2 + Visual Studio Community 2019上なら、求める動作を書けたのだけど。同じことを MinGW (gcc 6.3.0) で再現しようとしたところ躓いてしまった。
リソースコンパイラ windres 2.28 が resource.rc をコンパイルできない…。trackbar/sliderというか、要は "msctls_trackbar32" というコントロール(GUI部品)の記述行で syntax error が出てしまって…。
結局、問題を解決することはできなかった。諦めて該当行を EDITTEXT に置き換えて処理するようにしてしまった。
環境は、Windows10 x64 22H2 + MinGW (gcc 6.3.0)。
_昨日の実験 で、Windows10 x64 22H2 + Visual Studio Community 2019上なら、求める動作を書けたのだけど。同じことを MinGW (gcc 6.3.0) で再現しようとしたところ躓いてしまった。
リソースコンパイラ windres 2.28 が resource.rc をコンパイルできない…。trackbar/sliderというか、要は "msctls_trackbar32" というコントロール(GUI部品)の記述行で syntax error が出てしまって…。
CONTROL "Wait", IDC_WAITVALUE, "msctls_trackbar32", TBS_AUTOTICKS | TBS_BOTH | TBS_TOOLTIPS | WS_TABSTOP, 96,24,180,15 CONTROL "Speed", IDC_SPEED, "msctls_trackbar32", TBS_AUTOTICKS | TBS_BOTH | TBS_TOOLTIPS | WS_TABSTOP, 96,48,180,15 CONTROL "Number", IDC_NUMBER, "msctls_trackbar32", TBS_AUTOTICKS | TBS_BOTH | TBS_TOOLTIPS | WS_TABSTOP, 96,72,180,15
結局、問題を解決することはできなかった。諦めて該当行を EDITTEXT に置き換えて処理するようにしてしまった。
環境は、Windows10 x64 22H2 + MinGW (gcc 6.3.0)。
◎ ソース :
ひとまず MinGW (gcc 6.3.0) でも動くようになったサンプルを載せてみる。
今回はソース内に日本語コメントをガンガン書いてみた。
_01_dialogtest.c
_resource.h
_resource.rc
_Makefile
- メインウインドウのボタンを押すとリソースファイルで記述した設定ダイアログが表示される → DialogBox() を使う。
- 設定ダイアログに、現在の設定値を反映 → GetDlgItem()、SendMessage()、SetDlgItemText() を使う。
- 設定ダイアログ上で入力欄に数値を入力してOKボタンを押すと、メインウインドウ上でその設定値を読み取れる → GetDlgItem()、SendMessage()、GetDlgItemText()、IsDlgButtonChecked() を使う。
今回はソース内に日本語コメントをガンガン書いてみた。
_01_dialogtest.c
// DialogTest01.c // // Dialog access sample // // Last updated: <2024/01/16 22:17:34 +0900> #include <tchar.h> #include <wchar.h> #include <windows.h> #include <stdio.h> #include <stdlib.h> // もし trackbar を使う時は commctrl.h の include が必要 // #include <commctrl.h> // リソースファイルを使う時は include する #include "resource.h" // メインウインドウ上に設置するボタンのID #define IDC_BUTTON_OPENDLG 110 // global work グローバル変数 HINSTANCE hInst; // static WCHAR txt[1024]; // message work static char txt[1024]; // message work // global paramater work static int waitValue = 15; static int speedValue = 1000; static int numberValue = 1000; static int fps_display = 1; // ======================================== // prototype プロトタイプ宣言 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM); // ======================================== // entry point エントリーポイント。Win32 GUIアプリは最初にここが呼ばれる int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { TCHAR szWindowClass[] = TEXT("DIALOGTEST01"); // main window class name メインウインドウクラス名 TCHAR szTitle[] = TEXT("DialogTest01"); // title bar text タイトルバーに表示するテキスト WNDCLASSEX wc; MSG msg; // set window class properties (struct) // ウインドウクラス構造体の設定 { wc.cbSize = sizeof(wc); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; // ウインドウプロシージャを指定 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = NULL; wc.lpszClassName = szWindowClass; wc.hIcon = NULL; // 今回はアイコンを指定しない wc.hIconSm = NULL; // wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); // wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_SMALL)); if (RegisterClassEx(&wc) == 0) return FALSE; } // create window ウインドウを作成 { hInst = hInstance; HWND hWnd = CreateWindow( szWindowClass, szTitle, // タイトルバーのテキストを指定 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, // x, y 512, 288, // width, height NULL, NULL, hInstance, NULL); if (hWnd == NULL) return FALSE; // display window ウインドウを表示 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); } // main message loop メッセージループ { BOOL bRet; while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } } return (int)msg.wParam; } // ======================================== // main window procedure // メインウインドウプロシージャ。メインウインドウ上で何かが起きるとここが呼ばれる LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // static WCHAR txt[1024]; switch (message) { case WM_CREATE: // create main window. initialize // メインウインドウ作成時にここに来る。初期化処理をする // create "Open Dialog" button ボタンを1つ設置する CreateWindow( "BUTTON", "Open Dialog", // クラス名、ボタン上に表示するテキスト WS_CHILD | WS_VISIBLE, // スタイル 20, 20, 100, 24, // x, y, width, height hWnd, (HMENU)IDC_BUTTON_OPENDLG, // ボタンに紐づけるID hInst, NULL); break; case WM_COMMAND: { // application menu process int wmId = LOWORD(wParam); switch (wmId) { case IDC_BUTTON_OPENDLG: { // push "Open Dialog" button // "Open Dialog" ボタンが押されるとここに来る // Open dialog リソースファイル内で記述されてるダイアログを開く int ret; ret = DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc); // モーダルダイアログを開いているので、OK か Cancel が押されるまで処理は返ってこない // dialog results ダイアログが返してきた値で処理を分ける switch (ret) { case IDOK: // push "OK" button OKボタンが押された strcpy(txt, "IDOK"); // wcscpy_s(txt, TEXT("IDOK")); break; case IDCANCEL: // push "Cancel" button Cancelボタンが押された strcpy(txt, "IDCANCEL"); // wcscpy_s(txt, TEXT("IDCANCEL")); break; } InvalidateRect(hWnd, NULL, TRUE); } break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: { // draw main window メインウインドウ描画処理 PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // draw message "IDOK" or "IDCANCEL" のメッセージを表示 // TextOut(hdc, 20, 70, txt, lstrlen(txt)); TextOut(hdc, 20, 70, txt, strlen(txt)); // draw paramater value ダイアログ上で設定した値を表示 { char s[256]; sprintf(s, "wait=%d", waitValue); TextOut(hdc, 20, 90, s, strlen(s)); sprintf(s, "speed=%d", speedValue); TextOut(hdc, 20, 110, s, strlen(s)); sprintf(s, "number=%d", numberValue); TextOut(hdc, 20, 130, s, strlen(s)); sprintf(s, "fps_display=%d", fps_display); TextOut(hdc, 20, 150, s, strlen(s)); } EndPaint(hWnd, &ps); } break; case WM_DESTROY: // destroy window ウインドウ破棄時の処理 PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // 与えられた値を最小値と最大値の間に収める int clamp(int v, int minv, int maxv) { if (v < minv) return minv; if (v > maxv) return maxv; return v; } // set value to config dialog // 設定ダイアログの値を設定する void SetvalueToDialog(HWND hDlg, int waitValue, int speedValue, int numberValue, int fps_display) { // set edittext // EDITTEXTの内容を初期化 // SetDlgItemText()で EDITTEXTの内容(文字列)を変更できる { char s[256]; sprintf(s, "%d", waitValue); SetDlgItemText(hDlg, IDC_WAITVALUE, s); sprintf(s, "%d", speedValue); SetDlgItemText(hDlg, IDC_SPEED, s); sprintf(s, "%d", numberValue); SetDlgItemText(hDlg, IDC_NUMBER, s); } // set checkbox // CHECKBOXの状態を初期化する // GetDlgItem() でコントロールのハンドルを取得できる // SendMessage() でコントロールの状態を変更できる // BM_SETCHECK で状態の変更を指示 { HWND cHwnd; cHwnd = GetDlgItem(hDlg, IDC_FPSDISPLAY); SendMessage(cHwnd, BM_SETCHECK, ((fps_display == 0) ? BST_UNCHECKED : BST_CHECKED), 0); } } // get value from confgi dialog void GetValueFromDialog(HWND hDlg) { // get edittext text // EDITTEXTの内容(文字列)をint値にしてグローバル変数に記録 // GetDlgItemText() でEDITTEXTの内容(文字列)を取得 // atoi() で文字列をint値に変換 { char s[1024]; GetDlgItemText(hDlg, IDC_WAITVALUE, s, sizeof(s)); waitValue = clamp(atoi(s), 5, 200); GetDlgItemText(hDlg, IDC_SPEED, s, sizeof(s)); speedValue = clamp(atoi(s), 100, 4000); GetDlgItemText(hDlg, IDC_NUMBER, s, sizeof(s)); numberValue = clamp(atoi(s), 10, 4000); } // get checkbox status // CHECKBOXの状態をグローバル変数に記録 // IsDlgButtonChecked() で CHECKBOX の状態を取得できる fps_display = (IsDlgButtonChecked(hDlg, IDC_FPSDISPLAY) == BST_CHECKED) ? 1 : 0; } // ======================================== // dialog procedure // ダイアログプロシージャ。ダイアログ上でボタン等が押された際に呼ばれる INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: { // initialize dialog // ダイアログ初期化時の処理。GUI部品の状態を設定 // 設定ダイアログ上の値を設定 SetvalueToDialog(hDlg, waitValue, speedValue, numberValue, fps_display); } return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: // push "OK" button "OK"ボタンが押されたらここに来る GetValueFromDialog(hDlg); // 設定ダイアログ上の値を取得してグローバル変数に記録 EndDialog(hDlg, IDOK); // ダイアログを終了 return TRUE; case IDCANCEL: // push "Cancel" button EndDialog(hDlg, IDCANCEL); // ダイアログを終了 return TRUE; case IDC_RESET: { // push "Reset" button "Reset"ボタンが押された // change status text // ダイアログ上のLABELのテキストを変更 SetWindowText(GetDlgItem(hDlg, IDC_STATUS1), TEXT("Reset")); // ダイアログ上の値をデフォルト値に変更 SetvalueToDialog(hDlg, 15, 1000, 1000, 1); } return TRUE; } break; } return FALSE; }
_resource.h
#ifndef RESOURCE_H_ #define RESOURCE_H_ #define IDC_MYICON 2 #define IDD_DIALOGTEST01_DIALOG 102 #define IDI_APP_ICON 110 #define IDI_SMALL 111 #define IDR_MAINFRAME 128 #define IDD_DIALOG1 129 #define IDC_FPSDISPLAY 2003 #define IDC_RESET 2004 #define IDC_WAITVALUE 2005 #define IDC_SPEED 2006 #define IDC_NUMBER 2007 #define IDC_STATUS1 2008 #define IDC_STATIC -1 #endif
_resource.rc
/* resource */ #include <windows.h> /* #include <commctrl.h> */ #include "resource.h" /* Dialog */ IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 289, 140 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Setting" FONT 11, "Segoe UI" BEGIN DEFPUSHBUTTON "OK", IDOK, 172, 96, 50, 14 PUSHBUTTON "Cancel", IDCANCEL, 228, 96, 50, 14 CTEXT "ssstars GL setting", IDC_STATIC, 7, 7, 275, 8 LTEXT "Wait (5 - 200 msec)", IDC_STATIC, 12, 24, 72, 8 LTEXT "Speed (100 - 4000)", IDC_STATIC, 12, 48, 72, 8 LTEXT "Number (10 - 4000)", IDC_STATIC, 12, 72, 72, 8 LTEXT "Status", IDC_STATUS1, 7,120,275,13,SS_SUNKEN | WS_BORDER EDITTEXT IDC_WAITVALUE, 96, 24, 100, 16, ES_AUTOHSCROLL EDITTEXT IDC_SPEED, 96, 48, 100, 16, ES_AUTOHSCROLL EDITTEXT IDC_NUMBER, 96, 72, 100, 16, ES_AUTOHSCROLL AUTOCHECKBOX "FPS display", IDC_FPSDISPLAY, 12, 96, 53, 10 PUSHBUTTON "Reset", IDC_RESET, 76, 96, 50, 14 END
_Makefile
PROGRAM=01_dialogtest.exe SRC=$(PROGRAM:.exe=.c) OBJS=$(SRC:.c=.o) resource.o .PHONY: all all: $(PROGRAM) $(PROGRAM): $(OBJS) Makefile gcc $(OBJS) -mwindows -o $@ %.o: %.c gcc -O3 -o $@ -c $< resource.o: resource.rc Makefile windres $< -o $@ .PHONY: cleanall clean cleanall: clean rm -f $(PROGRAM) .PHONY: clean clean: rm -f $(OBJS)
◎ ビルドと実行結果 :
make と打ってビルドすると、01_dialogtest.exe が得られる。実行結果は以下のような感じ。設定ダイアログ上で設定した値を、メインウインドウ側で読めていることが分かる。
◎ 課題 :
ひとまず、MinGW を使っても、この手の処理を書くことができた。一部のコントロール(GU部品、ウィジェット)を使えない点がなんだか気になるけれど…。単に windres 2.28 (windres.exe) がサポートしてないのか、それとも書き方がおかしいのか。どちらが原因なのかは不明。
後は、先日実験した iniファイルの読み書き処理を追加すれば、スクリーンセーバの設定を設定ダイアログで変更、かつ、iniファイルに保存、といった処理も作れるのではないか、と…。
_mieki256's diary - C言語でiniファイルの読み書きをしたい
後は、先日実験した iniファイルの読み書き処理を追加すれば、スクリーンセーバの設定を設定ダイアログで変更、かつ、iniファイルに保存、といった処理も作れるのではないか、と…。
_mieki256's diary - C言語でiniファイルの読み書きをしたい
◎ 2024/01/17追記 :
MinGW + windres でも trackbar をリソースファイル内に記述する方法が分かった。
_2024/01/17の日記
にメモしておいた。
[ ツッコむ ]
#2 [anime] 「勇気爆発バーンブレイバーン」1話を視聴
録画していたので視聴。日本のロボットアニメの作画面に関しては第一人者とされている大張正己監督の作品。ロボットアニメ。制作はCygamesPictures。
Web上の感想を眺めていたら1話の最後のあたりでどうも皆さん衝撃を受けていたようで、これは一体何を見せられるのだろうとドキドキしながら視聴してみたら…。参りました。これはたしかに衝撃を受けるわ…。いやはや、そう来るか…。そうなっちゃうのか…。素晴らしい。
冒頭のバトルシーンを見て、ロボットのデザインに違和感があったのだよな…。大張監督の作風なら、こんな武骨(?)で特徴のない(?)なデザインはアリにしない印象だけど、もしや今作は今までと違う作風を模索していたりするのだろうか、などと想像していたら見事にやられた。参りました。
Web上の感想を眺めていたら1話の最後のあたりでどうも皆さん衝撃を受けていたようで、これは一体何を見せられるのだろうとドキドキしながら視聴してみたら…。参りました。これはたしかに衝撃を受けるわ…。いやはや、そう来るか…。そうなっちゃうのか…。素晴らしい。
冒頭のバトルシーンを見て、ロボットのデザインに違和感があったのだよな…。大張監督の作風なら、こんな武骨(?)で特徴のない(?)なデザインはアリにしない印象だけど、もしや今作は今までと違う作風を模索していたりするのだろうか、などと想像していたら見事にやられた。参りました。
[ ツッコむ ]
以上、1 日分です。