Tutorial 32: Multiple Document Interface (MDI)

This tutorial shows you how to create MDI application. It's actually not too difficult to do.

Theory:

Multiple Document Interface (MDI) is a specification for applications that handle multple documents at the same time. You are familiar with Notepad: It's an example of Single Document Interface (SDI). Notepad can handle only one document at a time. If you want to open another document, you have to close the previous one first. As you can imagine, it's rather cumbersome. Contrast it with Microsoft Word: Word can open arbitrary documents at the same time and let the user choose which document to use. Microsoft Word is an example of Multiple Document Interface (MDI).

MDI application has several characteristics that are distinctive. I'll list some of them:

The main window that contains the child windows is called a frame window. Its client area is where the child windows live, hence the name "frame". Its job is a little more elaborate than a usual window because it needs to handle some coordination for MDI.

To control an arbitrary number of child windows in your client area, you need a special window called client window. You can think of this client window as a transparent window that covers the whole client area of the frame window. It's this client window that is the actual parent of those MDI child windows. The client window is the real supervisor of the MDI child windows.
 
   
Frame Window
   
   
   
   
Client Window
   
   
|
   

|
|
|
|
|
MDI Child 1 
MDI Child 2 
MDI Child 3 
MDI Child 4 
MDI Child n 

Figure 1. The hierachy of an MDI application

Creating the Frame Window

Now we can turn our attention to the detail. First of all you need to create a frame window. It's created the same way as the normal window: by calling CreateWindowEx. There are two major differences from a normal window.

The first difference is that you MUST call DefFrameProc instead of DefWindowProc to process the Windows messages your window don't want to handle. This is one way to let Windows do the dirty job of maintaining MDI application for you. If you forget to use DefFrameProc, your application won't get the MDI feature. Period. DefFrameProc has the following syntax:

DefFrameProc proc hwndFrame:DWORD,    
                                   hwndClient:DWORD,
                                   uMsg:DWORD, 
                                   wParam:DWORD, 
                                   lParam:DWORD
If you compare DefFrameProc with DefWindowProc, you'll notice that the only difference between them is that DefFrameProc has 5 parameters while DefWindowProc has only 4. The extra parameter is the handle to the client window. This handle is necessary so Windows can send MDI-related messages to the client window.

The second difference is that, you must call TranslateMDISysAccel in the message loop of your frame window. This is necessary if you want Windows to handle MDI-related accelerator key strokes such as Ctrl+F4, Ctrl+Tab for you. It has the following syntax:

TranslateMDISysAccel proc hwndClient:DWORD,
                                                 lpMsg:DWORD
The first parameter is the handle to the client window. This should not come as a surprise to you because it's the client window that is the parent of all MDI child windows. The second parameter is the address of the MSG structure you filled by calling GetMessage. The idea is to pass the MSG structure to the client window so it could examine if the MSG structure contains the MDI-related keypresses. If so, it processes the message itself and returns a non-zero value, otherwise it returns FALSE.

The steps in creating the frame window can be summarized as follows:

  1. Fill in the WNDCLASSEX structure as usual
  2. Register the frame window class by calling RegisterClassEx
  3. Create the frame window by calling CreateWindowEx
  4. Within the message loop, call TranslateMDISysAccel.
  5. Within the window procedure, pass the unprocessed messages to DefFrameProc instead of DefWindowProc.

Creating the Client Window

Now that we have the frame window, we can create the client window. The client window class is pre-registered by Windows. The class name is "MDICLIENT". You also need to pass the address of a CLIENTCREATESTRUCT structure to CreateWindowEx. This structure has the following definition:
[CLIENTCREATESTRUCT:
        hWindowMenu:    D§ ?
        idFirstChild:         D§ ?]
