メニューアイテムの状態を変えてみる

アイテムの状態の設定

これまで作成したメニューのアイテムはすべて、選択可能なものだけでした。しかし、必要なときにメニューアイテムを選択不可能な状態にしたり、チェックマークを表示したり、文字列を変更したりできたほうが便利でしょう。今回はこういったことを実現してみたいを思います。


まずはAPI関数の説明です。メニューアイテムの状態を変更するには、 SetMenuItemInfo 関数を用います。 SetMenuItemInfo 関数は、メニューアイテムのさまざまな状態を変更することができます。

BOOL SetMenuItemInfoA(
    HMENU hMenu,             // メニューハンドル
    UINT  uItem,             // アイテムのIDまたは位置
    BOOL  fByPosition,       // フラグ
    LPMENUITEMINFO pItemInfo // MENUITEMINFO構造体アドレス
);

第1引数(hMenu パラメータ)には、変更したいアイテムを持つメニューハンドルを指定します。指定するアイテムを持つメニューが、ある別のメニューのサブメニューとなっている場合は、親メニューのハンドルを指定することもできるようになっています。すなわち、ウィンドウに関連付けられているメニューの中のアイテムならば、一番上にあるメニューバーのハンドルを指定するだけで、目的のアイテムを操作できます。

第2引数(uItem パラメータ)は、第3引数(fByPosition パラメータ)によって指定するものが変わります。 fByPosition に 0 (FALSE) を指定した場合は uItem はメニューアイテムIDを示します。また、 fByPosition に 1 (TRUE) を指定した場合は uItem はアイテムの位置のインデックスを示します。

第4引数(pItemInfo パラメータ)は、メニューアイテムの情報を格納した MENUITEMINFO 構造体のアドレスを指定します。

MENUITEMINFO 構造体は以下のように定義されています。

typedef struct tagMENUITEMINFO {
    UINT    cbSize;        // 構造体のサイズ
    UINT    fMask;         // 取得または設定するメンバ
    UINT    fType;         // アイテムのタイプ
    UINT    fState;        // アイテムの状態
    UINT    wID;           // アイテムID
    HMENU   hSubMenu;      // サブメニューのハンドル
    HBITMAP hbmpChecked;   // チェック表示時のビットマップ
    HBITMAP hbmpUnchecked; // チェック非表示時のビットマップ
    ULONG_PTR dwItemData;  // 任意の32ビット値
    LPTSTR  dwTypeData;    // アイテムの内容
    UINT    cch;           // アイテムの文字列の長さ
    HBITMAP hbmpItem;      // ビットマップハンドル
} MENUITEMINFO, *LPMENUITEMINFO;

cbSize メンバはこの構造体のサイズを表します。このメンバには 48 を指定しましょう。また、この構造体の古い定義では最後のメンバがなく、構造体サイズは 44 になります。したがって、最後のメンバを使わない場合は 44 を指定してもかまいません。

アイテムの状態(選択可・不可やチェックマーク表示・非表示など)を設定する場合は、 fMask メンバに 1 (MIIM_STATE) を指定し、 fState メンバに状態を示す値を指定します。

表示する文字列を変更したい場合は、 fMask メンバに $10 (MIIM_TYPE) を、 fType メンバに 0 (MFT_STRING) を指定し、 dwTypeData メンバに新しく表示する文字列のアドレスを指定します。

特定の状態のみの設定

SetMenuItemInfo 関数は、いろいろな変更を細かく行なうことができるのですが、ちょっと状態を変更するためだけにこれだけの操作をするのは少々面倒だと思うかもしれませんね。そこで、次のAPI関数も紹介しておきます。

表示状態の設定

まずは EnableMenuItem 関数です。この関数は、メニューアイテムの表示状態(選択可・不可の状態)を設定します。

BOOL EnableMenuItem(
    HMENU hMenu,    // メニューハンドル
    UINT  uItemID,  // アイテムのIDまたは位置
    UINT  uEnable   // 表示状態
);

チェックマークの設定

次に CheckMenuItem 関数です。この関数は、メニューアイテムにつけるチェックマークの表示状態を設定します。

DWORD CheckMenuItem(
    HMENU hMenu,    // メニューハンドル
    UINT  uItemID,  // アイテムのIDまたは位置
    UINT  uCheck    // チェックマークの表示状態
);

メニューの破棄について

さて、今回はメニューバーとショートカットメニューの両方を作成しますが、その際のちょっとしたテクニックを紹介します。

ポップアップ表示することを目的として作成されたショートカットメニューは、ウィンドウに関連付けられていないので、終了時には DestroyMenu 関数を呼び出してメニューを破棄しなければならないのでした。しかし、このショートカットメニューも、ウィンドウに関連付けられていさえすれば、いちいち破棄する必要はなくなります。

そこで1つの手段として、ポップアップメニューをダミーのアイテムとしてウィンドウのメニューバーに追加してしまうのです。具体的には、ポップアップメニューをメニューバーのアイテムとして追加する際に AppendMenu 関数の第4引数(表示文字列のアドレス)として 0 (NULL) を指定するのです。すると、見かけ上はメニューバーには表示されませんが、実際にはポップアップメニューがウィンドウに関連付けられるのです。

