Menüs

Inhaltsverzeichnis

Menüs
Menüs als externe Ressource
Menüs zur Laufzeit erzeugen
Menüs zur Laufzeit indirekt erzeugen
Zugriff auf das Systemmenü
Kontextmenüs
Arbeit mit Menüs
Shortcuts
Bitmaps vor Menüpunkten
Alles in einem Programm

Es gibt mindestens zwei Typen von Menüs: einmal als Bestandteil einer Menüleiste und dann als sogenanntes Kontextmenü, dessen Aufruf standardmäßig mit der rechten Maustaste oder der Tastenkombination Shift+F10 bzw. der auf neueren Tastaturen vorhandenen Windows-Taste erfolgt.
Beiden Menütypen ist gemein, dass sie verschachtelte Menüs enthalten können.

Die Erzeugung eines Menüs kann auf drei Arten geschehen: als externe Ressource, über direkte Funktionsaufrufe zur Laufzeit oder indirekt über das Befüllen einer Struktur mit anschließendem Funktionsaufruf zur Laufzeit. Die letzte Variante ist jedoch so komplex und im Programmiereralltag vermutlich zu selten, als dass ich darauf tiefer eingehen werde.

Menüs gehören zu einer Klasse von Daten, die als Ressourcen bezeichnet werden. Dabei wird zwischen Programm-eigenen und Windows-eigenen Ressourcen unterschieden. Die erstgenannten Ressourcen werden in der Programmdatei bzw. einer DLL gespeichert, die zweitgenannten als Standard von Windows bereit gestellt. Ressourcen werden erst bei Bedarf in den Speicher geladen. Zu ihnen zählen unter anderem auch Icons, Zeichenketten, Cursorformen, Dialoge, Bedienelemente auf Dialogen und Abkürzungsbefehlen (Shortcuts) in Menüs.
Das in der Einführung gezeigte Programm fenster.asm verwendet zwei von Windows bereit gestellte Ressourcen, nämlich das Icon und die Cursorform. Beide werden durch entsprechende Funktionsaufrufe (LoadIcon() und LoadCursor()) geladen. Der erste Parameter dieser Funkionen gibt die Quelle für die Ressource an - NULL steht hierbei für Windows, alles andere muss das Instanzhandle eines Programms oder einer DLL sein, Der Aufruf könnte auch invoke LoadCursor,hInstance,IDI_FENSTERICON lauten - die Ressource IDI_FENSTERICON würde dann im aktuellen Programm gesucht werden.

Kommen wir nun zu den Möglichkeiten der Menüdefinition.

Menüs als externe Ressource

Dieser Weg stellt eine recht einfache Möglichkeit der Definition dar, insbesondere deshalb, weil es grafische Werkzeuge gibt, bei denen die Menüstruktur einfach zusammengeklickt werden kann und als Ergebnis eine Ressourcendatei erzeugt wird. Dies gilt gleichermaßen für andere Ressourcen.

//Menüdefinitionen menu.rc
#define IDM_NEW    40001
#define IDM_OPEN   40002
#define IDM_SAVE   40003
#define IDM_SAVEAS 40004
#define IDM_EXIT   40005

#define IDM_CUT    40011
#define IDM_COPY   40012
#define IDM_INSERT 40013
#define IDM_UNDO   40014
#define IDM_WRAP   40015

WinMenu MENU
{
 POPUP "&Datei"
        {
         MENUITEM "&Neu" ,IDM_NEW
         MENUITEM "Ö&ffnen...",IDM_OPEN
         MENUITEM "&Speichern",IDM_SAVE
         MENUITEM "Speichern &unter...",IDM_SAVEAS
         MENUITEM SEPARATOR
         MENUITEM "&Beenden",IDM_EXIT
        }
 POPUP "&Bearbeiten"
        {
         MENUITEM "&Rückgängig",IDM_UNDO,GRAYED
         MENUITEM SEPARATOR
         MENUITEM "&Ausschneiden",IDM_CUT,GRAYED
         MENUITEM "&Kopieren",IDM_COPY,GRAYED
         MENUITEM "&Einfügen",IDM_INSERT,GRAYED
         MENUITEM SEPARATOR
         MENUITEM "&Wordwrap",IDM_WRAP,CHECKED
        }
}

Die Wahl der Menüpunkte orientiert sich an den Standards von Notepad.
Wie sie sehen können ist die Definition eines Menüs recht geradlinig. Dieses Menü hat den Namen WinMenu und enthält die Untermenüs (Popups) Datei und Bearbeiten mit jeweils einigen Menüpunkten. Jeder Bestandteil eines Untermenüs (MENUITEM) kann entweder ein richtiger Menüpunkt oder ein Separator sein. Letztere werden als waagerechte Striche dargstellt, die verschiedene Bereiche innerhalb eines Untermenüs voneinander abgrenzen. Wenn ein Menüpunkt kein Separator ist so hat er einen Namen und eine Nummer, unter der dieser Punkt im Programm ansprechbar sein wird. Diese Nummern werden hier mittels #define als Konstanten definiert.
Darüber hinaus kann ein Menüpunkt noch weitere optionale Eigenschaften erhalten, er kann bspw. von vornherein deaktiviert (GRAYED) sein oder als Schalter fungieren, der aktiviert (CHECKED) ist.
Bei der Menüdefinition werden Blöcke gebildet, die durch geschweifte Klammern begrenzt werden. Statt der Klammern ist auch die Verwendung der Schlüsselworte BEGIN und END möglich.

Um dieses Menü im Programm verwenden zu können sind mehrere Schritte notwendig. Als erstes muss die rein textuelle Form in binäre Form gebracht werden. Diese Aufgabe übernimmt der Ressoucencompiler rc.exe mittels der Kommandozeile rc menu.rc. Als Ergebnis wird die Datei rc.res erzeugt.
Danach erfolgt die Einbindung in das Programm. Dazu werden die Konstanten aus dem Ressourcenscript in den Abschnitt .CONST übernommen und im Abschnitt .DATA zusätzlich die Zeichenkette mit dem Menünamen definiert.

.DATA
...
...
cMenuName db "WinMenu",0

.CONST
IDM_NEW    EQU 40001
IDM_OPEN   EQU 40002
IDM_SAVE   EQU 40003
IDM_SAVEAS EQU 40004
IDM_EXIT   EQU 40005

IDM_CUT    EQU 40011
IDM_COPY   EQU 40012
IDM_INSERT EQU 40013
IDM_UNDO   EQU 40014
IDM_WRAP   EQU 40015

