Estew tutorial te muestra cómo crear una aplicación MDI. Realmente no es tan difícil hacerlo. Descarga el ejemplo.
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:
Dentro de la ventana principal, pueden haber múltiples ventanas hijas en el área cliente. Todas las ventanas hijas están sujetadas al área cliente.
Cuando minimizas una ventana hija, se minimiza a la esquina izquierda inferior del área cliente de la ventana principal.
Cuando maximizas una ventana hija, su título aparece con la de de la ventana principal.
Puedes cerrar una ventana hija presionando Ctrl+F4 y cambiar el foco entre las ventanas hijas presionando Ctrl+Tab
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
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í:
Llenar la estructura WNDCLASSEX como es usual
Registrar la clase de la ventana marco llamando a RegisterClassEx
Crear la ventana marco llamado a CreateWindowEx
Dentro del bucle de mensaje, llamar a TranslateMDISysAccel.
Dentro del procedimiento de ventana, pasar los mensajes no procesados a DefFrameProc en vez de a DefWindowProc.
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:
Obtener el handle al submenú que quieres que se anexe a la lista de ventanas.
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
Llamar a CreateWindowEx con el nombre de la clase "MDICLIENT", pasando en lParam la dirección de la estructura CLIENTCREATESTRUCT que llenaste.
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.
Puedes enviar el mensaje WM_MDICREATE a la ventana cliente, pasando en wParam la dirección de una estructura de tipo MDICREATESTRUCT. Este es el método más fácil y usual para crear ventanas hijas MDI.
.data? mdicreate MDICREATESTRUCT <> .... .code ..... [llenar los miembros de mdicreate] ...... invoke SendMessage, hwndClient, WM_MDICREATE,addr mdicreate,0
SendMessage regeresará el handle a la ventana hija MDI si esto ha sido hecho exitosamente. No necesitas salvar el handle. Puedes obtenerlo por otros medios si quieres. Tampoco necesitas salvar el handle. Si quieres, puedes obtenerlo por otros medios. MDICREATESTRUCT tiene la siguiente definición.
MDICREATESTRUCT STRUCT
szClass DWORD ?
szTitle DWORD ?
hOwner DWORD ?
x DWORD ?
y DWORD ?
lx DWORD ?
ly DWORD ?
style DWORD ?
lParam DWORD ?
MDICREATESTRUCT ENDS
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 |
Puedes llamar a CreateMDIWindow. Esta función tiene la siguiente sintaxis:
CreateMDIWindow proto lpClassName:DWORD lpWindowName:DWORD dwStyle:DWORD x:DWORD y:DWORD nWidth:DWORD nHeight:DWORD hWndParent:DWORD hInstance:DWORD lParam:DWORD
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
|
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
|
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.
Registrar las clases de ventanas, la clase de ventana marco y la clase de ventana hija MDI
Crear la ventana marco con CreateWindowEx.
Dentro del bucle de mensaje, llamar TranslateMDISysAccel para procesar las teclas aceleradoras relativas a MDI
Dentro del procedimiento de ventana de la ventana marco, llamar a DefFrameProc para manejar TODOS los mensajes no manejados por tu código.
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
Puedes crear una ventana hija MDI enviando WM_MDICREATE a la ventana cliente o, alternativamente, llamando a CreateMDIWindow.
Dentro del procedimiento de ventana de la ventana hija MDI, pasar todos los mensajes no manejados a DefMDIChildProc.
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
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.
[Iczelion's Win32 Assembly Homepage]
n u M I T_o r's Programming Page