none
¿Como debería abrir formularios dentro de un formulario MDI? RRS feed

  • Pregunta

  • Tengo un formulario MDI y formularios secundarios o hijos y además otros formularios que se desprenden de estos. Por lo tanto me gustaría saber como debo abrir los formularios hijos y formularios modales de estos hijos tomando en cuenta que se pasan variables o datos mediante estos.

    Es decir el formulario principal como debe abrir sus formularios hijos y estos como deberían de abrir los modales, a lo mejor no este muy bien mi planteamiento pero está es la idea ya que abro formularios para hacer operaciones de guardado y formularios con grillas que depende de estos. Bueno disculpen tanta palabrería.

    De antemano gracias.

    miércoles, 2 de marzo de 2016 23:32

Respuestas

  • Tanto los formularios hijos como los hijos de los hijos se abren igual: Se construye una instancia de la clase deseada (con New TipoDelFormulario), a esa instancia se le pone la propiedad MdiParent (si es que quieres que quede contenido dentro del MDI) y finalmente se llama a su método .ShowDialog (o se usa .Show si es que tiene que ser modal).

    A la hora de que un formulario tenga datos que dependen de otro, y que tengas que pasarles parámetros para que conozcan los datos correspondientes, lo que tienes que hacer es olvidarte de que son formularios, y tratarlos como Clases que se pasan datos de una a otra. Sencillamente usa todos los mecanismos que existen para comunicar entre clases, como por ejemplo pasarse datos en el constructor, o declarar propiedades o métodos públicos y llamarlos desde fuera de la clase.

    jueves, 3 de marzo de 2016 6:12
  • El primer error es debido al MdiParent. Los formularios que pertenecen a un MDI no son modales, por lo que no los puedes abrir con .ShowDialog(), sino que es obligatorio el .Show(). Si quieres que sea modal, no le asignes el .MdiParent.

    La segunda parte tiene que fallar siempre, tanto si usas el .Show como si usas el .ShowDialog. El acceso a los miembros de ese formulario se ha de hacer por medio de la instancia, y no de la clase, dado que se trata de propiedades de instancia y no estáticas. Es decir, tienes que acceder (por ejemplo) a verfrm1.lblID.Text en lugar de frmRegistro.lblID.Text.

    jueves, 3 de marzo de 2016 15:55
  • Mucho cuidado: NO DEBES CREARLA DE NUEVO. Esto es un fallo muy habitual, cuando un programador quiere acceder a un dato del form, vuelve a crear la instancia e intenta acceder al dato desde esa nueva instancia. Esto siempre devuelve un valor vacío que no se ve en pantalla, porque esa segunda instancia no es la que se estaba presentando en la pantalla, sino una segunda copia en memoria que no se llega a hacer visible. Tiene que ser necesariamente LA MISMA instancia que inicialmente creaste. Es decir, en tu ejemplo anterior, sería la variable que tú has llamado verfrm1.

    Evidentemente, tienes que tener cuidado de no perder esa variable, para luego poder volverla a usar. No vale que sea una variable local que sólo se declare dentro de una subrutina y se destruya al salir de esa subrutina, tienes que declararla en algún sitio de tu código fuente que sea común a la subrutina que abre el formulario y la subrutina que le pasa los datos (por ejemplo, podrías declararla como variable miembro del form MDI si es que las dos subrutinas están dentro de ese MDI). Si una subrutina está en un formulario y la otra en otro, tendrás que compartir la variable entre ambos usando algún mecanismo adecuado, por ejemplo, como argumento del constructor, o declarándola Shared si es que sabes con seguridad que solo vas a manejar una única instancia de ese formulario.

    jueves, 3 de marzo de 2016 16:24

