メニューアイテムのヘルプテキストの表示

メニューを表示させて、アイテムが選択されている状態にあるときに、そのアイテムの説明をウィンドウ下端のステータスバーに表示できたらいいですね。今回は、メニューアイテムのヘルプテキストをステータスバーに表示してみるようにします。

ウィンドウのサブクラス化による処理

一般に、メニューを開いている状態では、スクリプトの実行が止められてしまいます。そのため、その間に送信されてくる、選択メニューアイテムが変わったことを通知するメッセージが届いても、 hsgetmsg.dll 等を使ってスクリプトで処理をするということができませんでした。

そこで、この動作を行なうためには、ウィンドウをサブクラス化して、選択メニューアイテムが変わったことを通知するメッセージを処理することのできるウィンドウプロシージャに置き換えてやる必要があります。今回はこの方法を説明します。

ウィンドウメッセージの割り込み処理が実現されれば、これらの処理をスクリプト中で行なうことができるようになると思われますが、 HSP 2.55 の時点では、サブクラス化を行なわなくてはなりません。

WM_MENUSELECT メッセージの処理

メニューアイテムの選択状態が変わると、そのメニューを持つウィンドウ(オーナーウィンドウ)には WM_MENUSELECT メッセージが送信されます。

#define  WM_MENUSELECT    0x011F

WM_MENUSELECT
    uItem  = LOWORD( wParam );   // メニューアイテム
    uFlags = HIWORD( wParam );   // メニューフラグ
    hMenu  = (HMENU)lParam;      // メニューハンドル

uItem パラメータは、選択アイテムがサブメニューを持つ場合には選択アイテムの位置のインデックスが、そうでない場合(コマンドアイテムの場合)にはアイテムIDが指定されます。

uFlags パラメータは、選択アイテムの属性をあらわすフラグが指定されます。特に、選択アイテムがサブメニューを持つ場合には MF_POPUP メニューを含んでいます。

hMenu パラメータは、メニューハンドルが指定されます。

メニューが閉じられた場合には、 uFlags パラメータには 0xFFFF が、また uFlags パラメータには NULL が指定されるます。

シンプルモードステータスバーの利用

今回はメニューアイテムのヘルプテキストをステータスバーに表示します。それまでステータスバーに表示されていた内容を変えてしまわないようにするためには、シンプルモードステータスバーを利用するのが最適ですね。

メニューアイテムが選択された状態になったら、ステータスバーをシンプルモードに変更して、ヘルプテキストを設定します。そして、メニューが閉じられた時点で、ステータスバーを非シンプルモードに戻せばよいでしょう。

ステータスバーのシンプルモードについては、『シンプルモードステータスバー』の項を参照してください。

サンプルプログラム

スクリプト中で、メニューアイテムIDと対応するヘルプテキストをどのように保持していくかを考えてみましょう。最も簡単なのは、アイテムIDを格納する配列変数と、テキストを格納する文字列型配列変数を準備して、それぞれの要素で対応させるというものです。例えば、50個分のアイテムテキスト情報を格納するようにするには

    dim selID, 50
    sdim selText, 64, 50

というようにして、 selID.0 のアイテムIDを持つアイテムが選択されたときには selText.0 に格納された文字列がステータスバーに表示されるようにするのです。

今回はそのようにします。このとき必要となる情報は、まず、2つの変数のアドレスです。また、文字列変数の要素1つ分のサイズ(上のように書いた場合には 64 のこと)も必要な情報として渡さなければならないので注意しましょう。


まず、コード中に埋め込まなければならない情報について整理してみましょう。今回必要な情報は以下のようになります。

ここで、最後の「ステータスバーにテキストを表示する際の形式」というのは、 SB_SETTEXT メッセージの uType パラメータとして渡す値を示します。

いつもどおりに、構造体を定義して、必要な情報はすべてそれに格納してしまいましょう。必要となる情報を含む構造体を以下のように定義してみます。