Wenn das Programm nur mit einer Menüleiste arbeitet, dann kann diese direkt in der Fensterklassenstruktur angegeben werden. Hierzu genügt folgender Code mov wc.lpszMenuName,OFFSET cMenuName. Wenn mit mehreren Menüleisten gearbeitet wird, dann kann mit der Funktion LoadMenu() ein Handle zur jeweiligen Menüleiste ermittelt werden:
invoke LoadMenu,hInstance,ADDR cMenuName
Für unseren Fall genügt die Angabe in der Fensterklassenstruktur. Alles was jetzt noch eingebaut werden muss sind die Reaktionen auf die Betätigung der einzelnen Menüpunkte.
Ich belasse es für den Augenblick exemplarisch bei Reaktionen auf IDM_NEW und IDM_EXIT.

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  .IF uMsg == WM_DESTROY
    invoke PostQuitMessage,NULL
  .ELSEIF uMsg == WM_COMMAND
    mov eax,wParam
    .IF ax==IDM_EXIT
      invoke DestroyWindow,hWnd
    .ELSEIF ax==IDM_NEW
      invoke MessageBeep,MB_OK
    .ENDIF
  .ELSE
    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
  .ENDIF
  xor eax,eax
  ret
WndProc ENDP

Wie sie sehen können erfolgt die Änderung nur in der Fensterprozedur. Diese wird immer von Windows aufgerufen, wenn ein Ereignis für das Fenster, resp. die Fensterklasse, eintritt.
In C oder einer sonstigen Hochsprache würde die Unterscheidung der Ereignisse, deren numerischer Wert im Parameter uMsg geliefert wird, sicherlich durch eine Form der CASE- oder switch-Anweisung erfolgen. Hier kommen uns die Makrofähigkeiten des MASM zugute, die es sehr einfach machen, die Abfrage durch geschachtelte .IF-.ELSEIF-Direktiven vorzunehmen. Natürlich hindert sie niemand daran, die einzelnen Werte klassisch per CMP zu ermitteln.
Die Betätigung eines Menüpunktes erzeugt immer eine Nachricht des Typs WM_COMMAND. Für welchen konkreten Punkt die Nachricht gesendet wird lässt sich durch Untersuchung des niederwertigen Words des Parameters wParam ermitteln. Der hier gelieferte Werte entspricht dem numerischen Wert, wie er in der Ressource-Datei vergeben wurde. Daraus ergibt sich gleichzeitig die Obergrenze der numerischen Entsprechung der Menüpunkte.
Die Ereignisbearbeitung für WM_COMMAND sollte mehr oder weniger selbsterklärend sein. Hier zeigt sich auch der Sinn der Konstantendefinition weiter oben im Quelltext.
Die Reaktion auf IDM_EXIT besteht im Aufruf der API-Funktion DestroyWindow, die alle von hWnd abgeleiteten Fenster löscht und die Nachrichten WM_DESTROY und WM_NCDESTROY an das eigene Fenster sendet. Auf WM_DESTROY wird innerhalb der Nachrichtenverarbeitung ebenfalls reagiert, um WM_NCDESTROY kümmert sich die API-Funktion DefWindowProc.
Bei IDM_NEW wird die API-Funktion MessageBeep aufgerufen, die einfach nur den Standardton ausgibt. Um diesen Ton zu hören muss ihr Rechner über eine Soundkarte verfügen und in der Systemsteuerung muss für den Standardton ein Zuordnung erfolgt sein.
Hier nochmal das komplette Programm.

;menu.asm
.386
.MODEL flat,stdcall
OPTION casemap:none

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

WndProc PROTO :dword, :dword, :dword, :dword

.DATA
szCaption db "Fenster mit Menü",0
szClassName db "My_Class",0
cMenuName db "WinMenu",0

.DATA?			;uninitialisierte Daten
hInstance HINSTANCE ?
hIcon HANDLE ?
hCursor HANDLE ?
hWnd HWND ?
cCmdline LPSTR ?

.CONST
IDM_NEW    EQU 40001
IDM_OPEN   EQU 40002
IDM_SAVE   EQU 40003
IDM_SAVEAS EQU 40004
IDM_EXIT   EQU 40005

IDM_CUT    EQU 40011
IDM_COPY   EQU 40012
IDM_INSERT EQU 40013
IDM_UNDO   EQU 40014
IDM_WRAP   EQU 40015

.CODE
start:
  call mymain
  invoke ExitProcess,0


mymain PROC
  LOCAL wwd:dword,wht:dword,wtx:dword,wty:dword,wc:WNDCLASSEX
  
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke GetCommandLine
  mov cCmdline,eax

  mov wtx,CW_USEDEFAULT ;oder konkrete Angaben: 0
  mov wty,CW_USEDEFAULT ;0
  mov wwd,CW_USEDEFAULT ;400
  mov wht,CW_USEDEFAULT ;400
  
  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style,CS_BYTEALIGNCLIENT or CS_BYTEALIGNWINDOW
  mov wc.lpfnWndProc,OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_BTNFACE+1
  mov wc.lpszMenuName,OFFSET cMenuName
  mov wc.lpszClassName,OFFSET szClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov hIcon,eax
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov hCursor,eax
  mov wc.hCursor,eax
  
  invoke RegisterClassEx,ADDR wc
  
  invoke CreateWindowEx,WS_EX_LEFT,ADDR szClassName,ADDR szCaption,
                        WS_OVERLAPPEDWINDOW,wtx,wty,wwd,wht,NULL,NULL,hInstance,NULL
  mov hWnd,eax
  
  invoke ShowWindow,hWnd,SW_SHOWNORMAL
  invoke UpdateWindow,hWnd
  
  call MsgLoop
  ret
mymain ENDP
  

MsgLoop PROC
  LOCAL msg:MSG
  
  Startloop:
    invoke GetMessage,ADDR msg,NULL,0,0
    or eax,eax
    je Exitloop
    invoke DispatchMessage,ADDR msg
    invoke TranslateMessage,ADDR msg
    jmp Startloop
  Exitloop:
  mov eax,msg.wParam
  ret
MsgLoop ENDP

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  .IF uMsg == WM_DESTROY
    invoke PostQuitMessage,NULL
  .ELSEIF uMsg == WM_COMMAND
    mov eax,wParam
    .IF ax==IDM_EXIT
      invoke DestroyWindow,hWnd
    .ELSEIF ax==IDM_NEW
      invoke MessageBeep,MB_OK
    .ENDIF
  .ELSE
    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
  .ENDIF
  xor eax,eax
  ret
WndProc ENDP

END start

Kompiliert wird das Programm inklusive der Ressourcendatei mit folgenden Befehlen:

rc menu.rc
ml /c /coff menu.asm
link /SUBSYSTEM:windows /LIBPATH:d:\masm32\lib menu.obj menu.res

Menüs zur Laufzeit erzeugen

Normalerweise haben Fenster nur genau ein Menü, das in der Regel auch in einer Ressource definiert wird. Die dynamische Erzeugung von Menüs und Menüpunkten kommt meist dann ins Spiel, wenn mit verschiedenen Menüs gearbeitet wird oder sich der Inhalt des Menüs in Abhängigkeit von bestimmten Umständen ändert.
Im folgenden wird die Unterscheidung zwischen Menüleiste und Untermenü wichtig. Die Menüleiste befindet sich unterhalb der Titelleiste des Fensters. Sie stellt die Hauptmenüpunkte zur Verfügung. Untermenüs öffnen sich nach Auswahl eines Hauptmenüpunktes bzw., in tiefer verschachtelten Strukturen, nach Auswahl eines Untermenüpunktes.

