Sie sind auf Seite 1von 15

Aplicaciones MDI Qu son las aplicaciones MDI? Cmo crear una aplicacin MDI en Delphi?

Eliminar la creacin automtica de la ventana hija Al presionar la X de la ventana hija debe cerrarse, no minimizarse Caption ventana madre "-" caption ventana hija Solucionar el problema de flickering al maximizar ventanas hijas Empezar a implementar el men de frmChild Al intentar cerrar un documento modificado debe salir un mensaje Crear el men Ventana Solucionar algunos problemas al abrir documentos Solucionar el problema visual al abrir varios documentos a la vez Solucionar el problema de los 2 toolbars

aplicaciones mdi
qu son las aplicaciones mdi?
Una aplicacin con interfaz grfica de usuario o GUI (Graphical User Interface) es aquella diseada utilizando ventanas, mens, cuadros de dilogo, y dems caractersticas que hacen a una aplicacin fcil de usar. Delphi ofrece 2 tipos de interfaces de usuario: interface de un documento o SDI (Single DocumentInterface) e interface de mltiples documentos o MDI (Multiple DocumentInterface). En una aplicacin MDI, como su nombre lo indica, se puede abrir ms de una ventana hija (child window) dentro del espacio de una ventana madre (parent window). Esto quiere decir, que si por ejemplo nos encontramos creando un editor de texto, y lo hacemos MDI el usuario va a poder abrir y modificar varios documentos a la vez. En cambio, si decidimos hacer una aplicacin SDI, entonces el usuario se ver limitado a modificar un documento a la vez. Un claro ejemplo de este tipo de aplicacin es el Bloc de Notas o el WordPad que vienen con Windows. Antes de comenzar a develar el misterio de cmo crear una aplicacin MDI en Delphi y contarles algunos secretos aprendidos a fuerza de horas culo (sentado frente a la mquina) sera recomendable que vieras con tus propios ojos a una aplicacin MDI en accin para comprender mejor su estructura y funcionamiento. Para ello no tienes ms que abrir Delphi ir a File -> New... ir a la lengeta Projects y seleccionar MDI Application. Luego de elegir el directorio en donde guardar los archivos presiona F9 para compilar y correr la aplicacin. Ponte a jugar un rato creando, abriendo y cerrando documentos hasta que ya te sientas cmodo con la interfaz.

cmo crear una aplicacin mdi en delphi?


Una aplicacin MDI est compuesta por:

Ventana de marco (Frame Window): La ventana principal de la aplicacin. El espacio vacio entre las ventanas MDI hijas es conocido como area cliente y es en realidad la ventana de clientes. Ventana de clientes (Client Window): El administrador de las aplicaciones MDI. La ventana de clientes se encarga de manejar todos los comandos especficos del MDI y de manejar a las ventanas hijas que residen en su superficie -incluyendo el dibujo de las ventanas MDI hijas. La ventana de clientes es creada automticamente por la VCL cuando se crea una ventana de marco. Ventanas MDI hijas (MDI Child Windows): Son los documentos mismos. Las ventanas hijas no pueden dibujarse por fuera del area de clientes de la ventana de marco.

Medio complicado, no?. Bueno, lo de arriba vamos a pasarlo a un lenguaje bien coloquial, podemos decir que la ventana de marco es la ventana madre, la ventana cliente no la mencionaremos y slo hablaremos

del area de clientes y a las ventanas MDI hijas las llamaremos simplemente ventanas hijas. Entonces, crear una aplicacin MDI puede resumirse en la creacin de una ventana madre y al menos una ventana hija. En una aplicacin MDI puede haber solamente una ventana madre. Para crear esta ventana madre o principal en Delphi debes crear un nuevo proyecto, guardarlo, seleccionar el form creado y cambiar su propiedad FormStyle a fsMDIForm. Listo. Ahora bien, cada ventana madre necesita al menos una ventana hija. No conozco ninguna madre que lo sea sin tener un hijo, verdad. Bueno, el asunto es que para ello debemos crear un nuevo form y cambiar su propiedad FormStyle a fsMDIChild. Nota: Es muy comn nombrar a la ventana madre frmMain y a la ventana hija frmChild, y en caso de que sean varias podran nombrarse frmRTFChild, frmImageChild, etc. Eso es todo el misterio de crear aplicaciones MDI. Una verdadera pavada. Sin embargo, a medida que le vayas agregando cosas a tu aplicacin irn surgiendo todo tipo de problemas que debemos solucionar de manera diferente de como lo venamos haciendo. En primer lugar, si presionas F9 para compilar y correr la aplicacin vers que aparece nuestra ventana principal con una ventana hija dentro de ella. Esta aplicacin est muy pelada, hay que agregarle un men, un toolbar, un men que liste todas las ventanas hijas abiertas, que en el caption aparezca el nombre de la aplicacin "-" nombre del documento abierto, que no cree la ventana hija automaticamente al iniciarse la aplicacin, etc, etc.

Eliminar la creacin automatica de la ventana hija Empecemos de atrs para adelante. Si recuerdas lo que viste al correr hace instantes la aplicacin recordars que la ventana hija se cre automaticamente, pero en el futuro no queremos que esto suceda ya que para eso tendremos un item Nuevo para crear ventanas hijas en blanco o Abrir para crear ventanas hijas mostrando el archivo seleccionado. La solucin es muy fcil, debes ir a el men Project -> Options luego seleccionar la lengeta Forms. Una vez all veremos 2 listas: auto-created forms y available forms. Como vemos, nuestros 2 forms (frmMain y frmChild) se encuentran en la lista de auto-created forms. Por supuesto, lo que debemos hacer es pasar nuestro form hijo (frmChild) a la lista de available forms. Nota: Delphi, en forma predeterminada, agrega a la lista de auto-created forms todos los forms que vayamos agregando en tiempo de diseo. Esto significa que la aplicacin al iniciarse crea automaticamente todos los forms en esa lista. Quitar algunos forms de esa lista resulta conveniente hoy para nosotros para que no se cree automaticamente y la muestre al iniciar nuestro programa, pero en general, cuando trabajes en aplicaciones SDI, es decir, cuando trabajes en las aplicaciones que venias haciendo hasta ahora no se van a mostrar todos los Forms de la lista auto-created forms al iniciarse tu aplicacin, slo el primero. Los dems van a quedar ocultos en memoria para que luego puedas acceder a ellos ms rpido. Sin embargo, esto no siempre es una ventaja, ya que muchas veces esto hace que la carga del programa sea un tanto lenta y por eso muchas veces resulta conveniente sacar algunos forms de esa lista y crearlos en tiempo de ejecucin cuando se los necesite. Adems puede ocurrir que el usuario nunca abra muchos de los forms que estn en memoria, entonces, para qu cargarlos al iniciar?. Es un desperdicio de memoria y de velocidad al iniciarse la aplicacin.

Seguimos y hacemos que al presionar la X de la ventana hija se cierre y deje de minimizarse Primer problema solucionado. Si corremos la aplicacin nuevamente veremos que solamente aparece la ventana madre sin ninguna ventana hija adentro. Bien, ahora lleg la hora de empezar a hacer que la aplicacin sea util. Entonces, como nuestra aplicacin va a ser un editor de textos comun y corriente, vamos a la ventana hija y agregamos un control richedit. Le cambiamos las siguientes propiedades: Align=alCliente, Scrollbars=ssBoth, PlainText=true, Name=reMain. Volvemos a la ventana madre y agregamos un men. Creamos un men Archivo con los siguientes items: Nuevo, Abrir y Salir. Bien, ahora lleg la hora de sentarse y escribir un poco de cdigo. Empecemos por el evento OnClick del item Nuevo. procedure TfrmMain.mniArNuevoClick(Sender: TObject); var MyChildForm: TfrmChild; begin MyChildForm := TfrmChild.Create(Self); // podra haberse escrito MyChildForm := TfrmChild.Create(frmMain); MyChildForm.Caption := 'Sin Nombre ' + IntToStr(MDIChildCount); { idem MyChildForm.Caption := 'Sin Nombre ' + IntToStr(frmMain.MDIChildCount); } end;

Como se que eres una persona audaz y que le gusta comprender cada detalle de lo que se escribe, desglozarlo, estudiarlo y siempre preguntar seguramente te estars preguntando 3 cosas: 1) Self? Self hace referencia a la instancia de la clase en la que nos encontramos escribiendo cdigo. En este caso la clase es TfrmMain y su instancia es simplemente frmMain. Entonces, esa lnea tambin podra haberse escrito como se describe en la lnea siguiente. Por qu la clase es TFrmMain? Simplemente porque nos encontramos escribiendo cdigo dentro de un procedure perteneciente a esta clase, en este caso el TFrmMain.mniNuevoClick. 2) MDIChildCount? Bueno, todo form tiene 3 propiedades que cuando te encuentres creando aplicaciones MDI utilizars mucho. Estas son: MDIChildCount, MDIChildren y ActiveMDIChild. Pasemos a explicar la utilidad de cada una de ellas.

MDIChildCount: nos indica la cantidad de forms MDI hijos abiertos. MDIChildren: Se utiliza para hacer referencia a algn form MDI hijo en particular. As si escribo MDIChildren[0] me estar refiriendo al primer form MDI hijo y si escribo MDIChildren[MDIChildCount - 1] me estar refiriendo al ltimo. En general, lo utilizars para cuando tengas que hacer algo con todos los forms hijos abiertos. Por ejemplo, Cerrar todo o Guardar todo, etc. ActiveMDIChild: Se utiliza para obtener el form MDI hijo que actualmente tiene el foco.

Nota: El form desde el cual hacemos referencia a cualquiera de estas 3 propiedades debe ser un form MDI madre, es decir debe tener su propiedad FormStyle=fsMDIForm. De lo contrario, MDIChildCount y MDIChildren no tendrn sentido y ActiveMDIChild devolver nil. Esto quiere decir que no nos sirve hacer referencia a frmChild.MDIChildCount porque es una ventana MDI hija, tampoco nos servira hacer referencia a Form1.MDIChildCount pensando a Form1 como a un form con su propiedad FormStyle=fsNormal. Slo tiene sentido hacer referencia a frmMain.MDIChildCount o frmMain.MDIChildren[x] o frmMain.ActiveMDIChild si frmMain es una ventana MDI madre. 3) Create sin Free, ummm...? Si has pensado eso realmente debo felicitarte quiere decir que lo que escrib

en el artculo de la OOP ha servido y tu has sido un atento lector. Si no, no te aflijas es cuestin de prctica y estar mas canchero. Muy bien, pero donde esta el Free?. Es decir, hemos creado una ventana en tiempo de ejecucin, pero debemos liberar esa memoria ... al cerrarla. Muy bien, entonces vamos a frmChild y creamos un evento OnClose. procedure TfrmChild.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; end; Hay 4 opciones para Action: a. b. c. d. caNone: se le permite a la ventana cerrarse y no pasa nada ms. caHide: la ventana no se cierra, se esconde y queda residente en memoria para poder acceder a ella ms rpido en el futuro. caFree: la ventana se cierra y se libera toda la memoria asignada al form. caMinimize: la ventana en vez de cerrarse se minimiza. Esta es la predeterminada.

Bien, segundo problema solucionado, basta de presionar la X para cerrar la ventana y que me la minimizara. Ahora la cerrar y liberar toda la memoria asignada a esa ventana.

Seguimos y hacemos que el caption quede caption ventana madre "-" caption ventana hija Si recuerdas toda esta larga explicacin vino porque nos atrevimos a escribir 3 lineas locas en el evento frmMain.mniNuevoClick. Ahora bien, eso crea una ventana en blanco nueva, pero cmo hacemos para que la ventana nueva muestre un archivo que seleccione el usuario? Es decir, cmo hacer el clsico Abrir?. Primero que nada agreguemos un OpenDialog y modifiquemos sus propiedades: Filter=Archivos de texto puro (*.txt)|*.txt y Name=OpenDialog. Ahora s, pasemos al cdigo. procedure TfrmMain.mniArAbrirClick(Sender: TObject); var MyChildForm: TFrmChild; begin MyChildForm := TFrmChild.Create(Self); if OpenDialog.Execute then MyChildForm.Caption := OpenDialog.FileName; end;

Como ves el cdigo es muy parecido al escrito en mniNuevoClick. Debemos hacer algo al respecto, no te parece?. Siempre que haya 2 porciones de cdigo iguales o muy parecidas debes pensar en crear un nuevo procedure o function. Eso es exactamente lo que haremos nosotros. Crearemos un nuevo procedure llamado CrearVentanaMDIHija que tomar como parmetro un string llamado Nombre. procedure TfrmMain.CrearVentanaMDIHija(Nombre: string); var MyChildForm: TFrmChild; begin MyChildForm := TFrmChild.Create(Self); MyChildForm.Caption := Nombre; if FileExists(Nombre) then MyChildForm.reMain.Lines.LoadFromFile(Nombre);

end;

Lo de arriba, esto va para los ms experimentados tambin podra haberse escrito de la siguiente manera: procedure TfrmMain.CrearVentanaMDIHija(Nombre: string); begin with TFrmChild.Create(Self) do begin Caption := Nombre; if FileExists(Nombre) then reMain.Lines.LoadFromFile(Nombre); end; end;

La forma que elijas es slo cuestin de gustos, pero siempre es interesante conocer diferentes maneras de hacer lo mismo para que cuando veamos cdigo que no fue escrito por nosotros no nos resulte raro lo que escribieron. Bueno, muy bien, ahora veamos cmo quedarn nuestros eventos OnClick de los items Nuevo y Abrir. procedure TfrmMain.mniNuevoClick(Sender: TObject); begin CrearVentanaMDIHija('Sin Nombre ' + IntToStr(MDIChildCount + 1)); end; procedure TfrmMain.mniAbrirClick(Sender: TObject); begin if OpenDialog.Execute then CrearVentanaMDIHija(OpenDialog.FileName); end;

