Tutorial 8: Menu

今回は、ウィンドウのメニューをどうやって組み込むかを説明する。
 ソース1   ソース2   リソーススクリプト   実行結果 

Theory:

メニューはウィンドウズプログラムにおいて非常に重要な部品である。 メニューというのは、ユーザからの要求を受け付けるもので、 そのメニューを見ればプログラムの概観が理解でき、 付属のマニュアルなど見ずとも、そのプログラムをすぐにでも操作できるようになる。 メニューはそのようなユーザに便利なツールの一つなので、標準に従うべきだ。 その標準とは、最初が File、次が Edit、そして、最後が Help となっている。 EditとHelpの間に自分の使用したいメニューを追加することができる。 また、メニュー文字列に、省略記号(...)を追加することになっている(あなたの使用しているブラウザのメニューを見てみよ)。

メニューはリソースの1部で、他にもダイアログボックス、ストリングテーブル、アイコン、ビットマップなどがある。 リソースは通常、リソースファイル(*.rc)に記述することになっており、ソースファイルとは別になっている。 なので、リンク時にそれらを結合することにより、メニューと処理を含んだ実行ファイルを作成することができる。

リソーススクリプトはテキストエディタで作成することができるのだが、 表示する座標値や、その他の様々な属性を記述することになるので、非常に面倒である。 そのため通常は、VisualC++やBorlandC++などといった統合環境に、 リソースエディタという、リソーススクリプトをビジュアル画面から自動的に作成してくれるツールが付属しており、 それを使用すると簡単にリソーススクリプトを作成することができる。

例えば、メニューリソースを表示する場合、リソーススクリプトは以下のようになる。

MyMenu MENU
{
  [menu list here]
}

Cプログラマには、単に構造体を宣言しているだけだと思うかもしれない。 メニュー名は、MENUキーワードの前の文字列MyMenuとなり、 メニューに含まれるリストは、{ } で囲まれたところに記述することになっている。 もしくは、{ } のかわりに、BEGIN、END を使用してもよい。 このBEGIN、ENDの記述方法は Pascal使いには慣れたものだろう。

メニューに含まれるリストはMENUITEMもしくは、POPUPキーワードを使用する。

MENUITEM構文は選択された際に、メニューをポップアップしないメニューバーを定義する。 以下のように記述する。

MENUITEM "&text", ID [,options]

MENUITEMキーワードの次にメニューバーに使用したい文字列がくるのだが、 &記号に注意しなければならない。 &記号に続く文字列には、アンダーラインが付くようになっている。 &text の次は、メニューアイテムのIDである。 IDというのは、単なる整数値なのだが、そのメニューアイテムが選択されたときにウィンドウプロシジャへ送られてくるメッセージなので、 一意な値でなければならない。 最後の options は文字通り、オプションで、利用できるオプションは以下のようなものがある。

これらのオプションを or オペレータで複数使用することができるが、 INACTIVEオプションと、GRAYEDオプションは同時に使えない。

対して、POPUP構文は以下のようになっている。

POPUP "&text" [,options]
{
 [menu list]
}

POPUP構文は選択されたときに、メニューアイテムのリストが小さなポップアップウィンドウに表示されるメニューバーを定義するものである。 メニューリストはMENUITEMPOPUP構文で追加していく。 MENUITEMには特別な構文があり、MENUITEM SEPARATORというポップアップウィンドウに横線を描くものがある。

次は、スクリプトファイルをソースファイルから参照できるようにしないといけないのだが、その方法は2通りある。

当然、これら2つの違いは何かということを疑問に思うだろう。 WNDCLASSEX構造体に参照するメニューをセットする方法の場合、 ウィンドウクラスのデフォルトメニューがそのメニューになるので、 そのウィンドウクラスから作成したウィンドウは全て同じメニューとなる。

同じウィンドウクラスから作成したウィンドウでも、違うメニューとしたい場合、2つ目の方法を用いる。 この場合、WNDCLASSEX構造体に既にメニューがセットされていても、 CreateWindowEx関数でセットしたメニューが表示されるようになる。

次に、ユーザがメニューアイテムを選択したときに、どのようにウィンドウプロシージャに遷移するかを観察する。
ユーザがメニューアイテムを選択したら、ウィンドウプロシージャにWM_COMMANDメッセージが送られ、 wParamの下位ワードに選択されたメニューIDが格納されている。

さて、メニューに関する情報はこれで十分だ。それでは実際にやってみよう。

Example:

最初の例では、ウィンドウクラスにメニューをセットする方法でどのようにメニューを使用、作成するかをお見せしよう。

**************************************************************************************************************************

ソースファイル

**************************************************************************************************************************
.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0               ; The name of our menu in the resource file.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.const
IDM_TEST equ 1                   ; Menu IDs
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4