Für die Erzeugung des Menüs genügen die beiden API-Funktionen CreateMenu und AppendMenu. Der Aufbau des Menüs erfolgt sozusagen von unten nach oben: erst werden die Untermenüs erzeugt, die dann in die Menüleiste eingehängt werden. Als Alternative zur Funktion AppendMenu können sie auch auch die Funktion InsertMenuItem verwenden, die zwar flexibler ist und das Einfügen neuer Menüpunkte an beliebiger Stelle im Menü gestattet, aber für unseren einfachen Zweck ist AppendMenu vollkommen ausreichend.

Da die Ressourcendatei hier wegfällt müssen die Bezeichnungen der Menüpunkte als Zeichenketten im Programm hinterlegt werden. Zu diesem Zweck werden im .DATA-Abschnitt Variablen für die (Haupt)Menüpunkte definiert, ebenso wie zwei Variablen, die die Handles der Menüleiste sowie der Untermenüs aufnehmen im .DATA?-Abschnitt. Zur besseren Unterscheidung wird auch gleich ein anderer Fenstertitel verwendet:

.DATA
szCaption db "Fenster mit Menü zur Laufzeit",0
...
cMenuFile db "&Datei",0                    ;Hauptmenüpunkt
cMenuNew db "&Neu" ,0
cMenuOpen db "Ouml;&ffnen...",0
cMenuSave db "&Speichern",0
cMenuSaveAs db "Speichern &unter...",0
cMenuExit db "&Beenden",0
cMenuEdit db "&Bearbeiten",0               ;Hauptmenüpunkt
cMenuUndo db "&Rückgängig",0
cMenuCut db "&Ausschneiden",0
cMenuCopy db "&Kopieren",0
cMenuInsert db "&Einfügen",0
cMenuWordwrap db "&Wordwrap",0

.DATA?			;uninitialisierte Daten
...
hMenuBar HANDLE ?
hMenu HANDLE ?

An der Definition der Konstanten ändert sich nichts.
Die Menüdefinition habe ich in eine separate Funktion ausgelagert:
erzeugeMenu PROC
  invoke CreateMenu
  mov hMenuBar,eax                          ;Menüleiste
  invoke CreateMenu
  mov hMenu,eax                             ;Untermenü
  invoke AppendMenu,hMenu,MF_STRING,IDM_NEW,ADDR cMenuNew
  invoke AppendMenu,hMenu,MF_STRING,IDM_OPEN,ADDR cMenuOpen
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVE,ADDR cMenuSave
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVEAS,ADDR cMenuSaveAs
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING,IDM_EXIT,ADDR cMenuExit
  invoke AppendMenu,hMenuBar,MF_POPUP,hMenu,ADDR cMenuFile
  invoke CreateMenu
  mov hMenu,eax
  invoke AppendMenu,hMenu,MF_STRING,IDM_UNDO,ADDR cMenuUndo
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING OR MFS_GRAYED,IDM_CUT,ADDR cMenuCut
  invoke AppendMenu,hMenu,MF_STRING OR MFS_GRAYED,IDM_COPY,ADDR cMenuCopy
  invoke AppendMenu,hMenu,MF_STRING OR MFS_GRAYED,IDM_INSERT,ADDR cMenuInsert
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING OR MFS_CHECKED,IDM_WRAP,ADDR cMenuWordwrap
  invoke AppendMenu,hMenuBar,MF_POPUP,hMenu,ADDR cMenuEdit
  mov eax,hMenuBar
  ret
erzeugeMenu ENDP

Wie sie sehen, muss nicht für jedes Untermenü auch eine separate Variable für dessen Handle verwendet werden. Und wie sie ebenfalls sehen, macht es für die Funktionen keinen Unterschied, ob nun gerade die Menüleiste oder ein Untermenü erzeugt wird. Die Darstellung auf dem Bildschirm wird augenscheinlich durch Windows auf Grundlage der sich ergebenden Hierarchie erzeugt.
Der Typ des Menüpunktes wird durch den 2. Parameter von AppendMenu festgelegt. MF_STRING und MF_SEPARATOR sollten klar sein. MF_STRING kann noch mit einer Reihe weiterer Flags kombiniert werden, hier geschehen durch eine ODER-Verknüpfung mit den Flags MFS_GRAYED und MFS_CHECKED.
Das Einhängen eines Untermenüs geschieht wieder mit AppendMenu, der Typ ist diesmal MF_POPUP. Der dritte Parameter ist in diesem Fall keine Konstante, sondern das Handle des übergeordneten Menü(punkte)s.
Die Funktion gibt das Handle der Menüleiste in EAX zurück.

Im Hauptprogramm mymain erhält die Strukturvariable wc.lpszMenuName wieder den Wert NULL. Die Erzeugung des Menüs erfolgt nach dem Aufruf von CreateWindowEx und vor dem Aufruf von ShowWindow.

mymain PROC
  ...
  mov wc.lpszMenuName,NULL
  ...
  invoke CreateWindowEx,WS_EX_LEFT,ADDR szClassName,ADDR szCaption,
                        WS_OVERLAPPEDWINDOW,wtx,wty,wwd,wht,NULL,NULL,hInstance,NULL
  mov hWnd,eax
  call erzeugeMenu                          ;Menüstruktur erzeugen
  mov hMenuBar,eax                          ;Handle nochmal speichern
  invoke SetMenu,hWnd,eax                   ;das Menü im Fenster setzen
  
  invoke ShowWindow,hWnd,SW_SHOWNORMAL
  invoke UpdateWindow,hWnd
  
  call MsgLoop
  ret  
mymain ENDP

Der Aufruf von erzeugeMenu erfolgt hier nicht mit INVOKE, da es für diese Funktion keinen Prototyp gibt (der auch nicht erforderlich ist).

Menüs zur Laufzeit indirekt erzeugen

Die 3. Variante der Menüerzeugung ist gleichzeitig auch die komplizierteste. Indirekt bedeutet hier, dass erst ein Speicherbereich allokiert werden muss. In diesem Speicherbereich wird eine Struktur des Typs MENUITEMTEMPLATEHEADER, gefolgt von einer oder mehreren Strukturen des Typs MENUITEMTEMPLATE erwartet. Wie bereits weiter oben gesagt, werde ich hier keine weiteren Erklärungen liefern, zumal hier auch der etwas fortgeschrittenere Bereich der Speicheranforderung von Windows eine Rolle spielt und Zeichenketten in MENUITEMTEMPLATE in Unicode vorliegen müssen.

Zugriff auf das Systemmenü

