Tutorial 32: Interface de Múltiples Documentos [Multiple Document Interface (MDI)]

Estew tutorial te muestra cómo crear una aplicación MDI. Realmente no es tan difícil hacerlo. Descarga el ejemplo.

Teoría:

La Interface de Múltiples Documentos [Multiple Document Interface (MDI)] es una especificación para aplicaciones que manejan múltiples documentos al mismo tiempo. Ya estás familiarizado con Notepad: se trata de un buen ejenmplo de Interface de un Único Documento [Single Document Interface (SDI)]. Notepad sólo puede manejar un documento al mismo tiempo. Si quieres abrir otro documento, tienes que cerrar el anterior primero. Como puedes imaginar, es muy engorroso. Esto contrasta con Microsoft Word: Word puede abrir documentos arbitrarios al mismo tiempo y dejar que el usuario escoja cuál documento usar. Microsoft Word es un ejemplo de Interface de Múltiples Documentos [Multiple Document Interface (MDI)].

Una aplicación MDI tiene varias características que la distinguen. Daré una lista de ellas:

La ventana principal que contiene las ventanas hijas se llama ventana marco. Su área cliente es donde vive la ventana hija, de ahí el nombre "marco". Su tarea es un poco más elaborada que la de una ventana usual ya que necesita manejar alguna coordinación para la MDI.

Para controlar un número arbitrario de ventanas hijas en tu área cliente, necesitas una ventana especial llamada ventana cliente. Piensa en esta ventana cliente como una ventana transparente que cubre tdo el área cliente de la ventana marco. Es esta ventana cliente la verdadera ventana padre de aquellas ventanas hijas MDI. La ventana hija es el supervisor real de las ventanas hijas MDI.
 

 

 

Ventana Marco

 

 

 

 

 

 

 

 

 

Ventana Cliente

 

 

 

 

 

|

 

 


|

|

|

|

|

MDI Hija 1 

MDI Hija 2 

MDI Hija 3 

MDI Hija 4 

MDI Hija n 

 

 

 

 

 

Figure 1. Jerarquía de una aplicación MDI

Creando la Ventana Marco

Ahora podemos volcar nuestra atención hacia los detalles. Antes que nada necesitas crear primero una ventana marco. Se crea de la misma manera que se crea una ventana normal: llamando a CreateWindowEx. Hay dos grandes diferencias respecto a una ventana normal.

La primera diferencia es que DEBES llamar a DefFrameProc en vez de DefWindowProc para procesar los mensajes de ventana que no quiere manejar tu ventana. Esta es una manera de dejar que Windows haga el trabajo sucio de mantener la aplicación MDI por tí. Si olvidas usar DefFrameProc, tu aplicación no tendrá el rasgo MDI. DefFrameProc tiene la siguiente sintaxis:

DefFrameProc proc hwndFrame:DWORD,
hwndClient:DWORD,
uMsg:DWORD,
wParam:DWORD,
lParam:DWORD

Si comparas DefFrameProc con DefWindowProc, notarás que la única diferencia entre ellas es que DefFrameProc tiene 5 parámetros mientras que DefWindowProc sólo tiene 4. El parámetro extra es el handle para la ventana cliente. Este handle es necesario para que Windows pueda enviar mensajes relativos a MDI hacia la ventana cliente.

La segunda diferencia es que debes llamar a TranslateMDISysAccel en el bucle de mensajes de tu ventana marco. Es necesario si quieres que Windows maneje los golpes de tecla aceleradores relativos a la MDI tales como Ctrl+F4, Ctrl+Tab por tí. Tiene la siguiente sintaxis:

TranslateMDISysAccel proc hwndClient:DWORD,

lpMsg:DWORD

El primer parámetro es el handle a la ventana cliente. Este no debería sorprederte ya que es la ventana cliente el padre de todas las ventanas hijas MDI. El segundo parámetro es la dirección de la estructura MSG que llenaste llamando a GetMessage. La idea es pasar la estructura MSG a la ventana cliente de manera que puedas examinar si esta estructura contiene los golpes de tecla relativos a la MDI. Si es así, se procesa el propio mensaje y se regresa un valor diferente a cero, sino se regresa FALSE.

Los pasos al crear una ventana marco pueden ser resumidos así:

  1. Llenar la estructura WNDCLASSEX como es usual

  2. Registrar la clase de la ventana marco llamando a RegisterClassEx

  3. Crear la ventana marco llamado a CreateWindowEx

  4. Dentro del bucle de mensaje, llamar a TranslateMDISysAccel.

  5. Dentro del procedimiento de ventana, pasar los mensajes no procesados a DefFrameProc en vez de a DefWindowProc.

Creando la Ventana Cliente

Ahora que tenemos la ventana marco, podemos crear la ventana cliente. La clase de la venana cliente está preregistrada por Windows. El nombre de la clase es "MDICLIENT". También necesitas pasar la dirección de una estructura CLIENTCREATESTRUCT a CreateWindowEx. Esta estructura tiene la siguiente definición:

CLIENTCREATESTRUCT struct 
hWindowMenu  dd ? 
IdFirstChild       dd ?
CLIENTCREATESTRUCT ends

hWindowMenu es el handle para el submenú que Windows anexará como apéndice a la lista de los nombres de ventanas hijas MDI. Este rasgo requiere un poco de explicación. Si antes has usado una aplicación como Microsoft Word, notarás que hay un submenú llamado "ventana" (o "window") el cual, al activarse, despliega varios elementos de menú (menuitems) relativos al manejo de ventanas y en la parte inferior, la lista de las ventanas MDI abiertas en ese momento. Esa lista es mantenida internamente por Windows: no tienes que hacer nada especial para eso. Sólo hay que pasar el handle del submenú que quieres que aparezca en la lista en hWindowMenu y Windows se encargará del resto. Nota que el submenú puede ser CUALQUIER submenú: no tiene que ser uno llamado "ventana". La línea inferior es la que deberías pasar el handle al submenú que quieres que aparezca en la lista de ventanas. Si no quieres la lista, sólo pones NULL en hWindowMenu. Obtienes el handle al submenú llamando a GetSubMenu.

idFirstChild es el ID de la primera ventana hija MDI. Windows incrementa el ID para cada nueva ventana hija MDI que creó la aplicación. Por ejemplo, si pasas 100 a este campo, la primera ventana hija MDI tendrá 100 como ID, la segunda tendrá el ID de 101 y así. Este ID es enviado a la ventana marco via WM_COMMAND cuando la ventana hija MDI es seleccionada desde la lista de ventanas. Normamente pasarás estos mensajes WM_COMMAND "no manejados" [unhandled] a DefFrameProc. Uso la expresión "no manejado" porque los elementos de menú (menuitems) en la lista de la ventana no son creados por tu aplicación, así que tu aplicación no conoce sus IDs y no tiene el manejador (handle) para ellos. Este es otro caso especial para la ventana marco MDI: si tienes la lista de la ventana debes modificar un poco tu manejador (handler) de WM_COMMAND más o menos así:

.elseif
uMsg==WM_COMMAND 
    .if lParam==0
    ; este mensaje es generado desde un menú
    mov eax,wParam 
       .if ax==IDM_CASCADE

..... .elseif ax==IDM_TILEVERT

..... .else
invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, lParam
ret 
.endif

Normalmente, deberías ignorar los mensajes de casos no manejados. Pero en el caso de MDI,si los ignoras, cuando el usuario hace click sobre el nombre de una ventana hija MDI en la lista de ventanas,esa ventana no se activará. Necesitas pasarla a DefFrameProc para que puedan ser manejadas adecuadamente.

Una precaución a tomar respecto al valor de idFirstChild: no deberías usar 0. Tu lista de ventanas no se comportará adecuadamente, es decir, el marco de chequeo no aparecerá en frente de la primera ventana hija MDI aunque esté activa. Escoge un valor seguro tal como 100 o superior.

Habiendo llenado la estructura CLIENTCREATESTRUCT, puedes crear la ventana cliente llamando a CreateWindowEx con el nombre de clase predefinido, "MDICLIENT", y pasar la dirección de la estructura CLIENTCREATESTRUCT en lParam. También debes especificar el handle a la ventana marco en el parámetro hWndParent de manera que Windows conozca la interrelación padre-hija entre la ventana marco y la ventana cliente. Los estilos de ventana que deberías usar son: WS_CHILD ,WS_VISIBLE y WS_CLIPCHILDREN. Si olvidas WS_VISIBLE, no verás las ventanas hijas MDI aunque ellas hayan sido credas éxitosamente.

Los pasos para crear una ventana cliente son los siguientes:

  1. Obtener el handle al submenú que quieres que se anexe a la lista de ventanas.

  2. Poner el valor del handle de menú junto al valor que quieres usar para el ID de la primera ventana hija MDI en la estructura CLIENTCREATESTRUCT

  3. Llamar a CreateWindowEx con el nombre de la clase "MDICLIENT", pasando en lParam la dirección de la estructura CLIENTCREATESTRUCT que llenaste.

Creando la Ventana Cliente MDI

Ahora tienes la ventana marco y la ventana cliente. El terreno ya está preparado para la creación de la ventana hija MDI. Hay dos maneras de hacerlo.

 

szClass

la dirección de la clase de ventana que quieres usar como plantilla para la ventana hija MDI.

szTitle

la dirección del texto que quieres aparezca en la barra del título de la ventana hija

hOwner

el handle a la instancia de la aplicación

x,y,lx,ly

la coordenada superior izquierda y el ancho de la ventana hija

style

estilo de la ventana hija. Si creas la ventana hija con MDIS_ALLCHILDSTYLES, puedes usar cualquier estilo de ventana.

lParam

un valor de 32-bit definido para la aplicación. Esta es una manera de compartir valores entre las ventanas MDI. Si no necesitas usarlo, ponlo como NULL

Si observas bien los parámetros, encontrarás que son idénticos a los miembros de la estructura MDICREATESTRUCT, excepto para el hWndParent. Esencialmente es el mismo nombre de parámetros que tu pasas con WM_MDICREATE. MDICREATESTRUCT no tiene el campo hWndParent porque de todas maneras debes pasar toda la estructura a la ventana cliente actual con SendMessage.

En este punto, puedes hacerte varias preguntas: ¿cuál método debería usarse? ¿cuál es la diferencia entre los dos? He aquí una respuesta:
El método WM_MDICREATE crea la ventana hija MDI en el mismo hilo [thread] del código que hace la llamada. Eso significa que si la aplicación sólo tiene el hilo primario, todas las ventanas hijas MDI correrán en el contexto del hilo primario. Esto no es una gran cosa hasta que una o más de tus ventanas hijas MDI ejecuten alguna operación por mucho tiempo ¡Esto podría ser un problema! Piensa en ello: de repente toda tu aplicación parecerá congelarse, no responderá a nada hasta que la operación termine.

Para resolver este problema precisamente se diseñó CreateMDIWindow, que crea un hilo separado para cada ventana hija MDI de manera que si una hija MDI está ocupada, no arrastrará toda la aplicación con ella.

Todavía necesitan cubrirse algunos detalles sobre el procedimiento de ventana de la hija MDI. Como en el caso de la ventana marco, debes llamar a DefWindowProc para manejar los mensajes no procesados. En vez de eso, debes usar DefMDIChildProc. Esta función tiene exactamente los mismos parámetros que DefWindowProc.

Además de WM_MDICREATE, hay otros mensajes de ventanas asociados a MDI. He aquí una lista:
 

WM_MDIACTIVATE

Este mensaje puede ser enviado por la aplicación a la ventana cliente para decirle a ésta que active la hija MDI seleccionada. Cuando la ventana cliente reciba el mensaje, activará la ventana hija seleccionada y enviará WM_MDIACTIVATE a la hija que está siendo desactivada y la que está siendo activada. El uso de este mensaje es doble: puede ser usado por la aplicación para activar la ventana hija deseada. Y puede ser usada por la ventana hija MDI misma como el indicador de lo que está siendo activado/desactivaod. Por ejemplo, si cada ventana hija MDI tiene diferente menú, puede aprovechar esta oportunidad para cambiar el menú de la ventana marco cuando sea activada/desactivada.

WM_MDICASCADE
WM_MDITILE
WM_MDIICONARRANGE 

Estos mensajes pueden manejar el orden de las ventanas hijas MDI. Por ejemplo, si quieres que las ventanas hijas MDI se ordenen en forma de cascada, envías WM_MDICASCADE a la ventana cliente.

WM_MDIDESTROY