.code
start:
   invoke GetModuleHandle, NULL
   mov   hInstance,eax
   invoke GetCommandLine
    mov CommandLine,eax
   invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
   invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
   LOCAL wc:WNDCLASSEX
   LOCAL msg:MSG
   LOCAL hwnd:HWND
   mov  wc.cbSize,SIZEOF WNDCLASSEX
   mov  wc.style, CS_HREDRAW or CS_VREDRAW
   mov  wc.lpfnWndProc, OFFSET WndProc
   mov  wc.cbClsExtra,NULL
   mov  wc.cbWndExtra,NULL
   push hInst
   pop  wc.hInstance
   mov  wc.hbrBackground,COLOR_WINDOW+1
   mov  wc.lpszMenuName,OFFSET MenuName       ; Put our menu name here
   mov  wc.lpszClassName,OFFSET ClassName
   invoke LoadIcon,NULL,IDI_APPLICATION
   mov  wc.hIcon,eax
   mov  wc.hIconSm,eax
   invoke LoadCursor,NULL,IDC_ARROW
   mov  wc.hCursor,eax
   invoke RegisterClassEx, addr wc
   invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
          WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
          hInst,NULL
   mov  hwnd,eax
   invoke ShowWindow, hwnd,SW_SHOWNORMAL
   invoke UpdateWindow, hwnd
   .WHILE TRUE
               invoke GetMessage, ADDR msg,NULL,0,0
               .BREAK .IF (!eax)
               invoke DispatchMessage, ADDR msg
   .ENDW
   mov    eax,msg.wParam
   ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
   .IF uMsg==WM_DESTROY
       invoke PostQuitMessage,NULL
   .ELSEIF uMsg==WM_COMMAND
       mov eax,wParam
       .IF ax==IDM_TEST
           invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
       .ELSEIF ax==IDM_HELLO
           invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
       .ELSEIF ax==IDM_GOODBYE
           invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
       .ELSE
           invoke DestroyWindow,hWnd
       .ENDIF
   .ELSE
       invoke DefWindowProc,hWnd,uMsg,wParam,lParam
       ret
   .ENDIF
   xor   eax,eax
   ret
WndProc endp
end start

**************************************************************************************************************************

リソースファイル

**************************************************************************************************************************

#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4

FirstMenu MENU
{
 POPUP "&PopUp"
       {
        MENUITEM "&Say Hello",IDM_HELLO
        MENUITEM "Say &GoodBye", IDM_GOODBYE
        MENUITEM SEPARATOR
        MENUITEM "E&xit",IDM_EXIT
       }
 MENUITEM "&Test", IDM_TEST
}

Analysis:

まずはリソースファイルについて見ていこう。

#define IDM_TEST    1 /* equal to IDM_TEST equ 1*/
#define IDM_HELLO   2
#define IDM_GOODBYE 3
#define IDM_EXIT    4

これは、メニュースクリプトで使用するメニューIDを定義している。 1つのメニューで一意な値であれば、どんな値でもよい。

FirstMenu MENU

メニューの名前をMENUキーワードで定義している。

POPUP "&PopUp"
      {
       MENUITEM "&Say Hello",IDM_HELLO
       MENUITEM "Say &GoodBye", IDM_GOODBYE
       MENUITEM SEPARATOR
       MENUITEM "E&xit",IDM_EXIT
      }

ポップアップメニューを宣言し、それに4つのメニューアイテムを表示するようにしている。3つ目のアイテムはメニューセパレータだ。

MENUITEM "&Test", IDM_TEST

メインメニュー中にメニューバーを定義している。

次にソースコードを見ていこう。

MenuName       db "FirstMenu"                   , 0 ; The name of our menu in the resource file.
Test_string    db "You selected Test menu item" , 0
Hello_string   db "Hello, my friend"            , 0
Goodbye_string db "See you again, bye"          , 0

MenuName はリソースファイルで定義されているメニュー名だ。 リソースファイルでは、複数のメニューが定義できるので、 どのメニューを使用するかを記述しなければならない。 残りの3行は、メニューアイテムが選択されたときにメッセージボックスに表示する文字列である。

IDM_TEST    equ 1 ; Menu IDs
IDM_HELLO   equ 2
IDM_GOODBYE equ 3
IDM_EXIT    equ 4

ウィンドウプロシージャで使用するメニューIDを定義している。これらの値は、絶対にリソースファイルで定義されている値と同じでなければならない。

.ELSEIF uMsg==WM_COMMAND
    mov eax,wParam

    .IF ax==IDM_TEST
        invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
    .ELSEIF ax==IDM_HELLO
        invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
    .ELSEIF ax==IDM_GOODBYE
        invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
    .ELSE
        invoke DestroyWindow,hWnd
    .ENDIF

ウィンドウプロシージャでは、WM_COMMANDメッセージに対して処理を記述することになる。 ユーザがメニューアイテムを選択したときに、WM_COMMANDメッセージが飛んでくるとともに、 wParamの下位バイトには選択されたメニューIDが入っている。 そのため、wParamをeaxレジスタにストアして、 axレジスタの値がどのメニューIDと一致しているかを比較し、 そのメニューIDに適した処理を記述することになっている。 上のIF文の上から3つのケースは、上から順番にそれぞれ、Testを選択した、SayHelloを選択した、 SayGoodByeを選択した時に実行される処理で、 単にメッセージボックスを表示しているだけだ。

ユーザがExitメニューアイテムを選択したら、このウィンドウハンドルを引数にして DestroyWindows関数をCALLする。 見てわかるとおり、ウィンドウクラスにメニュー名を記述するのは非常に簡単である。 しかし、もう一つのメニューを表示する方法も使用できるのだが、残念ながらここに全部のソースコードを表示することはしない。 リソースファイルはどちらも同じものを使用し、ほんのちょっとしか違いがないからだ。 なので、その変更箇所だけお見せすることにしよう。

.data?
hInstance   HINSTANCE ?
CommandLine LPSTR     ?
hMenu       HMENU     ? ; handle of our menu

メニューハンドルをストアするためのHEMNU型の変数を定義している。

invoke LoadMenu, hInst, OFFSET MenuName
mov    hMenu,eax
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
   WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
   CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\
   hInst,NULL

CreateWindowEx関数をCALLする前に、 インスタンスハンドルとメニュー名へのポインタを引数にLoadMenu関数を呼び、 リソースファイル中のメニューハンドルを取得し、 そのメニューハンドルをCreateWindowEx関数に渡すことになる。


[戻る]