2024/01/18(木) [n年前の日記]
#1 [prog] トラックバーの横に数値入力欄をつけて連動できるか試した
_昨日、
トラックバー/スライダーだけでは値の指定が難しい場面もあるのではないか、数値入力欄やスピンボックスも必要になるのではないかと考えてしまったのだけど。実装はどのくらい大変なのか、ちょっと気になってしまったので、そのあたりを試してみた。
環境は、Windows10 x64 22H2 + Visual Studio Community 2019。
ダイアログ上では、トラックバー("msctls_trackbar32")、数値入力欄(EDITTEXT)、スピンコントロール(UpDownコントロール?、"msctls_updown32")を横に並べて配置した。これらを連動させないといけない…。
環境は、Windows10 x64 22H2 + Visual Studio Community 2019。
ダイアログ上では、トラックバー("msctls_trackbar32")、数値入力欄(EDITTEXT)、スピンコントロール(UpDownコントロール?、"msctls_updown32")を横に並べて配置した。これらを連動させないといけない…。
- スピンコントロールを操作すると、数値入力欄の数値が変化する。
- 数値入力欄の数値が変化したら、トラックバーのバーの位置が変化する。
- トラックバーを動かしたら、数値入力欄の数値が変化する。
◎ 実行結果 :
先に実行結果を見せておく。以下のような動作になった。どれも連動しているように見える。
◎ ソース :
とりあえず動いてるように見えるので、一応ソースを置いておく。このやり方で正しいのかどうかは分からんけど…。
_resource.h
以下のリソースファイル内の IDD_DIALOG1 のあたりが、ダイアログの指定。
_trackbarandspinbox.rc
_trackbarandspinbox.cpp
_framework.h
_targetver.h
_trackbarandspinbox.h
_resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ で生成されたインクルード ファイル。 // trackbarandspinbox.rc で使用 // #define IDC_MYICON 2 #define IDD_TRACKBARANDSPINBOX_DIALOG 102 #define IDS_APP_TITLE 103 #define IDC_TRACKBARANDSPINBOX 109 #define IDR_MAINFRAME 128 #define IDD_DIALOG1 129 #define IDC_SLIDER1 1000 #define IDC_EDIT1 1001 #define IDC_SPIN1 1002 #define IDC_STATIC -1 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 130 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1003 #define _APS_NEXT_SYMED_VALUE 110 #endif #endif
以下のリソースファイル内の IDD_DIALOG1 のあたりが、ダイアログの指定。
_trackbarandspinbox.rc
// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #ifndef APSTUDIO_INVOKED #include "targetver.h" #endif #define APSTUDIO_HIDDEN_SYMBOLS #include "windows.h" #undef APSTUDIO_HIDDEN_SYMBOLS ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // 日本語 (日本) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOG1 DIALOGEX 0, 0, 325, 66 STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Dialog" FONT 11, "Segoe UI", 400, 0, 0x0 BEGIN DEFPUSHBUTTON "OK",IDOK,204,42,50,14 PUSHBUTTON "Cancel",IDCANCEL,261,42,50,14 CONTROL "",IDC_SLIDER1,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_TOOLTIPS | WS_TABSTOP,36,18,188,12 EDITTEXT IDC_EDIT1,225,18,54,12,ES_AUTOHSCROLL | ES_NUMBER CONTROL "",IDC_SPIN1,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS,280,18,9,12 END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO BEGIN IDD_DIALOG1, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 318 TOPMARGIN, 7 BOTTOMMARGIN, 59 END END #endif // APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#ifndef APSTUDIO_INVOKED\r\n" "#include ""targetver.h""\r\n" "#endif\r\n" "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" "#include ""windows.h""\r\n" "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // String Table // STRINGTABLE BEGIN IDS_APP_TITLE "trackbarandspinbox" IDC_TRACKBARANDSPINBOX "TRACKBARANDSPINBOX" END #endif // 日本語 (日本) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
_trackbarandspinbox.cpp
// trackbarandspinbox.cpp // // trackbar + spinbox(edit + spin control) sample #include <Windows.h> #include <CommCtrl.h> #include "framework.h" #include "trackbarandspinbox.h" #define MAX_LOADSTRING 100 #define IDC_BUTTON_OPENDIALOG 110 // グローバル変数: HINSTANCE hInst; // 現在のインターフェイス WCHAR szTitle[MAX_LOADSTRING]; // タイトル バーのテキスト WCHAR szWindowClass[MAX_LOADSTRING]; // メイン ウィンドウ クラス名 static int number = 1000; // このコード モジュールに含まれる関数の宣言を転送します: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM); // entry point int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: ここにコードを挿入してください。 // グローバル文字列を初期化する LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_TRACKBARANDSPINBOX, szWindowClass, MAX_LOADSTRING); // ウインドウクラスを登録 { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = NULL; // icon無し wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = NULL; // menu無し wcex.lpszClassName = szWindowClass; wcex.hIconSm = NULL; // icon無し RegisterClassExW(&wcex); } // メインウインドウ生成 { hInst = hInstance; HWND hWnd = CreateWindowW( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, // x, y 320, 180, // width, height nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); } MSG msg; // メイン メッセージ ループ: while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } // // 関数: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: メイン ウィンドウのメッセージを処理します。 // // WM_COMMAND - アプリケーション メニューの処理 // WM_PAINT - メイン ウィンドウを描画する // WM_DESTROY - 中止メッセージを表示して戻る // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // メインウインドウ生成時の処理 // ”Open Dialog" ボタンを配置 CreateWindow( L"BUTTON", L"Open Dialog", WS_CHILD | WS_VISIBLE, 20, 20, 100, 24, // x, y, width, height hWnd, (HMENU)IDC_BUTTON_OPENDIALOG, // ボタンに割り当てるID hInst, NULL ); break; case WM_COMMAND: { int wmId = LOWORD(wParam); switch (wmId) { case IDC_BUTTON_OPENDIALOG: // "Open Dialog"ボタンが押されたのでダイアログを表示 DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc); InvalidateRect(hWnd, NULL, TRUE); // これを入れないとメインウインドウが再描画されない break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: // メインウインドウ描画処理 { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: HDC を使用する描画コードをここに追加してください... // 設定値を描画 { wchar_t szBuf[1024]; swprintf_s(szBuf, sizeof(szBuf) / sizeof(wchar_t), L"number=%d", number); TextOut(hdc, 20, 70, szBuf, lstrlen(szBuf)); } EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { static HWND hEdit1, hSlider1, hSpin1; int n; BOOL success; UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: // ダイアログ生成時の処理 hSlider1 = GetDlgItem(hDlg, IDC_SLIDER1); hEdit1 = GetDlgItem(hDlg, IDC_EDIT1); hSpin1 = GetDlgItem(hDlg, IDC_SPIN1); // SpinBox(UpDOwnコントロール)を設定 SendMessage(hSpin1, UDM_SETBUDDY, (WPARAM)hEdit1, 0); // EDITTEXTと連動 SendMessage(hSpin1, UDM_SETRANGE, (WPARAM)0, (LPARAM)MAKELONG(4000, 10)); // 値の範囲を設定 SendMessage(hSpin1, UDM_SETPOS, 0, (LPARAM)number); // 現在の値を設定 // trackbarを設定 SendMessage(hSlider1, TBM_SETRANGE, TRUE, (LPARAM)MAKELONG(10, 4000)); // 値の範囲を設定 SendMessage(hSlider1, TBM_SETTICFREQ, 500, 0); // 目盛りの間隔を設定 SendMessage(hSlider1, TBM_SETPOS, TRUE, (LPARAM)number); // 現在の値を設定 return (INT_PTR)TRUE; case WM_HSCROLL: // trackbarが操作された if ((HWND)(lParam) == hSlider1) { int n; n = SendMessage(hSlider1, TBM_GETPOS, 0, 0); // get trackbar pos SendMessage(hSpin1, UDM_SETPOS, 0, (LPARAM)n); // set SpiBox pos } return (INT_PTR)TRUE; case WM_COMMAND: // ダイアログからコマンドを受け取る switch (LOWORD(wParam)) { case IDOK: // OKボタンが押された n = GetDlgItemInt(hDlg, IDC_EDIT1, &success, FALSE); if (success) number = n; EndDialog(hDlg, LOWORD(wParam)); // ダイアログを終了 return (INT_PTR)TRUE; case IDCANCEL: // Cancelボタンが押された EndDialog(hDlg, LOWORD(wParam)); // ダイアログを終了 return (INT_PTR)TRUE; case IDC_EDIT1: // EDIT欄が変更された n = GetDlgItemInt(hDlg, IDC_EDIT1, &success, FALSE); // get edit int value if (success) SendMessage(hSlider1, TBM_SETPOS, TRUE, (LPARAM)n); // set trackbar pos return (INT_PTR)TRUE; default: break; } break; } return (INT_PTR)FALSE; }
_framework.h
_targetver.h
_trackbarandspinbox.h
◎ 少し解説 :
ダイアログプロシージャ、DlgProc() の中を見れば、やってることが分かるかなと…。
スピンコントロール(UpDownコントロール)は、どの入力欄(EDITTEXT)と連動させるかを指定できる。SendMessage() に UDM_SETBUDDY を指定することで、連動させるコントロールを設定する。ただ、その場合、リソースファイル(.rc)内のスピンコントロールについては、以下のスタイルを指定しておかないといけない。UDS_SETBUDDYINT というのが、他のコントロールと連動させるための指定だろうか。
スピンコントロールは、値が変化する範囲を指定できる。このあたり、トラックバーとよく似ている。
更に、スピンコントロールは、現在の値を保持している。これもまた、トラックバーと似ている。
つまり、トラックバーとスピンコントロールは、見た目は違うけれど、内部的に持っているものがほぼ同じ、似たもの同士と言えそう。
トラックバー(横向き)が操作されると、WM_HSCROLL というメッセージが送られてくる。ちなみに縦向きのトラックバーなら、WM_VSCROLL が送られてくるらしい。
その際、lParam には、そのメッセージを送ってきたトラックバーのハンドルが入っているので、そのハンドルを利用して対象となるトラックバーを絞り込んで(?)、トラックバーの現在値を、SendMessage() を使って取得することができる。
トラックバーの現在値が得られたら、それをスピンコントロールの現在値として設定してやれば、入力欄(EDITTEXT)の表示も自動的にその値になってくれる。これで、トラックバーの操作を入力欄に反映させることはできた。本当にこのやり方でいいのか分からんけど、一応それっぽく動いてる。
スピンコントロールの2つのボタンをクリックすると、入力欄(EDITTEXT)上の値も変化する。あるいは、入力欄にカーソルを合わせて、直接数値を打ち込んでもいいけど…。何にせよ、入力欄の値が変化すると、WM_COMMAND と、その入力欄のIDが送られてくる。入力欄の数値を int として読み取れたら、その値をトラックバーの現在値に設定してやる。これで、入力欄の操作や、スピンコントロールの操作を、トラックバーにも反映させることができた。本当にこのやり方でいいのか分からんけど。
そんな感じで…。
しかし…。一々こんな風に書かないといけないものだろうか?
巷のアプリは、もっと膨大な数のトラックバーやスピンボックスがずらりと並んでいると思うのだけど。その一つ一つにこんな処理を毎回書いていくのは、さすがに大変なのでは…。何かもっと上手いやり方、便利な方法が用意されてそうな気がする…。
スピンコントロール(UpDownコントロール)は、どの入力欄(EDITTEXT)と連動させるかを指定できる。SendMessage() に UDM_SETBUDDY を指定することで、連動させるコントロールを設定する。ただ、その場合、リソースファイル(.rc)内のスピンコントロールについては、以下のスタイルを指定しておかないといけない。UDS_SETBUDDYINT というのが、他のコントロールと連動させるための指定だろうか。
UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS
スピンコントロールは、値が変化する範囲を指定できる。このあたり、トラックバーとよく似ている。
更に、スピンコントロールは、現在の値を保持している。これもまた、トラックバーと似ている。
つまり、トラックバーとスピンコントロールは、見た目は違うけれど、内部的に持っているものがほぼ同じ、似たもの同士と言えそう。
トラックバー(横向き)が操作されると、WM_HSCROLL というメッセージが送られてくる。ちなみに縦向きのトラックバーなら、WM_VSCROLL が送られてくるらしい。
その際、lParam には、そのメッセージを送ってきたトラックバーのハンドルが入っているので、そのハンドルを利用して対象となるトラックバーを絞り込んで(?)、トラックバーの現在値を、SendMessage() を使って取得することができる。
トラックバーの現在値が得られたら、それをスピンコントロールの現在値として設定してやれば、入力欄(EDITTEXT)の表示も自動的にその値になってくれる。これで、トラックバーの操作を入力欄に反映させることはできた。本当にこのやり方でいいのか分からんけど、一応それっぽく動いてる。
スピンコントロールの2つのボタンをクリックすると、入力欄(EDITTEXT)上の値も変化する。あるいは、入力欄にカーソルを合わせて、直接数値を打ち込んでもいいけど…。何にせよ、入力欄の値が変化すると、WM_COMMAND と、その入力欄のIDが送られてくる。入力欄の数値を int として読み取れたら、その値をトラックバーの現在値に設定してやる。これで、入力欄の操作や、スピンコントロールの操作を、トラックバーにも反映させることができた。本当にこのやり方でいいのか分からんけど。
そんな感じで…。
- スピンコントロールの操作を、入力欄に反映。
- 入力欄の操作を、トラックバーに反映。
- トラックバーの操作を、スピンコントロールに反映。
しかし…。一々こんな風に書かないといけないものだろうか?
巷のアプリは、もっと膨大な数のトラックバーやスピンボックスがずらりと並んでいると思うのだけど。その一つ一つにこんな処理を毎回書いていくのは、さすがに大変なのでは…。何かもっと上手いやり方、便利な方法が用意されてそうな気がする…。
◎ 余談。入力欄の種類 :
巷の解説ページを見ていて気付いたけれど、入力欄(EDITTEXT)に、ES_NUMBER というスタイル指定を追加してやれば、その入力欄は数字しか受け付けない/アルファベットを打っても拒否してくれる入力欄になってくれるっぽい。数値しか扱わない EDITTEXT にしたい場合は指定したほうが良さそう。
◎ 参考ページ :
[ ツッコむ ]
以上です。