typedef struct {
    LRESULT (WINAPI *pfnCallWindowProc)(WNDPROC,HWND,UINT,WPARAM,LPARAM);
    LRESULT (WINAPI *pfnSendMessage)(HWND,UINT,WPARAM,LPARAM);
    WNDPROC pfnOrgProc;
    int     nItemCount;
    UINT   *pItemID;
    LPSTR   pItemText;
    int     nTextSize;
    HWND    hStatus;
    UINT    uStatusType;
} MENUHELP_DATA;

関数のアドレスはこれまでどおり FARPROC 型で指定してもいいのですが、コンパイラによる型チェックを厳密に行なうために、関数型や引数の型なども記述してあります。


新しいウィンドウプロシージャのソースコードです。

ソースファイル subproc.c

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

typedef struct {
    LRESULT (WINAPI *pfnCallWindowProc)(WNDPROC,HWND,UINT,WPARAM,LPARAM);
    LRESULT (WINAPI *pfnSendMessage)(HWND,UINT,WPARAM,LPARAM);
    WNDPROC pfnOrgProc;
    int nItemCount;
    UINT *pItemID;
    LPSTR pItemText;
    int nTextSize;
    HWND hStatus;
    UINT uStatusType;
} MENUHELP_DATA;

LRESULT CALLBACK SubProc(
    HWND hWnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    int i;
    LPSTR pText;
    HWND hStatus;
    UINT nItemID, *pID, uSBType;
    LRESULT (WINAPI *pfnSendMessage)(HWND,UINT,WPARAM,LPARAM);
    MENUHELP_DATA *volatile pTemp = (MENUHELP_DATA *)0x11111111, *pData = pTemp;

    if (message == WM_MENUSELECT) {
        pfnSendMessage = pData->pfnSendMessage; // SendMessage 関数のアドレス
        hStatus = pData->hStatus;               // ステータスバーのハンドル

        if (HIWORD(wParam) == 0xFFFF && lParam == NULL) {
            // メニューが閉じられた場合
            // 非シンプルモードステータスバーにする
            pfnSendMessage(hStatus, SB_SIMPLE, FALSE, 0);
        } else {
            // メニューアイテムが選択されている場合
            // シンプルモードステータスバーにする
            pfnSendMessage(hStatus, SB_SIMPLE, TRUE, 0);
            // テキストが表示されていない状態にする
            uSBType = SB_SIMPLEID | pData->uStatusType;
            pfnSendMessage(hStatus, SB_SETTEXT, uSBType, (LPARAM)NULL);
            if ( !(HIWORD(wParam) & MF_POPUP) ) {
                // サブメニューを持たない(コマンドを持つ)アイテムの場合
                nItemID = LOWORD(wParam);   // アイテムID
                pID = pData->pItemID;
                for (i=0; i< pData->nItemCount; i++) {
                    if (pID[i] != nItemID)  continue;
                    // ステータスバーにテキスト設定
                    pText = pData->pItemText + pData->nTextSize * i;
                    pfnSendMessage(hStatus, SB_SETTEXT, uSBType, (LPARAM)pText);
                    break;
                }
            }
        }
    }
    return pData->pfnCallWindowProc(pData->pfnOrgProc, hWnd,
                            message, wParam, lParam);
}

埋め込まれる情報は、始めに定義した内部構造体(MENUHELP_DATA 構造体)のアドレス1つだけです。後でわかりやすいように、アドレスを 0x11111111 としておきます。


ソースコードをコンパイルして、マシン語コードを生成します。VC++で行なった場合には、以下のようなコードが取り出せます。(取り出されるコードはコンパイラの種類や最適化設定などにより異なります。)

