ドロップダウンメニュー

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

メニューの分類

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

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

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

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

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

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

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

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

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

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

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

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

HMENU CreatePopupMenu(VOID);

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

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

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

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

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

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

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

サンプルスクリプト

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

#uselib "user32.dll"
#cfunc CreateMenu "CreateMenu"
#cfunc CreatePopupMenu "CreatePopupMenu"
#func AppendMenu  "AppendMenuA" int,int,int,sptr
#func SetMenu     "SetMenu"     int,int
#func DrawMenuBar "DrawMenuBar" int
                        
#define NULL            0
#define WM_COMMAND      0x0111
#define MF_POPUP        0x10
#define MF_SEPARATOR    0x800

; メニュー項目IDを定義
#define CMD_ID_OPEN     1   ; 「開く」項目のID
#define CMD_ID_SAVE     2   ; 「保存」項目のID
#define CMD_ID_QUIT     3   ; 「終了」項目のID
#define CMD_ID_MESSAGE  4   ; 「メッセージ表示」項目のID

; 「ファイル」メニューの作成
hmenuFile = CreatePopupMenu()
AppendMenu hmenuFile, 0, CMD_ID_OPEN, "開く(&O)"
AppendMenu hmenuFile, MF_SEPARATOR, 0, NULL
AppendMenu hmenuFile, 0, CMD_ID_QUIT, "終了(&Q)"

; 「ヘルプ」メニューの作成
hmenuHelp = CreatePopupMenu()
AppendMenu hmenuHelp, 0, CMD_ID_MESSAGE, "メッセージ表示(&M)"

; メニューバーの作成
hmenu = CreateMenu()
AppendMenu hmenu, MF_POPUP, hmenuFile, "ファイル(&F)"
AppendMenu hmenu, MF_POPUP, hmenuHelp, "ヘルプ(&H)"

; メニューをウィンドウに割り当てる
SetMenu hwnd, hmenu

; メニューを再描画
DrawMenuBar hwnd

; メッセージハンドラを設定
oncmd gosub *OnCommand, WM_COMMAND

stop

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

; 選択されたメニュー項目ID (wParamの下位ワード)
itemID = wparam & $FFFF

if itemID == CMD_ID_OPEN {
    dialog "*", 16
    if stat {
        dialog refstr+"を開きました"
    }
}
if itemID == CMD_ID_SAVE {
    dialog "*", 17
    if stat {
        dialog refstr+"を保存しました"
    }
}
if itemID == CMD_ID_QUIT {
    dialog "終了します", 0
    end
}
if itemID == CMD_ID_MESSAGE {
    dialog "メニュー作成のテストです", 0, "メッセージ表示"
}
return