Tutorial 8: Menüs
In diesem Tutorial werden wir lernen, wie wir mit Menüs in unseren Fenstern arbeiten.
Laden Sie das Beispiel 1 und herunter.
Theorie:
Menüs sind eine der wichtigsten Komponenten in ihrem Fenster. Menüs präsentieren eine Liste an Funktionen, die ein Programm den Benutzer bietet. Der Benutzer muss nicht erst das Handbuch lesen, das mit dem Programm kommt, um das Programm nutzen zu können, sondern er kann die Menüs durchgehen um sich einen Überblick zu beschaffen, was das Programm leisten kann und sofort anfangen damit herumzuspielen. Da ein Menü ein Tool ist, das dem Benutzer schnellen Zugriff erlaubt, sollten Sie dem Standard folgen. Kurz gesagt sollten die ersten beiden Menü-Elemente Datei und Bearbeiten sein und das letzte sollte Hilfe sein. Sie können ihre eigenen Menü-Elemente zwischen Bearbeiten und Hilfe einfügen. Wenn ein Menü-Element eine Dialog-Box aufruft, sollten Sie eine Auslassung (...) an den Menü-String anhängen.
Menüs sind Arten von Ressourcen. Es gibt verschiedene Arten von Ressourcen, so wie Dialog-Boxen, Icons, Bitmaps, Menüs, etc. Ressourcen werden in einer separaten Datei gespeichert, die Ressource-Datei genannt wird und normalerweise die Endung .rc hat. Sie fügen dann die Ressourcen mit ihrem Source-Code während des Linkens zusammen. Das endgültige Produkt ist eine ausführbare Datei, welche beides enthält, Befehle und Ressourcen.
Sie können Ressourcen-Skripts mit jeglichem Text-Editor schreiben. Sie setzen sich aus Sätzen zusammen, die das Aussehen und andere Attribute der Ressourcen beschreiben, die in einem Programm verwendet werden. Obwohl Sie ihre Ressource-Skripts in einem Text-Editor schreiben können, ist es recht mühsam. Eine bessere Alternative ist das Benutzen eines Ressource-Editors, mit welchem Sie mit Leichtigkeit Ressourcen visuell designen können. Ressource-Editors sind normalerweise in Compiler-Umgebungen wie Visual C++, Borland C++, etc. enthalten.
Sie beschreiben eine Menü-Ressource folgendermaßen:
MyMenu MENU
{
[Menü liste hier]
}
C Programmierer erkennen vielleicht die Ähnlichkeit zur Deklaration einer Struktur. MyMenu ist der Menü-Name gefolgt von dem Schlüsselwort MENU und der Menü-Liste in den geschweiften Klammern. Alternativ können Sie statt den geschweiften Klammern auch BEGIN und END benutzen, wenn Sie wollen. Diese Syntax wird Pascal-Programmierern mehr vertraut sein.
Menü-Liste kann entweder ein MENUITEM oder ein POPUP Statement sein.
MENUITEM Statement definiert eine Menü-Zeile, was kein Popup-Menü aufruft, wenn es ausgewählt wird. Die Syntax ist wie folgt:
MENUITEM "&text", ID [,options]
Sie beginnt mit dem Schlüsselwort MENUITEM gefolgt von dem Text, den Sie in der Menü-Zeile anzeigen lassen wollen. Beachten Sie das Ampersand-Zeichen (&). Es verursacht den folgenden Buchstaben unterstrichen darzustellen. Nach dem Text folgt die ID des Menü-Elements. Die ID ist eine Zahl, die benutzt wird, um das Menü-Element in der Nachricht, die an die Fensterprozedur gesendet wird, wenn es ausgewählt wird, zu identifizieren. Demnach muss jede Menü-ID einmalig sein.
Optionen sind optional. Verfügbare Optionen sind folgende:
- GRAYED
Das Menü-Elemnt ist nicht verfügbar und erzeugt keine WM_COMMAND-Nachricht. Der Text ist grau.
- INACTIVE
Das Menü-Elemnt ist nicht verfügbar und erzeugt keine WM_COMMAND-Nachricht. Der Text wird normal angezeigt.
- MENUBREAK
Dieses Element und die folgenden Elemente erscheinen in einer neuen Zeile des Menüs.
- HELP
Dieses Element und die folgenden Elemente sind rechts-ausgerichtet.
Sie können eins der oben genannten Optionen benutzen oder Sie mit dem OR-Operator kombinieren. Achten Sie darauf, dass INACTIVE und GRAYED nicht miteinander kombiniert werden können.
POPUP Statement hat folgende Syntax:
POPUP "&text" [,options]
{
[menü liste]
}
Das POPUP Statement definiert eine Menü-Zeile, die, wenn Sie ausgewählt wird, aufklappt und eine Liste weiterer Menü-Elemente in einem kleinen Popup-Fenster anzeigt. Die Menü-Liste kann entweder ein MENUTIEM oder POPUP ein Statement sein. Es gibt ein besonderes MENUITEMOPUP Statement, MENUITEM SEPARATOR, welches eine horizontale Linie in das Popup-Fenster zeichnet.
Als nächsten Schritt, nachdem Sie ihr Menü Ressource-Skript beendet haben, ist, dieses in ihrem Programm anzusprechen.
Sie können das an zwei verschiedenen Orten in ihrem Programm machen.
- Im lpszMenuName Element der WNDCLASSEX Struktur. Sagen wir, Sie haben ein Menü names "FirstMenu", dann können Sie ihr Menü ihrem Fenster so zuordnen:
.DATA
MenuName db "FirstMenu",0
...........................
...........................
.CODE
...........................
mov wc.lpszMenuName, OFFSET MenuName
...........................
- Im Menü Handle Parameter von CreateWindowEx so wie hier:
.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\
...........................
Sie werden sich vielleicht Fragen was der Unterschied zwischen diesen beiden Methoden ist.
Wenn Sie das Menü in der WNDCLASSEX-Struktur refernzieren, wird das Menü das Standard-Menü für die Fenster-Klasse. Jedes Fenster dieser Klasse wird das selbe Menü haben.
Wenn Sie für jedes Fenster der selben Klasse verschiedene Menüs haben möchten, müssen Sie die zweite Methode wählen. In diesem Fall wird jedes Fenster, das ein Menü-Handle in seiner CreateWindowEx-Funktion übergibt, ein Menü haben, dass das Standard-Menü in der WNDCLASSEX-Struktur 'überschreibt'.
Als nächstes werden wir uns anschauen, wie ein Menü die Fenster-Prozedur benachrichtigt, wenn der Benutzer ein Menü-Element auswählt.
Wenn der Benutzer ein Menü-Element auswählt, erhält die Fenster-Prozedur eine WM_COMMAND-Nachricht. Das untere Word von wParam enthält die Menü-ID des ausgewählten Elements.
Nun haben wir die wichtigsten Informationen um ein Menü zu erstellen und zu benutzen. Tun wir's.
Beispiel:
Das erste Beispiel zeigt, wie man ein Menü erzeugt und benutzt, indem der Menü-Name in einer Fenster-Klasse spezifiziert wird.
.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 ; Der Name unseres Menüs in der Ressource-Datei.
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 ; Hier kommt unser Menü-Name hin
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
**************************************************************************************************************************
Menu.rc
**************************************************************************************************************************
#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
}
Analyse:
Lassen Sie uns erst die Ressource-Datei analysieren.
#define IDM_TEST 1 /* equal to IDM_TEST equ 1*/
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
Die oberen Zeilen definieren die Menü-IDs, die vom Menü-Skript benutzt werden. Sie können jeden Wert einer ID zuweisen, solange dieser Wert einmalig in dem Menü ist.
FirstMenu MENU
Deklarieren Sie ihr Menü mit dem Schlüsselwort MENU.
POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
Definieren Sie ein Popup-Menü mit vier Menü-Elementen, wobei der dritte ein Menü-Seperator ist.
MENUITEM "&Test", IDM_TEST
Definiert eine Menü-Zeile im Hauptmenü.
Als nächstes werden wir den Source Code unter die Lupe nehmen.
MenuName db "FirstMenu",0 ; Der Namen unseres Menüs in der Ressource-Datei.
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 ist der Name des Menüs in der Ressource-Datei. Beachten Sie, dass Sie mehr als ein Menü in der Ressource-Datei definieren können, so dass Sie genauer spezifizieren müssen, welches Menü Sie benutzen wollen. Die übrigen drei Zeilen definieren die Texte, welche in den MessageBoxen angezeigt werden, die von den einzelnen Menü-Elementen, die der Benutzer anwählt, aufgerufen werden.
IDM_TEST equ 1 ; Menu IDs
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
Definiert Menü-IDs für die Benutzung in der Fenster-Prozedur. Diese Werte MÜSSEN identisch zu denen in der Ressource-Datei definierten sein.
.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
In der Fenster-Prozedur bearbeiten wir die WM_COMMAND Nachricht. Wenn der Benutzer ein Menü-Element auswählt, wird die Menü-ID des Menü-Elements an die Fensterprozedur im unteren Word von wParam gesendet, zusammen mit der WM_COMMAND Nachricht. So, dass wir den Wert von wParam in EAX speichern und den Wert von AX mit der Menü-ID vergleichen, die wir vorher definiert haben, und entsprechend reagieren. In den ersten drei Fällen, wenn der Benutzer das Menü-Element Test, Say Hello oder Say GoodBye auswählt, zeigen wir lediglich einen String in einer Message Box an.
Wenn der Benutzer das Menü-Element Exit auswählt, rufen wir DestroyWindow, zusammen mit dem Handle unseres Fensters als Parameter, auf, damit unser Fenster geschlossen wird.
Wie Sie sehen können, ist das spezifizieren eines Menü-Namens in einer Fenster-Klasse ziemlich einfach. Wie dem auch sei, Sie können auch eine alternative Methode benutzen, um ein Menü in ihrem Fenster zu laden. Ich werde hier nicht den kompletten Source Code hier zeigen. Die Ressource-Datei bleibt bei beiden Methoden die selbe. Es gibt nur wenige kleine Änderungen im Source, welche ich unten zeigen werde.
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ; handle unseres Menüs
Definiert eine Variable des Typs HMENU, um unser Menü-Handle zu speichern.
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
Bevor CreateWindowEx aufgerufen wird, rufen wir LoadMenu mit dem Instanz-Handle und einen Pointer auf den Namen unseres Menüs auf. LoadMenu gibt das Handle unseres Menüs aus der Ressource-Datei zurück, welches wir dann CreateWindowEx übergeben.
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage