Forms en paneles, adiós a los formularios MDI

ACTUALIZACIÓN IMPORTANTE!: En uno de los comentarios un lector me hizo caer en cuenta de un grave error, el código, como estaba, generaba siempre una nueva instancia del formulario y solo removíamos la referencia del contenedor… lo que significaba un uso excesivo de memoria. El articulo fue actualizado explicando está ultima implementación. Cualquier sugerencia adicional es siempre bienvenida.

Cuando necesitamos trabajar múltiples “vistas” dentro de nuestros formularios por lo general recurrimos al control TabControl, pero cuando necesitamos ya una funcionalidad es muy compleja o simplemente queremos separar estas funcionalidades en interfaces diferentes e integrarlas en un solo formulario, recurrimos a los formularios MDI, en estos podemos trabajar con formularios padres (contenedor) y sus formularios hijos (contenidos), personalmente no tengo nada en contra de estos, pero me parecen un tanto tediosos de trabajar y algo estrictos en su personalización.

Es por eso que en una ocasión, cuando necesitaba hacer un proyecto de ese estilo, me decidí por integrar otro método para acoplar formularios hijos en uno padre SIN QUE ESTE FUERA UN MDI CONTAINER, ni trabajar con los TabControls. Al ver la sección de contenedores en el ToolBox de Visual Studio vi el Panel, un control tan simple pero tan potente que a la final sería la mejor solución a mi problema, aquí les dejo como lo hice.

Una vez aclarados estos antecedentes, manos al teclado, lo primero que vamos a hacer es crear un proyecto de tipo Windows Forms, a este le vamos a agregar tres Forms (Padre, Hijo1, Hijo2):

Form Padre e hijos

Al formulario Padre, le vamos a agregar dos botones (btMostrarHijo1, btMostrarHijo2) y un panel (panelContenedor), así:

así luce el form padre

Y en los Forms hijos agregamos etiquetas grandes y distintivas (o lo que quieras agregar), así:

Así lucen los forms hijos

Ahora, en el lado que nos gusta (el lado del código) veamos cómo implementar estos formularios en el panel, en el evento click del botón btMostrarHijo1 agregamos el siguiente código:

private void btMostrarHijo1_Click(object sender, EventArgs e)
{
            if (this.panelContenedor.Controls.Count > 0)
                this.panelContenedor.Controls.RemoveAt(0);
            Hijo1 form = Application.OpenForms.OfType<Hijo1>().FirstOrDefault();
            Hijo1 hijo1 = form ?? new Hijo1();
            hijo1.FormBorderStyle = FormBorderStyle.None;
            hijo1.Dock = DockStyle.Fill;
            this.panelContenedor.Controls.Add(hijo1);
            this.panelContenedor.Tag = hijo1;
            hijo1.Show();
}

La explicación de cómo funciona este código es muy simple, primero preguntamos si existe algún control en el interior del panel, de ser verdadero lo eliminamos. Luego creamos una nueva instancia del formulario a agregar Luego preguntamos si existe algún formulario del tipo Hijo1 que ya este abierto, si existe usamos esa misma instancia, de lo contrario creamos una nueva y sobre este objeto reescribimos algunas de sus propiedades, TopLevel establece si el formulario debe mostrarse como ventana nivel superior, FormBorderStyle define el estilo de los bordes de nuestro formulario (en nuestro caso no queremos mostrarlos), Dock establece como se acoplara el control a su contenedor principal (en nuestro caso queremos que rellene todo el panel), Por ultimo lo agregamos al panel, establecemos la instancia como contenedor de datos de nuestro panel y lo mostramos, el resultado se verá así:

Y si tengo 50 botones en mi formulario padre, ¿tengo que escribir todo este código 50 veces? La respuesta es NO, por fortuna para nosotros podemos escribir un método que nos ahorre a un más el trabajo, en nuestro caso crearemos un método void (sin retorno de datos) que recibirá como parámetro la instancia del  Formulario a mostrar, así:

private void AddFormInPanel(Form fh)
{
            if (this.panelContenedor.Controls.Count &amp;gt; 0)
                this.panelContenedor.Controls.RemoveAt(0);            
            fh.TopLevel = false;
            fh.FormBorderStyle = FormBorderStyle.None;
            fh.Dock = DockStyle.Fill;
            this.panelContenedor.Controls.Add(fh);
            this.panelContenedor.Tag = fh;
            fh.Show();
} 

Y para acudir a este, desde el evento click del botón lo hacemos así:

private void btMostrarHijo2_Click(object sender, EventArgs e)
{            
        var form = Application.OpenForms.OfType<Hijo2>().FirstOrDefault();
        Hijo2 hijo = form ?? new Hijo2();
        AddFormInPanel(hijo);
}