xdim subproc, 52
subproc.0 = $83ec8b55, $565310ec, $f045c757, $11111111, $81f0758b, $011f0c7d
subproc.6 = $850f0000, $00000096, $8b10558b, $4e8b0446, $f045891c, $6610eac1
subproc.12 = $fffffa81, $89f44d89, $1375f855, $5d39db33, $530e7514, $04096853
subproc.18 = $ff510000, $3367ebd0, $016a53db, $00040968, $d0ff5100, $53207e8b
subproc.24 = $00ffcf81, $01bb0000, $57000004, $f475ff53, $f6f055ff, $7510f845
subproc.30 = $0c468b3d, $00fc6583, $1055b70f, $85104e8b, $892b7ec0, $4d8bf84d
subproc.36 = $741139f8, $fc45ff0e, $04f84583, $7cfc4539, $8b13ebed, $af0f1846
subproc.42 = $4603fc45, $53575014, $fff475ff, $75fff055, $1075ff14, $ff0c75ff
subproc.48 = $76ff0875, $5f16ff08, $c2c95b5e, $00000010

次に、「11 11 11 11」などとなっていた部分に正しい値(MENUHELP_DATA 構造体のアドレス)を格納しなければなりません。先頭からのオフセットを考えて、 memcpy 命令または ll_poke4 命令で格納します。上のコードの場合は、以下のようになります。

オフセット 仮の値 実際の値
$C (12) 11111111 MENUHELP_DATA 構造体のアドレス

サンプルスクリプト

では、スクリプトを書いてみることにしましょう。