Jedes Fenster, das mit der Eigenschaft MS_SYSMENU erzeugt wurde, enthält in der linken oberen Ecke ein Systemmenü. Da unser Fenster mit WS_OVERLAPPEDWINDOW erzeugt wurde, eine aus mehreren Werten zusammen gesetzte Konstante, u. a. auch WS_SYSMENU, haben wir dieses Systemmenü ebenfalls.

Menüpunkte des Systemmenüs senden im Gegensatz zu "normalen" Menüpunkten Nachrichten des Typs WM_SYSCOMMAND, nicht WM_COMMAND. Die für die Menüpunkte verwendeten IDs/Konstanten sollten Werte unterhalb von 0F000h bzw. 61440d haben. Die üblichen WM_SYSCOMMAND-Nachrichten entsprechen den mit SC_ beginnenden Konstanten (außer SC_HANDLE und SC_LOCK).
Es unbedingt darauf zu achten, dass die nicht selbst behandelten WM_SYSCOMMAND- Nachrichten an DefWindowProc weitergeleitet werden, weil sonst das Programm nur noch über den Task-Manager beendet werden kann.
Folgende Programmerweiterungen sind notwendig:

.DATA
cMenuHello db "Say Hello",0
cMenuStd db "Standard",0

.DATA?
hSysMenu HANDLE ?

.CONST
IDM_SYS_HELLO EQU 40020
IDM_SYS_STD EQU 40021


mymain PROC
  ...
  invoke CreateWindowEx,WS_EX_LEFT,ADDR szClassName,ADDR szCaption,
                        WS_OVERLAPPEDWINDOW,wtx,wty,wwd,wht,NULL,NULL,hInstance,NULL
  mov hWnd,eax
  
  invoke GetSystemMenu,hWnd,FALSE
  mov hSysMenu,eax
  invoke AppendMenu,eax,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hSysMenu,MF_STRING,IDM_SYS_HELLO,ADDR cMenuHello
  invoke AppendMenu,hSysMenu,MF_STRING,IDM_SYS_STD,ADDR cMenuStd
  
  invoke ShowWindow,hWnd,SW_SHOWNORMAL
  invoke UpdateWindow,hWnd
  
  call MsgLoop
  ret
mymain ENDP

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  .IF uMsg == WM_DESTROY
  ...
  .ELSEIF uMsg == WM_SYSCOMMAND
    mov eax,wParam
    .IF ax==IDM_SYS_HELLO
      invoke MessageBox,hWnd,ADDR cMenuHello,ADDR szCaption,MB_OK
    .ELSEIF ax==IDM_SYS_STD
      invoke GetSystemMenu,hWnd,TRUE
    .ELSE
      invoke DefWindowProc,hWin,uMsg,wParam,lParam
    .ENDIF
  .ELSE
    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
  .ENDIF
  xor eax,eax
  ret
WndProc ENDP

Das Handle des Systemmenüs erhalten wir mit GetSystemMenu(). Der erste Parameter ist das Handle des Fensters, der zweite Parameter hat eine Doppelfunktion: wenn FALSE übergeben wird, dann liefert die Funktion das Handle einer Kopie des Systemmenüs zurück. Diese zunächst identische Kopie kann verändert werden. Wird TRUE übergeben, dann stellt die Funktion das von Windows definierte, ursprüngliche Systemmenü wieder her und liefert NULL als Ergebnis.

In mymain wird das Systemmenü ermittelt und eine Trennlinie sowie zwei Punkte angehängt. Die entsprechenden Konstanten- und Textdefinitionen dafür sollten klar sein.
Für die Reaktion wurde ein weiterer Zweig in WndProc eingebaut, in dem bei Klick auf "Say Hello" einfach der Text des Menüpunktes in einer Messagebox ausgegeben wird. Bei Klick auf Standard wird der von Windows definierte Standard wiederhergestellt.

Kontextmenüs

Kontextmenüs werden unter Windows bekanntlich mit der rechten Maustaste bzw. einer Tastenkombination geöffnet. Diese Notwendigkeit ergibt sich aus der fehlenden Menüleiste.
Ich demonstriere hier den einfachsten Fall: ein komplettes Untermenü soll als Kontextmenü bereit gestellt werden, die Menüs werden zur Laufzeit (mit AppendMenu) erzeugt. Unter diesen Umständen ist es dann relativ simpel: es muss lediglich eine weitere Variable mit dem Handle des Untermenüs befüllt und eine Reaktion auf die Nachricht WM_RBUTTONUP eingebaut werden.

.DATA?			;uninitialisierte Daten
...
hKontextMenu HANDLE ?

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  LOCAL point:POINT                         ;neu
  .IF uMsg == WM_DESTROY
    invoke PostQuitMessage,NULL
  .ELSEIF uMsg == WM_RBUTTONUP
    mov eax,lParam
    movzx eax,ax
    mov point.x,eax
    mov eax,lParam
    shr eax,16
    mov point.y,eax
    invoke ClientToScreen,hWnd,ADDR point
    invoke TrackPopupMenu,hKontextMenu,TPM_RIGHTBUTTON,point.x,point.y,0,hWnd,NULL
  .ELSEIF uMsg == WM_COMMAND
  ...
WndProc ENDP

erzeugeMenu PROC
  invoke CreateMenu
  mov hMenuBar,eax
  invoke CreateMenu
  mov hMenu,eax
  invoke AppendMenu,hMenu,MF_STRING,IDM_NEW,ADDR cMenuNew
  invoke AppendMenu,hMenu,MF_STRING,IDM_OPEN,ADDR cMenuOpen
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVE,ADDR cMenuSave
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVEAS,ADDR cMenuSaveAs
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING,IDM_EXIT,ADDR cMenuExit
  invoke AppendMenu,hMenuBar,MF_POPUP,hMenu,ADDR cMenuFile
  invoke CreateMenu
  mov hMenu,eax
  mov hKontextMenu,eax          ; hier das neue Menü speichern
  invoke AppendMenu,hMenu,MF_STRING,IDM_UNDO,ADDR cMenuUndo
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING OR MFS_GRAYED,IDM_CUT,ADDR cMenuCut
  invoke AppendMenu,hMenu,MF_STRING,IDM_COPY,ADDR cMenuCopy
  invoke AppendMenu,hMenu,MF_STRING OR MFS_GRAYED,IDM_INSERT,ADDR cMenuInsert
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING OR MFS_CHECKED,IDM_WRAP,ADDR cMenuWordwrap
  invoke AppendMenu,hMenuBar,MF_POPUP,hMenu,ADDR cMenuEdit
  mov eax,hMenuBar
  ret
erzeugeMenu ENDP