Mejor, no?. Ahora, por qu MDIChildCount + 1 y no slo MDIChildCount como hacamos antes (ver ms arriba)?. Simplemente porque estamos haciendo referencia a MDIChildCount antes de crear la nueva ventana. Es decir, antes hacamos referencia a MDIChildCount despus de MyChildForm := TfrmChild.Create(Self), ahora lo estamos haciendo antes porque le estamos mandando el dato como parmetro a la funcin que crea la nueva ventana (CrearVentanaHija). Ok, es el momento de correr la aplicacin y ver cul es el resultado de todo el cdigo que acabamos de escribir. Si maximizas una de las ventanas hijas vers que el caption pasa a: Caption de la ventana madre "" Caption de la ventana hija abierta. As nuestro tercer problema est solucionado y la VCL hizo el trabajo por nosotros. Pero, paremos la pelota un segundo y miremos detenidamente el cdigo que escribimos hasta ahora. Hagamos un pequeo experimento y pongamos un Breakpoint (F5) en la nica lnea del evento OnClose de frmChild. Corremos la aplicacin, abrimos varias ventanas y luego presionamos en la X que cierra el form madre. Como vemos la aplicacin se cerr sin pasar por los eventos OnClose de cada ventana hija, y por lo tanto sin liberar la memoria asignada a cada una de ellas. Bueno, eso no es del todo correcto. Por qu?. Porque cada vez que crebamos una ventana hija le pasbamos como parmetro Owner a frmMain (MyChildForm := TFrmChild.Create(Self)). Esto quiere decir que frmMain se hace responsable de liberar la memoria de cada form hijo al liberarse la memoria de frmMain, es decir, al cerrarse frmMain. Nota: Hablando de cerrar, la barra de ttulo de los forms MDI hijos (Minimizar, Maximizar, Cerrar, etc.)

desaparece al maximizarse si no hay un men. Si creas un proyecto nuevo con 2 forms, uno fsMDIForm y otro fsMDIChild, corres la aplicacin y maximizas la ventana hija comprenderas de qu estoy hablando. Ahora agregale un men cualquiera y vuelve a correr la aplicacin. Ves la diferencia?. Cuarto problema solucionado. Bien, como ves hemos solucionados varios problemas a los que alguien acostumbrado a crear aplicaciones SDI no hubiera podido resolver ni detectar al primer intento. Pero, basta de chachara y pasemos a resolver otro problema.

Seguimos y solucionamos el problema de flickering al maximizar ventanas hijas Es muy comn en este tipo de aplicaciones que las ventanas hijas aparezcan ya maximizadas al crearlas. Es decir, que cuando el usuario va a Nuevo o Abrir se le abre una nueva ventana (en blanco o con texto) maximizada. Muy bien, para empezar a resolver ese problema no tenemos ms que hacer lo mismo que hacemos cuando queremos que cualquier ventana se maximize. Debemos modificar la propiedad WindowState=wsMaximized. Ahora s, corremos la aplicacin y vemos los resultados. Ummm... qu pas?. Cuando vamos a Nuevo o Abrir se abre la ventana pero es como si se abriera en su tamao normal y luego se maximizara, no aparece maximizada de entrada. Ese movimiento de la ventana realmente es horrible. Cmo solucionarlo? Veamos... procedure TfrmMain.CrearVentanaMDIHija(Nombre: string); begin LockWindowUpdate(Handle); with TFrmChild.Create(Self) do begin Caption := Nombre; if FileExists(Nombre) then reMain.Lines.LoadFromFile(Nombre); end; LockWindowUpdate(0); end;

Y eso? Las API de Windows siempre al rescate han venido a salvarnos nuevamente de un aprieto. El problema es que la nueva ventana nace con el tamao que le indiquemos en sus propiedades Height y Width y luego se maximiza en caso de que su propiedad WindowState=wsMaximized. LockWindowUpdate, entonces, lo que hace es hacer que la ventana no se dibuje hasta que est completamente maximizada. Es decir, impide que la ventana se repinte a medida que se va agrandando y le da ese efecto tan interesante de que se est maximizando. Da la sensacin de movimiento, pero en este caso no nos sirve, no nos interesa y adems queda horrible, asi que le quitamos todo esa exquisitez. Quinto problema solucionado. Ahora slo nos queda escribir el cdigo para el evento OnClick de mniSalir. procedure TfrmMain.mniArSalirClick(Sender: TObject); begin Close; end;

Seguimos y empezamos a implementar al men de frmChild

