murnana's diary

プリントの裏に書くとか、そんな感じです

【Win32API】リソースで読み込んだダイアログプロシージャを、クラス化する方法

散々悩んでしまったのでメモ。 詳しい質問は答えられない可能性があります。あとはググってくだせぇ。

やりたいこと

  • リソースファイル(.rc)にあるダイアログを、プログラム上でクラスとして扱いたい。
  • ダイアログプロシージャがstaticなのは仕方ないが、中でメンバ変数を扱いたい
  • コンストラクタでダイアログを出し、その中で入力を受け付け、元のプログラムにもどる処理をしたい。

ダイアログには種類がある

ダイアログにはモーダルダイアログボックスとモードレスダイアログボックスがあります。違いは作成元プログラムが動くか動かないかです。

【モーダルダイアログ】 作成された途端、そのダイアログが閉じるまで、作成元プログラムがストップします。

作成: DialogBox 関数 DialogBoxParam 関数

終了: EndDialog 関数

【モードレスダイアログ】 ダイアログを動かしつつ、ダイアログ生成元も動く。

作成: CreateDialog 関数

終了: DestroyWindow 関数

今回は?

モーダルダイアログボックスを作成します。 また、リソースファイルに予めダイアログがあるとします。

ダイアログ作成時

インスタンス化と同時にダイアログを出すため、コンストラクタにてDialogBoxParam関数を呼びます。

// コンストラクタ内
int re = DialogBoxParam(
    インスタンスハンドル,
    MAKEINTRESOURCE(ダイアログIDマクロ),
    ウィンドウハンドル,
    ダイアログプロシージャ,
    (LPARAM)this);

インスタンスハンドルはWinMain関数にある引数と同じです。

ダイアログIDマクロは.rc内でダイアログ生成時に、ヘッダに用意されるマクロです。何もしていなければIDD_ホニャララになっています。

ウィンドウハンドルはこのダイアログウィンドウを管理するウィンドウを入れます。

ダイアログプロシージャはstaticなメンバ関数として用意します。

最後に自身を入れます。

返ってくる値はメンバとしてとっておきます。このとき、失敗すると-1になります。失敗した原因を知りたいときはGetLastError()関数を使うと良いでしょう。

この関数が呼ばれると、成功時ダイアログプロシージャに入ります。

ダイアログプロシージャにて

ちょっと面倒です。あっ…命名規則ばらばらだった…。

目的に応じて、中身は書き換えましょう。

INT_PTR CALLBACK クラス名::DigProc(HWND _window,UINT _message,WPARAM _w_param,LPARAM _l_param) {
    //_プロパティリストからthisポインタを取得
    クラス名* thisPtr =
        (クラス名 *)GetWindowLongPtr( _window, GWL_USERDATA );

    switch (_message)
    {
        // ダイアログ生成時
        case WM_INITDIALOG:
        {
            //_プロパティリストにオブジェクトハンドル(thisポインタ)を設定する
            thisPtr = (クラス名 *)_l_param;
            SetWindowLongPtr(_window,GWL_USERDATA,(LONG_PTR)thisPtr);

            return (INT_PTR)TRUE;
            break;
        }

        // ダイアログコマンド
        // ※.rcにボタンの記述があることが前提
        case WM_COMMAND:
        {
            if (LOWORD(_w_param) == IDOK || LOWORD(_w_param) == IDCANCEL)
            {
                EndDialog(_window, LOWORD(_w_param));
                return (INT_PTR)TRUE;
            }
            break;
        }

        default: break;
    }
    return (INT_PTR)FALSE;
}

メッセージ処理の前にthisポインタに当たるものを取得します。

しかしこのままでは不明(null)になっています。そこでWM_INITDIALOGメッセージ取得時*1にウィンドウプロパティとして、ポインタを登録します。SetWindowLongPtrがそれに当たります。

以降、GetWindowLongPtrから取得が可能です。

モーダルダイアログボックス抽象化へのヒント

実際に作ったわけじゃないですが参考までに。 うん、でもできるという保証はないんだ。すまない。

  • コンストラクタの引数をインスタンスハンドル・ウィンドウハンドル・ダイアログIDにして作成
  • staticメンバ関数としてダイアログプロシージャを作成し、ダイアログ作成関数に入れる
  • 純粋仮想関数として、ダイアログプロシージャのそっくりさんを作成。派生先で使用。
  • staticなダイアログプロシージャ内で、thisPtr確定後、そっくりさんを呼ぶ

参考

*1:ダイアログ生成時に一度だけ呼ばれる。ウィンドウズプロシージャ的にはWM_CREATEに当たる