In der Ereignisbehandlung für WM_RBUTTONUP wird im Parameter lParam die Position des Mausklicks geliefert, im LoWord die X-, im HiWord die Y-Position. Diese beiden Werte müssen in Bildschirmkoordinaten umgerechnet werden, da in lParam die Koordinaten relative zum Fenster enthalten sind. Die Umrechnung führt die Funktion ClientToScreen() durch - nach deren Aufruf stehen in point die Bildschirmkoordinaten.
Für die Anzeige und Handhabung des Kontextmenüs ist die Funktion TrackPopupMenu() verantwortlich. Diese Funktion erhült das Handle des Menüs, Flags, die Position der linken oberen Ecke des anzuzeigenden Menüs, einen reservierten Wert, der immer Null sein muss, das Fenster sowie einen Parameter, der von Windows ignoriert wird und deswegen NULL ist (vermutlich sollte hier mal ein Zeiger auf ein Clipping-Rechteck Beachtung finden, diese Funktion wird aber durch TrackPopupMenuEx() abgedeckt).

Da die Menüpunkte identisch mit denen aus der Menüleiste sind und auch von TrackPopupMenu() WM_COMMAND-Nachrichten versendet werden, muss an der Ereignisbehandlung nichts gedreht werden.

Das gezeigte Vorgehen wurde nur unter Win 2000 getestet. In den mir vorliegenden Dokumentationen wird ausdrücklich darauf hingewiesen, dass zur Laufzeit erzeugte Kontextmenüs mit der Funktion CreatePopupMenu() zu erzeugen sind. Sollte dieses Beispiel bei ihnen nicht in dieser Form funktionieren, dann versuchen sie es bitte mit CreatePopupMenu(). Die Verwendung und die Parameter entsprechen CreateMenu().

Bei Menüs aus Ressourcendateien muss etwas anders vorgegangen werden.
An der Definition ändert sich nichts, das Kontextmenü muss jedoch mittels LoadMenu() geladen und von diesem Menü das erste Untermenü verwendet werden. In etwa auf diese Art:

invoke LoadMenu,hInstance,ADDR cMenuName
invoke GetSubMenu,eax,0
mov hKontextMenu,eax
Diese Codezeilen sollten nach CreateWindowEx() erscheinen oder in der Ereignisbehandlung der Nachricht WM_CREATE, die bisher noch nicht behandelt wurde (wird bei Erzeugung, aber vor der Anzeige des Fensters gesendet).

Arbeit mit Menüs

Wie auf die Auswahl eines Menüpunktes reagiert werden kann wurde bereits gezeigt. Das Windows-API stellt jedoch Funktionen bereit, die die Zustandsabfrage und Manipulation von Menüs ermöglichen.

In den vorherigen Beispielen ist im Untermenü Bearbeiten der Menüpunkt Wordwrap enthalten. Dieser stellt, wie durch die entsprechende Kennzeichnung erkennbar, einen Schalter dar. Hier der Code, wie auf die Auswahl dieses Punktes reagiert werden kann.

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  .IF uMsg == WM_DESTROY
    invoke PostQuitMessage,NULL
  .ELSEIF uMsg == WM_COMMAND
    mov eax,wParam
    .IF ax==IDM_EXIT
      invoke DestroyWindow,hWnd
    .ELSEIF ax==IDM_NEW
      invoke MessageBeep,MB_OK
    .ELSEIF ax==IDM_WRAP
      invoke GetMenuState,hMenuBar,IDM_WRAP,MF_BYCOMMAND
      and eax,MFS_CHECKED
      jnz @@ischecked
        invoke CheckMenuItem,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_CHECKED
        jmp short @@endecheckwordwrap
      @@ischecked:
        invoke CheckMenuItem,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_UNCHECKED
      @@endecheckwordwrap:
    .ENDIF
  .ELSE
    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
  .ENDIF
  xor eax,eax
  ret
WndProc ENDP

Die einzige Änderung erfolgt in der Ereignisbehandlung - es wurde noch ein Zweig für IDM_WRAP eingebaut. Die API-Funktion GetMenuState() gibt entweder den Status eines konkreten Menüpunktes oder zusätzlich die Anzahl der Elemente eines Untermenüs zurück, je nachdem, was als 2. Parameter übergeben wird. Der dritte Parameter bestimmt, ob es sich beim 2. Parameter um eine ID oder eine nullbasierte relative Position zum Menüanfang handelt.
Das niederwertige Word des Ergebnisses enthält die mit OR kombinierten Stati des Elementes (die entsprechenden Konstanten beginnen mit MFS_ oder MFT_). Eine AND-Verknüpfung mit MFS_CHECKED sagt uns dann, ob das Element gerade an- oder ausgeschaltet ist. Die entsprechenden Reaktion erfolgt durch die API- Funktion CheckMenuItem(). Der 3. Parameter gibt wieder den Typ des 2. Parameters an, wird aber zusätzlich mit dem Wert kombiniert, den das Element annehmen soll.

Menüpunkte aktivieren oder deaktivieren erfolgt mit der Funktion EnableMenuItem(). Die Parameter entsprechen weitgehend denen von CheckMenuItem(), beim letzten Parameter macht nur eine Verknüpfung mit MF_ENABLED oder MF_GRAYED Sinn.

    ...
    .ELSEIF ax==IDM_NEW
      invoke MessageBeep,MB_OK
    .ELSEIF ax==IDM_COPY
      invoke GetMenuState,hMenuBar,IDM_INSERT,MF_BYCOMMAND
      and eax,MFS_GRAYED
      jz @@endegrayedcopy
        invoke EnableMenuItem,hMenuBar,IDM_INSERT,MF_BYCOMMAND OR MF_ENABLED
      @@endegrayedcopy:
    .ELSEIF ax==IDM_WRAP
    ...

In diesem speziellen Fall würde der Aktivierung des Menüpunktes Einfügen eine Prüfung der Zwischenablage vorausgehen, zur Darstellung des Verfahrens genügt es jedoch.
Beachten Sie, dass der obige Ausschnitt nur funktioniert, wenn der Menüpunkt Kopieren nicht als GRAYED definiert ist bzw. anderweitig aktiviert wurde.

Die Änderung der Bezeichnung eines Menüpunktes kann über die Funktionen ModifyMenu() und SetMenuItemInfo() erfolgen. Bei letzterer muss vorher eine Struktur des Typs MENUITEMINFO gefüllt werden. Beide Funktionen sind dabei in der Lage, die Aufgaben der Funktionen CheckMenuItem() und EnableMenuItem() auszuführen, wobei für genau diese Aufgaben auch diese speziellen Funktionen verwendet werden sollten.
Im Beispiel wird es wieder um den Punkt Wordwrap gehen. Der neue Status des Punktes wird jetzt auch durch eine neue Bezeichnung angezeigt.

.DATA
...
cMenuWordwrap db "&Wordwrap ist an",0
cMenuWordwrapOff db "&Wordwrap ist aus",0
...

.CODE
...
WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
    ...
    .ELSEIF ax==IDM_WRAP
      invoke GetMenuState,hMenuBar,IDM_WRAP,MF_BYCOMMAND
      and eax,MFS_CHECKED
      jnz @@ischecked
        invoke ModifyMenu,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_STRING OR MF_CHECKED,IDM_WRAP,\
               ADDR cMenuWordwrap
        jmp short @@endecheckwordwrap
      @@ischecked:
        invoke ModifyMenu,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_STRING OR MF_UNCHECKED,IDM_WRAP,\
               ADDR cMenuWordwrapOff
      @@endecheckwordwrap:
    ...

