mieki256's diary



2024/01/15(月) [n年前の日記]

#1 [prog] C/C++で設定ダイアログを表示してみたい

C/C++ と OpenGL を使ってWindows用のスクリーンセーバを作りたい。えてしてWindows用のスクリーンセーバは、設定ダイアログを表示して、動作を多少変更することができたりする。

そんなわけで、C/C++ を使って設定ダイアログを表示する方法について調べていた。
一般的なスクリーンセーバの場合、設定ダイアログ上でGUI部品(コントロール、ウィジェットとも呼ばれる)を配置するには、リソースファイル(拡張子が .rc)を使う。
_ダイアログ(Win32API)(C言語) - 超初心者向けプログラミング入門

また、設定ダイアログ上のGUI部品を操作するための関数も用意されていた。

一応、Visual Studio Community 2019 上で試したところ、それらしく動くプログラムを作れたのだけど…。これを MinGW (gcc 6.3.0) で再現しようとしたら、リソースコンパイラ windres.exe が syntax error を吐いて、なかなか上手く行かない…。windres.exe では、msctls_trackbar32 (trackbar、slider) は指定できないのだろうか…? ググっても情報が全く出てこないし…。

Visual Studio版 :

とりあえず、Visual Studio Community 2019上で動作したソースを載せておく。

どういう動作をするサンプルかというと…。
  • メインウインドウ上の "Open Dialog" ボタンを押すと、ダイアログが開く。(リソースファイルで定義したダイアログを開けている)
  • ダイアログ上で値を変更して "OK" ボタンを押すと、メインウインドウ上の値の表示も変わる。(ダイアログ上で設定した値を読み取れている。)

_dialogtest01.cpp
#include <Windows.h>
#include <wchar.h>
#include <stdlib.h>
#include <tchar.h>
#include <CommCtrl.h>

#include "framework.h"
#include "DialogTest01.h"

#define MAX_LOADSTRING 100

#define IDC_BUTTON_OPENDLG 110

// global work
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];                  // title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // main window class name