Envía este mensaje a la ventana cliente para que destruya una ventana hija MDI. Deberías usar este mensaje en vez de llamar a DestroyWindow, porque si la ventana hija MDI está maximizada, este mensaje restaurará el título de la ventana marco. Si usas DestroyWindow, el título de la ventana marco no será restaurado.

WM_MDIGETACTIVE

Envía este mensaje para recuperar el handle de la ventana hija MDI actual.

WM_MDIMAXIMIZE
WM_MDIRESTORE 

Envía WM_MDIMAXIMIZE para maximizar la ventana hija MDI y WM_MDIRESTORE para restaurarla a su estado previo. Siempre usa estos mensajes para las operaciones. Si usas ShowWindow con SW_MAXIMIZE, la ventana hija MDI maximizará muy bien pero tendrás problemas cuando trates de restaurarla a su tamaño previo. Sin embargo, puedes minimizar la ventana hija MDI sin problemas con ShowWindow.

WM_MDINEXT

Envía este mensaje a la ventana cliente para activar la siguiente o la previa ventana hija MDI de acuerdo a los valores en wParam y lParam.

WM_MDIREFRESHMENU

Envía este mensaje a la ventana cliente para refrescar el menú de la ventana marco. Nota que debes llamar a DrawMenuBar para actualizar la barra de menú después de enviar este mensaje.

WM_MDISETMENU

Envía este mensaje a la ventana cliente para reemplazar todo el menú de la ventana marco o sólo el submenú de la ventana. Debes usar este mensaje en vez de SetMenu. Después de enviar este mensaje, debes llamar DrawMenuBar para actualizar la barra de menú. Normalmente usarás este mensaje cuando la ventana hija MDI activa tenga su propio menú y quieres que reemplace el menú de la ventana marco mientras que la ventana hija está activa.

Ahora revisaré los pasos a seguir para crear una aplicación MDI.

  1. Registrar las clases de ventanas, la clase de ventana marco y la clase de ventana hija MDI

  2. Crear la ventana marco con CreateWindowEx.

  3. Dentro del bucle de mensaje, llamar TranslateMDISysAccel para procesar las teclas aceleradoras relativas a MDI

  4. Dentro del procedimiento de ventana de la ventana marco, llamar a DefFrameProc para manejar TODOS los mensajes no manejados por tu código.

  5. Crear la ventana cliente llamando a CreateWindowEx usando el nombre de la clase de ventana predefinido, "MDICLIENT", pasando la dirección de una estructura CLIENTCREATESTRUCT en lParam. Normalmente, deberías crear la ventana cliente dentro del manejador [handler] WM_CREATE del procedimiento de ventana marco

  6. Puedes crear una ventana hija MDI enviando WM_MDICREATE a la ventana cliente o, alternativamente, llamando a CreateMDIWindow.

  7. Dentro del procedimiento de ventana de la ventana hija MDI, pasar todos los mensajes no manejados a DefMDIChildProc.

  8. Si existe, usar la versión MDI de los mensajes. Por ejemplo, usar WM_MDIDESTROY en vez de llamar a DestroyWindow

.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 "Are you sure you want to close this window?",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 
;=============================================
; Registrar la clase de la ventana marco
;=============================================
mov wc.cbSize,SIZEOF WNDCLASSEXmov 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
;================================================
; Registrar la clase de la ventana hija MDI
;================================================
 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 S_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
;=======================================
; Inicializar 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



Análisis:

Lo primero que hace el programa es registrar la clase de ventana de la ventana marco y la de la ventana hija MDI. Después de eso se llama a CreateWindowEx para crear la ventana marco. Dentro del manejador (handler) WM_CREATE de la ventana marco, creamos la ventana cliente:

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

Se llama a GetMenu para obtener el handle del menú de la ventana marco, a ser usada en la llamada a GetSubMenu. Nota que pasamos el valor 1 a GetSubMenu porque el submenu que queremos que aparezca en la lista de ventana es el segundo submenú. Luego llenamos los miembros de la estructura CLIENTCREATESTRUCT. Después, inicializamos la estrcutura MDICLIENTSTRUCT. Nota que no necesitamos hacerlo ahí. Sólo conviene hacerlo en WM_CREATE.

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