Listo. Acabamos de finalizar la implementacin de nuestro mini-men. Ahora, si me voy a manejar con varios documentos necesito ms opciones como Guardar, Guardar como..., Guardar todo, Cerrar, Cerrar todo. Ms an, necesito un men Edicin en donde pueda Cortar, Copiar, Pegar, Seleccionar todo, Buscar, Reemplazar, etc, etc. Bueno, lleg la hora de complicarnos un poco ms. S, no porque crear mens sea muy complicado, sino ms bien porque al crearlos estamos definiendo la estructura del cdigo que escribiremos posteriormente. Qu quiere decir eso? Bueno donde y qu tipo de cdigo escribiremos depende de donde coloquemos los mens. Los mens, en este caso, pueden ir en la ventana madre o en la ventana hija. Da lo mismo ponerlo en un lugar o en otro?, habr que crear ms de 1 menu?, todas estas son preguntas que debemos contestarnos antes de proceder y escribir cdigo. Este paso, debo admitirlo, puede resultar una estupidez pero no lo es. Yo mismo me he envuelto en complicaciones espectaculares por no haber pensado detenidamente la estructura de mis mens. Sin embargo, despus de horas de idas y venidas, he llegado a desarrollar un mtodo para construir mis mens que no es el nico y quiz no sea el mejor pero es el que yo entiendo ms claramente y me parece el ms adecuado para la mayora de mis aplicaciones MDI. Ahora bien, como te habrs dado cuenta nuestro mini-men en frmMain, la ventana madre, slo tiene 3 opciones: Nuevo, Abrir y Salir. Eso no es casual, hay un razonamiento detrs. En primer lugar, djame aclarar que el men que diseemos debes pensarlo como el men que ver el usuario cuando no haya ninguna ventana hija abierta. Por lo tanto, agregar en nuestro men opciones de Guardar o Cerrar sera inutil y mucho ms lo sera agregar un men Edicin. Listo el pollo. Ya tenemos el men de frmMain y toda su implementacin. El segundo paso es crear el men que el usuario ver cuando haya al menos 1 ventana hija abierta. Ese men lo crearemos en frmChild y es el que adems de Nuevo, Abrir y Salir, tendr Guardar, Guardar como, Guardar todo, Cerrar, Cerrar todo. Veamos cul ser su implementacin. Comencemos por los 3 items que se repiten: Nuevo, Abrir y Salir. procedure TfrmChild.mniArNuevoClick(Sender: TObject); begin frmMain.mniArNuevoClick(Sender); end; procedure TfrmChild.mniArAbrirClick(Sender: TObject); begin frmMain.mniArAbrirClick(Sender); end; procedure TfrmChild.mniArSalirClick(Sender: TObject); begin frmMain.mniArSalirClick(Sender); end;

Como ves, en los elementos que se repiten solamente nos limitamos a llamar a los OnClick de frmMain que habamos implementado hace unos minutos. Bien, el siguiente paso ser escribir el cdigo para Guardar, Guardar como y Guardar todo. procedure TfrmChild.mniArGuardarComoClick(Sender: TObject); begin

if SaveDialog.Execute then begin Guardar(SaveDialog.FileName); Caption := SaveDialog.FileName; end; end; procedure TfrmChild.mniArGuardarClick(Sender: TObject); begin if DocNuevo = False then Guardar(Caption) else mniArGuardarComoClick(Sender); end; procedure TfrmChild.Guardar(Nombre: string); begin reMain.Lines.SaveToFile(Nombre); reMain.Modified := False; DocNuevo := False; end; procedure TfrmChild.mniArGuardarTodoClick(Sender: TObject); var i: integer; begin for i := frmMain.MDIChildCount - 1 downto 0 do TfrmChild(frmMain.MDIChildren[i]).mniArGuardarClick(Sender); end;

Varias cosas para aclarar. En primer lugar dejame decir que Guardar es un procedure declarado en la seccin private de frmChild y DocNuevo es una variable boolean declarada en la seccin public. Por qu cree este procedure Guardar? Simplemente porque me ahorraba un par de lneas. Por qu cree una variable boolean llamada NuevoDoc?. Bueno, para comprenderlo debemos entender primero cmo funciona nuestro mtodo de guardado. Bsicamente hay 2 preguntas que la mquina se debe hacer para saber qu mtodo de guardado debe utilizar, si es que tiene que utilizar alguno despus de todo. 1) se le han realizado modificaciones al documento? 2) es un documento nuevo o ya esta guardado en el disco? Si el archivo no ha sufrido ninguna modificacin no lo guardaremos. De lo contrario pasaremos a hacernos la segunda pregunta. Si es un documento nuevo, no existe en nuestro disco duro, por lo tanto cuando intentemos guardarlo debe aparecer un cuadro de dilogo preguntndole al usuario donde desea guardar el archivo y que le ponga un nombre. De lo contrario, es decir, si el documento ya existe en el disco duro entonces lo guardamos y listo. DocNuevo, entonces, lo que hace es contestarnos nuestra segunda pregunta. Pero, qu pasa con la primera, cmo sabemos si el documento ha sido modificado?. Bueno, muchos comenten el error de crear una nueva variable de tipo boolean como NuevoDoc llamada por ejemplo Modificado y la van modificando segn sea necesario. Modificado=False cuando se crea/abre/guarda un documento y Modificado=True

cuando se produce el evento OnChange del RichEdit, en nuestro caso, reMain. Ahora bien, los de Borland, conocedores de nuestros problemas diarios ya incluyeron esta variable dentro del RichEdit. Es la propiedad Modified. Esta propiedad nos ahorra 2 cosas: tener que declarar una nueva variable y tener que modificar la propiedad en el evento OnChange del RichEdit. Automaticamente, Modified=True cada vez que se da un evento OnChange. Sin embargo, el RichEdit no tiene forma de saber cuando queremos que Modified=False, as que eso todava sigue en nuestras manos. Como ya dije eso debe ocurrir cuando se crea/abre/guarda un documento, pero nosotros en el ejemplo anterior vimos cmo hacerlo al guardar, nos falta modificar los procedures para crear/abrir documentos implementados en frmMain quedando estos de la siguiente manera: procedure TfrmMain.mniArNuevoClick(Sender: TObject); begin CrearVentanaMDIHija('Sin Nombre ' + IntToStr(MDIChildCount + 1)); TfrmChild(ActiveMDIChild).DocNuevo := True; end; procedure TfrmMain.mniArAbrirClick(Sender: TObject); begin if OpenDialog.Execute then CrearVentanaMDIHija(OpenDialog.FileName); TfrmChild(ActiveMDIChild).DocNuevo := False; end;

Bueno, por primera vez, estamos haciendo uso de la propiedad ActiveMDIChild de la ventana madre. Como ves, esta propiedad nos devuelve un valor TForm, por lo que debemos convertirla a TFrmChild para poder acceder a nuestra variable pblica DocNuevo. Para ello hacemos lo que se llama un casting (conversin de un tipo de datos en otro). Sin embargo, esa no es la nica manera de hacerlo, veamos otras alternativas: procedure TfrmMain.mniArNuevoClick(Sender: TObject); begin CrearVentanaMDIHija('Sin Nombre ' + IntToStr(MDIChildCount + 1)); (ActiveMDIChild as TfrmChild).DocNuevo := True; end; otra forma de lograr el mismo efecto sera: procedure TfrmMain.mniArNuevoClick(Sender: TObject); var MyChildForm: TfrmChild; begin MyChildForm := ActiveMDIChild as TFrmChild; CrearVentanaMDIHija('Sin Nombre ' + IntToStr(MDIChildCount + 1)); MyChildForm.DocNuevo := True; end;

A la hora de implementar Cerrar y Cerrar todo la cosa se torna bastante ms sencilla. Veamos... procedure TfrmChild.mniArCerrarClick(Sender: TObject); begin Close; end; procedure TfrmChild.mniArCerrarTodoClick(Sender: TObject);

var i: integer; begin for i := frmMain.MDIChildCount - 1 downto 0 do frmMain.MDIChildren[i].Close; end;

Si hay algo que seguramente te ha llamado la atencin eso debe ser ... por qu el conteo hacia abajo?. Fjate bien que el for va de mayor a menor y no de menor a mayor como generalmente solemos escribirlo. Por qu? Imagnate la siguiente lista y pongmosle un ndice a cada item de esa lista: [0] [1] [2] [3] Form0 Form1 Form2 Form3

Si borramos de arriba hacia abajo, por ejemplo, si borramos el item 0 quedarn 3 items [0] Form1, [1] Form2, [2] Form3. Luego el for pasa a borrar el item 1 (se est salteando uno -error) y queda una lista con los items [0] Form1 y [1] Form3. Al querer borrar el item 2 no lo va a encontrar y va a salir error. Lo mismo ocurrira cuando itente acceder al item 3. Un verdadero desastre. En cambio, si se empieza a borrar desde el final acceder al item [3] Form3, lo borrar, luego pasar al [2] Form2 y as hasta llegar al primero sin problemas. Nota: El primer form MDI hijo tiene ndice 0 y el ltimo MDIChildCount - 1.