hWindowMenu is the handle to the submenu that Windows will append the list of MDI child window names. This feature requires a little explanation. If you ever use an MDI application like Microsoft Word before, you'll notice that there is a submenu named "window" which, on activation, displays various menuitems related to window management and at the bottom, the list of the MDI child window currently opened. That list is internally maintained by Windows itself: you don't have to do anything special for it. Just pass the handle of the submenu you want the list to appear in hWindowMenu and Windows will handle the rest. Note that the submenu can be ANY submenu:it doesn't have to be the one that is named "window". The bottom line is that, you should pass the handle to the submenu you want the window list to appear. If you don't want the list, just put NULL in hWindowMenu. You get the handle to the submenu by calling GetSubMenu.

idFirstChild is the ID of the first MDI child window. Windows increments the ID for each new MDI child window the application created. For example, if you pass 100 to this field, the first MDI child window will have the ID of 100, the second one will have the ID of 101 and so on. This ID is sent to the frame window via WM_COMMAND when the MDI child window is selected from the window list. Normally you'll pass this "unhandled" WM_COMMAND messages to DefFrameProc. I use the word "unhandled" because the menuitems in the window list are not created by your application thus your application doesn't know their IDs and doesn't have the handler for them. This is another special case for the MDI frame window: if you have the window list, you must modify your WM_COMMAND handler a bit like this:

..elseif D@uMsg = &WM_COMMAND
      ..if D@lParam 0                     ; this message is generated from a menu
          mov eax  D@wParam
          if ax==IDM_CASCADE
                 .....
          elseif ax = &IDM_TILEVERT
                .....
          else
                call 'USER32.DefFrameProcA' D@Adressee D§hwndClient D@Message D@wParam D@lParam 
                Exit
          endif
Normally, you would just ignore the messages from unhandled cases. But In the MDI case, if you ignore them, when the user clicks on the name of an MDI child window in the window list, that window won't become active. You need to pass them to DefFrameProc so they can be handled properly.

A caution on the value of idFirstChild: you should not use 0. Your window list will not behave properly, ie. the check mark will not appear in front of the name of the first MDI child even though it's active. Choose a safe value such as 100 or above.

Having filled in the CLIENTCREATESTRUCT structure, you can create the client window by calling CreateWindowEx with the predefined class name,"MDICLIENT", and passing the address of the CLIENTCREATESTRUCT structure in lParam. You must also specify the handle to the frame window in the hWndParent parameter so Windows knows the parent-child relationship between the frame window and the client window. The window styles you should use are: WS_CHILD ,WS_VISIBLE and WS_CLIPCHILDREN. If you forget WS_VISIBLE, you won't see the MDI child windows even if they were created successfully.

The steps in creating the client window are as follows:

  1. Obtain the handle to the submenu that you want to append the window list to.
  2. Put the value of the menu handle along with the value you want to use as the ID of the first MDI child window in a CLIENTCREATESTRUCT structure
  3. call CreateWindowEx with the class name "MDICLIENT", passing the address of the CLIENTCREATESTRUCT structure you just filled in in lParam.

Creating the MDI Child Window

Now you have both the frame window and the client window. The stage is now ready for the creation of the MDI child window. There are two ways to do that.
 
szClass the address of the window class you want to use as the template for the MDI child window.
szTitle the address of the text you want to appear in the title bar of the child window
hOwner the instance handle of the application
x,y,lx,ly the upper left coordinate and the width and height of the child window
style child window style. If you create the client window with MDIS_ALLCHILDSTYLES, you can use any window style. 
lParam an application-defined 32-bit value. This is a way of sharing values among MDI windows. If you don't need to use it, set it to NULL
If you look closely at the parameters, you'll find that they are identical to the members of MDICREATESTRUCT structure, except for the hWndParent. Essentially it's the same number of parameters you pass with WM_MDICREATE. MDICREATESTRUCT doesn't have the hWndParent field because you must pass the whole structure to the correct client window with SendMessage anyway.
At this point, you may have some questions: which method should I use? What is the difference between the two? Here is the answer:

The WM_MDICREATE method creates the MDI child window in the same thread as the calling code. That means if the application only has the primary thread, all MDI child windows run in the primary thread context. This is not a big issue until one or more of your MDI childs perform some lengthy operation. That could be a problem! Think about it, suddenly your whole application will seem to freeze, won't respond to anything until the operation ends.

This problem is exactly what CreateMDIWindow is designed to solved. CreateMDIWindow creates a separate thread for each MDI child window. Thus if one MDI child is busy, it won't drag the whole application down with it.

A little more detail needs to be covered about the window procedure of the MDI child. As with the frame window case, you must not call DefWindowProc to handle the unprocessed messages. Instead, you must use DefMDIChildProc. This function has exactly the same parameters as DefWindowProc.

In addition to WM_MDICREATE, there are other MDI-related window messages. I'll list them below:
 
WM_MDIACTIVATE This message can be sent by the application to the client window to instruct the client window to activate the selected MDI child. When the client window receives the message, it activates the selected MDI child window and sends WM_MDIACTIVATE to the child being deactivated and activated. The use of this message is two-fold: it can be used by the application to activate the desired child window. And it can be used by the MDI child window itself as the indicator that it's being activated/deactivated. For example, if each MDI child window has different menu, it can use this opportunity to change the menu of the frame window when it's activated/deactivated.
WM_MDICASCADE
WM_MDITILE
WM_MDIICONARRANGE 
These messages handle the arrangement of the MDI child windows. For example, if you want the MDI child windows to arrange themselves in cascading style, send WM_MDICASCADE to the client window. 
WM_MDIDESTROY Send this message to the client window to destroy an MDI child window. You should use this message instead of calling DestroyWindow because if the MDI child window is maxmized, this message will restore the tile of the frame window. If you use DestroyWindow, the title of the frame window will not be restored.
WM_MDIGETACTIVE Send this message to retrieve the handle of the currently active MDI child window.
WM_MDIMAXIMIZE
WM_MDIRESTORE 
Send WM_MDIMAXIMIZE to maximize the MDI child window and WM_MDIRESTORE to restore it to previous state. Always use these messages for the operations. If you use ShowWindow with SW_MAXIMIZE, the MDI child window will maximize fine but it will have the problem when you try to restore it to previous size. You can minimize the MDI child window with ShowWindow without problem, however.
WM_MDINEXT Send this message to the client window to activate the next or the previous MDI child window according to the values in wParam and lParam.
WM_MDIREFRESHMENU Send this message to the client window to refresh the menu of the frame window. Note that you must call DrawMenuBar to update the menu bar after sending this message.
WM_MDISETMENU Send this message to the client window to replace the whole menu of the frame window or just the window submenu. You must use this message instead of SetMenu. After sending this message, you must call DrawMenuBar to update the menu bar. Normally you will use this message when the active MDI child window has its own menu and you want it to replace the menu of the frame window while the MDI child window is active.

I'll review the steps in creating an MDI application for you again below.

  1. Register the window classes, both the frame window class and the MDI child window class
  2. Create the frame window with CreateWindowEx.
  3. Within the message loop, call TranslateMDISysAccel to process the MDI-related accelerator keys
  4. Within the window procedure of the frame window, call DefFrameProc to handle ALL messages unhandled by your code.
  5. Create the client window by calling CreateWindowEx using the name of the predefined window class, "MDICLIENT", passing the address of a CLIENTCREATESTRUCT structure in lParam. Normally, you would create the client window within the WM_CREATE handler of the frame window proc
  6. You can create an MDI child window by sending WM_MDICREATE to the client window or, alternatively, by calling CreateMDIWindow.
  7. Within the window proc of the MDI child window, pass all unhandled messages to DefMDIChildProc.
  8. Use MDI version of the messages if it exists. For example, use WM_MDIDESTROY instead of calling DestroyWindow

Example:

____________________________________________________________________________________________

[M00_Menu  1000                  M00_New  1001                   M00_Close  1002
 M00_Exit  1003                  M00_Tile_Horizontal  1004       M00_Tile_Vertical  1005
 M00_Cascade  1006]

[ClassName:          B§ 'MDIASMClass',0
 MDIClientName:      B§ 'MDICLIENT',0
 MDIChildClassName:  B§ 'Win32asmMDIChild',0
 MDIChildTitle:      B§ 'MDI Child',0
 AppName:            B§ 'Win32asm MDI Demo',0
 ClosePromptMessage: B§ 'Are you sure you want to close this window?',0]

[hInstance: D§ ?    hMainMenu: D§ ?    hwndClient: D§ ?]
 
[MDICREATESTRUCT:
 @szClass:  D§ 0
 @szTitle:  D§ 0
 @hOwner:   D§ 0
 @x:        D§ &CW_USEDEFAULT
 @y:        D§ &CW_USEDEFAULT
 @lx:       D§ &CW_USEDEFAULT
 @ly:       D§ &CW_USEDEFAULT
 @style:    D§ 0
 @lParam:   D§ 0]

[hwndFrame: D§ ?]

____________________________________________________________________________________________
____________________________________________________________________________________________

[WNDCLASSEX:
 @cbSize:           D§ len
 @style:            D§ &CS_HREDRAW__&CS_VREDRAW
 @lpfnWndProc:      D§ MainWindowProc
 @cbClsExtra:       D§ 0
 @cbWndExtra:       D§ 0
 @hInstance:        D§ 0
 @hIcon:            D§ 0
 @hCursor:          D§ 0
 @hbrBackground:    D§ &COLOR_APPWORKSPACE
 @lpszMenuName:     D§ M00_Menu
 @lpszClassName:    D§ ClassName
 @hIconSm:          D§ 0]

[MSG:
 @message:  D§ 0
 @paramL:   D§ 0
 @paramH:   D§ 0
 @time:     D§ 0
 @hwnd:     D§ 0]
 
____________________________________________________________________________________________
____________________________________________________________________________________________

Main:
    call 'KERNEL32.GetModuleHandleA' &NULL
    mov D§hInstance eax, D§WNDCLASSEX@hInstance eax, D§MDICREATESTRUCT@hOwner eax

    ___________________________________________
  ; Register the frame window class
    ___________________________________________
    call 'USER32.LoadIconA' &NULL &IDI_APPLICATION
    mov D§WNDCLASSEX@hIcon eax, D§WNDCLASSEX@hIconSm eax
    call 'USER32.LoadCursorA' &NULL &IDC_ARROW
    mov D§WNDCLASSEX@hCursor eax
    call 'USER32.RegisterClassExA' WNDCLASSEX
    _____________________________________________
  ; Register the MDI child window class
    _____________________________________________
    mov D§WNDCLASSEX@lpfnWndProc ChildProc
    mov D§WNDCLASSEX@hbrBackground &COLOR_WINDOW+1
    mov D§WNDCLASSEX@lpszClassName MDIChildClassName
    call 'USER32.RegisterClassExA' WNDCLASSEX
    call 'USER32.CreateWindowExA' &NULL ClassName AppName,
                                  &WS_OVERLAPPEDWINDOW__&WS_CLIPCHILDREN  &CW_USEDEFAULT,
                                  &CW_USEDEFAULT  &CW_USEDEFAULT  &CW_USEDEFAULT  &NULL 0,
                                  D§hInstance &NULL
        mov D§hwndFrame eax

    call 'USER32.ShowWindow' D§hwndFrame &SW_SHOWNORMAL
    call 'USER32.UpdateWindow' D§hwndFrame
    
    
