メニューバー

メニューバー

Windowsアプリケーションなら当たり前というぐらいに、いろいろなアプリケーションがウィンドウにメニューバーを持っています。しかし、HSPでは、標準機能だけではメニューを実現させることができません。そこで今回は、Windows APIを用いてメニューバーを表示させてみることにします。

今回作成するのは右のようなメニューバーです。メニューの項目にサブメニューを持たせていないので、メニュー項目をクリックしても新しいメニューが表示されてきたりはしません。


Visual C++ などのプログラミング言語では、メニューを作成するのに『メニューリソース』と呼ばれるものを使うことができるため、メニューを作るのは簡単なのですが、HSPでメニューを作ろうとすると、一からAPIで作成していかなければならないため、やや煩雑になってしまいます。とりあえずは簡単なところからやっていきましょう。

メニューの作成

今回は階層が1つだけの簡単なメニューを作成します。作成する手順は以下のようになります。

  1. メニューを作成する。
  2. メニュー項目を追加する。項目が複数あれば必要なだけ実行する。
  3. メニューをウィンドウに割り当てる。
  4. メニューを再描画する。

では、順番に見ていきましょう。

メニューバーの作成

まずはメニューバーを作成します。メニューバーを作成するにはCreateMenu関数を呼び出します。

HMENU CreateMenu(VOID);

この関数は、メニューバーとしてウィンドウに表示するためのメニューを作成して、そのハンドルを返します。このハンドルはメニューを識別するための値で、以後、このメニューを操作するにはこのハンドルを用いることになります。


余談になりますが、メニューもまた、Windowsオブジェクトの1つです。すなわち、『メニューオブジェクト』であるということです。したがって、他のWindowsオブジェクトと同じように、ハンドルによって識別されています。

メニューなどのようなユーザーインターフェースの機能は、主にuser32.dllによって提供されているものです。つまり、以前に紹介したWindowsオブジェクトの3つの分類から考えると、メニューは「ユーザーオブジェクト」に属するというわけです。

Windowsオブジェクトについての詳細は、『プロセス間共有メモリ』の項を参照してください。

メニュー項目の追加

メニューを作成したら、そのメニューに項目を追加していきます。メニュー項目を追加するにはAppendMenu関数を呼び出します。

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

この関数は、指定されたメニューの最後尾に新しい項目を追加します。追加する項目の数だけ呼び出します。

第3引数(uIDNewItemパラメータ)には、追加するメニュー項目のIDを指定するようになっています。ユーザーがメニュー項目を選択したとき、Windowsはそのメニューを所有するウィンドウにWM_COMMANDメッセージを送信します。このメッセージには付加情報として、wParamパラメータの下位ワードに、このパラメータで指定された項目IDが含まれています。項目ごとに違うIDを指定していくことで、メッセージが送られたときにどの項目が選択されたのかを判別することができます。

第4引数(pNewItemパラメータ)には、メニュー項目に表示する文字列のアドレスを指定します。この文字列に、「終了(&Q)」というように、アルファベットの直前にアンパサンド(&)を指定すると、[ALT]キーとそのアルファベットのキーを同時に押すことでその項目を選択することができるようになります。アンパサンド自体はメニューには表示されず、アルファベットに下線が引かれます。

ウィンドウへの割り当てと再描画

さて、メニューの準備ができたら、ウィンドウにそのメニューを割り当てて、さらに再描画を行ないます。これによってウィンドウにメニューバーが表示されます。

ウィンドウにメニューを割り当てるにはSetMenu関数を使います。

BOOL SetMenu(
    HWND  hWnd,     // ウィンドウハンドル
    HMENU hMenu     // メニューハンドル
);

続いて、メニューの再描画を行なうためにDrawMenuBar関数を呼び出します。

BOOL DrawMenuBar(
    HWND hWnd,      // ウィンドウハンドル
);

