Iczelion - 32 - Multiple Document Interface (MDI)

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.

Frame Fenster
|
Client Fenster
|

|
|
|
|
|
MDI Child 1
MDI Child 2
MDI Child 3
MDI Child 4
MDI Child n
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:

  1. Füllen Sie die WNDCLASSEX Struktur wie immer
  2. Registrieren Sie die Fram-Fenster-Klasse, indem Sie RegisterClassEx aufrufen
  3. Erzeugen Sie das Frame-Fenster, indem Sie CreateWindowEx aufrufen
  4. Innerhalb der Message Loop TranslateMDISysAccel aufrufen.
  5. 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:

  1. Ermitteln Sie das Handle des Submenüs, indem Sie die Fenstr-Liste anhängen wollen.
  2. 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
  3. 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.

  • Sie können eine WM_MDICREATE Nachricht an das Client-Fenster, die Adresse einer MDICREATESTRUCT Struktur in wParam übergebend. Das ist der einfachste und die gängiste Methode, ein MDI-Child-Fenster zu erzeugen.
    .data? mdicreate MDICREATESTRUCT .... .code ..... [füllen Sie die Elemente von mdicreate] ...... invoke SendMessage, hwndClient, WM_MDICREATE,addr mdicreate,0
    SendMessage wird das Handle des neu erzeugten MDI-Child-Fensters zurückliefern, wenn der Aufruf erfolgreich war. Sie müssen das Handle allerdings nicht speichern. Sie können es ermitteln, wenn Sie es benötigen. MDICREATESTRUCT hat folgende Definition.

  • MDICREATESTRUCT STRUCT szClass DWORD ? szTitle DWORD ? hOwner DWORD ? x DWORD ? y DWORD ? lx DWORD ? ly DWORD ? style DWORD ? lParam DWORD ? MDICREATESTRUCT ENDS
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:
    CreateMDIWindow proto lpClassName:DWORD lpWindowName:DWORD dwStyle:DWORD x:DWORD y:DWORD nWidth:DWORD nHeight:DWORD hWndParent:DWORD hInstance:DWORD lParam:DWORD
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:

  1. Registrieren Sie die beiden Fenster-Klassen, die Frame-Fenster-Klasse und die MDI-Child-Fenster-Klasse.
  2. Erzeugen Sie das Frame-Fenster mit CreateWindowEx.
  3. Rufen Sie innerhalb der Message-Loop TranslateMDISysAccel auf, um MDI-relevante Shortcuts zu bearbeiten.
  4. Rufen Sie innerhalb der Fenster-Prozedur des Frame-Fenster DefFrameProc auf, um ALLE Nachrichten, die nicht von Ihnen bearbeitet werden, abarbeiten zu lassen.
  5. 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
  6. Sie können ein MDI-Child-Fenster erzeugen, indem Sie WM_MDICREATE an das Client-Fenster senden oder, alternativ durch aufrufen von CreateMDIWindow.
  7. Innerhalb der Fenster-Prozedur des MDI-Child-Fensters reichen Sie alle unbearbeiteten Nachrichten an DefMDIChildProc.
  8. 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