domingo, 6 de septiembre de 2009

Comunicar formularios MDI

 

Introducción


Este post representa la continuación de Comunicar formularios de forma desacoplada

Pero a diferencia del anterior en esta oportunidad se trabajara con formularios MDI

 

Diferencias


Al hacerse uso de formularios MDI, algo que ya no podrá ser utilizado es el parámetro Owner en el método Show(), al realizar la apertura del formulario hijo.

Es por ello que será necesito hacer uso de una propiedad en el formulario hijo para salvar este inconveniente, y poder así determinar que formulario esta ejecutando la acción de apertura.

 

Primer Paso – Definición de la interfaz

A diferencia el ejemplo anterior en este oportunidad se hará uso de un valor algo mas complejo que simple texto en la comunicación, es por ello el uso de un objeto DataTable como medio de transporte de datos entre los formularios.

Aunque nada impediría que se utilices clases custom creadas por uno.

public interface IForm
{
    bool LoadDataGridView(DataTable dataTableParam);
}

En este caso la interfaz también retorna un valor que indicará si el procesamiento se realizó correctamente.

 

Segundo Paso – Definición del formulario padre

Al igual que en el anterior post el formulario padre deberá implementar una interfaz, la cual permitirá desacoplar la dependencia durante la comunicación.

public partial class Form1 : Form, IForm
{
    public Form1()
    {
        InitializeComponent();
    }

    #region IForm Members

    public bool LoadDataGridView(DataTable dataTableParam)
    {
        DataGridView1.DataSource = dataTableParam;

        return true;
    }

    #endregion


    private void Button1_Click(object sender, EventArgs e)
    {
        Form2 form2 = new Form2();

        form2.MdiParent = this.MdiParent;

        form2.Opener = this;

        form2.Show();
    }

}

Debe remarcarse algunos detalles, como es en este caso el uso de la propiedad MdiParent, que por supuesto siempre hará referencia al FormPrincipal, y que este ha sido declarado como MdiContainer

También hay que marcar la línea 26, en donde se visualiza el uso de la propiedad adicional, en este caso llamada Opener, es en esta donde se asignara el form que realizo la apertura del formulario.

 

Tercer paso – Definición del formulario hijo

Este formulario simplemente tendrá un botón que realizará el cierre de si mismo, pero durante esta operación se generarán los datos que serán pasados al formulario que se registro en la propiedad Opener.

Si bien este es un ejemplo, la misma técnica podría ser utilizada para realizar distintas acciones y pasaje de datos entre formularios.

public partial class Form2 : Form
{
    public IForm Opener { get; set; }

    public Form2()
    {
        InitializeComponent();
    }