Die Funktion CheckMenuItem() ist hier nicht mehr notwendig, da MofifyMenu() deren Aufgabe übernimmt. Dazu muss dann bei den Flags sowohl die Änderung der Bezeichnung (MF_STRING) als auch des Check-Status (MF_CHECKED) angegeben werden.
Die ersten drei Parameter entsprechen denen von CheckMenuItem(). Da ModifyMenu() auch neue Untermenüs einhängen kann, ist im 4. Parameter die Angabe des Handles eines neues Menüs oder die ID des geänderten Punktes erforderlich. Der 5. Parameter hängt wieder vom 3. Parameter ab. Wenn dieser MF_STRING enthält, dann wird ein Zeiger auf einen String erwartet, bei MF_BITMAP wäre es das Handle einer Bitmap-Grafik.

Shortcuts

Mit Shortcuts werden Tastenkombinationen bezeichnet, die die Nutzer eines Programmes verwenden können, um nicht jedes mal erst ein Menü öffnen zu müssen, um einen Befehl auszuführen.
Prominenteste Beispiele unter Windows dürften die Shortcuts für Programm beenden (Alt+F4), Hilfe (F1), Kopieren (Strg-C), Ausschneiden (Strg+X) sowie Einfügen (Strg+V) sein. Es gibt noch eine Reihe weiterer Standard-Shortcuts, die sie in ihren eigenen Programmen nach Möglichkeit nicht umdefinieren sollten. Daneben gibt es Quasi-Standards wie bspw. Strg+N (neues Dokument erzeugen) und Strg+S (Datei speichern),die vor allem durch Textverarbeitungen Verbreitung finden.

Auf der Programmiererseite sind Shortcuts Tastenbefehle, die Nachrichten der Typen WM_COMMAND und WM_SYSCOMMAND erzeugen. Um die Verarbeitung der Shortcuts muss man sich als Programmierer nur in geringem Maße selbst kümmern. Als Shortcuts kommt jede Kombination von virtuellen Tasten (die Konstanten, die mit VK_ beginnen) oder Buchstaben mit Strg, Shift/Umschalt sowie Alt in Frage.

Shortcuts werden entweder in der Ressourcendatei definiert, es gibt dafür den Ressourcentyp ACCELERATORS, oder zur Programmlaufzeit mittels CreateAcceleratorTable(). Wenn Shortcuts angeboten werden, dann sollte dies auch an den Menüpunkten erkennbar sein. Die Abtrennung der Bezeichnung vom Shortcut kann mittels des Steuerzeichens \t in der Beschriftung des Punktes erreicht werden.
Hier als Beispiel die um Shortcuts erweiterte Ressourcendatei von weiter oben.

#define IDM_NEW    40001
#define IDM_OPEN   40002
#define IDM_SAVE   40003
#define IDM_SAVEAS 40004
#define IDM_EXIT   40005

#define IDM_CUT    40011
#define IDM_COPY   40012
#define IDM_INSERT 40013
#define IDM_UNDO   40014
#define IDM_WRAP   40015

#define VK_DELETE  46
#define VK_INSERT  45

MY_MENU MENU
{
 POPUP "&Datei"
        {
         MENUITEM "&Neu" ,IDM_NEW
         MENUITEM "Ö&ffnen...",IDM_OPEN
         MENUITEM "&Speichern",IDM_SAVE
         MENUITEM "Speichern &unter...",IDM_SAVEAS
         MENUITEM SEPARATOR
         MENUITEM "&Beenden",IDM_EXIT
        }
 POPUP "&Bearbeiten"
        {
         MENUITEM "&Rückgängig",IDM_UNDO,GRAYED
         MENUITEM SEPARATOR
         MENUITEM "&Ausschneiden\tStrg+X",IDM_CUT,GRAYED
         MENUITEM "&Kopieren\tStrg+C",IDM_COPY,GRAYED
         MENUITEM "&Einfügen\tStrg+V",IDM_INSERT,GRAYED
         MENUITEM SEPARATOR
         MENUITEM "&Wordwrap",IDM_WRAP,CHECKED
        }
}

MY_MENU ACCELERATORS
{
  "X",      IDM_CUT,    VIRTKEY, CONTROL,NOINVERT
  VK_DELETE,IDM_CUT,    VIRTKEY, SHIFT,  NOINVERT
  "C",      IDM_COPY,   VIRTKEY, CONTROL,NOINVERT
  VK_INSERT,IDM_COPY,   VIRTKEY, CONTROL,NOINVERT
  "V",      IDM_INSERT, VIRTKEY, CONTROL,NOINVERT
  VK_INSERT,IDM_INSERT, VIRTKEY, SHIFT,  NOINVERT
}

Beachten Sie bitte, dass sowohl die Menüdefinition als auch die Shortcuts den gleichen Namen tragen, der jetzt dem Klassennamen des Programms entspricht. Es werden nur für die Befehle Ausschneiden, Kopieren und Einfügen Shortcuts definiert, dabei werden auch die etwas älteren Shortcuts (Umschalt+Entf, Strg+Einfg, Umschalt+Einfg) mitdefiniert. NOINVERT sorgt dafür, dass bei Wahl des Shortcuts nicht der zugehörige Hauptmenüpunkt hervorgehoben wird.

Die Einbindung der Acceleratoren (eigentlich Beschleuniger oder Beschleuigertasten) erfolgt an zwei Stellen. Zum einen müssen die Shortcuts geladen werden, zum anderen muss Windows uns mitteilen, ob einer der Shortcuts gesendet wurde.
Punkt 1 wird durch die API-Funktion LoadAccelerators() erledigt. Dazu muss sie entweder in mymain oder in der Ereignisbehandlung von WM_CREATE aufgerufen werden:

invoke LoadAccelerators,hInstance,ADDR szClassName
mov hAccel,eax

Für die Verarbeitung der Shortcuts (die uns Windows abnimmt), muss erstmalig eine Änderung an MsgLoop vorgenommen werden.

MsgLoop PROC
  LOCAL msg:MSG
  
  Startloop:
    invoke GetMessage,ADDR msg,NULL,0,0
    or eax,eax
    je Exitloop
    invoke TranslateAccelerator,hWnd,hAccel,ADDR msg
    or eax,eax
    jnz Startloop
    invoke DispatchMessage,ADDR msg
    invoke TranslateMessage,ADDR msg
    jmp Startloop
  Exitloop:
  mov eax,msg.wParam
  ret
MsgLoop ENDP

Wenn TranslateAccelerator() einen Shortcut in ein entsprechendes WM_COMMAND- Ereignis umgewandelt hat, dann wird in EAX ein Wert ungleich Null geliefert. In diesem Fall kann die nächste Nachricht aus der Warteschlange entnommen werden.

Bitmaps vor Menüpunkten

In vielen Anwendungen werden vor einzelnen Menüpunkten noch kleine Bilder dargestellt, die die Funktion des Menüpunktes versinnbildlichen, z. B. ein Diskettensymbol vor dem Punkt Speichern und ein Ordner-Symbol vor Öffnen.

Die dazugehörigen Bitmaps können sowohl in der Ressourcedatei und damit im Programm enthalten sein oder als Datei auf dem Datenträger liegen. Der folgende Ausschnitt stellt das Vorgehen für externe Dateien dar.

...
.DATA
...
...
cBMPFile db "file.bmp",0
cBMPCancel db "cancel.bmp",0

.DATA?			;uninitialisierte Daten
...
...
hBMPFile HANDLE ?
hBMPCancel HANDLE ?
...

mymain PROC
  ...
  ...
  invoke LoadImage,NULL,ADDR cBMPFile,IMAGE_BITMAP,0,0,LR_LOADFROMFILE
  mov hBMPFile,eax
  invoke SetMenuItemBitmaps,hMenuBar,IDM_SAVEAS,MF_BYCOMMAND,eax,eax
  
  invoke LoadImage,NULL,ADDR cBMPCancel,IMAGE_BITMAP,0,0,LR_LOADFROMFILE
  mov hBMPCancel,eax
  invoke SetMenuItemBitmaps,hMenuBar,IDM_EXIT,MF_BYCOMMAND,eax,eax
  
  invoke ShowWindow,hWnd,SW_SHOWNORMAL
  invoke UpdateWindow,hWnd
  
  call MsgLoop
  ret
mymain ENDP

Die Funktion LoadImage() kann sowohl Bitmaps als auch Icons sowie normale und animierte Cursor laden. Der 6. Parameter enthält Ladeoptionen, insbesondere für Farbanpassungen. Wenn dieser Parameter das Flag LR_LOADFROMFILE enthält, dann beschreibt der 2. Parameter die Adresse des Strings mit der Pfadangabe zur Datei. In diesem Fall ist der erste Parameter auf NULL zu setzen. Dieser enthält eigentlich das Instanzhandle derjenigen Datei, die die Bitmaps als interne Ressource beinhaltet. Parameter 3 gibt an, ob Bitmaps, Icons (IMAGE_ICON) oder Cursor (IMAGE_CURSOR) geladen werden sollen. Parameter 4 und 5 bestimmen die Größenanpassung der zu ladenden Ressource. In der oben dargestellten Konstellation bewirkt 0,0, dass die tatsächliche Größe der Ressource verwendet wird.

Wenn die Funktion LoadImage() korrekt funktioniert hat, dann liefert sie das Handle auf das Bitmap zurück, ansonsten null. Der Rückgabewert wird in einer Variablen gespeichert und als 4. und 5. Parameter der Funktion SetMenuItemBitmaps() verwendet. Die ersten drei Parameter stimmen mit denen der Funktion ModifyMenu() überein. Parameter vier und fünf sind für Menüpunkte mit dem Flag MFS_CHECKED gedacht: in Parameter vier wird das Handle für das Bitmap passend zum Zustand UNCHECKED übergeben, in Parameter fünf zum Zustand CHECKED. Bei Menüpunkten ohne dieses Flag wird in beiden Parametern das gleiche übergeben.

Der Menüpunkt wird mit dem Cursorbalken AND-verknüpft. Wie sie am Punkt Beenden sehen können, kommt es dabei bei farbigen Bitmaps zu unschönen Ergebnissen. Die Empfehlung lautet hier, monochrome Bitmaps zu verwenden. Die korrekte Darstellung der Farbe ist vermutlich nur dann gewährleistet, wenn der Menüpunkt mit dem Flag MF_OWNERDRAW erzeugt wurde.

Alles in einem Programm

Hier sehen sie nochmal das Programm mit allen Punkten, die bis jetzt zu Menüs besprochen wurden. Ich bin der Einfachheit halber bei den zur Laufzeit erzeugten Menüs geblieben. Die Aufrufe von CheckMenuItem() und EnableMenuItem() wurden durch ModifyMenu() ersetzt und sind nur noch als Kommentar enthalten. Das Menü wird weiterhin zur Programmlaufzeit erzeugt, die Shortcuts werden jedoch in einer Ressourcendatei erwartet. Dieser Mix ist sicher nicht die beste Wahl. Beim Linken des Programms muss die Ressourcendatei auf jeden Fall mit angegeben werden.

;kntxtmnu.asm - Kontextmenu
.386
.MODEL flat,stdcall
OPTION casemap:none

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

WndProc PROTO :dword, :dword, :dword, :dword

.CONST
IDM_NEW    EQU 40001
IDM_OPEN   EQU 40002
IDM_SAVE   EQU 40003
IDM_SAVEAS EQU 40004
IDM_EXIT   EQU 40005

IDM_CUT    EQU 40011
IDM_COPY   EQU 40012
IDM_INSERT EQU 40013
IDM_UNDO   EQU 40014
IDM_WRAP   EQU 40015

IDM_SYS_HELLO EQU 40020
IDM_SYS_STD EQU 40021

.DATA
szCaption db "Kontextmenü",0
szClassName db "My_Class",0
cMenuName db "WinMenu",0
cMenuFile db "&Datei",0                             ;Hauptmenüpunkt
cMenuNew db "&Neu" ,0
cMenuOpen db "Ö&ffnen...",0
cMenuSave db "&Speichern",0
cMenuSaveAs db "Speichern &unter...",0
cMenuExit db "&Beenden",0
cMenuEdit db "&Bearbeiten",0                        ;Hauptmenüpunkt
cMenuUndo db "&Rückgängig",0
cMenuCut db "&Ausschneiden",9,"Strg+X",0
cMenuCopy db "&Kopieren",9,"Strg+C",0
cMenuInsert db "&Einfügen",9,"Strg+V",0
cMenuWordwrap db "&Wordwrap ist an",0
cMenuWordwrapOff db "&Wordwrap ist aus",0
cMenuHello db "Say Hello",0
cMenuStd db "Standard",0
lChecked db 1
cBMPFile db "file.bmp",0
cBMPCancel db "cancel.bmp",0

.DATA?			;uninitialisierte Daten
hInstance HINSTANCE ?
hIcon HANDLE ?
hCursor HANDLE ?
hWnd HWND ?
cCmdline LPSTR ?
hMenuBar HANDLE ?
hMenu HANDLE ?
hSysMenu HANDLE ?
hKontextMenu HANDLE ?
hAccel HANDLE ?
hBMPFile HANDLE ?
hBMPCancel HANDLE ?

.CODE
start:
  call mymain
  invoke ExitProcess,0


