Tutorial 32: Multiple Document Interface (MDI)
Dieses Tutorial zeigt Ihnen, wie Sie MDI Applikation erzeugen. Es ist nicht wirklich schwierig. Laden Sie das Beispiel herunter.
Theorie:
Multiple Document Interface
(MDI) ist eine Spezifikation für Applikationen, dass multiple Dokumente zur selben Zeit händelt. Sie sind vertraut mit Notepad: Das ist ein Beispiel für's Single Document Interface (SDI). Notepad kann nur ein Dokument zur Zeit händeln. Wenn Sie ein anderes Dokument öffnen wollen, müssen Sie das vorherige vorher schließen. Wie Sie sich vorstellen können, ist das ziemlich unbequem. Vergleichen Sie mit Microsoft Word: Word kann mehrere Dokumente zur selben Zeit öffnen und lässt den Benutzer wählen, welches Dokument er benutzen möchte. Microsoft Word ist ein Beispiel für's Multiple Document Interface (MDI).
MDI Applikationen haben verschieden Charakteristika, die entscheidend sind. Ich werde einige davon auflisten:
- Innerhalb des Haupt-Fensters können mehrere Child-Fenster auf der Client Area sein. Alle Child-Fenster werden an der Client Area geclippt.
- Wenn Sie ein Child-Fenster minimieren, wird es in die linke untere Ecke der Client Area des Hauptfensters minimiert.
- Wenn Sie Child-Fenster maximieren, wird der Titel mit dem Titel des Hauptfensters angezeigt.
- Sie können ein Child-Fenster durch drücken von Strg+F4 schließen und zwischen den einzelnen Child-Fenstern wechseln, indem Sie Strg+Tab drücken.
Das Hauptfenster, dass die Child-Fenster enthält, wird Frame Fenster (Rahmen-Fenster) genannt. Auf seiner Client Area leben due Child-Fenster, daher der Name "frame". Seine Arbeit ist etwas komplizierter, als die eines normalen Fensters, da es die Koordination für's MDI händeln muss.
than a usual window because it needs to handle some coordination for MDI.
Um eine willkürliche Anzahl an Child-Fenstern in Ihrer Client Area zu kontrollieren, benötigen Sie ein spezielles Fenster, das Client Fenster genannt wird. Sie können sich dieses Client Fenster als transparentes Fenster vorstellen, dass die gesamte Client Area des Frame Fensters abdeckt. Es ist dieses Client Fenster, dass das eigentlich Parent-Fenster der MDI-Child-Fenster ist. Das Client Fenster ist der wirkliche Administrator der MDI-Child-Fenster.
Figure 1. Die Hierarchie einer MDI Applikation
Das Frame Fenster erzeugen
Nun können wir ins Detail gehen. Als erstes müssen Sie ein Frame Fenster erzeugen, so wie Sie ein ganz normales Fenster erzeugen: indem Sie CreateWindowEx aufrufen. Es gibt zwei Haupt-Unterschiede zu einem normalen Fenster.
Der erste Unterschied ist, dass Sie DefFrameProc statt
DefWindowProc aufrufen MÜSSEN, um die Fenster-Nachrichten abzuarbeiten, die Ihr Fenster nicht bearbeiten möchte. Das ist ein Weg um Windows die dreckige Arbeit einer MDI Applikation für Sie zu machen. Wenn Sie vergessen DefFrameProc in Ihrer Applikation zu benutzen, wird Ihre Applikation keine MDI Features haben. DefFrameProc hat folgende Syntax:
DefFrameProc proc hwndFrame:DWORD,
hwndClient:DWORD,
uMsg:DWORD,
wParam:DWORD,
lParam:DWORD
Wenn Sie DefFrameProc mit DefWindowProc vergleichen, werden Sie feststellen, das der einzige Unterschied ist, dass DefFrameProc 5 Parameter hat, während DefWindowProc nur 4 hat. Der zusätzliche Parameter ist zum händeln des Client Fensters. Dieses Handle ist notwendig, damit Windows MDI-relevante Nachrichten an das Client Fenster senden kann.
Der zweite Unterschied ist, dass Sie TranslateMDISysAccel in der Message-Loop Ihres Frame-Fensters aufrufen müssen. Das ist notwendig, wenn Sie wollen, dass Windows MDI-relevante Shortcuts, so wie Strg+F4, Strg+Tab, für Sie barbeitet. Sie hat folgende Syntax:
TranslateMDISysAccel proc hwndClient:DWORD,
lpMsg:DWORD
Der erste Parameter ist das Handle des Client Fensters. Das sollte Sie nicht überraschen, da das Client Fenster das Parent alle MDI Child-Fenster ist. Der zweite Parameter ist die Adresse einer MSG Struktur, die Sie füllen, indem Sie GetMessage aufrufen. Die Idee ist, dass Sie die MSG -Struktur an das Client Fenster reichen, so dass es untersuchen kann, ob die MSG Struktur MDI-relevante Tastendrücke enthält. Wenn dem so ist, bearbeitet es die Nachricht selbst und liefert einen Wert ungleich null zurück, ansosnten wir FALSE zurückgeliefert.
Die Schritte um ein Frame-Fenster zu erzeugen, können wie folgt zusammengefasst werden:
- Füllen Sie die WNDCLASSEX Struktur wie immer
- Registrieren Sie die Fram-Fenster-Klasse, indem Sie RegisterClassEx aufrufen
- Erzeugen Sie das Frame-Fenster, indem Sie CreateWindowEx aufrufen
- Innerhalb der Message Loop TranslateMDISysAccel aufrufen.
- Innerhalb der Fenster-Prozedur die unbearbeitete Nachricht an DefFrameProc weiterreichen, anstatt DefWindowProc.
Das Client Fenster erzeugen
Nun, da wir das Frame-Fenster haben, erzeugen wir das Client-Fenster. Die Client-Fenster-Klasse ist ein von Windows vor-registriert. Der Klassen-Name ist "MDICLIENT". Sie müssen auch die Adresse einer CLIENTCREATESTRUCT Struktur CreateWindowEx übergeben.
Diese Struktur hat folgende Definition:
CLIENTCREATESTRUCT struct
hWindowMenu dd ?
idFirstChild dd ?
CLIENTCREATESTRUCT ends
hWindowMenu
ist das Handle des Submenüs, wo Windows die Liste der MDI-Child-Fenster-Namen anhängt. Dieses Feature benötigt ein klein wenig Erklärung. Wenn Sie jemals eine MDI Applikation so wie Microsoft Word vorher benutzt haben, werden Sie festgestellt haben, dass ein Submenü namens "window" (Fenster) gibt, welches, nachdem es angeklickt wurde, verschiedene Menüelemente die Fenster-Management-mäßig relevant waren und am Ende der Liste das aktuell geöffnete Child-Fenster. Diese Liste wir von Windows selbst erstellt und verwaltet: Sie müssen sich überhaupt nicht drum kümmern. Nur das Handle des Submenüs übergeben, in dem die Liste erscheinen soll, in hWindowMenu übergeben und Windows kümmert sich um den Rest. Beachten Sie, dass das Subemenü JEGLICHES Submenü sein kann: es muss nicht jenes sein, das den Namen "window" trägt.
Folglich sollten Sie das Handle des Submenüs, dass die Liste anzeigen soll, übergeben. Wenn Sie die Liste nicht möchten, speichern Sie in hWindowMenu einfach NULL. Sie können das Handle des Submenüs erhalten, indem Sie GetSubMenu aufrufen.
idFirstChild ist die ID des ersten MDI Child-Fenster. Windows inkrementiert die ID für jedes neue MDI Child-Fenster, das die Applikation erzeugt. Wenn Sie zum Beispiel 100 in diesem Feld übergeben, wird das erste MDI Child-Fenster die ID 100 haben, das zweite die ID 101 und so weiter. Diese ID wird an das Frame-Fenster via WM_COMMAND gesendet, wenn das MDI Child-Fenster aus der Fenster-Liste ausgewählt wird. Normalerweise reichen Sie diese "unbehandelte"
WM_COMMAND Nachrichten an DefFrameProc weiter. Ich benutze das Wort "unbehandelt" da die Menüelemente in der Fenster-Liste nicht von Ihrer Applikation erzeugt werden, da Ihre Applikation deren IDs nicht kennt und nicht den Handler dafür hat. Das ist ein andere spezieller Fall, für das MDI Frame-Fenster: wenn Sie die Fenster-Liste haben, müssen Sie den WM_COMMAND Handler ein klein wenig modifizieren, ungefähr so:
.elseif uMsg==WM_COMMAND
.if lParam==0 ; diese Nachricht ist von einem Menü generiert wurde
mov eax,wParam
.if ax==IDM_CASCADE
.....
.elseif ax==IDM_TILEVERT
.....
.else
invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, lParam
ret
.endif
Normalerweise würden Sie die Nachrichten unbehandelter Fälle einfach ignorieren. Aber im MDI Fall, würde das Fenster nicht aktiv werden, wenn der Benutzer auf den Namen des MDI Child-Fenster in der Fenster-Liste klicken würde, wenn Sie diese Nachricht ignorieren würden. Sie müssen sie DefFrameProc übergeben, so dass sie angemessen bearbeitet werden können.
Eine Warnung bezüglich des Wertes idFirstChild: Sie sollten nicht 0 benutzen. Ihre Fenster-Liste würde sich nicht korrekt verhalten, dh. der Haken wird nicht vor dem aktiven MDI Child-Fenster-Namen erscheinen, auch wenn es aktiv ist. Wählen Sie einen sicheren Wert wie 100 oder darüber.
Nachdem Sie die CLIENTCREATESTRUCT Struktur gefüllt haben, können Sie ein Client-Fenster erzeugen, indem Sie CreateWindowEx mit dem vordefinierten Klassen-Namen,"MDICLIENT", aufrufen und die Adresse der CLIENTCREATESTRUCT Struktur in lParam übergeben. Sie müssen auch das Handle des Frame-Fenster im hWndParent Parameter angeben, so dass Windows die Parent-Child-Beziehung zwischen Frame-Fenster und Client-Fenster , kennt. Die Fenster-Stile die Sie benutzen sollten: WS_CHILD ,WS_VISIBLE und WS_CLIPCHILDREN. Wenn Sie WS_VISIBLE vergessen, werden Sie das MDI Child-Fenster nicht sehen, auch wenn es erfolgreich erzeugt wurde.
Die Schritte die zur Erzeugung des Client-Fensters nötig sind, sind folgende:
- Ermitteln Sie das Handle des Submenüs, indem Sie die Fenstr-Liste anhängen wollen.
- Speichern Sie den Wert des Menü-Handles mit dem Wert, den Sie als ID des ersten MDI Child-Fenster benutzen wollen, in einer CLIENTCREATESTRUCT Struktur
- Rufen Sie CreateWindowEx mit dem Klassen-Namen "MDICLIENT" auf, die Adresse der eben gefüllten CLIENTCREATESTRUCT Struktur übergeben Sie in lParam.
Erzeugen des MDI-Child-Fenster
Nun haben Sie sowohl das Frame-Fenster als auch das Client-Fenster. Nun ist man bereit, für die Erzeugung des MDI-Child-Fensters. Es gibt zwei Wege, das zu machen.
szClass |
Die Adresse der Fenster-Klasse, die Sie als Template für das MDI-Child-Fenster benutzen wollen. |
szTitle |
Die Adresse des Textes, den Sie als Titel für das Child-Fenster benutzen wollen |
hOwner |
Das Insanz-Handle der Applikation |
x,y,lx,ly |
Die obere linke Koordinate und die Breite und Höhe des Child-Fensters |
style |
Child-Fenster-Stil. Wenn Sie ein Child-Fenster mit MDIS_ALLCHILDSTYLES erzeugen, können Sie jeden Fenster-Stil benutzen.
|
lParam |
Ein Applikation definierter 32-Bit Wert. Das ist ein Weg Werte zwischen MDI-Fenstern auszutauschen. Wenn Sie das nicht benötigen, setzen Sie hier NULL |
- Sie können CreateMDIWindow aufrufen. Diese Funktion hat folgende Syntax:
Wenn Sie sich die Parameter genau anschauen, werden Sie identische Elemente zur MDICREATESTRUCT Struktur finden, außer hWndParent. Essentiell ist es die selbe Anzahl der Parameter, die Sie mit WM_MDICREATE übergeben. MDICREATESTRUCT hat das hWndParent Feld nicht, da Sie sowieso die gesamte Struktur an das Client-Fenster mit SendMessage senden müssen.
An diesem Punkt habe Sie vielleicht einige Fragen: welche Methode sollte ich benutzen? Was ist der Unterschied zwischen den beiden? Hier ist die Antwort:
Die WM_MDICREATE Methode erzeugt ein MDI-Child-Fenster im selben Thread wie der aufrufende Code. Das bedeutet, dass wenn eine Applikation nur einen Haupt-Thread hat, das alle MDI-Child-Fenster im Haupt-Thread-Kontext laufen. Das ist kein großes Problem, wenn nicht ein oder mehrere MDI Childs langwierige Berechnungen ausführen. Das könnte ein Problem sein! Denken Sie darüber nach, plötzlich scheint Ihre Applikation einzufrieren und antwortet so lange nicht mehr, bis die Berechnung abgeschlossen ist.
Genau für dieses Problem wurde CreateMDIWindow designed, damit es gelöst wird. CreateMDIWindow erzeugt einen eigenen Thread für jedes MDI-Child-Fenster. So dass, wenn ein MDI-Child beschäftigt ist, nicht die ganze Applikation darunter zu leiden hat.
Ein klein wenig mehr Detail über die Fenster-Prozedur eines MDI-Childs muss noch abgedeckt werden. Wie bei dem Frame-Fenster Fall, dürfen Sie nicht DefWindowProc aufrufen, um unbearbeitete Nachrichten zu händeln. Statt dessen, müssen Sie DefMDIChildProc benutzen. Diese Funktion hat exakt die selben Parameter wie DefWindowProc.
Zusätzlich zu WM_MDICREATE, gibt es andere MDI-relevanten Fenster-Nachrichten. Ich werde sie hier aufliste:
WM_MDIACTIVATE |
Diese Nachricht kann von der Applikation an das Client-Fenster gesendet werden, um dem Client-Fenster Anweisung zu geben, dass es das selektierte MDI-Child aktivieren soll. Wenn das Client-Fenster die Nachricht erhählt, aktiviert es das selektierte MDI-Child-Fenster und sendet WM_MDIACTIVATE an das Child, das deaktiviert wird und an das, dass aktiviert wird. Diese Nachricht hat einen zweifachen Nutzen: sie kann von der Applikation benutzt werden, um das gewünschte Child-Fenster zu aktivieren. Und es kann vom MDI Child-Fenster selbst benutzt werden, als Indikator, dass es aktiviert/deaktiviert wurde. Wenn jedes MDI-Child-Fenster zum Beispiel ein anderes Menü hat, kann es diese Möglichkeit nutzen, um das Menü des Frame-Fensters zu ändern, wenn es aktiviert/deaktiviert wird. |
WM_MDICASCADE
WM_MDITILE
WM_MDIICONARRANGE |
Diese Nachrichten händeln die Anordnung der MDI Child-Fenster. Wenn Sie zum Beispiel die MDI-Child-Fenster kaskadierend darstellen wollen, senden Sie WM_MDICASCADE an das Client-Fenster. |
WM_MDIDESTROY |
Senden Sie diese Nachricht an das Client-Fenster, um das MDI-Child-Fenster zu zerstören. Sie sollten diese Nachricht benutzen, anstatt
DestroyWindow aufzurufen, da, wenn das MDI-Child-Fenstr maximiert ist, diese Nachricht den Titel des Frame-Fensters wieder herstellt. Wenn Sie DestroyWindow benutzen, wird der Titel des Frame-Fensters nicht wiederhergestellt. |
WM_MDIGETACTIVE |
Senden Sie diese Nachricht, um das Handle des aktuell aktiven MDI-Child-Fensters zu erhalten. |
WM_MDIMAXIMIZE
WM_MDIRESTORE |
Senden Sie WM_MDIMAXIMIZE um das MDI-Child-Fenster zu maximieren und WM_MDIRESTORE um den vorherigen Status wieder herzustellen. Benutzen Sie immer diese Nachrichten, für diese Operationen. Wenn Sie ShowWindow mit SW_MAXIMIZE benutzen, wird das MDI-Child-Fenster sich auch maximieren, aber es wird Problemen haben, wenn Sie versuchen, die vorherige Größe wieder herzustellen. Sie können das MDI-Child-Fenster aber ohne Probleme mit ShowWindow minimieren. |
WM_MDINEXT |
Senden Sie diese Nachricht an das Client-Fenster, um das nächste oder vorherige MDI-Child-Fenster, je nach den Werten in wParam und lParam, zu aktivieren. |
WM_MDIREFRESHMENU |
Senden Sie diese Nachricht an das Client-Fenster, um das Menü des Frame-Fensters zu aktuallisieren. Beachten Sie, dass Sie DrawMenuBar aufrufen müssen, um die Menü-Leiste auch wirklich zu aktualiseren, nachdem Sie diese Nachricht gesendet haben. |
WM_MDISETMENU |
Senden Sie diese Nachricht an das Client-Fenster, um das komplette Menü des Frame-Fensters zu ersetzen oder nur das Fenster-Submenü. Sie müssen diese Nachricht statt SetMenu benutzen. Nachdem Sie diese Nachricht gesendet haben, müssen Sie DrawMenuBar aufrufen, um die Menü-Leiste zu aktuallisieren. Normalerweise werden Sie diese Nachricht benutzen, wenn das aktive MDI-Child-Fenster sein eigenes Menü hat und Sie das Menü das Frame-Fensters ersetzen wollen, während das MDI-Child-Fenster aktiv ist. |
Ich werde die nötigen Schritte, um eine MDI Applikation zu erzeugen, für Sie noch einmal zusammenfassen:
- Registrieren Sie die beiden Fenster-Klassen, die Frame-Fenster-Klasse und die MDI-Child-Fenster-Klasse.
- Erzeugen Sie das Frame-Fenster mit CreateWindowEx.
- Rufen Sie innerhalb der Message-Loop TranslateMDISysAccel auf, um MDI-relevante Shortcuts zu bearbeiten.
- Rufen Sie innerhalb der Fenster-Prozedur des Frame-Fenster DefFrameProc auf, um
ALLE Nachrichten, die nicht von Ihnen bearbeitet werden, abarbeiten zu lassen.
- Erzeugen Sie das Client-Fenster, indem Sie CreateWindowEx mit dem Namen der vordefinierten Fenster-Klasse "MDICLIENT" aufrufen und übergeben die Adresse der CLIENTCREATESTRUCT Struktur in lParam. Normalerweise würden Sie das Client-Fenstr innerhalb des WM_CREATE Handler der Frame-Fenster-Prozedur erzeugen
- Sie können ein MDI-Child-Fenster erzeugen, indem Sie WM_MDICREATE an das Client-Fenster senden oder, alternativ durch aufrufen von
CreateMDIWindow.
- Innerhalb der Fenster-Prozedur des MDI-Child-Fensters reichen Sie alle unbearbeiteten Nachrichten an DefMDIChildProc.
- Benutzen Sie MDI Version der Nachrichten, wenn sie existiert. Benutzen Sie zum Beispiel WM_MDIDESTROY anstatt DestroyWindow aufzurufen.
Beispiel:
.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
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.const
IDR_MAINMENU equ 101
IDR_CHILDMENU equ 102
IDM_EXIT equ 40001
IDM_TILEHORZ equ 40002
IDM_TILEVERT equ 40003
IDM_CASCADE equ 40004
IDM_NEW equ 40005
IDM_CLOSE equ 40006
.data
ClassName db "MDIASMClass",0
MDIClientName db "MDICLIENT",0
MDIChildClassName db "Win32asmMDIChild",0
MDIChildTitle db "MDI Child",0
AppName db "Win32asm MDI Demo",0
ClosePromptMessage db "Sind Sie sicher, dass Sie dieses Fenster schließen wollen?",0
.data?
hInstance dd ?
hMainMenu dd ?
hwndClient dd ?
hChildMenu dd ?
mdicreate MDICREATESTRUCT
hwndFrame dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
;=============================================
; Registriere die Frame Fenster Klasse
;=============================================
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 hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
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
;================================================
; Registriere die MDI Child Fenster Klasse
;================================================
mov wc.lpfnWndProc,offset ChildProc
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszClassName,offset MDIChildClassName
invoke RegisterClassEx,addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,\
hInst,NULL
mov hwndFrame,eax
invoke LoadMenu,hInstance, IDR_CHILDMENU
mov hChildMenu,eax
invoke ShowWindow,hwndFrame,SW_SHOWNORMAL
invoke UpdateWindow, hwndFrame
.while TRUE
invoke GetMessage,ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMDISysAccel,hwndClient,addr msg
.if !eax
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endif
.endw
invoke DestroyMenu, hChildMenu
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL ClientStruct:CLIENTCREATESTRUCT
.if uMsg==WM_CREATE
invoke GetMenu,hWnd
mov hMainMenu,eax
invoke GetSubMenu,hMainMenu,1
mov ClientStruct.hWindowMenu,eax
mov ClientStruct.idFirstChild,100
INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,\
WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\
hInstance,addr ClientStruct
mov hwndClient,eax
;=======================================
; Initialisiere die MDICREATESTRUCT
;=======================================
mov mdicreate.szClass,offset MDIChildClassName
mov mdicreate.szTitle,offset MDIChildTitle
push hInstance
pop mdicreate.hOwner
mov mdicreate.x,CW_USEDEFAULT
mov mdicreate.y,CW_USEDEFAULT
mov mdicreate.lx,CW_USEDEFAULT
mov mdicreate.ly,CW_USEDEFAULT
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_EXIT
invoke SendMessage,hWnd,WM_CLOSE,0,0
.elseif ax==IDM_TILEHORZ
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
.elseif ax==IDM_TILEVERT
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
.elseif ax==IDM_CASCADE
invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0
.elseif ax==IDM_NEW
invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate
.elseif ax==IDM_CLOSE
invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
invoke SendMessage,eax,WM_CLOSE,0,0
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_MDIACTIVATE
mov eax,lParam
.if eax==hChild
invoke GetSubMenu,hChildMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
.else
invoke GetSubMenu,hMainMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
.endif
invoke DrawMenuBar,hwndFrame
.elseif uMsg==WM_CLOSE
invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
.if eax==IDYES
invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
.endif
.else
invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
ChildProc endp
end start
Analyse:
Als erstes registriert das Programm die Fenster-Klasse des Frame-Fensters und des MDI-Child-Fensters. Danach, ruft es CreateWindowEx auf, um das Frame-Fenster zu erzeugen. Im WM_CREATE Handler des Frame-Fenster erzeugen wir das Client Fenster:
LOCAL ClientStruct:CLIENTCREATESTRUCT
.if uMsg==WM_CREATE
invoke GetMenu,hWnd
mov hMainMenu,eax
invoke GetSubMenu,hMainMenu,1
mov ClientStruct.hWindowMenu,eax
mov ClientStruct.idFirstChild,100
invoke CreateWindowEx,NULL,ADDR MDIClientName,NULL,\
WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,\
hInstance,addr ClientStruct
mov hwndClient,eax
Es ruft GetMenu auf, um das Menü-Handle des Frame-Fensters zu ermitteln, um es im GetSubMenu Aufruf zu nutzen. Beachten Sie, dass wir GetSubMenu den Wert 1 übergeben, da wir das Sub-Menü, wo die Fenster Liste angezeigt werden soll, das zweite Sub-Menü ist. Dann füllen wir die Elemente der CLIENTCREATESTRUCT Struktur.
Als nächstes initialisieren wir die MDICLIENTSTRUCT Struktur. Beachten Sie, dass wir das nicht unbedingt hier machen müssen. Es ist nur von Vorteil wenn wir es bei WM_CREATE machen.
mov mdicreate.szClass,offset MDIChildClassName
mov mdicreate.szTitle,offset MDIChildTitle
push hInstance
pop mdicreate.hOwner
mov mdicreate.x,CW_USEDEFAULT
mov mdicreate.y,CW_USEDEFAULT
mov mdicreate.lx,CW_USEDEFAULT
mov mdicreate.ly,CW_USEDEFAULT
Nachdem das Frame Fenster erzeugt wurde (und auch das Client Fenster), rufen wir LoadMenu auf, um das Child-Fenster-Menü aus der Ressource zu laden. Wir brauchen dieses Menü-Handle, weshalb wir das Menü des Frame-Fensters mit dem ersetzen können, sobald ein MDI Child-Fenster present ist. Vergessen Sie nicht DestroyMenu mit dem Handle aufzurufen, bevor die Applikation zu Windows zurückkehrt. Normalerweise räumt Windows automatisch das Menü auf, dass mit dem Fenster verbunden ist, wenn die Applikation beendet wird, aber in diesem Fall ist das Child-Fenster Menü mit keinem Fenster verbunden, weswegen wertvoller Speicher selbst nach dem Applikations-Ende verschwendet wird.
invoke LoadMenu,hInstance, IDR_CHILDMENU
mov hChildMenu,eax
........
invoke DestroyMenu, hChildMenu
Innerhalb der Message Loop rufen wir TranslateMDISysAccel auf.
.while TRUE
invoke GetMessage,ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMDISysAccel,hwndClient,addr msg
.if !eax
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endif
.endw
Wenn TranslateMDISysAccel einen Wert ungleich null zurückliefert, bedeutet das, dass die Nachricht schon von Windows behandelt wurde, so dass Sie nichts mehr mit der Nachricht machen müssen. Wenn 0 zurückgegeben wird, ist die Nachricht nicht MDI-relevant und sollte so wie immer behandelt werden.
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.....
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
Beachten Sie, dass wir innerhalb der Fenster-Prozedur des Frame-Fenster, DefFrameProc aufrufen, um die Nachrichten zu händeln, in denen wir nicht interessiert sind.
Der größte Teil der Fenster-Prozedur ist der WM_COMMAND Handler. Wenn der Benutzer "New" im File Menu anklickt, erzeugen wir ein neues MDI Child-Fenster.
.elseif ax==IDM_NEW
invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate
In unserem Beispiel erzeugen wir das MDI Child Fenster, indem wir WM_MDICREATE an das Client Fenster senden, die Adresse der MDICREATESTRUCT Struktur in lParam übergebend.
ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_MDIACTIVATE
mov eax,lParam
.if eax==hChild
invoke GetSubMenu,hChildMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
.else
invoke GetSubMenu,hMainMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
.endif
invoke DrawMenuBar,hwndFrame
Wenn das MDI Child-Fenster erzeugt wurde, wird WM_MDIACTIVATE überwacht, um zu sehen, ob es das aktive Fenster ist. Dazu wird der Wert von lParam, wo das Handle des aktiven Child-Fensters enthalten ist, mit dem eigenen Handle verglichen. Wenn sie übereinstimmen, ist es das aktive Fenster und der nächste Schritt ist das Ersetzen des Frame-Fenster-Menüs, durch das eigene. Da das original Menü ersetzt wird, müssen Sie Windows erneut mitteilen, in welchem Submenü die Fenster-Liste erscheinen soll. Das ist der Grund, warum wir erneut GetSubMenu aufrufen müssen, um das Handle des Submenüs zu erhalten. Wir senden eine WM_MDISETMENU Nachricht an das Client Fenster, um das gewünschte Ergeniss zu erzielen. wParam von WM_MDISETMENU enthält das Handle des Menüs, das Sie mit dem original Menü ersetzen wollen. lParam enthält das Handle des Submenüs, ind dem die Fenster-Lister erscheinen soll. Nachdem wir WM_MDISETMENU gesendet haben, rufen wir DrawMenuBar auf, um das Menü zu erneuen, ansonsten wär Ihr Menü einfach nur Müll.
.else
invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
ret
.endif
Innerhalb der Fenster Prozedur des MDI Child Fensters, müssen Sie alle unbehandelten Nachrichten an DefMDIChildProc statt an DefWindowProc weiterleiten.
.elseif ax==IDM_TILEHORZ
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
.elseif ax==IDM_TILEVERT
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
.elseif ax==IDM_CASCADE
invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0
Wenn der Benutzer eins der Memüelemente im Fenster Submenü auswählt, senden wir die korrespondierende Nachricht an das Client-Fenster. Wenn der Benutzer auswählt ide Fenster nebeneinander (Tiled) anzuordnen, senden wir WM_MDITILE an das Client Fenster, und spezifizieren in wParam, welche Art von Anordnung wir haben möchten. WM_CASCADE ist ähnlich.
.elseif ax==IDM_CLOSE
invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
invoke SendMessage,eax,WM_CLOSE,0,0
Wenn der Benutzer das "Close" Menüelement auswählt, müssen wir das Handle des aktuell aktiven MDI Child-Fensters erfragen, indem wir erst WM_MDIGETACTIVE an das Client-Fenster senden. Der Rückgabewert in EAX ist das Handle des zur Zeit aktiven MDI Child-Fensters. Danach senden wir WM_CLOSE an das Fenster.
.elseif uMsg==WM_CLOSE
invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
.if eax==IDYES
invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
.endif
Innerhalb der Fenster-Prozedur des MDI Childs, wird, wenn WM_CLOSE erhalten wird, eine Message Box angezeigt, die den Benutzer fragt, ob er das Fenster wirklich schließen möchte. Wenn die Antwort ja ist, senden wir WM_MDIDESTROY an das Client-Fenster. WM_MDIDESTROY schließt das Child-Fenster und stellt den Titel des Frame-Fensters wieder her.
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage