Ayer les mostré a mis alumnos cómo crear aplicaciones donde una ventana contiene a todas las demás en la aplicación. Aquí les comparto el proyecto ejemplo.
https://github.com/israel-munoz/dotnet-winforms-mdicontainer
Multi-Document Interface
Las aplicaciones MDI son aquellas donde se tiene una ventana principal, y todas las ventanas de la aplicación se abren dentro de ésta. .NET nos da la facilidad de crear aplicaciones de este tipo con simplemente cambiar una propiedad en nuestra forma principal.
La propiedad IsMdiParent
de la clase Form define si la ventana será contenedor de otras formas. Activando esta propiedad, nuestra ventana cambiará su diseño.
Agregar ventanas al contenedor
De igual forma, agregar ventanas al la forma principal no es nada complicado. Solamente hay que indicarle a la forma que su «padre MDI» es la ventana principal. Esto lo podemos hacer desde la opción del menú que abrirá esta ventana.
InnerForm form = new InnerForm(); form.MdiParent = this; form.Show();
Esto es lo único que se necesita para que la ventana aparezca dentro de la ventana padre, no como una ventana independiente con su propio espacio en la barra de procesos de Windows.
Ventanas tipo diálogo
Si queremos mostrar una ventana en forma de diálogo, es decir, que aparezca encima de toda la aplicación y la bloquee hasta que la ventana sea cerrada, debemos usar el método ShowDialog
en lugar de Show. Pero hay que estar concientes que un diálogo no puede estar dentro del contenedor. Esto tiene mucho sentido, pues una ventana que bloquee a la aplicación no puede estar dentro de ésta.
Afortunadamente, si a nosotros se nos olvida, .NET se encargará de recordárnoslo cuando la aplicación truene al intentar ejecutar la función ShowDialog si le definimos un MdiParent a la forma.
Evitar ventanas repetidas
Vamos a entrar a algo más tedioso, que es el asegurarnos que si una ventana ya está abierta, no sea instanciada de nuevo. Para esto agregaremos una colección de objetos en nuestra ventana principal, la cual contendrá las ventanas abiertas. Para lo que vamos a hacer, la clase Dictionary
es la más adecuada, usando string
para la clave y Form
para el valor.
private readonly Dictionary<string, Form> ventanasActivas; public MainForm(){ InitializeComponents(); ventanasActivas = new Dictionary<string, Form>(); }
En cada opción del menú, donde se abrirá una ventana hija, tenemos que revisar si dicha ventana ya existe en pantalla. De ser así, solo muestra la misma ventana, y en caso contrario, crea una nueva instancia y la agrega a la lista.
private void menu_Click(object sender, EventArgs e){ string const clave = "ventana1"; FormVentana form; if (ventanasActivas.ContainsKey(clave)){ form = ventanasActivas[clave] as FormVentana; form.Activate(); } else { form = new FormVentana { MdiParent = this; } form.FormClosing += ((s, e) => ventanasActivas.Remove(clave)); ventanasActivas.Add(clave, form); form.Show(); } }
Voy a explicar parte por parte lo que se está haciendo:
string clave = "ventana1"; FormVentana form;
Lo primero que hacemos es declarar dos variables: una será la clave que se usará para identificar a la ventana en la lista de ventanas activas, y la otra variable será la ventana que abriremos, pero ésta última no la vamos a instanciar aun.
if (ventanasActivas.ContainsKey(clave))
Las acciones posteriores dependerán si la ventana ya existe en la aplicación o no. Para esto verificamos si ya hay un elemento en el listado de ventanas activas con la clave que declaramos anteriormente.
form = ventanasActivas[clave] as FormVentana; form.Activate();
Si la ventana existe, la variable form (la que no instanciamos al declararla), será igual al valor del elemento en la lista de ventanas activas, es decir, será igual a la forma del mismo tipo actualmente abierta.
Hay que recordar que el listado guarda elementos de tipo Form
. Cualquier ventana creada será derivada de ese tipo, pero en C# no nos permite asignar un valor a una variable cuando no son exactamente el mismo tipo, por lo que es necesario hacer un cast con la cláusula as
.
Como la ventana ya está abierta, lo único que haremos será pasarla a primer plano (ponerla frente a todas las ventanas), ejecutando su método Activate
.
form = new FormVentana { MdiParent = this; }
Si la ventana no existe, creamos una nueva instancia sobre la variable form, asignándole como MdiParent la ventana principal.
form.FormClosing += ((s, e) => ventanasActivas.Remove(clave));
Recordemos que si la ventana ya existe en el listado, no se abrirá de nuevo. Cerrar una ventana no es lo mismo que quitarla del listado de ventanas activas. Si las estamos agregando manualmente, hay que quitarlas del mismo modo.
Agregaremos un handler al evento FormClosing
de la aplicación, en el cual eliminaremos la ventana de la lista. Desde .NET 3.5 no es necesario crear un método distinto para asignarlo a un evento de un control. En el ejemplo, usamos una expresión lambda con únicamente la instrucción para borrar la forma de la lista.
ventanasActivas.Add(clave, form); form.Show();
Finalmente solo nos queda agregar la ventana a la lista con la clave correspondiente, y mostrarla en pantalla. Al ser la primera vez que va a mostrarse, usamos el método Show
.
Ahora sí, las ventanas que se abran usando este método no tendrán más de una instancia a la vez. Claro que este código hay que aplicarlo a cada ventana. Claro que esto se puede simplificar, pero aquí solo estoy mostrando el panorama general. Será cuestión de cada quien buscar la manera más eficiente de lograr este objetivo.
Excelente!
Me gustaría ver toda el código de las ventas que ocupaste, ya que no sé porque tuve que cambiar el nombre del párametro en form.FormClosing += ((s, e)) por form.FormClosing += ((s, ex)
hola buenas tardes, el dropbox no esta online, me gusto tu ejemplo y queria probarlo.
Saludos,