Después que es creada la ventana marco (y también la ventana cliente), llamamos a LoadMenu para cargar el menú de la ventana hija a partir del recurso. Necesitamos obtener este handle al menú de manera que podamos reemplazar el menú de la ventana marco con él cuando una ventana hija MDI esté presente. No olvides llamar a DestroyMenu sobre el handle antes de que la aplicación salga a Windows. Normalmente Windows liberará automáticamente el menú asociado con una ventana cuando la aplicación termine en este caso, el menú de la ventana hija no está asociado con ninguna ventana, así que estará ocupando todavía una cantidad valiosa de memoria incluso después de que la aplicación termine.

invoke LoadMenu,hInstance, IDR_CHILDMENU
mov hChildMenu,eax ........
invoke DestroyMenu, hChildMenu

Dentro del bucle de mensaje, llamamos a TranslateMDISysAccel.

 
 .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

Si TranslateMDISysAccel regresa un valor diferente a cero, significa que el mensaje ya fue manejado por Windows así que no necesitas hacer nada más con el mensaje. Si regresa 0, el mensaje no es relativo a MDI y debería ser manejado como es usual.

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

Nota que el procedimiento de ventana de la ventana marco, llamamos a DefFrameProc para manejar los mensajes en los cuales no estamos interesados.

La mayor parte del procedimiento de ventana la ocupa la sección que procesa WM_COMMAND. Cuando el usuario selecciona "New" del menú File, creamos una nueva ventana hija MDI.

.elseif ax==IDM_NEW
invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate

En nuestro ejemplo, creamos la ventana hija MDI enviando WM_MDICREATE a la ventana cliente, pasando la dirección de la estructura MDICREATESTRUCT en lParam.

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

Cuando la ventana hija MDI es creada, monitorea WM_MDIACTIVATE para ver si se trata de la ventana activa. Esto lo hace comparando el valor de lParam que contiene el handle a ventana hija activa con su propio handle. Si coinciden, es la ventana activa y el siguiente paso es reemplazar el menú de la ventana marco por el de sí misma. Como el menú original será reemplazado, tienes que decirle de nuevo a Windows en cuál submenú debe aparecer la lista de ventanas. Es por eso que debemos llamar una vez más a GetSubMenu para recuperar el handle al submenú. Enviamos el mensaje WM_MDISETMENU a la ventana cliente para alcanzar el resultado deseado. wParam de WM_MDISETMENU contiene el handle al menú que te gustaría reemplazar en el menú original. lParam contiene el handle del submenú que quieres que aparezca en la lista de ventanas. Justo después de enviar WM_MDISETMENU, llamamos a DrawMenuBar para referescar el menú, sino tu menú será un desastre.

.else
	invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
	ret .endif
Dentro del procedimiento de la ventana hija MDI, debes pasar todos los mensajes no manejados [unhandled messages ] a DefMDIChildProc
en vez de enviarlos a DefWindowProc.
.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

Cuando el usuario seleccione uno de los elementos de menú [menuitems] en el submenú de la ventana, enviamos el mensaje correspondiente a la ventana cliente. Si el usuario elige organizar las ventanas en forma de mosaico, enviamos WM_MDITILE a la ventana cliente, especificando en wParam qué tipo de organización de mosaicos queremos, de una manera similar a WM_CASCADE.

 
.elseif ax==IDM_CLOSE
	invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
	invoke SendMessage,eax,WM_CLOSE,0,0

Si el usuario elige el elemento de menú [menuitem] debemos primero obtener el handle de la ventana hija MDI activa en ese momento enviando el mensaje WM_MDIGETACTIVE a la ventana cliente. El valor de regereso en eax  es el handle de la ventana hija MDI activa en ese momento. Después de eso, enviamos WM_CLOSE a esa ventana.

 
.elseif uMsg==WM_CLOSE
	invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
	.if eax==IDYES
		invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
	.endif

Dentro del procedimiento de ventana de la hija MDI, cuando se recibe WM_CLOSE, despliega un cuadro de mensaje preguntando al usuario si realemnte quiere cerrar la ventana. Si la respuesta es sí, enviamos WM_MDIDESTROY a la ventana cliente. WM_MDIDESTROY cierra la ventana hija MDI y restablece el título de la ventana marco.


 

Índice

Siguiente

[Iczelion's Win32 Assembly Homepage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por:   n u M I T_o r

www.000webhost.com