メニューの作成が面倒なので、今回は hsgetmsg モジュール添付の menu.as で定義されているメニュー操作モジュール命令を使います。

    #include "llmod.as"
    #include "hsgetmsg.as"
    #include "menu.as"
    #include "xdim.as"

    #define WM_COMMAND     $111

    ; メニューアイテムIDを定義
    #define CMD_OPEN       $1001        ;「開く」
    #define CMD_SAVE       $1002        ;「保存」
    #define CMD_QUIT       $1003        ;「終了」
    #define CMD_MESSAGE    $1004        ;「メッセージ表示」

    ; メニューの作成
    set_menu : hmenu = stat             ; メニューバーの作成
    set_popupmenu : hPopup = stat       ; 「ファイル」メニュー
    add_menu  hPopup, "開く(&O)", CMD_OPEN
    add_menu  hPopup, "保存(&S)", CMD_SAVE
    add_menu  hPopup, ""
    add_menu  hPopup, "終了(&Q)", CMD_QUIT
    addsub_menu hmenu, "ファイル(&F)", hPopup
    set_popupmenu : hPopup = stat       ; 「ヘルプ」メニュー
    add_menu  hPopup, "メッセージ表示(&M)", CMD_MESSAGE
    addsub_menu hmenu, "ファイル(&F)", hPopup
    draw_menu                           ; メニュー再描画

    ; ステータスバーの作成
    dllproc "InitCommonControls", pm, 0, D_COMCTL
    pm = 0,0,0,0,$50000003,0,0
    _makewnd pm, "msctls_statusbar32"
    hStatus = pm.0                      ; ステータスバーのハンドル
    ; ステータスバーを3つのパーツに分ける
    partspos = 50, 120, -1              ; パーツの位置
    ; SB_SETPARTS メッセージ送信
    pm = hStatus, $404, 3
    getptr pm.3, partspos
    sendmsg pm
    ; パーツの表示テキストを設定
    sdim msgtext , 128, 3
    msgtext.0 = "パーツ1", "\tパーツ2", "\t\tパーツ3"
    repeat 3
        ; SB_SETTEXT メッセージ送信
        pm = hStatus, $401, cnt
        getptr pm.3, msgtext.cnt
        sendmsg pm
    loop

    ; hsgetmsg.dll によるウィンドウのサブクラス化
    set_subclass
    hwnd = stat                 ; HSPウィンドウのハンドル
    set_message WM_COMMAND      ; 取得メッセージ設定
    ; メッセージパラメータ用変数
    dup msg,  msgval.1          ; メッセージが格納される変数
    dup wprm, msgval.2          ; wParamパラメータが格納される変数
    dup lprm, msgval.3          ; lParamパラメータが格納される変数

    ; 新しいウィンドウプロシージャのコード
    xdim subproc, 52
    subproc.0 = $83ec8b55, $565310ec, $f045c757, $11111111, $81f0758b, $011f0c7d
    subproc.6 = $850f0000, $00000096, $8b10558b, $4e8b0446, $f045891c, $6610eac1
    subproc.12 = $fffffa81, $89f44d89, $1375f855, $5d39db33, $530e7514, $04096853
    subproc.18 = $ff510000, $3367ebd0, $016a53db, $00040968, $d0ff5100, $53207e8b
    subproc.24 = $00ffcf81, $01bb0000, $57000004, $f475ff53, $f6f055ff, $7510f845
    subproc.30 = $0c468b3d, $00fc6583, $1055b70f, $85104e8b, $892b7ec0, $4d8bf84d
    subproc.36 = $741139f8, $fc45ff0e, $04f84583, $7cfc4539, $8b13ebed, $af0f1846
    subproc.42 = $4603fc45, $53575014, $fff475ff, $75fff055, $1075ff14, $ff0c75ff
    subproc.48 = $76ff0875, $5f16ff08, $c2c95b5e, $00000010

    ; メニューアイテムのヘルプテキスト
    menunum = 4
    dim selID, menunum
    sdim selText, 64, menunum
    selID = CMD_OPEN, CMD_SAVE, CMD_QUIT, CMD_MESSAGE
    selText.0 = "ファイルをオープンします。"
    selText.1 = "ファイルに保存します。"
    selText.2 = "アプリケーションを終了します。"
    selText.3 = "メッセージダイアログを表示します。"

    ; 内部構造体( MENUHELP_DATA 構造体)
    ; API 関数アドレスを取得
    dll_getfunc procdata.0, "CallWindowProcA", D_USER
    dll_getfunc procdata.1, "SendMessageA", D_USER
    ; 現在のウィンドウプロシージャアドレス取得
    pm = hwnd, -4 : setwndlong pm, 1
    procdata.2 = stat           ; ウィンドウプロシージャアドレス
    ; ヘルプテキストを格納した変数の情報
    procdata.3 = menunum        ; 配列変数に格納されているアイテム情報の数
    getptr procdata.4, selID    ; アイテムIDを格納した配列変数アドレス
    getptr procdata.5, selText  ; テキストを格納した配列変数アドレス
    procdata.6 = 64             ; テキストを格納した配列変数の1要素サイズ
    procdata.7 = hStatus        ; ステータスバーのハンドル
    procdata.8 = $100           ; ステータスバー表示形式(SBT_NOBORDERS)

    ; MENUHELP_DATA 構造体のアドレスをマシン語コードに埋め込む
    getptr p, procdata
    memcpy subproc, p, 4, $C

    ; ウィンドウのサブクラス化
    pm = hwnd, -4
    getptr pm.2, subproc        ; 新しいウィンドウプロシージャのアドレス
    setwndlong pm

*mainloop
    get_message
    if msgval {
        if msg == WM_COMMAND : gosub *lb_on_command
    } else {
        wait 10
    }
    goto *mainloop

*lb_on_command
    ; メニュー以外から送られた場合は何もしない
    if lprm != 0 : return

    itemid = wprm & $FFFF       ; 選択されたメニューアイテムID
    if itemid == CMD_OPEN {
        dialog "*", 16
        if stat {
            dialog refstr+"を開きました"
        }
    }
    if itemid == CMD_SAVE {
        dialog "*", 17
        if stat {
            dialog refstr+"を保存しました"
        }
    }
    if itemid == CMD_QUIT {
        dialog "終了します", 0
        end
    }
    if itemid == CMD_MESSAGE {
        dialog "メニュー作成のテストです", 0, "メッセージ表示"
    }
    return