L1: call 'USER32.GetMessageA' MSG &NULL 0 0 | On eax = 0, jmp L9>
 
    call 'USER32.TranslateMDISysAccel' D§hwndClient MSG
    
    If eax = 0
        call 'USER32.TranslateMessage' MSG
        call 'USER32.DispatchMessageA' MSG
    End_If
    
    jmp L1<

L9: call 'KERNEL32.ExitProcess' 0

____________________________________________________________________________________________
____________________________________________________________________________________________

[CLIENTCREATESTRUCT:  @hWindowMenu: D§ 0    @idFirstChild: D§ 0]
 
Proc MainWindowProc:
    Arguments @Adressee, @Message, @wParam, @lParam
    
    pushad
    
    ...If D@Message = &WM_CREATE
        call 'USER32.GetMenu' D@Adressee | mov D§hMainMenu eax
        call 'USER32.GetSubMenu' D§hMainMenu 1
        mov D§CLIENTCREATESTRUCT@hWindowMenu eax
        mov D§CLIENTCREATESTRUCT@idFirstChild 100
        call 'USER32.CreateWindowExA' &NULL MDIClientName &NULL,
                            &WS_CHILD__&WS_VISIBLE__&WS_CLIPCHILDREN  &CW_USEDEFAULT,
                            &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT D@Adressee &NULL,
                            D§hInstance CLIENTCREATESTRUCT
            mov D§hwndClient eax
        ________________________________________
        ; Initialize the MDICREATESTRUCT
        ________________________________________
        mov D§MDICREATESTRUCT@szClass MDIChildClassName
        mov D§MDICREATESTRUCT@szTitle MDIChildTitle
        push D§hInstance
        pop D§MDICREATESTRUCT@hOwner
        mov D§MDICREATESTRUCT@x &CW_USEDEFAULT
        mov D§MDICREATESTRUCT@y &CW_USEDEFAULT
        mov D§MDICREATESTRUCT@lx &CW_USEDEFAULT
        mov D§MDICREATESTRUCT@ly &CW_USEDEFAULT
        
    ...Else_If D@Message = &WM_COMMAND
        ..If D@lParam = 0
            mov eax D@wParam 
            .If ax = M00_Exit
                call 'USER32.SendMessageA' D@Adressee &WM_CLOSE 0 0
            .Else_If ax = M00_Tile_Horizontal
                call 'USER32.SendMessageA' D§hwndClient &WM_MDITILE &MDITILE_HORIZONTAL 0
            .Else_If ax = M00_Tile_Vertical
                call 'USER32.SendMessageA' D§hwndClient &WM_MDITILE &MDITILE_VERTICAL 0
            .Else_If ax = M00_Cascade
                call 'USER32.SendMessageA' D§hwndClient &WM_MDICASCADE &MDITILE_SKIPDISABLED 0    
            .Else_If ax = M00_New
                call 'USER32.SendMessageA' D§hwndClient &WM_MDICREATE 0 MDICREATESTRUCT
            .Else_If ax = M00_Close
                call 'USER32.SendMessageA' D§hwndClient &WM_MDIGETACTIVE 0 0
                call 'USER32.SendMessageA' eax &WM_CLOSE 0 0
            .Else
                call 'USER32.DefFrameProcA' D@Adressee D§hwndClient D@Message D@wParam D@lParam  
                Exit
            .End_If
        ..End_If
        
    ...Else_If D@Message = &WM_DESTROY
        call 'USER32.PostQuitMessage' &NULL
        
    ...Else
        popad
        call 'USER32.DefFrameProcA' D@Adressee D§hwndClient D@Message D@wParam D@lParam  
        Exit
        
    ...End_If
    
    popad | mov eax &FALSE
EndP

____________________________________________________________________________________________