static WCHAR 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
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // init global sstring
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_DIALOGTEST01, szWindowClass, MAX_LOADSTRING);

    // Register window class
    {
        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 = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DIALOGTEST01));
        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

        wcex.lpszMenuName = nullptr;        // set menu

        wcex.lpszClassName = szWindowClass;
        wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

        RegisterClassExW(&wcex);
    }

    // init application. create main window
    {
        hInst = hInstance;

        HWND hWnd = CreateWindowW(
            szWindowClass,
            szTitle,
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT,   // x, y
            320, 240,                       // width, height
            nullptr,
            nullptr,
            hInstance,
            nullptr
        );

        if (!hWnd) return FALSE;

        ShowWindow(hWnd, nCmdShow);     // display main window
        UpdateWindow(hWnd);
    }

    MSG msg;

    // main message loop
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        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

        // create "Open Dialog" button
        CreateWindow(
            L"BUTTON", L"Open Dialog",
            WS_CHILD | WS_VISIBLE,
            20, 20, 100, 24,        // x, y, width, height
            hWnd, (HMENU)IDC_BUTTON_OPENDLG, hInst, NULL
        );
        break;

    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        switch (wmId)
        {
        case IDC_BUTTON_OPENDLG:
        {
            // push "Open Dialog" button

            // Open dialog
            int ret;
            ret = DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc);

            // dialog results
            switch (ret)
            {
            case IDOK:
                // push "OK" button
                wcscpy_s(txt, L"IDOK");         // set text message
                break;

            case IDCANCEL:
                // push "Cancel" button
                wcscpy_s(txt, L"IDCANCEL");     // set text message
                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
        TextOut(hdc, 20, 70, txt, lstrlen(txt));

        // draw paramater value
        {
            WCHAR s[256];

            swprintf_s(s, L"wait=%d", waitValue);
            TextOut(hdc, 20, 90, s, lstrlen(s));

            swprintf_s(s, L"speed=%d", speedValue);
            TextOut(hdc, 20, 110, s, lstrlen(s));

            swprintf_s(s, L"number=%d", numberValue);
            TextOut(hdc, 20, 130, s, lstrlen(s));

            swprintf_s(s, L"fps_display=%d", fps_display);
            TextOut(hdc, 20, 150, s, lstrlen(s));
        }

        EndPaint(hWnd, &ps);
    }
    break;

    case WM_DESTROY:
        // destroy main window
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// dialog procedure
INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case WM_INITDIALOG:
    {
        // initialize dialog
        HWND cHwnd;

        // wait slider (trackbar)
        cHwnd = GetDlgItem(hDlg, IDC_SLIDER1);      // get hundle
        SendMessage(cHwnd, TBM_SETRANGE, TRUE, MAKELPARAM(5, 200)); // set range
        SendMessage(cHwnd, TBM_SETTICFREQ, 5, 0);                   // set tick
        SendMessage(cHwnd, TBM_SETPOS, TRUE, (LPARAM)waitValue);    // set position

        // speed slider
        cHwnd = GetDlgItem(hDlg, IDC_SLIDER2);
        SendMessage(cHwnd, TBM_SETRANGE, TRUE, MAKELPARAM(100, 4000));
        SendMessage(cHwnd, TBM_SETTICFREQ, 100, 0);
        SendMessage(cHwnd, TBM_SETPOS, TRUE, (LPARAM)speedValue);

        // number slider
        cHwnd = GetDlgItem(hDlg, IDC_SLIDER3);
        SendMessage(cHwnd, TBM_SETRANGE, TRUE, MAKELPARAM(10, 4000));
        SendMessage(cHwnd, TBM_SETTICFREQ, 100, 0);
        SendMessage(cHwnd, TBM_SETPOS, TRUE, (LPARAM)numberValue);

        // init checkbox
        SendMessage(GetDlgItem(hDlg, IDC_CHECK1), BM_SETCHECK,
            ((fps_display == 0) ? BST_UNCHECKED : BST_CHECKED), 0);
    }
    return (INT_PTR)TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            // push "OK" button
            // get paramater and set global work

            waitValue = SendMessage(GetDlgItem(hDlg, IDC_SLIDER1), TBM_GETPOS, NULL, NULL);
            speedValue = SendMessage(GetDlgItem(hDlg, IDC_SLIDER2), TBM_GETPOS, NULL, NULL);
            numberValue = SendMessage(GetDlgItem(hDlg, IDC_SLIDER3), TBM_GETPOS, NULL, NULL);

            if (SendMessage(GetDlgItem(hDlg, IDC_CHECK1), BM_GETCHECK, 0, 0) == BST_UNCHECKED)
                fps_display = 0;
            else
                fps_display = 1;

            EndDialog(hDlg, IDOK);
            return (INT_PTR)TRUE;

        case IDCANCEL:
            // push "Cancel" button
            EndDialog(hDlg, IDCANCEL);
            return (INT_PTR)TRUE;

        case IDC_BUTTON_RESET:
        {
            // push "Reset" button

            // change status text
            SetWindowText(GetDlgItem(hDlg, IDC_STATUS1), TEXT("Reset"));

            // set slider position
            SendMessage(GetDlgItem(hDlg, IDC_SLIDER1), TBM_SETPOS, TRUE, 15);
            SendMessage(GetDlgItem(hDlg, IDC_SLIDER2), TBM_SETPOS, TRUE, 1000);
            SendMessage(GetDlgItem(hDlg, IDC_SLIDER3), TBM_SETPOS, TRUE, 1000);

            // change checkbox
            SendMessage(GetDlgItem(hDlg, IDC_CHECK1), BM_SETCHECK, BST_CHECKED, 0);
        }
        return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

_resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ で生成されたインクルード ファイル。
// DialogTest01.rc で使用
//
#define IDC_MYICON                      2
#define IDD_DIALOGTEST01_DIALOG         102
#define IDS_APP_TITLE                   103
#define IDI_DIALOGTEST01                104
#define IDI_SMALL                       105
#define IDC_DIALOGTEST01                106
#define IDR_MAINFRAME                   128
#define IDD_DIALOG1                     129
#define IDC_CHECK1                      1000
#define IDC_BUTTON_RESET                1001
#define IDC_SLIDER1                     1002
#define IDC_SLIDER2                     1003
#define IDC_SLIDER3                     1004
#define IDC_STATUS1                     1005
#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         1007
#define _APS_NEXT_SYMED_VALUE           110
#endif
#endif

_dialogtest01.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

/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_DIALOGTEST01        ICON                    "DialogTest01.ico"

IDI_SMALL               ICON                    "small.ico"


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_DIALOG1 DIALOGEX 0, 0, 289, 140
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
 FONT 11, "Segoe UI", 400, 0, 0x0
BEGIN
    DEFPUSHBUTTON   "&OK",IDOK,168,96,50,14
    PUSHBUTTON      "&Cancel",IDCANCEL,228,96,50,14
    PUSHBUTTON      "Reset",IDC_BUTTON_RESET,84,96,50,14
    CONTROL         "",IDC_SLIDER1,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_TOOLTIPS | WS_TABSTOP,96,24,180,15
    CONTROL         "",IDC_SLIDER2,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_TOOLTIPS | WS_TABSTOP,96,48,180,15
    CONTROL         "",IDC_SLIDER3,"msctls_trackbar32",TBS_AUTOTICKS | TBS_BOTH | TBS_TOOLTIPS | WS_TABSTOP,96,72,180,15
    CONTROL         "FPS display",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,96,53,10
    CTEXT           "ssstars GL setting",IDC_STATIC,7,7,275,8
    LTEXT           "Wait (5 - 200msec)",IDC_STATIC,12,24,63,8
    LTEXT           "Speed (100 - 4000)",IDC_STATIC,12,48,64,8
    LTEXT           "Number (10 - 4000)",IDC_STATIC,12,72,66,8
    LTEXT           "Status",IDC_STATUS1,7,120,275,13,SS_SUNKEN | WS_BORDER
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_ABOUTBOX, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 162
        TOPMARGIN, 7
        BOTTOMMARGIN, 55
    END

    IDD_DIALOG1, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 282
        TOPMARGIN, 7
        BOTTOMMARGIN, 133
    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           "DialogTest01"
    IDC_DIALOGTEST01        "DIALOGTEST01"
END

#endif    // 日本語 (日本) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

_dialogtest01.h
_framework.h

ビルドして実行すると以下のような見た目になる。

ss_dialogtest01.png

分かったことをメモ :

試してみて分かったことをメモ。

リソースファイルの記述内容に基づいてダイアログを表示するには、DialogBox() を使う。

_第13章 ダイアログボックス
_DialogBox関数でリソースからモーダルダイアログを作る | ぬの部屋(仮)

DialogBox() には、リソースファイル内でダイアログ関係を定義してある部分のID(今回のソースなら、IDD_DIALOG1)と、ダイアログプロシージャを指定して呼び出す。これだけでダイアログが表示される。

ダイアログプロシージャ(今回は DlgProc() が担当)は、ダイアログ上でユーザが何か操作をしたときに呼ばれる関数、らしい。たぶん。

ダイアログプロシージャが呼ばれた際は、ダイアログが今どんな状態なのかをメッセージとして送ってくる。
  • WM_INITDIALOG ... ダイアログが表示された際に一度だけ呼ばれる。ダイアログ上の部品の値等を初期化したい時はここで行う。
  • WM_COMMAND ... ボタン等が押された時に呼ばれる。一緒に送られてきたIDを見て、どのボタンが押されたのかを判別して処理を書ける。

ダイアログ上のGUI部品の値を設定したり、値を読み取ったりするときは、SendMessage() その他を使う。get したいのか、set したいのかを指定することで、値を読んだり、値を変更したりできる。また、SendMessage() 以外にも、特定の種類のGUI部品に対して簡単に操作できる関数が用意されていたりもする。(EDITTEXT に対する GetDlgItemText(), SetDlgItemText() とか。)

SendMessage() には、どのGUI部品を対象にしたいのか、ハンドルなるものを渡して教えてやらないといけない。 GUI部品のハンドルは、GetDlgItem() で取得できる。

_リソースから作成したダイアログボックスのコントロールへアクセスする | ぬの部屋(仮)

ダイアログ上にtrackbar(slide) を出したい時は、"msctls_trackbar32" を指定してやる。コレを使う時は、.c/.cpp 内で、commctrl.h を include してやる必要がある。そうしないと、TBM_* というシンボルが使えない。

_トラックバー - インコのWindowsSDK

MinGW版でハマってる :

ということで、Visual Studio上では動いてくれたけど、MinGW (gcc 6.3.0) では上手く行かない…。リソースコンパイラ windres.exe が syntax error を出す。

渡してる resource.rc は以下。

/* resource */

#include <windows.h>
#include <commctrl.h>
#include "resource.h"

/* Icon */
IDI_APP_ICON    ICON    "appli.ico"
IDI_SMALL       ICON    "small.ico"

/* Dialog */
IDD_DIALOG1 DIALOG DISCARDABLE  0, 0, 289, 140
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
 FONT 11, "Segoe UI"
BEGIN
  DEFPUSHBUTTON "OK",      IDOK,      168, 96, 50, 14
  PUSHBUTTON    "Cancel",  IDCANCEL,  228, 96, 50, 14
  PUSHBUTTON    "Reset",   IDC_RESET, 10, 100, 50, 14
  AUTOCHECKBOX  "FPS display", IDC_FPSDISPLAY, 12, 96, 53, 10
  CTEXT "ssstars GL setting", IDC_STATIC, 7,7,275,8
  LTEXT "Wait (5 - 200msec)", IDC_STATIC, 12,24,63,8
  LTEXT "Speed (100 - 4000)", IDC_STATIC, 12,48,64,8
  LTEXT "Number (10 - 4000)", IDC_STATIC, 12,72,66,8
  LTEXT "Status", IDC_STATUS1, 7,120,275,13,SS_SUNKEN | WS_BORDER
  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
END

以下のあたりでエラーを出している。これは trackbar(slider?) を定義してる。"msctls_trackbar32" というのが、trackbar のはずだけど…。
  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

もしかすると、windres.exe は、このコントロール(GUI部品、ウィジェット)に未対応だったりするのだろうか…? 代わりに EDITTEXT を記述すると、あっさりビルドが通るし…。

以上、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