メニューバーを作成してみる ACT-2

前回はメニューバー上のアイテムだけの、最も簡単なメニューの例を示しました。今回はもう少し複雑にして、メニューバー上のアイテムを選択するとドロップダウンメニューが表示されるようにしてみましょう。

メニューの分類

本題に入る前に、メニューの種類について、簡単に説明しておきましょう。

メニューには、その形状や特徴に応じて、いくつかの呼び名があります。

まず、前回作成された、ウィンドウ上部に表示されるメニューがメニューバーです。メニューバーはトップレベルメニューと呼ばれることもあります。

次に、メニューバーのアイテムが選択されたときに、そのアイテムの下にポップアップ表示されるメニューがドロップダウンメニューです。

ドロップダウンメニューの中には、子供メニューを持つアイテムを持つものがあります。このときの子供メニューはサブメニューと呼ばれ、アイテムの横にポップアップ表示されます。

マウスを右クリップしたときに、マウスカーソルの位置に、メニューがポップアップ表示されることがありますね。このようなメニューはショートカットメニューと呼ばれます。ショートカットメニューは、ウィンドウのある場所やアイコンなどに関連付けられて表示されることが多いので、コンテキストメニューと呼ばれることもあります。

ドロップダウンメニュー、サブメニュー、ショートカットメニューの3つは、スクリーン上にポップアップ表示されるので、これらを総称してポップアップメニューと呼ぶことがあります。

これらとは別に、ウィンドウのタイトルバーやタスクバーのアイコンを右クリックしたり、タイトルバーの小さいアイコンを左クリックしたときに表示されるメニューがありますね。これは、Windowsによって定義・管理されるているメニューで、ウィンドウメニューと呼ばれるものです。また、システムメニューとか、コントロールメニューと呼ばれることもあります。

ドロップダウンメニューの追加

さて、改めて確認しておくと、今回作成するのは、ドロップダウンメニューを持つメニューバーです。メニューバー上のアイテムが選択されたときに、新しいメニューがポップアップ表示されるようにします。

メニューバー上のアイテムにドロップダウンメニューを付けるには、まずドロップダウンメニューとして表示するためのメニューオブジェクトを作成しておいて、それをメニューアイテムとしてメニューバーに追加します。

今回のように、メニューバーではなくドロップダウンメニューのためのメニューオブジェクトを作成するには、 CreateMenu 関数ではなくて CreatePopupMenu 関数を呼び出します。

HMENU CreatePopupMenu(VOID);

この関数は、ポップアップメニューと呼ばれるメニューオブジェクト(ショートカットメニュー、サブメニュー、ドロップダウンメニュー)を作成して、そのハンドルを返します。このハンドルは、作成されたメニューオブジェクトを識別するための値で、以後、このメニューを操作するにはこのハンドルを用いることになります。

メニューを作成したら、前回の場合と同様に AppendMenu 関数でメニューバーにメニューアイテムを追加していきます。

BOOL AppendMenuA(
    HMENU  hMenu,      // メニューハンドル
    UINT   uFlags,     // オプションフラグ
    UINT   uIDNewItem, // アイテム ID またはサブメニューのハンドル
    PCTSTR pNewItem    // 表示する文字列
);

この関数の第2引数(uFlags パラメータ)で $800 (MF_SEPARATOR) を指定すると、そこにセパレータ(区切り線)が追加されます。

メニューアイテムとしてプルダウンメニューやサブメニューを追加したい場合、第2引数(uFlags パラメータ)に $10 (MF_POPUP) を指定し、第3引数(uIDNewItem パラメータ)に CreatePopupMenu 関数によって作成された他のメニューオブジェクトのハンドルを指定することで実現できます。このプルダウンメニューやサブメニューに対しても AppendMenu 関数でサブメニューをアイテムとして追加していくことができます。これにより、第3階層、第4階層と、メニューの階層を深くしていくことができます。

ドロップダウンメニューの準備ができたら、それをメニューバーにアイテムとして追加します。この場合も、サブアイテムを追加するときと同様に、 AppendMenu 関数の uFlags パラメータに $10 (MF_POPUP) を指定し、 uIDNewItem パラメータには追加するドロップダウンメニューのハンドルを指定します。

これ以外の処理については、前回とほぼ同様です。

サンプルスクリプト

さて、実際にスクリプトを書いてみます。メニューの階層構造を以下のようにしてみましょう。括弧中の名前は、メニューアイテムIDにつける定数名です。

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

    #define WM_COMMAND    0x0111

    ; メニューアイテムIDを定義
    #define CMD_OPEN       1      ;「開く」アイテムのID
    #define CMD_SAVE       2      ;「保存」アイテムのID
    #define CMD_QUIT       3      ;「終了」アイテムのID
    #define CMD_MESSAGE    4      ;「メッセージ表示」アイテムのID

    ;---------------「ファイル」メニューの作成-------------------
    dllproc "CreatePopupMenu", pm, 0, D_USER
    hmenufile = dllret            ; 「ファイル」メニューハンドル

    mesbuf = "開く(&O)"
    pm = hmenufile, 0, CMD_OPEN
    getptr pm.3, mesbuf
    dllproc "AppendMenuA", pm, 4, D_USER

    mesbuf = "保存(&S)"
    pm = hmenufile, 0, CMD_SAVE
    getptr pm.3, mesbuf
    dllproc "AppendMenuA", pm, 4, D_USER

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

    mesbuf = "終了(&Q)"
    pm = hmenufile, 0, CMD_QUIT
    getptr pm.3, mesbuf
    dllproc "AppendMenuA", pm, 4, D_USER

    ;----------------「ヘルプ」メニューの作成--------------------
    dllproc "CreatePopupMenu", pm, 0, D_USER
    hmenuhelp = dllret            ; 「ヘルプ」メニューハンドル

    mesbuf = "メッセージ表示(&M)"
    pm = hmenuhelp, 0, CMD_MESSAGE
    getptr pm.3, mesbuf
    dllproc "AppendMenuA", pm, 4, D_USER

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

    mesbuf = "ファイル(&F)"
    pm = hmenu, $10, hmenufile    ; 「ファイル」メニュー追加
    getptr pm.3, mesbuf
    dllproc "AppendMenuA", pm, 4, D_USER

    mesbuf = "ヘルプ(&H)"
    pm = hmenu, $10, hmenuhelp    ; 「ヘルプ」メニュー追加
    getptr pm.3, mesbuf
    dllproc "AppendMenuA", pm, 4, D_USER

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

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

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

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

    ;----------------------メインループ--------------------------
*mainloop
    get_message msgval
    if msgval {
        if msg == WM_COMMAND {
            gosub *lb_on_command
        }
    } else {
        wait 10
    }
    goto *mainloop

    ;-------------WM_COMMAND が送られたときの処理----------------
*lb_on_command
    ; メニュー以外から送られた場合は何もしない
    if lprm != 0 : return

    ; 選択されたメニューアイテムID
    itemid = wprm & $FFFF

    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

前回よりは複雑ですが、慣れてしまえばなんてことはないでしょう。スクリプト中で同じ処理が繰り返されているところがあるので(特にメニューアイテムの追加)、こういった部分をモジュール化してしまうのもいいでしょう。いろいろと試してみてください。