Todas las respuestas

  • Tanto los formularios hijos como los hijos de los hijos se abren igual: Se construye una instancia de la clase deseada (con New TipoDelFormulario), a esa instancia se le pone la propiedad MdiParent (si es que quieres que quede contenido dentro del MDI) y finalmente se llama a su método .ShowDialog (o se usa .Show si es que tiene que ser modal).

    A la hora de que un formulario tenga datos que dependen de otro, y que tengas que pasarles parámetros para que conozcan los datos correspondientes, lo que tienes que hacer es olvidarte de que son formularios, y tratarlos como Clases que se pasan datos de una a otra. Sencillamente usa todos los mecanismos que existen para comunicar entre clases, como por ejemplo pasarse datos en el constructor, o declarar propiedades o métodos públicos y llamarlos desde fuera de la clase.

    jueves, 3 de marzo de 2016 6:12
  • Hola Alberto, me marca error cuando mando a llamar a mi formulario desde el principal de este manera:

     Dim verfrm1 As New frmRegistro
            verfrm1.MdiParent = Me
            verfrm1.ShowDialog()


    Pero cuando lo abro de esta manera no marca error:

    Dim verfrm1 As New frmRegistro
            verfrm1.MdiParent = Me
            verfrm1.Show()

    Sin embargo cuando desde el formulario 1 mando a llamar a un formulario Usando ShowDialog() no logro pasar datos de una grilla(en el form2) a mi form1:

    Private Sub dgvRegistros_CellContentDoubleClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgvRegistros.CellContentDoubleClick
            If (e.RowIndex = -1) Then
                Return
            End If
            Dim dr As DataGridViewRow = dgvRegistros.SelectedRows(0)
                Me.Hide()
                frmRegistro.lblID.Text = dr.Cells(0).Value.ToString()
                frmRegistro.cbo1.Text = dr.Cells(1).Value.ToString()
                frmRegistro.txtNo.Text = dr.Cells(2).Value.ToString()   
    End Sub

    Gracias por tu respuesta

    jueves, 3 de marzo de 2016 15:45
  • El primer error es debido al MdiParent. Los formularios que pertenecen a un MDI no son modales, por lo que no los puedes abrir con .ShowDialog(), sino que es obligatorio el .Show(). Si quieres que sea modal, no le asignes el .MdiParent.

    La segunda parte tiene que fallar siempre, tanto si usas el .Show como si usas el .ShowDialog. El acceso a los miembros de ese formulario se ha de hacer por medio de la instancia, y no de la clase, dado que se trata de propiedades de instancia y no estáticas. Es decir, tienes que acceder (por ejemplo) a verfrm1.lblID.Text en lugar de frmRegistro.lblID.Text.

    jueves, 3 de marzo de 2016 15:55
  • Ya entendí ¿ entonces como puedo acceder a esa instancia o es que tengo que crearla de nuevo?
    jueves, 3 de marzo de 2016 16:15
  • Mucho cuidado: NO DEBES CREARLA DE NUEVO. Esto es un fallo muy habitual, cuando un programador quiere acceder a un dato del form, vuelve a crear la instancia e intenta acceder al dato desde esa nueva instancia. Esto siempre devuelve un valor vacío que no se ve en pantalla, porque esa segunda instancia no es la que se estaba presentando en la pantalla, sino una segunda copia en memoria que no se llega a hacer visible. Tiene que ser necesariamente LA MISMA instancia que inicialmente creaste. Es decir, en tu ejemplo anterior, sería la variable que tú has llamado verfrm1.

    Evidentemente, tienes que tener cuidado de no perder esa variable, para luego poder volverla a usar. No vale que sea una variable local que sólo se declare dentro de una subrutina y se destruya al salir de esa subrutina, tienes que declararla en algún sitio de tu código fuente que sea común a la subrutina que abre el formulario y la subrutina que le pasa los datos (por ejemplo, podrías declararla como variable miembro del form MDI si es que las dos subrutinas están dentro de ese MDI). Si una subrutina está en un formulario y la otra en otro, tendrás que compartir la variable entre ambos usando algún mecanismo adecuado, por ejemplo, como argumento del constructor, o declarándola Shared si es que sabes con seguridad que solo vas a manejar una única instancia de ese formulario.

    jueves, 3 de marzo de 2016 16:24
  • Estuve leyendo sobre interfaces, ¿Es este el método que necesito utilizar?
    jueves, 3 de marzo de 2016 16:29
  • Podrías, pero para una cosa tan sencilla no merece la pena. Simplemente haz pública directamente la propiedad que quieres compartir (como aparentemente estás haciendo), no hace falta que la hagas miembro de una interfaz.

    Nótese que incluso aunque usaras una interfaz, no te librarías de tener que compartir la variable que representa la instancia entre la parte llamante y la parte llamada, lo único que pasaría es que la variable sería del tipo de la interfaz en lugar de ser del tipo del form (y se te complicaría un poco el código porque dentro del form tendrías que meter la implementación de la interfaz).

    jueves, 3 de marzo de 2016 16:38
  • Muchas gracias por responder, estoy un poco revuelta, ya entendi que tengo que hacer publica mi variable para que la pueda acceder desde el otro form para no tener que crear la instancia de nuevo. Entonces que tengo que modificar al llamar al formulario 2 que es el que tiene la grilla  para que este reciba la variable que usare para mandar los datos de regreso.

    Dim verfrm2 As New frmLista verfrm2.ShowDialog()

    'es necesaria esta linea?

    verfrm2.Dispose()

    De antemano gracias y disculpa es que soy muy novata :(


    • Editado Rosa Dev jueves, 3 de marzo de 2016 17:03
    jueves, 3 de marzo de 2016 16:48
  • El Dispose() en teoría se debería hacer cuando el formulario ya no se necesita, para liberar los recursos empleados por éste. Si es modal, típicamente se hace a la vuelta del .Show. Si es no-modal, lo más corriente es no llamar al Dispose, dejando que se limpie automáticamente al cerrar la aplicación.

    Ya me he perdido en cuál es tu formulario 2 y desde dónde estás llamando a qué cosa. ¿Podrías hacer un esquema o resumen poniéndoles nombres, indicando dónde se abre cada formulario, y cuál es el que tiene que pasar algo a cuál otro, y así vemos qué es lo que hay que modificar?

    jueves, 3 de marzo de 2016 19:33
  • Bueno el Formulario 1 tengo un botón de Buscar que tiene el siguiente código:

    'creamos una nueva instancia del formulario
            ' Dim verform As New frmBusqueda
            verform.CargarDatos()
            verform.lblOperacion.Text = "Registro"
            verform.ShowDialog(Me)
            verform.Dispose()

    Esto abre un formulario (formulario 2) que contiene una grilla que cargo con datos y en este formulario uso el evento cellcontentdobleclik para que al dar doble clic sobre una fila de la grilla los datos de las celdas se manden al Formulario 1 que es el que tiene los controles:

    Private Sub dgvbusqueda_CellContentDoubleClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgvbusqueda.CellContentDoubleClick 'si el indice es igual a -1(fila de columnas ) entonces regresa If (e.RowIndex = -1) Then Return End If

    Dim dr As DataGridViewRow = dgvbusqueda.SelectedRows(0) form1.txtNo.Text = dr.Cells(0).Value.ToString() Me.Hide() End Sub


    bueno disculpa que haya cambiado un poco, es que he hecho mas pruebas. Pero esto es lo que tengo.

    El objetivo que entendi es declarar la variable como publica y mardarsela al formulario 2 o algo así para que yo pueda usar la instancia sin tener que crear otra.
    • Editado Rosa Dev jueves, 3 de marzo de 2016 19:54
    jueves, 3 de marzo de 2016 19:51
  • Bueno, tal como lo tienes, está un poco al revés de lo necesario. Es decir, ahi vemos la variable verform, que se usaría si el formulario1 tuviese que pasarle datos al formulario2. Pero dado que lo que quieres es que el 2 se los pase al 1, lo que necesitas es que el 2 conozca la variable que se utilizó cuando se hizo el New del 1.

    Suponiendo que el formulario1 lo hayas construido así:

    Dim f1 as New Form1
    f1.MdiParent=Me
    f1.ShowDialog()

    pues entonces tienes que guardar esa variable f1. Si solo se crea una única instancia del Form1, entonces puedes declarar la f1 como variable Public Shared en un Module, por ejemplo.

    Después, cuando desde el formulario 2 quieras pasarle datos al 1, haz este pequeño cambio respecto a la línea que tienes escrita:

    f1.txtNo.Text = dr.Cells(0).Value.ToString()

    (observa nuestra variable f1 en lugar de Form1)

    Recuerda que para que esto funcione, el txtNo debe tener el modificador Public.

    jueves, 3 de marzo de 2016 21:33
  • Gracias alberto lo voy a probar, aunque estuve haciendo unas pruebas usando este metodo y al parecer funciona, solo me gustaría que me digas si es correcto hacerlo de esta manera:

    Esto lo declare en el Formulario 2

    'variable privada en el form 2

    'frmRegistro es mi formulario 1

    Private form As frmRegistro Public Sub New(parent_form As frmRegistro) form = parent_form InitializeComponent() End Sub

    Private Sub dgvbusqueda_CellContentDoubleClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgvbusqueda.CellContentDoubleClick
            'si el indice es igual a 1(filas de columnas ) entonces regresa
            If (e.RowIndex = -1) Then
                Return
            End If
            Dim dr As DataGridViewRow = dgvbusqueda.SelectedRows(0)

    'form es mi formulario 1

    'envio el valor de la celda a mi formulario 1 que tiene el control textbox

            form.txtNo.Text = dr.Cells(0).Value.ToString()
            Me.Hide()

        End Sub

    Ya estuve haciendo pruebas y al parecer funciona, pero no sé si esta correcto este planteamiento.

    Te agradecería tu opinión y probare lo que me indicaste.

    jueves, 3 de marzo de 2016 21:47
  • Cree un modulo y agregue esto y me marco error
    Module Module1
        Public Shared f1 As frmRegistro
    End Module

    jueves, 3 de marzo de 2016 22:08
  • Ya estuve haciendo pruebas y al parecer funciona, pero no sé si esta correcto este planteamiento..

    Sí, esto parece correcto. Al crear el nuevo formulario le estás pasando una referencia al otro formulario que tiene que modificar, y se la pasas a través del constructor, que es una de las maneras que yo te sugería.

    Aunque funciona, si quieres algo más elegante y mantenible a largo plazo, hay una mejora que puedes hacer: Un inconveniente que tiene este método es que los dos formularios quedan fuertemente acoplados, ya que el segundo formulario necesita tener un conocimiento detallado del primero puesto que ataca directamente a sus controles (el txtNo en este caso). Esto perjudica al mantenimiento del formulario1, en el que no se podrían modificar los controles de cualquier manera puesto que se vería afectado el formulario2. Para evitarlo, es preferible encapsular la modificación de los contenidos en algún método público del formulario1, y que el formulario2 solo llame a ese método y no directamente a los controles. Para mayor encapsulación, y evitar que accidentalmente el formulario2 manipule otros contenidos del 1 que no estaba previsto modificar desde fuera del mismo, sería bueno pasarle únicamente ese método (o métodos) en lugar de pasarle el form completo. Una forma de conseguir esto consiste en encapsular dichos métodos dentro de una interfaz, y pasar del form1 al 2 únicamente la interfaz, en lugar de pasar el propio form.

    viernes, 4 de marzo de 2016 6:08
  • Cree un modulo y agregue esto y me marco error

    Cuando algo te dé un error y tengas que preguntar sobre ello en un foro, acostúmbrate a copiar siempre el texto completo y detallado del error; no digas simplemente "me marco error" si quieres que las personas que lo leen puedan ayudarte mejor.

    Aunque no has dicho cuál es el error, *creo* que puede ser por la palabra Public, que sería necesaria si se tratara de un Class pero creo recordar que en el Module no se debe poner.

    viernes, 4 de marzo de 2016 6:12
  • Disculpa Alberto, aquí está:

    viernes, 4 de marzo de 2016 14:43
  • Ah, vale. Es una tontería: las variables que se declaran en los módulos no deben llevar la palabra "Shared" (siempre son Shared automáticamente por el hecho de estar en un Module).
    viernes, 4 de marzo de 2016 15:52
  • Bueno gracias por todo Alberto, requiero estudiar mucho. Saludos :)
    viernes, 4 de marzo de 2016 16:02