mymain PROC
  LOCAL wwd:dword,wht:dword,wtx:dword,wty:dword,wc:WNDCLASSEX
  
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke GetCommandLine
  mov cCmdline,eax

  mov wtx,CW_USEDEFAULT ;oder konkrete Angaben: 0
  mov wty,CW_USEDEFAULT ;0
  mov wwd,CW_USEDEFAULT ;400
  mov wht,CW_USEDEFAULT ;400
  
  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style,CS_BYTEALIGNCLIENT or CS_BYTEALIGNWINDOW
  mov wc.lpfnWndProc,OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_BTNFACE+1
  mov wc.lpszMenuName,NULL
  mov wc.lpszClassName,OFFSET szClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov hIcon,eax
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov hCursor,eax
  mov wc.hCursor,eax
  
  invoke RegisterClassEx,ADDR wc
  
  invoke CreateWindowEx,WS_EX_LEFT,ADDR szClassName,ADDR szCaption,
                        WS_OVERLAPPEDWINDOW,wtx,wty,wwd,wht,NULL,NULL,hInstance,NULL
  mov hWnd,eax
  call erzeugeMenu
  mov hMenuBar,eax
  invoke SetMenu,hWnd,eax
  
  invoke GetSystemMenu,hWnd,FALSE
  mov hSysMenu,eax
  invoke AppendMenu,eax,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hSysMenu,MF_STRING,IDM_SYS_HELLO,ADDR cMenuHello
  invoke AppendMenu,hSysMenu,MF_STRING,IDM_SYS_STD,ADDR cMenuStd

  invoke LoadAccelerators,hInstance,ADDR szClassName
  mov hAccel,eax
  
  invoke LoadImage,NULL,ADDR cBMPFile,IMAGE_BITMAP,0,0,LR_LOADFROMFILE
  mov hBMPFile,eax
  invoke SetMenuItemBitmaps,hMenuBar,IDM_SAVEAS,MF_BYCOMMAND,eax,eax
  
  invoke LoadImage,NULL,ADDR cBMPCancel,IMAGE_BITMAP,0,0,LR_LOADFROMFILE
  mov hBMPCancel,eax
  invoke SetMenuItemBitmaps,hMenuBar,IDM_EXIT,MF_BYCOMMAND,eax,eax
  
  invoke ShowWindow,hWnd,SW_SHOWNORMAL
  invoke UpdateWindow,hWnd
  
  call MsgLoop
  ret
mymain ENDP
  

MsgLoop PROC
  LOCAL msg:MSG
  
  Startloop:
    invoke GetMessage,ADDR msg,NULL,0,0
    or eax,eax
    je Exitloop
    invoke TranslateAccelerator,hWnd,hAccel,ADDR msg
    or eax,eax
    jnz Startloop
    invoke TranslateMessage,ADDR msg
    invoke DispatchMessage,ADDR msg
    jmp Startloop
  Exitloop:
  mov eax,msg.wParam
  ret
MsgLoop ENDP

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  LOCAL point:POINT                         ;neu
  .IF uMsg == WM_DESTROY
    invoke DestroyAcceleratorTable,hAccel
    invoke PostQuitMessage,NULL
  .ELSEIF uMsg == WM_RBUTTONUP
    mov eax,lParam
    movzx eax,ax
    mov point.x,eax
    mov eax,lParam
    shr eax,16
    mov point.y,eax
    invoke ClientToScreen,hWnd,ADDR point
    invoke TrackPopupMenu,hKontextMenu,TPM_RIGHTBUTTON,point.x,point.y,0,hWnd,NULL
  .ELSEIF uMsg == WM_COMMAND
    mov eax,wParam
    .IF ax==IDM_EXIT
      invoke DestroyWindow,hWnd
    .ELSEIF ax==IDM_NEW
      invoke MessageBeep,MB_OK
    .ELSEIF ax==IDM_COPY
      invoke GetMenuState,hMenuBar,IDM_INSERT,MF_BYCOMMAND
      and eax,MFS_GRAYED
      jz @@endegrayedcopy
        invoke EnableMenuItem,hMenuBar,IDM_INSERT,MF_BYCOMMAND OR MF_ENABLED
      @@endegrayedcopy:
    .ELSEIF ax==IDM_WRAP
      invoke GetMenuState,hMenuBar,IDM_WRAP,MF_BYCOMMAND
      and eax,MFS_CHECKED
      jnz @@ischecked
        ;invoke CheckMenuItem,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_CHECKED
        invoke ModifyMenu,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_STRING OR MF_CHECKED,IDM_WRAP,\
               ADDR cMenuWordwrap
        jmp short @@endecheckwordwrap
      @@ischecked:
        ;invoke CheckMenuItem,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_UNCHECKED
        invoke ModifyMenu,hMenuBar,IDM_WRAP,MF_BYCOMMAND OR MF_STRING OR MF_UNCHECKED,IDM_WRAP,\
               ADDR cMenuWordwrapOff
      @@endecheckwordwrap:
      mov lChecked,al  
    .ENDIF
  .ELSEIF uMsg == WM_SYSCOMMAND
    mov eax,wParam
    .IF ax==IDM_SYS_HELLO
      invoke MessageBox,hWnd,ADDR cMenuHello,ADDR szCaption,MB_OK
    .ELSEIF ax==IDM_SYS_STD
      invoke GetSystemMenu,hWnd,TRUE
    .ELSE
      invoke DefWindowProc,hWin,uMsg,wParam,lParam
    .ENDIF
  .ELSE
    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
  .ENDIF
  xor eax,eax
  ret
WndProc ENDP

erzeugeMenu PROC
  invoke CreateMenu
  mov hMenuBar,eax
  invoke CreateMenu
  mov hMenu,eax
  invoke AppendMenu,hMenu,MF_STRING,IDM_NEW,ADDR cMenuNew
  invoke AppendMenu,hMenu,MF_STRING,IDM_OPEN,ADDR cMenuOpen
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVE,ADDR cMenuSave
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVEAS,ADDR cMenuSaveAs
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING,IDM_EXIT,ADDR cMenuExit
  invoke AppendMenu,hMenuBar,MF_POPUP,hMenu,ADDR cMenuFile
  invoke CreateMenu
  mov hMenu,eax
  mov hKontextMenu,eax          ; hier das neue Menü speichern
  invoke AppendMenu,hMenu,MF_STRING,IDM_UNDO,ADDR cMenuUndo
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING OR MFS_GRAYED,IDM_CUT,ADDR cMenuCut
  invoke AppendMenu,hMenu,MF_STRING,IDM_COPY,ADDR cMenuCopy
  invoke AppendMenu,hMenu,MF_STRING OR MFS_GRAYED,IDM_INSERT,ADDR cMenuInsert
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING OR MFS_CHECKED,IDM_WRAP,ADDR cMenuWordwrap
  invoke AppendMenu,hMenuBar,MF_POPUP,hMenu,ADDR cMenuEdit
  mov eax,hMenuBar
  ret
erzeugeMenu ENDP
END start