Proc ChildProc:
    Arguments @hChild, @uMsg, @wParam, @lParam
    
    pushad
    
    ...If D@uMsg = &WM_MDIACTIVATE
        mov eax D@lParam
        .If eax = D@hChild
            call 'USER32.GetMenu' D@hChild
            push eax
                call 'USER32.GetSubMenu' eax 1 | mov edx eax
            pop eax
            call 'USER32.SendMessageA' D§hwndClient &WM_MDISETMENU eax edx
        .Else
            call 'USER32.GetSubMenu' D§hMainMenu 1 | mov edx eax
            call 'USER32.SendMessageA' D§hwndClient &WM_MDISETMENU D§hMainMenu edx   
        .End_If
        call 'USER32.DrawMenuBar' D§hwndFrame
        
    ...Else_If D@uMsg = &WM_CLOSE
        call 'USER32.MessageBoxA' D@hChild ClosePromptMessage AppName &MB_YESNO
        If eax = &IDYES
            call 'USER32.SendMessageA' D§hwndClient &WM_MDIDESTROY D@hChild 0
        End_If  
        
    ...Else
        popad
        call 'USER32.DefMDIChildProcA' D@hChild D@uMsg D@wParam D@lParam
        Exit
        
    ...End_If
    
    popad | mov eax &FALSE
EndP

Analysis:

The first thing the program does is to register the window classes of the frame window and the MDI child window. After that, it calls CreateWindowEx to create the frame window. Within the WM_CREATE handler of the frame window, we create the client window:
    ...If D@Message = &WM_CREATE
        call 'USER32.GetMenu' D@Adressee | mov D§hMainMenu eax
        call 'USER32.GetSubMenu' D§hMainMenu 1
        mov D§CLIENTCREATESTRUCT@hWindowMenu eax
        mov D§CLIENTCREATESTRUCT@idFirstChild 100
        call 'USER32.CreateWindowExA' &NULL MDIClientName &NULL,
                            &WS_CHILD__&WS_VISIBLE__&WS_CLIPCHILDREN  &CW_USEDEFAULT,
                            &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT D@Adressee &NULL,
                            D§hInstance CLIENTCREATESTRUCT
            mov D§hwndClient eax
It calls GetMenu to obtain the handle to the menu of the frame window, to be used in the GetSubMenu call. Note that we pass the value 1 to GetSubMenu because the submenu we want the window list to appear is the second submenu. Then we fill the members of the CLIENTCREATESTRUCT structure.
Next, we initialize the MDICLIENTSTRUCT structure. Note that we don't need to do it here. It's only convenient to do it in WM_CREATE.

                mov D§MDICREATESTRUCT@szClass MDIChildClassName
        mov D§MDICREATESTRUCT@szTitle MDIChildTitle
        push D§hInstance
        pop D§MDICREATESTRUCT@hOwner
        mov D§MDICREATESTRUCT@x &CW_USEDEFAULT
        mov D§MDICREATESTRUCT@y &CW_USEDEFAULT
        mov D§MDICREATESTRUCT@lx &CW_USEDEFAULT
        mov D§MDICREATESTRUCT@ly &CW_USEDEFAULT

Within the message loop, we call TranslateMDISysAccel.

L1: call 'USER32.GetMessageA' MSG &NULL 0 0 | On eax = 0, jmp L9>
 
    call 'USER32.TranslateMDISysAccel' D§hwndClient MSG
    
    If eax = 0
        call 'USER32.TranslateMessage' MSG
        call 'USER32.DispatchMessageA' MSG
    End_If
    
    jmp L1<
If TranslateMDISysAccel returns a non-zero value, it means the message was already handled by Windows itself so you don't need to do anything to the message. If it returns 0, the message is not MDI-related and thus should be handled as usual.
    ...Else
        popad
        call 'USER32.DefFrameProcA' D@Adressee D§hwndClient D@Message D@wParam D@lParam  
        Exit
        
    ...End_If
Note that within the window procedure of the frame window, we call DefFrameProc to handle the messages we are not interested in.

The bulk of the window procedure is the WM_COMMAND handler. When the user selects "New" from the File menu, we create a new MDI child window.

            .Else_If ax = M00_New
                call 'USER32.SendMessageA' D§hwndClient &WM_MDICREATE 0 MDICREATESTRUCT