サンプルスクリプト

さあ、スクリプトを書いてみましょう。

    #include "llmod.as"
    #include "hsgetmsg.as"

    #define WM_COMMAND    0x0111

    ; メニューアイテムIDを定義
    #define CMD_DISP_WND_2       2     ; ウィンドウID = 2
    #define CMD_DISP_WND_3       3     ; ウィンドウID = 3
    #define CMD_DISP_WND_4       4     ; ウィンドウID = 4
    #define CMD_CHNG_WND_2      12     ;
    #define CMD_CHNG_WND_3      13     ;
    #define CMD_CHNG_WND_4      14     ;
    #define CMD_QUIT            20     ; 終了

    buf = ""
    getptr lpbuf, buf                  ; 文字列変数 buf へのポインタ

    ; 「表示ウィンドウ」メニューの作成
    dllproc "CreatePopupMenu", pm, 0, D_USER
    hmenuWindow = dllret               ; ドロップダウンメニューハンドル
    repeat 3, 2
        screen cnt, 300, 200
        title "Window ID = "+cnt
        fdispwnd.cnt = 1
        buf = "Window ID = "+cnt
        pm = hmenuWindow, 8, cnt, lpbuf
        dllproc "AppendMenuA", pm, 4, D_USER
    loop

    pm = hmenuWindow, $800, 0, 0       ; 区切り線を指定
    dllproc "AppendMenuA", pm, 4, D_USER

    buf = "終了(&Q)"
    pm = hmenuWindow, 0, CMD_QUIT, lpbuf
    dllproc "AppendMenuA", pm, 4, D_USER

    ; ショートカットメニューの作成
    dllproc "CreatePopupMenu", pm, 0, D_USER
    hmenuPopup = dllret
    repeat 3, 2
        buf = "Window ID = "+cnt+"をアクティブに"
        pm = hmenuPopup, 0, 10+cnt, lpbuf
        dllproc "AppendMenuA", pm, 4, D_USER
    loop

    ; メニューバーの作成
    dllproc "CreateMenu", pm, 0, D_USER
    hmenu = dllret                     ; メニューハンドル

    buf = "表示ウィンドウ(&W)"
    pm = hmenu, $10, hmenuWindow, lpbuf ; 「表示ウィンドウ」メニュー追加
    dllproc "AppendMenuA", pm, 4, D_USER

    pm = hmenu, $10, hmenuPopup, 0     ; ダミーのアイテムとして追加
    dllproc "AppendMenuA", pm, 4, D_USER

    ; ウィンドウのサブクラス化
    gsel 0
    set_subclass
    hwnd = stat                        ; HSPウィンドウのハンドル
    set_message WM_COMMAND             ; 取得メッセージ設定

    ; メニューをウィンドウに割り当てる
    pm = hwnd, hmenu
    dllproc "SetMenu", pm, 2, D_USER

    ; メニューを再描画
    pm = hwnd                          ; メニューハンドル
    dllproc "DrawMenuBar", pm, 1, D_USER

    ; メッセージパラメータ用変数
    dup msg,  msgval.1                 ; メッセージが格納される変数
    dup wprm, msgval.2                 ; wParamパラメータが格納される変数
    dup lprm, msgval.3                 ; lParamパラメータが格納される変数

    ; メインループ
*mainloop
    wait 10
    ; ウィンドウメッセージ処理
    repeat
        get_message msgval
        if msgval == 0 : break
        if msg == WM_COMMAND : gosub *onCommand
    loop
    stick kcode,, 1
    if kcode & 512 {                   ; 右クリックされたとき
        ginfo 1                        ; アクティブウィンドウ取得
        activewnd = prmx
        if activewnd != -1 {
            repeat 3, 2
                if (cnt = activewnd) | (fdispwnd.cnt = 0) {
                    pm = hmenu, 10+cnt, 1
                } else {
                    pm = hmenu, 10+cnt, 0
                }
                dllproc "EnableMenuItem", pm, 3, D_USER
            loop
            ginfo 0                    ; マウス座標取得
            ; メッセージはウィンドウID=0 に送られるようにしておく
            pm = hmenuPopup, 0, prmx, prmy, hwnd, 0
            dllproc "TrackPopupMenuEx", pm, 6, D_USER
        }
    }
    goto *mainloop

    ;---------------WM_COMMAND が送られたときの処理------------------
*onCommand
    itemid = wprm & $FFFF              ; 選択されたメニューアイテムID

    if (itemid >= 2) & (itemid <= 4) {
        if fdispwnd.itemid {
            gsel itemid, -1
            pm = hmenu, itemid, 0
            dllproc "CheckMenuItem", pm, 3, D_USER
            fdispwnd.itemid = 0
        } else {
            gsel itemid, 1
            pm = hmenu, itemid, 8
            dllproc "CheckMenuItem", pm, 3, D_USER
            fdispwnd.itemid = 1
        }
    }
    if (itemid >= 12) & (itemid <= 14) {
        gsel itemid - 10, 1
    }
    if itemid = CMD_QUIT : end
    return