mieki256's diary



2024/01/18(木) [n年前の日記]

#1 [prog] トラックバーの横に数値入力欄をつけて連動できるか試した

_昨日、 トラックバー/スライダーだけでは値の指定が難しい場面もあるのではないか、数値入力欄やスピンボックスも必要になるのではないかと考えてしまったのだけど。実装はどのくらい大変なのか、ちょっと気になってしまったので、そのあたりを試してみた。

環境は、Windows10 x64 22H2 + Visual Studio Community 2019。

ダイアログ上では、トラックバー("msctls_trackbar32")、数値入力欄(EDITTEXT)、スピンコントロール(UpDownコントロール?、"msctls_updown32")を横に並べて配置した。これらを連動させないといけない…。

実行結果 :

先に実行結果を見せておく。以下のような動作になった。どれも連動しているように見える。

ソース :

とりあえず動いてるように見えるので、一応ソースを置いておく。このやり方で正しいのかどうかは分からんけど…。

_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 というのが、他のコントロールと連動させるための指定だろうか。
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 にしたい場合は指定したほうが良さそう。

参考ページ :

#2 [prog] iniファイルが妙な場所に作られてしまって悩んだ

C/C++を使った場合のiniファイルの読み書きの仕方も分かったし、設定ダイアログ上の値を読み書きする方法も分かったので、先日作成した「宇宙飛行」スクリーンセーバモドキのOpenGL描画プログラムをスクリーンセーバ化しようと作業を始めたのだけど…。

C:\Window\SysWOW64\ 以下に、スクリーンセーバのプログラム、.scr と、設定ファイル、.ini をコピーして、スクリーンセーバから .ini に設定を書き込もうとしたものの、.ini の内容が全く更新されなくて悩んでしまった。

スクリーンセーバの設定値は、たしかに変更されていて、設定ダイアログを開いても設定値がちゃんと反映されているのに、.ini の内容は以前と変わってない。何故。

試しに .ini ファイルを削除してみたけれど、その後もまるで .ini があるかのように動作してしまう。設定した値も読み取れている。しかし .ini ファイルは存在していない…。どういうこと…?

おそらくはどこか謎の場所に .ini があって、それを読み書きしているのだろうけど、一体どの場所に存在しているのか…。

Cドライブ内を検索しまくったところ、何故か以下のフォルダに .ini があった…。
C:\Users\(USERNAME)\AppData\Local\VirtualStore\Windows\SysWOW64\ssstars_opengl.ini

VirtualStoreのせいだった :

ググってみたところ、Windows Vista以降は、各種権限が厳しくなってシステム絡みのフォルダに書き込みができなくなっていたそうで。しかしそれでは昔のアプリが動かなくなってしまうので、VirtualStore という機能?を追加して、そちらを読み書きすることでどうにかしていたらしい…。

_Windows の VirtualStore 機能 - ラボラジアン
_VirtualStoreにやられた。。。 - ak days

C:\Windows\SysWOW64\ 内の .ini を読み書きしているつもりが、実際には、ユーザーフォルダ\AppData\Local\VirtualStore\ 内の .ini を読み書きしていたのだな…。だから C:\Windows\SysWOW64\ 以下の .ini を削除しても、何事もなかったように動いたわけで…。

さて、どうしたもんか。どうせユーザフォルダ内の VirtualStore\*.ini ファイルにアクセスしているなら、最初からユーザフォルダ内に設定ファイルを保存して読み書きするようにしてしまったほうがいいよな…。

となると、ユーザフォルダのPATHを取得する方法、AppData\Roaming\ 以下にスクリーンセーバ名でディレクトリを作る方法について調べないと…。

いや、本当にそれでいいのだろうか。もしユーザ名が日本語だった場合、正常にPATHを取得して読み書きできるのだろうか。それに、ユーザフォルダ内に謎の設定ファイルが勝手に作られるスクリーンセーバもなんだか怖くないか。アンインストールしたい場合はどうすればいいのか。

そう考えると、C:\Windows\SysWOW64\ 内に設定ファイルもあるものとして作っておいて、Windows Vista以降はここに設定ファイルがありますよとドキュメントで一言触れておくだけのほうがマシだったりしないか…。このあたり、どの方針が妥当なのだろう。

#3 [prog] スクリーンセーバのフレームレートが安定しない

C/C++ と OpenGL でWindows用スクリーンセーバを作っているところだけど、何故かフレームレートが安定しない。

スクリーンセーバ設定ウインドウを表示して、「プレビュー」をクリックした時は、60FPSで動いてくれるのだけど。実際に時間が経ってスクリーンセーバとして呼び出されると、40 - 50FPS で描画されてしまう。おそらくはバッググラウンドで何かがせっせと動いていて負荷がかかっているか、もしくはアイドル状態になるとCPUその他が省電力に切り替わってパフォーマンスが出ないのではないかと想像しているけれど…。

何にせよ、処理落ちっぽい見た目になって動きがギクシャクしてしまうので、前回のフレームと今回のフレームの時間差をミリ秒で取得してオブジェクトの速度に反映させてしまうことにした。時間取得は timeGetTime() を利用。1000 で割って、0.xxxx の値にして、1秒間あたりの移動量に掛けてやる。この値をそのフレームの移動速度にする。

_【Windows】実行時間の計測 #Windows - Qiita
_C/C++ programming technique @ catfish
_C言語/C++ 処理時間計測 入門

これで多少はマシになった。まだちょっとギクシャク感はあるけれど、明らかな処理落ち状態よりマシだろう…。

以上、1 日分です。

過去ログ表示

Prev - 2024/01 - Next
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project