今回は、ウィンドウのメニューをどうやって組み込むかを説明する。
ソース1 | ソース2 | リソーススクリプト | 実行結果 |
|
メニューはウィンドウズプログラムにおいて非常に重要な部品である。 メニューというのは、ユーザからの要求を受け付けるもので、 そのメニューを見ればプログラムの概観が理解でき、 付属のマニュアルなど見ずとも、そのプログラムをすぐにでも操作できるようになる。 メニューはそのようなユーザに便利なツールの一つなので、標準に従うべきだ。 その標準とは、最初が 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 は文字通り、オプションで、利用できるオプションは以下のようなものがある。
- GRAYED
これで指定されたメニューアイテムはグレー表示となり、選択できないようになるため、WM_COMMANDメッセージも生成できない。- INACTIVE
これで指定されたメニューアイテムは、WM_COMMANDメッセージは生成できないのだが、文字列は通常通り表示される。- MENUBREAK
これ以降のメニューアイテムは新しい列が作成され、そこに表示される。- HELP
これ以降のメニューアイテムは右揃えになる。これらのオプションを or オペレータで複数使用することができるが、 INACTIVEオプションと、GRAYEDオプションは同時に使えない。
対して、POPUP構文は以下のようになっている。
POPUP "&text" [,options] { [menu list] }POPUP構文は選択されたときに、メニューアイテムのリストが小さなポップアップウィンドウに表示されるメニューバーを定義するものである。 メニューリストはMENUITEMかPOPUP構文で追加していく。 MENUITEMには特別な構文があり、MENUITEM SEPARATORというポップアップウィンドウに横線を描くものがある。
次は、スクリプトファイルをソースファイルから参照できるようにしないといけないのだが、その方法は2通りある。
- WNDCLASSEX構造体のメンバにある、lpszMenuName にセットする。つまり、メニュー名を "FirstMenu" にしたい場合は以下のようにする。
.DATA MenuName db "FirstMenu",0 ........................... ........................... .CODE ........................... mov wc.lpszMenuName, OFFSET MenuName ...........................- CreateWindowsEx関数のメニューハンドルパラメータをセットする
.DATA MenuName db "FirstMenu",0 hMenu HMENU ? ........................... ........................... .CODE ........................... invoke LoadMenu, hInst, OFFSET MenuName mov hMenu, eax invoke CreateWindowEx,NULL,OFFSET ClsName,\ OFFSET Caption, WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,\ NULL,\ hMenu,\ hInst,\ NULL\ ...........................当然、これら2つの違いは何かということを疑問に思うだろう。 WNDCLASSEX構造体に参照するメニューをセットする方法の場合、 ウィンドウクラスのデフォルトメニューがそのメニューになるので、 そのウィンドウクラスから作成したウィンドウは全て同じメニューとなる。
同じウィンドウクラスから作成したウィンドウでも、違うメニューとしたい場合、2つ目の方法を用いる。 この場合、WNDCLASSEX構造体に既にメニューがセットされていても、 CreateWindowEx関数でセットしたメニューが表示されるようになる。
次に、ユーザがメニューアイテムを選択したときに、どのようにウィンドウプロシージャに遷移するかを観察する。
ユーザがメニューアイテムを選択したら、ウィンドウプロシージャにWM_COMMANDメッセージが送られ、 wParamの下位ワードに選択されたメニューIDが格納されている。さて、メニューに関する情報はこれで十分だ。それでは実際にやってみよう。
|
最初の例では、ウィンドウクラスにメニューをセットする方法でどのようにメニューを使用、作成するかをお見せしよう。
************************************************************************************************************************** ソースファイル ************************************************************************************************************************** .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 }
|
まずはリソースファイルについて見ていこう。
#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" , 0MenuName はリソースファイルで定義されているメニュー名だ。 リソースファイルでは、複数のメニューが定義できるので、 どのメニューを使用するかを記述しなければならない。 残りの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,NULLCreateWindowEx関数をCALLする前に、 インスタンスハンドルとメニュー名へのポインタを引数にLoadMenu関数を呼び、 リソースファイル中のメニューハンドルを取得し、 そのメニューハンドルをCreateWindowEx関数に渡すことになる。