In our example, we create the MDI child window by sending WM_MDICREATE to the client window, passing the address of the MDICREATESTRUCT structure in lParam.
Proc ChildProc:
    Arguments @hChild, @uMsg, @wParam, @lParam
    
    pushad
    
    ...If D@uMsg = &WM_MDIACTIVATE
        mov eax D@lParam
        .If eax = D@hChild
            call 'USER32.GetMenu' D@hChild
            push eax
                call 'USER32.GetSubMenu' eax 1 | mov edx eax
            pop eax
            call 'USER32.SendMessageA' D§hwndClient &WM_MDISETMENU eax edx
        .Else
            call 'USER32.GetSubMenu' D§hMainMenu 1 | mov edx eax
            call 'USER32.SendMessageA' D§hwndClient &WM_MDISETMENU D§hMainMenu edx   
        .End_If
        call 'USER32.DrawMenuBar' D§hwndFrame
When the MDI child window is created, it monitors WM_MDIACTIVATE to see if it's the active window. It does this by comparing the value of the lParam which contains the handle of the active child window with its own handle. If they match, it's the active window and the next step is to replace the menu of the frame window to its own. Since the original menu will be replaced, you have to tell Windows again in which submenu the window list should appear. That's why we must call GetSubMenu again to retrieve the handle to the submenu. We send WM_MDISETMENU message to the client window to achieve the desired result. wParam of WM_MDISETMENU contains the handle of the menu you would like to replace the original menu. lParam contains the handle of the submenu you want the window list to appear. Right after sending WM_MDISETMENU, we call DrawMenuBar to refresh the menu else your menu will be a mess.
    ...Else
        popad
        call 'USER32.DefMDIChildProcA' D@hChild D@uMsg D@wParam D@lParam
        Exit
        
    ...End_If
Within the window procedure of the MDI    child window, you must pass all unhandled messages to DefMDIChildProc    instead of DefWindowProc.
            .Else_If ax = M00_Tile_Horizontal
                call 'USER32.SendMessageA' D§hwndClient &WM_MDITILE &MDITILE_HORIZONTAL 0
            .Else_If ax = M00_Tile_Vertical
                call 'USER32.SendMessageA' D§hwndClient &WM_MDITILE &MDITILE_VERTICAL 0
            .Else_If ax = M00_Cascade
                call 'USER32.SendMessageA' D§hwndClient &WM_MDICASCADE &MDITILE_SKIPDISABLED 0
When the user selects one of the menuitems in the window submenu, we send the corresponding message to the client window. If the user chooses to tile the windows, we send WM_MDITILE to the client window, specifying in wParam what kind of tiling we want. WM_CASCADE is similar.
            .Else_If ax = M00_Close
                call 'USER32.SendMessageA' D§hwndClient &WM_MDIGETACTIVE 0 0
                call 'USER32.SendMessageA' eax &WM_CLOSE 0 0
If the user chooses "Close"    menuitem, we must obtain the handle of the currently active MDI child window    first by sending WM_MDIGETACTIVE to the client window. The return value in eax    is the handle of the currently active MDI child window. After that, we send    WM_CLOSE to that window.
    ...Else_If D@uMsg = &WM_CLOSE
        call 'USER32.MessageBoxA' D@hChild ClosePromptMessage AppName &MB_YESNO
        If eax = &IDYES
            call 'USER32.SendMessageA' D§hwndClient &WM_MDIDESTROY D@hChild 0
        End_If
Within the window procedure of the MDI child, when WM_CLOSE is received, it displays a message box asking the user if he really wants to close the window. If the answer is yes, we send WM_MDIDESTROY to the client window. WM_MDIDESTROY closes the MDI child window and restores the title of the frame window.

[Iczelion's Win32 Assembly Homepage]