De esta forma es mucho más sencillo y agradable el trabajar con formularios contenidos dentro de otros.

Descarga el ejemplo

Bien espero les sea de utilidad en sus proyectos.

Hasta el próximo post.

About these ads

48 comentarios en “Forms en paneles, adiós a los formularios MDI

  1. Buen post amigo Nicolas, ademas recodermos que ya webforms esta siendo reemplazado por un lenguaje basado en marcas como lo es WPF, el cuál es muy liviano, portente y permite una personalizaciín increible sin necesidad de estar matandonos la cabeza creando controles… y por cierto los formularios MDI no están soportados nativamente en WPF, en codeplex existe un proyecto con el cual los podemos implementar !

  2. Eh echo este ejemplo en mi sistema abro normal pero cuando quiero
    abrir el mismo formulario me sale este error:

    No se puede obtener acceso al objeto elimnado.
    Nombre de objeto :(en mi caso) ‘Frm_Obra’

    Cual podria ser la solucion. Gracias.

    • Hola, lo que te entiendo es que En tu form padre cuando intentas abrir DOS veces el mismo form hijo, es decir que das dos veces click en el boton que abre a ese hijo, te salta esa excepcion?? caray pues he probado y no, hice la prueba así como te comento pero no salta la excepcion.
      Si no es este el problema, especificame un poco mas a detalle que haces para que salga y te ayudo.
      Saludos y gracias por el comentario.

      • Ya lo reolvis eske de nu menu me llama a varios forms… mejor hice un formPadre MDI pero igual lo que publicaste ya chevere

        Gracias…

  3. Amigos d esta pagina tengo un problema en i proyecto csharp.net es el siguiente:
    En mi proyecto Presentacion donde van los formularios eh creado 2 carpetas con forms dentro, pero ahora lo quiero es crear otra carpeta e incluir un formulario hasta ahi vamos bien pero cuando voy a la clase Program.cs , la me dice de donde quiero arrancan, en esta parte del codigo:
    Application.Run(new ……()); en los puntos suspensivos quiero colocar mi carpera recien creada y el form que acabo de incluir pero no me sale en el cuadro intelgente
    Alguna ayuda ..

    Gracias.

    • Hola, para esto que deseas hacer, solo necesitas agregar la referencia a la carpeta que esta dentro de la solucion, así:

      using PruyetcName.Carpeta;

      Y luego en el metodo Run de Application, lo haces normal:

      Application.Run(new NombreForm());

      Saludos

  4. buenas,
    tengo un problema cuando intento seleccionar el contenido de un textbox con el mouse o haciendo click-derecho dentro de uno, todo esto cuando tengo un formulario dentro del panel.
    ¿saben alguna solucion?

    gracias.

    • Hola,

      mm… esta muy rara la situcion que comentas, pasa con todos los TextBox??… descargue el ejemplo que publique en el SkyDrive para probar si con este pasaba y no, osea SI pude seleccionar texto con el mouse, y acudir al menu contextual del mismo, pude copiar, pegar y demas. Si quieres podrias especificar mas detalles y miramos una solucion :)

      Saludos y gracias por la pregunta.

  5. muy buena explicación, estaba buscando justo este tipo de ejemplos y me vino muy bien para hacer una pantalla principal en la que no se llene de ventanas.

    Muchas gracias

  6. Hola, quisiera que me ayudaran en algo tengo el form_principal que tiene un boton q me lleva al form_hijo1….y en el form_hijo1 hay un boton que me lleva al form_hijo2…el problema esta en que cuando le doy click para abiri el form_hijo2, este no me aparece contenido en el panel del form_principal sino que aparece aparte…..sera que alguien me puede ayudar con esto…..Gracias

  7. ESTOY TRABAJANDO CON ALGO SIMILAR AL ULTIMO EJEMPLO (FORM HIJOS CONTENIDOS EN UN PANEL DEL FORM PADRE) MI DUDA ES: COMO SE LE HACE PARA GUARDAR INFORMACION DEL FORM HIJO EN EL PADRE SI AMBOS ESTAN ABIERTOS AL MISMO TIEMPO, O COMO LE ALMACENA INFORMACION CONTENIDA EN EL FORM HIJO EN EL PADRE POR MEDIO DE BOTON GUARDAR

    • Hola.

      Antes de nada, perdón por tardar en mi respuesta :-( espero ya lo tengas solucionado.

      Ahora bien, no entiendo bien a que te refieres con guardar información de un hijo a un padre :-S … pero si entiendo bien lo que planetas se puede solucionar con Eventos, es decir exponer un evento en el padre y que este se dispare cuando se haga alguna modificación en uno de los hijos… si necesitas más detalles porfa infórmame y te colaboro con un ejemplo :-D

      Saludos.

    • Hola.

      umm, así a simple vista se me ocurre que puedes resolverlo con una colección de formularios, algo como un List donde al hacer clic con el botón de navegación hagas algo como: var form = Formularios[++_indexActual] y el resultado lo pases al AddFormInPanel(form)

      Saludos.

  8. exelente pero como hago para que se comuniquen el form1 con el padre, en si ya lo tengo eso pero cuando pongo this dentro del show me sale excepcion, me explico cuando pongo hijo1.show(this) me dice remove the form any parent form before calling show

    • Discúlpame… pero no entiendo :-S o sea, ese problema se debería resolver con esta validación :
      if (panelContenedor.Controls.Count > 0)
      panelContenedor.Controls.RemoveAt(0);

      Pero no entiendo bien que es lo que haces cuando dices comunicar el Form1 con el padre… podrías agregar algo de tu código?

      Saludos.

  9. Hola, una duda, en una aplicacion que tengo he sustituido mis forms mdi por forms con panel, el problema que tengo es que en la ventana hijo no me aparecen los textos de mi tooltip, cuando uso los form mdi se muestran sin problema.

  10. Excelente info, lo que si estoy investigando y aun no pude encontrar es como se hace para hacer referencia a los controles que tengo en el formulario hijo desde el formulario padre una vez que ya esta cargado dentro del Panel, agradeceria tu ayuda, desde ya muchas gracias

  11. tengo un formulario padre el cual tiene un link para un hijo…queda bien dentro del panel hijo pero cuando pretendo que del form.hijo llame otro form2…el form2 nuevo no queda en el panel…en el form hijo le coloco un panel y el form2 aparece en el panel…pero cuando retorno con un close al formulario hijo el panel queda y el panel no desaparece en el fom hijo

  12. Excelente. Buen trabajo.
    Generalmente no comento estos post, pero el tuyo, lo merece con creces.
    Simple, fácil y rápido! ;)
    Saludos desde Colombia.

    Muchas gracias!

  13. hola tengo un proyecto en el cual tengo dos paneles en los cuales e cargado un formulario en cada uno y deseo llamar desde un formulario hijo llamar a otro en el otro panel

  14. hola, porfavor hechenme la mano para resolver mi problema ya que llevo unas dos semanas intentando resolverlo, antes que nada no soy experto en programacion pero estoy enrolado y aprendiendo mas sobre la marcha…estoy remplazando los MDI por paneles, tengo un Form principal (padre) con una interfaz de 3 paneles, simila a outlook, en el cual utilizo un TreeView para cambiar de ventanas en un panel (Forms hijos).
    En los form hijos estoy utilizando DataGridView para presentar mis datos, es asunto es que cuando ejecuto el programa y doy clic en un treeNode, se abre mi ventana, en este caso, el panel con el dataGridView correcto, vualvo a darle clic y se queda la misma ventana (panel), pero se agregan los mismos datagridview dos veces y asi sucesivamente se van incrementando…como le puedo hace para que no suceda esto??
    a continuacion coloco el codigo…de antemano gracias y espero aber sido claro en el planteamiento de mi problema.

    ==Este es el codigo de mi Form padre para remplazo de MDI por paneles:==

    private void pnl3_Paint(object formHijo)
    {
    if (this.pnl3.Controls.Count > 0)
    this.pnl3.Controls.RemoveAt(0);
    Form fh = formHijo as Form;
    fh.TopLevel = false;
    fh.FormBorderStyle = FormBorderStyle.None;
    fh.Dock = DockStyle.Fill;
    this.pnl3.Controls.Add(fh);
    this.pnl3.Tag = fh;
    fh.Show();

    }
    private void SelecForm(object sender, TreeNodeMouseClickEventArgs e)
    {
    ((TreeView)sender).SelectedNode = e.Node;

    if (e.Node.Name == “TreeGroups”)
    {
    pnl3_Paint(new GrupoForm());

    }
    if (e.Node.Name == “TreechildSala1″)
    {
    pnl3_Paint(new frmSala1());

    }

    }
    ===================================================================================================

    ==Este es el codigo de mi Form hijo==
    public partial class GrupoForm : Plantilla
    {
    public static DataGridView dgvPantalla = new DataGridView();
    public static TcpListener Servidor = new TcpListener(IPAddress.Any, 4000);
    public static List Clientes = new List();
    public static List IDClientes = new List();
    public static List PuertosOcupados = new List();

    public static int SFILA = 0;

    public GrupoForm()
    {
    InitializeComponent();
    dgvPantalla.Dock = DockStyle.Fill;
    dgvPantalla.Columns.Add(“Computadora”, “Computadora”);
    dgvPantalla.Columns.Add(“Inicio”, “Inicio”);
    dgvPantalla.Columns.Add(“Final”, “Final”);
    dgvPantalla.Columns.Add(“Costo”, “Costo”);
    dgvPantalla.Rows.Add(100);
    dgvPantalla.ReadOnly = true;
    gbD.Controls.Add(dgvPantalla);
    dgvPantalla.RowEnter += new DataGridViewCellEventHandler(dgvPantalla_RowEnter);

  15. Hola que tal!

    Tengo tiempo usando panel como si fueran MDI y me ha funcionado muy bien hasta ahora que se me presento un gran inconveniente, Quisiera ver si puedes ayudarme a decirme como puedo hacer para abrir un Form3 en el panel del Form1 mandándolo hablar en un boton del Form2 previamente cargado en el panel del Form1, espero me haya explicado.

    Saludos, excelente Blog =)

    • Hola Hermilio, lamento la tardanza en responder (Y espero hayas logrado ya resolver tu inquietud), pero andaba algo ocupado :S.
      Podrías ser un poco más explicito? no entiendo bien la parte de: “del Form1 mandándolo hablar en un boton del Form2″ si tienes algún fragmento de código para revisarlo…

      • Que onda como estas?, Decidí resolver el problema que tenia de otra manera pero igual si me sirvió mucho el código que muestras aquí. Muchas gracias igual por la ayuda =)

        Saludos.

  16. Hola, no soy un experto en C# así que voy a hablar con bastante desconocimiento sobre el tema y puede que lo que comente tenga sentido o que no tenga sentido alguno.
    Yo he utilizado la solución que propones que también he visto en algún otro sitio pero… RemoveAt creo que solo elimina la referencia Control, en nuestro caso un Form, de la colección Controls pero no lo libera.
    Al hacer eso, ese Form deja de estar referenciado (tanto Hijo1 hijo1 = new Hijo1(); como Form fh = formHijo as Form; al abandonar el ámbito de su función pierden esa referencia y luego al hacer RemoveAt pierden la única referencia que tenían, esto hace que queden ocupando una zona de memoria, hasta que el colector de basura entre en acción, si entra.)
    Yo he probado lo siguiente:
    if (this.panelContenedor.Controls.Count > 0)
    ((Form)this.panelContenedor.Controls.Controls[0]).Close();
    Hice una pequeña comparativa de uso de la memoria (que tampoco se si fue fidedigna) y con RemoveAt se iba incrementando y con Close no.
    Observé un problema con la solución de Close: si se clickeaba muy rápido (muy muy rápido) el botón que lanza este método, cuando llega a la línea del .Show arroja un System.ObjectDisposedException. Esto lo solventé con una variable bool a modo de semáforo para no procesar un click hasta que no termine la función.
    Si alguien puede opinar algo sobre el tema estaría bien, saludos.

  17. Hola.
    ¡Gracias por el comentario @Pepito! no me fijé en el momento de escribir el articulo que los Forms claramente están quedando abiertos (los años pasaron y no me di cuenta) y se abren nuevos (new Form()) cada vez que se hace click para cambiar de *hijo*… Tu solución está bien, aunque implica que se cierre, se haga dispose y se vuelva a crear el objeto con cada click.
    Otra opción puede ser reutilizar el objeto (el form instanciado) algo de la forma:

                Hijo1 form = Application.OpenForms.OfType<Hijo1>().FirstOrDefault();
                Hijo1 hijo1 = form ?? new Hijo1();
    

    Voy a hacer mediciones y a editar el articulo.

    Nuevamente muchas gracias!

    Saludos.

  18. Tengo el siguiente inconveniente, si cambiamos esta línea:
    hijo1.FormBorderStyle = FormBorderStyle.None;

    por

    hijo1.FormBorderStyle = FormBorderStyle.Sizable;
    //O cualquier otro valor: FixedSingle, Fixed3D, FixedDialog, etc.

    Ya no se pude seleccionar el contenido de los TextBox con el mouse dentro del formulario HIJO, lo que resulta realmente incómodo. ¿Alguna sugerencia?

  19. Hola buen día, quisiera que me ayudaran con este problema, tengo un form1(padre) el cual contiene un panelPrincipal en el cual se carga otro form2(hijo) que a su vez contiene otro panelSecundario que contiene un form3(hijo de hijo), como hago, para mediante código en este ultimo form3, volver al principio osea antes de que estuvieran cargados el form3 y form2, osea que solo este el form1(padre) y el panelPrincipal sin nada contendido.

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s