Seguimos y hacemos que al intentar cerrar un documento modificado salga un cuadro de dilogo Ahora pasemos a ver cmo debemos hacer para que si se intenta cerrar un documento con modificaciones aparezca un cuadro de dilogo preguntando si se quiere guardar o no. procedure TfrmChild.FormCloseQuery(Sender: TObject; var CanClose: Boolean); var Resp: word; begin CanClose := True; if reMain.Modified then begin Resp := MessageDlg('Se han realizado modificaciones en ' + Caption + '"' + #13#10 + 'Desea guardarlas?', mtConfirmation, [mbYes, mbNo, mbCancel], 0); case Resp of mrYes: mniArGuardarClick(Sender); mrCancel: CanClose := False; end; end; end;

Como ves, este no es el evento OnClose sino el evento OnCloseQuery. Por qu escribimos nuestro cdigo aqu y no en OnClose?. Simplemente porque aqu se nos permite cancelar el cierre de la ventana en caso de que sea necesario modificando la propiedad CanClose.

Nota: El cuadro de dilogo que mostramos con MessageDlg nos avisa que se han realizado modificaciones y nos indica cul es el documento en cuestin, punto que resulta de extrema importancia ya que ahora nuestro programa puede abrir varios documentos a la vez. Bien, terminamos con la parte dificil, ahora viene la parte ms fcil: cosntruir nuestro men Edicin. Como la misin de este artculo se limita a hablar sobre las aplicaciones MDI ser lo ms breve posible. Veamos entonces cmo implementar nuestro nuevo men. procedure TfrmChild.mniEdCortarClick(Sender: TObject); begin reMain.CutToClipboard; end; procedure TfrmChild.mniEdCopiarClick(Sender: TObject); begin reMain.CopyToClipboard; end; procedure TfrmChild.mniEdPegarClick(Sender: TObject); begin reMain.PasteFromClipboard; end; procedure TfrmChild.mniEdSelTodoClick(Sender: TObject); begin reMain.SelectAll; end;

Seguimos y creamos el men Ventana Antes de terminar con este tema de los mens, nos falta un ltimo punto. Como bien dije al comienzo, la mayora de las aplicaciones MDI son identificables por el men, generalmente Ventana, en el que se listan todas las ventanas MDI hijas abiertas en ese momento. Muy bien, cmo hacemos eso en Delphi?. Bueno, comencemos por crear un men Ventana y agregarle: Mosaico Horizontal, Mosaico Vertical, Cascada, Ordenar conos y un separador. Veamos su implementacin. procedure TfrmMain.mniVeMosaicoHClick(Sender: TObject); begin TileMode := tbHorizontal; Tile; end; procedure TfrmMain.mniVeMosaicoVClick(Sender: TObject); begin frmMain.TileMode := tbVertical; { puse frmMain para que se vea claramente que TileMode } frmMain.Tile; // Tile, Cascade y ArrangeIcons pertenecen a frmMain. end; procedure TfrmMain.mniVeCascadaClick(Sender: TObject); begin

Cascade; end; procedure TfrmMain.mniVeOrganizarIconosClick(Sender: TObject); begin ArrangeIcons; end;

Nota: para insertar un separador, apreta Ins en el lugar en donde desees agregar un nuevo item de men y ponle "-" como Caption. Bien, esto nos sirve para que el usuario de nuestro programa pueda distribuir visualmente las ventanas hijas de la forma ms adecuada a cada situacin, pero no resolvi el problema que habamos planteado. Para decirle a Delphi que muestre un listado de las ventanas MDI hijas abiertas en el men Ventana todo lo que tenemos que hacer es modificar la propiedad WindowMenu de la ventana MDI madre. En nuestro caso, vamos a frmMain y cambiamos WindowMenu=mniVentana. Cuidado!: De no agregar el separador en el men que se asigne como WindowMenu la lista no aparecer. Nota: modificar WindowMenu y llamar a Tile, TileMode, Cascade y ArrangeIcons slo tiene sentido si corresponden a una ventana MDI madre. Sin embargo, si corremos la aplicacin veremos que al abrir/crear un documento nuestro men Ventana desaparece y slo quedan los 2 mens que habamos creado en frmChild: Archivo y Edicin. Para solucionar este problema debemos combinar ambos mens, el definido en frmMain y el definido en frmChild. Para ello debemos modificar una simple propiedad de cada uno de los titulados de nuestros mens (Archivo y Ventana en frmMain y Archivo y Edicin en frmChild). Esa propiedad es GroupIndex y lo que hace es fusionar los mens segn el nmero que all le indiquemos. Por ejemplo: En frmMain... Archivo [GroupIndex=0] Ventana [GroupIndex=2] En frmChild... Archivo [GroupIndex=0] Edicin [GroupIndex=1] Cuando abramos cualquier documento, el men fusionado tendr esta forma: Archivo - Edicin - Ventana Los elementos del men de la ventana hija reemplazan a los elementos del men de la ventana madre que tengan el mismo GroupIndex, en nuestro caso los mens Archivo, y adems inserta a los dems mens de la ventana hija en el lugar que corresponda segn la propiedad GroupIndex de los dems mens de la ventana madre. Es decir, que inserta a Edicin (de frmChild) entre Archivo (de frmChild, porque reemplaza a Archivo de frmMain) y Ventana (de frmMain). Se complica, no?. Bueno, en realidad no tanto, en caso de que no hayas comprendido lo aqu explicado es recomendable leer la ayuda de Delphi con respecto a la propiedad GroupIndex de los items de los mens.

Seguimos y solucionamos unos problemas al abrir documentos En primer lugar, ya que se pueden ver varios documentos a la vez, sera entonces una buena idea poder permitirle a usuario abrir varios documentos a la vez. Para ello, debemos ir a frmMain, seleccionar el OpenDialog y modificar su propiedad Options habilitndole la opcin ofAllowMultiSelect. Con esto logramos que en el cuadro de dilogo se puedan elegir varios documentos, pero no hacemos que se abran todos, recordemos que al abrir un archivo estbamos haciendo referencia a OpenDialog.FileName (ver frmMain.mniArAbrirClick). Ahora debemos hacer uso de la propiedad Files de OpenDialog. En segundo lugar, debemos solucionar un pequeo "error" de nuestro programa que no verifica si el documento que se est intentando abrir ya est abierto. Todo esto, se soluciona realizando unas pequeas modificaciones y agregados a nuestro frmMain.mniArAbrirClick. Veamos... procedure TfrmMain.mniArAbrirClick(Sender: TObject); var i, x: integer; begin if OpenDialog.Execute then for i := 0 to OpenDialog.Files.Count - 1 do begin {verificar si el doc ya est abierto} for x := 0 to MDIChildCount - 1 do if OpenDialog.Files[i] = MDIChildren[x].Caption then begin MDIChildren[x].SetFocus; Exit; end; CrearVentanaMDIHija(OpenDialog.Files[i]); TfrmChild(ActiveMDIChild).DocNuevo := False; end; end;

Seguimos y solucionamos el problema de visualizacin al abrir varios documentos a la vez Aparentemente no hay ningn problema con el cdigo que escribimos hace unos segundos. Sin embargo, si corres la aplicacin y abres varios documentos de un slo intento vers que hay un problema en la visualizacin de las ventanas al abrirse cada una de ellas. Como se soluciona?. Fcil, recuerdas que alguna vez al comienzo de este artculo te haba dicho que debas cambiar la propiedad WindowState de frmMain a wsMaximized?. Bueno, demos marcha atrs con ese asunto y volvamos a asignarle wsNormal. Lo que haremos entonces ser modificar esa propiedad en tiempo de ejecucin como veremos a continuacin. procedure TfrmMain.CrearVentanaMDIHija(Nombre: string); begin LockWindowUpdate(Handle); with TFrmChild.Create(Self) do begin Caption := Nombre; if FileExists(Nombre) then reMain.Lines.LoadFromFile(Nombre);

reMain.Modified := False; WindowState := wsMaximized; end; LockWindowUpdate(0); end;

Bien, ya casi estamos al final, pero nos falta algo fundamental ... un toolbar.

Seguimos y solucionamos el problema de los 2 toolbars No es necesario que les explique cmo hacer un toolbar, pero el caso lo merece. Primero arrastramos un toolbar a frmMain, lo llamamos tlbMain y le agregamos 3 botones: nuevo, abrir y salir. Seleccionamos el botn de nuevo, vamos al Object Editor la lengeta Events, y seleccionamos mniArNuevoClick como su evento OnClick. Como vers es el mismo procedimiento utilizado por el men. Sigue con el resto de los botones segn corresponda. Ahora vamos a frmChild y hacemos exactamente lo mismo pero esta vez agregando ms botones: nuevo, abrir, guardar, cortar, copiar, pegar y salir. El toolbar se llamar tblMain, como su homnimo en frmMain y los botones tendrn como evento OnClick su homnimo en el men, obviamente el men de frmChild. Nota: Por supuesto, en ambos casos tambin hay que agregar un ImageList que almacene las imgenes de los botones. Si corremos la aplicacin tal y como est veremos que al abrir/crear nuevos documentos aparecen 2 toolbars en vez de 1. Cmo lo solucionamos? Bueno, para empezar creemos un procedimiento en frmMain de tipo public. Veamos su implementacin. procedure TfrmMain.DeterminarToolbar(AToolbar: TToolBar); begin if (MDIChildCount = 1) and (AToolBar = nil) then tlbMain.Parent := self else begin tlbMain.Parent := nil; if not (AToolBar = nil) then AToolBar.Parent := self; end; end;

Las llamadas a DeterminarToolbar son las siguientes. procedure TfrmChild.FormActivate(Sender: TObject); begin frmMain.DeterminarToolbar(tlbMain); end; procedure TfrmChild.FormDeactivate(Sender: TObject); begin tlbMain.Parent := nil; end;

procedure TfrmChild.FormDestroy(Sender: TObject); begin frmMain.DeterminarToolbar(nil); end;

El razonamiento que hay detrs es el siguiente: si no hay documentos abiertos el toolbar que se debe mostrar es el de frmMain, si hay al menos 1 abierto, entonces el toolbar que se debe mostrar es el de frmChild. Ahora bien, para comprender exactamente lo que este cdigo implica debemos recordar primero el significado de la propiedad Parent. El componente considerado Padre o Parent de otro es el componente en donde ese "hijo" se va a dibujar. Es as que todos los componentes dentro de Form1 lo tienen como Parent, los componentes dentro de un panel tienen al panel como Parent y as. Entonces si a DeterminarToolbar le pasan como parmetro algo diferente de nil, muestra ese toolbar que le pasan (AToolbar), de lo contrario muestra el toolbar de frmMain. Nota: tlbMain.Parent := nil tiene el efecto de "esconder" el toolbar. El de frmMain se esconde en DeterminarToolbar cuando se considere necesario y el de frmChild se esconde al ejecutarse el evento OnDeactivate de frmChild.

Das könnte Ihnen auch gefallen