ウィンドウにメニューを割り当ててから、メニュー項目を追加していくこともできます。この場合は、項目を追加し終わった後で必ずDrawMenuBar関数を呼び出して、メニューを再描画するようにしてください。

メニューオブジェクトの削除について

以前まで扱ってきたWindowsオブジェクトは、必要なくなった時点で削除しなければなりませんでした。では、メニューもオブジェクトである以上は削除しておく必要があるのでしょうか。

結論から言うと、今回の場合は削除する必要がありません。本来ならメニューもDestroyMenu関数を呼び出して、メニューオブジェクトの削除をしなければならないのですが、ウィンドウに割り当てられているメニュー(およびそのサブメニュー)は、ウィンドウが破棄されるときに自動的に削除されるようになっているためです。

WM_COMMAND メッセージの処理

ここまでの処理により、ウィンドウにはメニューバーが表示されています。あとはメニュー項目が選択された時にウィンドウが受け取るメッセージを監視していればOKです。項目が選択されると、ウィンドウはWM_COMMANDメッセージを受け取ります。WM_COMMANDメッセージのメッセージコードは0x0111です。

WM_COMMANDメッセージで渡されるwParamパラメータの下位ワードの値は選択されたメニュー項目のIDになります。この値からどの項目が選択されたのかを知ることができます。

ただし、このWM_COMMANDメッセージは、メニュー以外でもさまざまな目的で使われているため、WM_COMMANDメッセージを受け取ったからといって、メニュー項目が選択されたのだとは限らないのです。

例えば、エディットコントロール(mesbox命令やinput命令で作成されるHSPオブジェクト)やコンボボックス、リストボックスなどの『Windows標準コントロール』と呼ばれるものは、何らかのイベントが発生したときに、親ウィンドウに対してWM_COMMANDメッセージを送信します。また、ツールバーなどの『コモンコントロール』にも、WM_COMMANDメッセージを送信するものがあります。

したがって、mesbox命令を使ってウィンドウにエディットコントロールを作成してしまっていると、例えばエディットコントロールをクリックしたりしただけでも、親ウィンドウはWM_COMMANDメッセージを受け取ってしまうわけです。

そこで、メニューから送られたのか、それ以外のコントロールから送られたのかを判別する必要があります。WM_COMMANDメッセージがコントロールから送られると、lParamパラメータの値がコントロールのハンドルになるのですが、メニューから送られた場合にはこの値が0になるのです。そこで、lParamパラメータが0であるかどうかで判別することにします。

サンプルスクリプト

実際にスクリプトを書いてみます。メニュー項目IDの値を最初に定数名として#defineまたは#constで定義しておくと、スクリプトがよりわかりやすくなります。メニューを設置するウィンドウのウィンドウハンドルが必要になるので、システム変数hwndを指定します。

#uselib "user32.dll"
#cfunc CreateMenu "CreateMenu"
#func AppendMenu  "AppendMenuA" int,int,int,sptr
#func SetMenu     "SetMenu"     int,int
#func DrawMenuBar "DrawMenuBar" int

#define WM_COMMAND 0x0111

; メニュー項目IDを定義
#define CMD_ID_MESSAGE  1   ; 「表示」項目のID
#define CMD_ID_QUIT     2   ; 「終了」項目のID

; メニューを作成
hmenu = CreateMenu()

; メニュー項目を追加
AppendMenu hmenu, 0, CMD_ID_MESSAGE, "表示(&M)"
AppendMenu hmenu, 0, CMD_ID_QUIT, "終了(&Q)"

; メニューをウィンドウに割り当てる
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_MESSAGE {
    dialog "メニュー項目が選択されました", 0, "メニューテスト"
}
if itemID == CMD_ID_QUIT {
    dialog "終了します", 0, "メニューテスト"
    end
}
return

このスクリプトを実行させてみましょう。メニューアイテムをクリックすると、それぞれの処理を行なっています。また、[Alt]+[M] や [Alt]+[Q] でもアイテムを選択することができるようになっています。