    private DataTable LoadDataTable()
    {

        DataTable dt = new DataTable();

        dt.Columns.Add("Id");
        dt.Columns.Add("Nombre");

        for(int i=0 ; i <= 4; i++){
            DataRow row = dt.NewRow();

            row["Id"] = i;
            row["Nombre"] = String.Format("Nombre {0}", i);

            dt.Rows.Add(row);

        }

        return dt;
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    private void Form2_FormClosing(object sender, FormClosingEventArgs e)
    {
        DataTable dataTable = LoadDataTable();

        bool estadoOperacion = this.Opener.LoadDataGridView(dataTable);

        e.Cancel = !estadoOperacion;
    }

}

La línea 3 define la propiedad Opener cuya utilización se visualizo en el código del Form1, la declaración de la misma utiliza el concepto de propiedades autoimplementadas.

La interacción entre formularios retorna un resultado que el formulario hijo utilizara para saber si debe proseguir con el cierre del formulario o cancelarlo.

En este simple ejemplo el formulario padre retorna siempre un estado satisfactorio de la operación, pero podría ser utilizado para capturar errores e informarlo al formulario hijo de esta situación.

 

Conclusión


La interacción entre formulario en un entorno MDI requiere de ciertas modificaciones al uso normal de iteración entre estos, igualmente la idea básica no es afectada.

Solo la imposibilidad de definir un formulario como owner añadió cierta complejidad al ejemplo, pero fue salvada con el uso de propiedades.

 

[C#] 
[VB.NET] 

44 comentarios:

  1. esta muy bien explicado. me sirvio mucho, a un q lo apliquede otra forma.

    Zac-Nicte

    ResponderEliminar
  2. Muchas gracias de nuevo Leandro.
    Tu Blog es mi referencia en el desarrollo y nuevas ideas.
    Excelente aporte, me fue de gran ayuda

    ResponderEliminar
  3. Saludos leandro, me sirvio de mucho esa forma de comunicacion entre forms desde un MDI, he aplicado correctamente esta forma y funciona, pero tengo un problemita, por decir: al abrir un formulario hijo llamado "form1" desde MDI principal en estado MAXIMIZED, y luego al abrir otro hijo "form2" en estado NORMAL desde el "form1", el "form2" se maxima automaticamente, como podria solucionar eso.

    ResponderEliminar
  4. hola Fernando

    el tema hasta donde se es que los forms en un entorno MDI se comportan de esa forma

    quzias para evitarlo deberias abrir ese form por fuera del mdi, usando el ShowDialog() para que se vea modal

    es mas sino recuerdo mal cuando estas en un entorno mdi las opciones de ControlBox para quitarle las acciones de maximizado y minimizado no te deja hacerlo, igual podrias probar de ver si va por ese lado, quita las propiedades de MaximizeBox, pero estoy seguro que no tendra efecto

    saludos

    ResponderEliminar
  5. Hola Leandro, muchas gracias por tus aportes.
    En un entorno de pruebas la comunicacion de ambos formularios con la interfaz me funciona correctamente, pero luego cuando lo quiero aplicar en mi primer proyecto vb.net en el formulario hijo en esta linea:

    Public Property Opener() As IForm

    Me da este mensaje de error:'Opener' no puede exponer el tipo 'IForm' fuera del proyecto a través de class 'Buscar_cliente'.
    Por mas que busco, no me doy cuenta como solucionarlo. Podras guiarme para ver por donde puede venir el problema.

    Muchas gracias, saludos, Hugo.

    ResponderEliminar
  6. hola hugodoy

    la interfaz IForm donde la defienes?

    recuerda que deberia estar por fuera de la clase Buscar_cliente
    o sea a nivel de namespace es que defines la interfaz

    veras que yo alli defino un archivo IForm.vb donde defino la interfaz

    saludos

    ResponderEliminar
  7. Hola Leandro !

    La defino a nivel de espacio de nombres:
    ...
    Interface IForm
    Sub ChangeTextBoxText(ByVal text As String)
    End Interface
    ...
    Textualmente copie una similar a la de tus ejemplos.

    La implemento desde la clase pedidos_de_facturacion :
    ...
    Public Class PEDIDOS_DE_FACTURACION
    '***
    Implements IForm
    ' ASIGNO LA CONEXION POR AHORA FIJA EN EL \DOCUMENTS\SCC.MDF
    Private Const cs As String = "Data Source=.\SQLEXPRESS;" & _
    "AttachDbFilename=C:\Users\hugodoy\Documents\SCC.mdf;" & _
    "Integrated Security=True;" & _
    "Connect Timeout=30;" & _
    "User Instance=True"

    Private cont As Integer = 0
    ...

    Por ultimo en el metodo button1_click llamo a la clase buscar_cliente:
    ...

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click


    Dim form As New Buscar_cliente()
    form.MdiParent = Me.MdiParent
    form.Show()
    End Sub

    ...
    Sigo sin darme cuenta por donde esta el inconveniente.

    Saludos, Hugo.

    ResponderEliminar
  8. Perdon, lo deje asi porque me daba el error !!!

    El metodo button1_click llamo a la clase buscar_cliente:
    ...
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim form As New Buscar_cliente()
    form.MdiParent = Me.MdiParent
    form.Show()

    ' ' ' 'Dim form As New Buscar_cliente
    ' ' ' 'form.MdiParent = Me.MdiParent
    ' ' ' 'form.Opener = CType(Me, IForm)
    ' ' ' 'form.Show(Me)

    End Sub

    ...

    Saludos, Hugo.

    ResponderEliminar
  9. hola hugodoy

    la explicacion muy bien, pero te falto mencionar cual es el problema, si hay un error cual es ? que mensaje estas recibiendo

    veo ademas que en ningun momento defines la propiedad "Opener", veras en el ejmeplo que debes definir una propiedad en el form hijo.

    ademas PEDIDOS_DE_FACTURACION que seria ? es un formulario ? porque en este implementa la interfaz pero despues usa
    Dim form As New Buscar_cliente()

    como se puede interpretar eso, define la IForm en una clase pero despues instancias otra distinta

    creo que el problema es que estas mezclando las cosas

    saludos

    ResponderEliminar
  10. Hola Leandro


    El mensaje de error es:
    'Opener' no puede exponer el tipo 'IForm' fuera del proyecto a través de class 'Buscar_cliente'

    Me lo da el compilador, no en tiempo de ejecucion.



    Pedidos_de_facturacion es un Formulario



    Button1_clic (como me daba error, "probando" le mande cualquiera) en realidad lo que estaba con comentarios era lo corresponde. Cuando me da el error, el codigo me queda asi:
    ...
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    ' ' ' 'Dim form As New Buscar_cliente()
    ' ' ' ' form.MdiParent = Me.MdiParent
    ' ' ' ' form.Show()

    Dim form As New Buscar_cliente
    form.MdiParent = Me.MdiParent
    form.Opener = CType(Me, IForm)
    form.Show(Me)

    End Sub
    ...
    Asi estaria bien ?


    Una vez mas muchas gracias por tus aportes.

    Saludos, Hugo.

    ResponderEliminar
  11. hola hugodoy

    pero porque hace esto:

    form.Opener = CType(Me, IForm)

    el opener deberia ser el propio formulario

    form.Opener = Me

    solo asignas el Me que es el form que realiza la apertura
    luego dentro del form hijo es que casteas al IForm

    saludos

    ResponderEliminar
  12. Leandro excelente ejemplo como siempre, tengo un consulta. Tengo un Form1 en el cual tengo un textbox codproducto este realiza la busqueda del producto si el mismo cuenta con mas de una unidad de medida debe abrirse el form2 con las unidades de medida existentes para ese codigo de producto. La consulta esencial es en el primer form1 hago el query con el codproducto para luego enviar esos datos al form2?. Agradeceria tu orientacion.

    ResponderEliminar
  13. Hola Leandro, Feliz Año.
    Solo para comentarte que el error:
    'Opener' no puede exponer el tipo 'IForm' fuera del proyecto a través de class 'Buscar_cliente' ya no me dio mas.
    Fijandome en la ayuda en linea: http://msdn.microsoft.com/es-es/library/90htzsex(VS.90).aspx
    vi que podia ser un problema de tipo de variable, entonces hice este pequeño cambio que me dio resultados:
    ...
    Public Class Buscar_cliente
    '***
    Private formOpener As IForm

    '*** CAMBIE EL TIPO PUBLIC A FRIEND !
    Friend Property Opener() As IForm

    Get
    Return formOpener
    End Get
    Set(ByVal value As IForm)
    formOpener = value
    End Set
    End Property
    ...
    y ya no me dio mas el error.
    Saludos, Hugo.

    ResponderEliminar
  14. hola hugodoy

    que bien que pudiste resolver el problema

    pero me suena raro el error que comentas, con Public deberia haber funcionado, salvo que la Interface este definida de forma que esta se exponga distinta

    has validado que la definicion de IForm se Public

    Public Interface IForm

    End Interface


    saludos

    ResponderEliminar
  15. hola Richard

    estas equivocando el camino, no deberias ejecutar una query en el form1 para luego pasar el resultado al form2

    deberias tomar la minima informacion que necesitas para poder realizar la query, quizas pasar alguna informacion puntual y esto pasar al otro formulario

    para ahi si realizar la query en el form2

    saludos

    ResponderEliminar
  16. Hola Leandro

    Si, perfecto, tenes razon.

    Defini la Interface como Publica
    ...
    Public Interface IForm
    Sub ChangeTextBoxText(ByVal text As String)
    End Interface
    ...

    y volvi atras el Friend a Public
    ...
    Public Class Buscar_cliente
    '***
    Private formOpener As IForm

    '*** VOLVI A CAMBIAR EL TIPO FRIEND POR PUBLIC!
    Public Property Opener() As IForm

    Get
    Return formOpener
    End Get
    Set(ByVal value As IForm)
    formOpener = value
    End Set
    End Property
    ...

    Gracias !

    Saludos, Hugo.

    ResponderEliminar
  17. hola leandro gracias por tus aportes

    veras intento combinar tus ejemplo de "C# comunicar formularios MDI" y "[DataGridView] Parte 3 – Pasaje de información entre grillas en distintos formulario" el problema que tengo es que intento combinar tus codigos para poder comunicar formularios para poder pasar informacion entre DataGridViews pero al abrir el formulario donde paso me sale este mensaje de error "Un formulario que no es de nivel superior no se puede mostrar como un cuadro de diálogo modal. Quite el formulario de los formularios primarios que lo contengan antes de llamar a Show"
    es que intento hacer esto para crear un sistema para pasar datos entre DataGridView pero con un formulario como el de tu ejemplo

    me podrias ayudar gracias y si lo necesitas te paso los codigos del sistema

    ResponderEliminar
  18. hola Ascencio

    no entendi muy bien el problema, pareciera como que quiere usar un formulario MDI pero como modal, o sea abriendolo por medio del ShowDialog(), pero en un entorno mdi no puede hacer esto

    no se si es este el problema, porque no me quedo claro

    saludos

    ResponderEliminar
  19. Saludos, necesito una ayudita urgente.
    Te explico; tengo el formulario MDI el principal con una barra de tareas con una opcion de imprimir, esta opcion imprimir debe ejecutar el metodo de imprimir que le creo en los formularios hijos del formulario que esta activo en ese momento.
    Como haria esto. De antemano muchas gracias.

    ResponderEliminar
  20. hola,, excelente ejemplo,, una pregunta, como hago para utilizar un metodo que esta en un formulario que no es el padre

    ResponderEliminar
  21. hola Jdl

    pero existe algun medio de conexion entre estos formularios? o alguna forma en la cual puedas tomar la instancia del form que quieres invocar

    con la instancia puedes invocar directo, sin la instancia podrias usar

    [Winforms] Singleton - Pasar datos entre formulario

    la parte de evento para realizar una accion en el otro form

    saludos

    ResponderEliminar
  22. las dos forms se inicializan desde una ventana principal con la propiedad mdiContanier lo que quiero es que al ingresar un registro desde el form1 este actualice la lista de registros que hay en el form2,, pero el form2 no es hijo del form 1 ,, muchas gracias por tu atencion,

    ResponderEliminar
  23. hola Jdl

    pero entocnes quiere decir que desde el form principal puede teenr las instancias de estos dos form que se abren independientemente

    porque si es asi podrias pasar la instancia de un form al otro para que luego se accedan entre ellos

    o sea si usaste
    Form2 frm2 = nre Form2();
    frm2.Show();

    luego en otra accion podrias usar

    Form3 frm3 = nre Form3();
    frm3.FormRelacionado = frm2;
    frm3.Show();

    por puesto crea una propiedad en el Form3 que sea del tipo form para poder asignar la instancia del mismo

    saludos

    ResponderEliminar
  24. Leandro buen dia... Te felicito por tu Blog y ejemplos, me han sido de mucha utilidad...

    Actualmente estoy queriendo implementar tu codigo pero me marca un error...

    Inconsistent accessibility: property type 'appVarias...' is less accessible than property 'appVarias.../Form.Opener'

    En el siguiente Link te envio el codigo que modifique para agregar tu ejemplo.
    https://dl.dropbox.com/u/28531869/%5Bcsharp%5DWinDataGridViewPasajeInformacion.rar

    Como puedes observar, estoy fusionando dos ejemplos que tu haz desarrollado, para darme a entender lo que deseo hacer te lo explico a continuación:

    Tengo un Form Principal, Form1 y Form2.
    El principal es MDI, y se llama al Form1, en form 1 tengo un grid tal como en tu ejemplo, desde este form se llama al form2 que tengo un datagridview que lo cargo desde la BD con datos varios, lo que quiero es seleccionar varias filas del grid de Form2, y al presionar un boton del form2, este envie los datos de las filas seleccionadas al form1, de ahi yo trabajaria con los datos del form1.

    Espero me puedas apoyar con la solucion a mi problema, te lo agradezco de antemano.

    Saludos, desde Honduras.

    Moisés.

    ResponderEliminar
  25. hola Moisés

    has validado la declaracion de la propiedad, verificaste que sea definida como "public" ?


    saludos

    ResponderEliminar
  26. Excelente Leandro!!!

    Eso era, es que no lo habia declarado como publico, ya me funciona como queria.

    Saludos desde Honduras.
    Moisés Banegas

    ResponderEliminar
  27. Hola!! Estoy siguiendo tu ejemplo de Comunicar formularios de forma desacoplada estoy trabajando en visual basic 2010, pero al momento de hacer

    Dim _formInterface As IForm = CType(Me.Owner, IForm)

    _formInterface.ChangeTextBoxText(TextBox1.Text)

    me manda el siguiente error : Referencia a objeto no establecida como instancia de un objeto

    Segui tu ejemplo paso a paso,odrias decirme que estoy haciendo mal .

    Saludos

    ResponderEliminar
  28. hola Marisel

    pero validad que el form padre este implementando la interfaz de forma correcta ?

    ademas valida que al abrir el form hijo uses

    Dim frm As New FormHijo
    frm.Show(Me)

    ese Me que le pasas en el Show define el Owner

    saludos

    ResponderEliminar
  29. Hola leandro, cuando creo este evento dentro del datagridview


    if (e.RowIndex < 0 || e.ColumnIndex != dgvEstudios.Columns["btnEliminar"].Index) { return; }
    else
    {
    Int32 taskID = (Int32)dgvEstudios[0, e.RowIndex].Value;
    MessageBox.Show(" " + taskID.ToString());
    }


    me arroja un error por que el valor de la celda no lo está capturando me devuelve un NULL, pero este mismo evento yo lo probé en un datagridview que está en un formulario que no posee mdiParent y me funcionó bien.

    si sabes a que se debe este problema agradecería tu ayuda
    de antemano muchas gracias

    ResponderEliminar
  30. el evento es el CellClick
    private void dgvEstudios_CellClick(object sender, DataGridViewCellEventArgs e)
    {}

    ResponderEliminar
  31. hola blogluis

    como es eso de MdiParent porque no quedo claro

    sera que estas accediendo a un grid que esta en otro formulario ?

    que pasa si usas

    dgvEstudios.Rows[e.RowIndex].Cells[0].Value

    saludos

    ResponderEliminar
  32. Excelente tutorial!!! Use el de VB.net Gracias

    ResponderEliminar
  33. Saludos, como le puedo hacer para que en un solo "form padre" se puedan habrir dos formularios distintos pero que sean hijos del mismo padre?

    Form1 (padre)
    Form2 (hijo), Form3 (hijo)

    aclarando que el diseño de ambos hijos es diferente y para diferente funcion.

    ResponderEliminar
  34. hola ROBERTO

    desde el Form2 cuando abres el form3 usarias

    Form3 frm3 = new Form3();
    frm3.MdiParent = this.MdiParent;
    frm3.Show();

    o sea asignas al form3 el mismo mdiparent que tiene el form2

    saludos

    ResponderEliminar

  35. Hola Leonardo, es posible que el form2 sea un modal??

    Saludos

    ResponderEliminar
  36. hola MatiCris

    si estas en un ambiente MDI y ese forma parte del mdi container no puede mostrarlo como modal

    para poder hacerlo no deberias definir el MdiParent

    saludos

    ResponderEliminar
  37. Gracias por tu respuesta Leandro funciono a la perfección.

    Saludos

    ResponderEliminar
  38. Gracias por el aporte, me funcionó bien.

    ResponderEliminar
  39. Gracias por tu aporte Leandro, tengo una grid con los nombre de columnas definidas ejemplo: articulo,nombre,precio.... al utilizar tu explicacion me agrega las columnas, como hago para que se inserten en las mismas columnas... Gracias

    ResponderEliminar
    Respuestas
    1. hola
      No se si entendi del todo el problema, pero si el grid esta enlazado a datos quizas debas agregar a este los nuevos datos que provienen del otro forma y al final volver asignar el DataSource para que actualice. Quzias debas usar linq para ubicar que row actualizar en luegar de udar un Add() directo
      saludos

      Eliminar
  40. Gracias Leandro, ya solucione el problem utlize lo siguiente dataGridView7.AutoGenerateColumns = false;
    dataGridView7.DataSource = dataTableParam;
    dataGridView7.Columns[0].DataPropertyName = "Aticulo";

    Ahora tengo otro problema, tengo un botton que me envia a limpiar el datagridviewer con esta sentencia .. dataGridView7.Rows.Clear(); pero me sale el siguiente error "No se controlo Argument Exception ...No se puede borrar esta lista." que tengo que hacer ....Gracias por la ayuda

    ResponderEliminar
  41. Leandro me hago entender mejor, en el datagridviewer del formulario PADRE donde muestro los datos que he traido del datagridviewer HIJO no me los permite borrar con rows.clear()...
    me sale el siguiente error "No se controlo Argument Exception ...No se puede borrar esta lista." ...Gracias.

    ResponderEliminar
  42. Gracias muy util, me a servido de mucho

    ResponderEliminar