Cómo pasar datos a un formulario
Por Enrique Martínez Montejo
Última revisión: 21/09/2010
 

Muchos usuarios de Visual Basic piensan que un formulario de una aplicación Windows Forms es diferente a otra clase cualquiera de las muchas existentes en el marco de trabajo de .NET. Piensan en él como si fuera una clase especial por el mero hecho que se puede diseñar. Y esa digamos que es la única diferencia más notable existente entre un formulario Windows Forms y otra clase cualquiera, como bien pudiera ser la clase DataTable o la clase TextBox, por poner unos ejemplos.

Un formulario cualquiera de nuestra aplicación es una clase que hereda de la clase System.Windows.Forms.Form, y como cualquier otra clase que se puede instanciar, a parte de disponer de un constructor predeterminado, también admite la sobrecarga de constructores, por lo que podemos tener varios constructores con diferentes firmas, entendiendo por firma las distintas listas de parámetros que admite un procedimiento.

Si disponemos de un formulario que actuará de cuadro de diálogo para mostrar y modificar, por ejemplo, los datos de un Cliente concreto, o necesitamos el valor de una fecha para mostrar las facturas que han sido emitidas en dicha fecha, deberemos de tener un mecanismo para que el formulario en cuestión acceda a dicha información.

En versiones de Visual Basic anteriores a .NET, la única manera que existía de disponer de valores generales entre los formularios, clases y restantes módulos de la aplicación, consistía en declarar en un módulo BAS variables públicas (Public) o amigables (Friend), lo que se conocía como variables globales a la aplicación. Si bien era un mecanismo fácil de acceso a los valores, también presentaba numerosos problemas difíciles de detectar y de depurar, sobre todo si no se tenía un control estricto sobre los valores que podían almacenar las variables globales, y en qué parte de la aplicación se modificaba su valor. Con la llegada de la plataforma .NET, todo esto pasó a ser historia.

El constructor de la clase

El construtor de una clase es simplemente el procedimiento New, el cual se ejecutará cuando se desee crear una nueva instancia de una clase cualquiera, incluida una nueva instancia de un formulario cualquiera de nuestro proyecto.

Cuando en el código fuente ejecutamos

Dim frm As New NombreFormulario()

estamos creando una nueva instancia de la clase NombreFormulario, lo que significa que se ejecutará el código contenido en el método New del formulario o clase llamado NombreFormulario:

Public Class NombreFormulario

    Public Sub New ()

    End Sub

End Class

Este sería el constructor predeterminado de una clase cualquiera que herede de la clase base Form, o si lo prefiere, de un formulario de nuestro proyecto. Pero ocurre que el diseñador de Visual Studio no muestra el constructor predeterminado, al menos si está diseñando una aplicación de Visual Basic .NET.  Por ese motivo es por lo que entiendo que los usuarios de Visual Basic ven a la clase Form de una manera distinta a una clase Cliente o una clase Proveedor, al menos en cuanto a la estructura de la clase se refiere.

El hecho de que el diseñador de Visual Studio no muestre el constructor por defecto, no significa que no exista, ya que éste se crea automáticamente cuando compilemos nuestro proyecto. Pero tampoco, nada impide que nosotros no podamos crear explícitamente el constructor predeterminado (el método New sin parámetros). Pero si lo hacemos, no hay que olvidar efectuar una llamada al procedimiento InitializeComponent, cosa que hace automáticamente el diseñador de Visual Studio cuando escribimos Public Sub New y pulsamos la tecla Enter, tal y como se muestra a continuación:

Public Sub New()

    ' Llamada necesaria para el diseñador.
    InitializeComponent()

    ' Agregue cualquier inicialización después de la llamada a InitializeComponent().

End Sub

El procedimiento InitializeComponent es un método que, desde la versión 2005 de Visual Basic .NET, se encuentra en el archivo Designer del formulario, archivo que por defecto se encuentra oculto en la ventana Explorador de soluciones, y que podrá editar si previamente pulsa el botón Mostrar todos los archivos existente en la citada ventana. En dicho procedimiento se incluyen los valores de las propiedades que previamente se han definido en la ventana Propiedades, tanto para el propio formulario como para cada uno de sus controles. Si no sabe muy bien lo que está haciendo, será mejor que no edite directamente el procedimiento InitializeComponent, y que sea a través de la ventana Propiedades donde modifique las propiedades del formulario o las de cualquier control incluido en el mismo.

Normalmente, tras la llamada al método InitializeComponent escribiremos las instrucciones que deseamos que se ejecuten cuando se crea una nueva instancia de la clase. Por ejemplo, podemos utilizar el procedimiento New para iniciar los valores de los campos o variables declaradas a nivel de la propia clase, o bien para ejecutar cualquier otra operación requerida para  la correcta inicialización de la clase o del formulario.

Por último indicar que podremos crear una nueva instancia de una clase, siempre y cuando el procedimiento Sub New de la misma tenga un modificador de acceso válido o de visibilidad apropiada:

En ningún caso podremos crear una nueva instancia de una clase si el modificador de acceso de su constructor es privado (Private), salvo que se cree la instancia desde dentro de otros procedimientos incluidos en la misma clase.

Clases abstractas

En el apartado anterior he comentado los modificadores válidos que debe tener el procedimiento o constructor New para poder crear una nueva instancia de la clase, por lo que en principio, y dependiendo del modificador especificado, así podremos crear o no una nueva instancia de una clase. Pero puede ocurrir que el diseñador de una clase desee que la misma sólo se utilice como clase base para poder crear nuevas clases derivadas de ella.

Se dice que una clase es abstracta o virtual cuando sólo se puede utilizar como una clase base, por tanto, no se puede crear directamente un objeto o instancia a partir de ella. En Visual Basic .NET se define una clase abstracta especificando la palabra clave MustInherit en la declaración de la propia clase:

Public MustInherit Class NombreClase

    Public Sub New()

    End Sub

End Class

En éste caso no podremos crear una nueva instancia de la clase NombreClase, aunque el modificador de acceso del procedimiento New sea público, es decir, no podremos ejecutar

Dim frm As New NombreClase()

ya que en tiempo de diseño o compilación, obtendremos la siguiente excepción: 'New' no se puede usar en una clase declarada como 'MustInherit'.

No es muy frecuente que las clases que definen a un formulario se marquen con la palabra clave MustInherit, aunque nada impide que así se haga, sobre todo si tenemos diseñado un formulario que deseamos utilizarlo como clase base para que pueda ser heredado por otros formularios. No obstante le advierto que, si hereda un formulario de otro formulario base marcado como abstracto, le va resultar difícil abrir el formulario heredado con el diseñador de Visual Studio, ya que a la hora de abrirlo obtendría el siguiente mensaje de error:  El diseñador debe crear una instancia de tipo 'NombreEnsamblado.NombreFormularioBase', pero no se puede crear porque el tipo se ha declarado como abstracto.

En éste supuesto, no le quedaría más remedio que diseñar manualmente el formulario heredado del formulario marcado como abstracto.

Constructores sobrecargados

Se dice que un método se encuentra sobrecargado cuando existen varias declaraciones con el mismo nombre de procedimiento pero con distintas listas de parámetros en su firma.  De esta manera podemos tener dos procedimientos para obtener un objeto Cliente, pero uno lo obtendremos mediante el nombre del Cliente, y otro mediante su identificador, tal y como se muestra a continuación:

Public Overloads Function ObtenerCliente(ByVal nombre As String) As Cliente

End Function

Public Overloads Function ObtenerCliente(ByVal id As Integer) As Cliente

End Function

Fíjese que los dos procedimientos se diferencian en el tipo de dato que admiten cada uno de sus parámetros. El primero admite un tipo de dato String, mientras que el segundo admite un Integer. Si ambos procedimientos tuvieran idénticos tipos de datos, el compilador no sabría qué procedimiento se tendría que ejecutar cuando se invoque una llamada al mismo, de ahí que se obtenga la correspondiente excepción en tiempo de diseño. El único requisito para sobrecargar un procedimiento es que tenga un número distinto de parámetros, y a igual número de parámetros, que el orden de los mismos tenga diferentes tipos de datos.

Al igual que se sobrecarga un procedimiento, también se puede sobrecargar el constructor de una clase, de ésta manera, junto con el constructor predeterminado de una clase, podemos tener varios procedimientos Sub New con diferentes listas de parámetros, de ésta manera, a la hora de crear una nueva instancia de la clase, podremos pasar a la misma los valores necesarios y oportunos para que se inicialice correctamente:

Public Sub New()

End Sub

Public Sub New(ByVal nombre As String)

End Sub

Public Sub New(ByVal id As Integer)

End Sub

Public Sub New(ByVal nombre As String, ByVal id As Integer)

End Sub

Por último indicar que se utiliza la palabra clave Overloads para especificar nuestra intención de sobrecargar explícitamente un procedimiento. No es obligatoria su utilización, pero si se define en un procedimiento sobrecargado, se tiene que definir en todos aquellos con idéntico nombre. La única excepción es que no se puede especificar la palabra clave Overloads para sobrecargar el constructor de una clase.

Un ejemplo para pasar datos a un formulario

Vamos a ver un ejemplo práctico que muestra cómo pasar datos a un formulario. Para ello me voy a basar en una consulta que aparece con demasiada frecuencia en el foro en español de Visual Basic .NET: cómo pasar a un formulario los datos existentes en una fila de un control DataGridView.

Imagine que tiene un formulario donde muestra una lista de Clientes en un control DataGridView, y desea que al efectuar doble clic sobre una fila cualquiera, se muestre un formulario con los datos del Cliente seleccionado, tal y como se muestra en la siguiente imagen:

En el cuadro de diálogo Datos del Cliente, le gustaría modificar los datos del registro seleccionado, de tal manera que al cerrar dicho cuadro de diálogo, se actualice la información en el control DataGridView.

Al disponer en nuestro proyecto de un formulario dedicado a cumplir una misión en concreto (mostrar y actualizar los datos de un Cliente), la pregunta que se tiene que hacer sería ¿qué datos necesito para mostrar la información? La respuesta es bien sencilla: los datos del Cliente seleccionado, datos que el control DataGridView nos lo brinda en formato de objeto DataGridViewRow. Pues éste sería el parámetro que le tiene que pasar al constructor del formulario Datos del Cliente, el objeto DataGridViewRow donde el usuario ha efectuado doble clic.

En el formulario Listado de Clientes, éste sería el código que tendría que ejecutar:

Public Class Form1

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load

        ' Acoplamos al formulario el control DataGridView
        DataGridView1.Dock = DockStyle.Fill

        ' El control DataGridView será de sólo lectura
        DataGridView1.ReadOnly = True

        Try
            ' Rellenamos el control DataGridView.
            DataGridView1.DataSource = GetData()

        Catch ex As Exception
            MessageBox.Show(ex.Message)

        End Try

    End Sub

    Private Sub DataGridView1_CellDoubleClick( _
        ByVal sender As Object, _
        ByVal e As DataGridViewCellEventArgs) Handles DataGridView1.CellDoubleClick

        ' Referenciamos la fila.
        '

        Dim row As DataGridViewRow = DataGridView1.CurrentRow

        Using frm As New Form2(row)

            ' Mostramos el formulario Form2.
            '

            frm.ShowDialog()

        End Using

    End Sub

    Private Function GetData() As DataTable

        ' Creamos el objeto DataTable.
        '

        Dim dt As New DataTable("Clientes")

        ' Añadimos las columnas.
        '

        dt.Columns.Add(New DataColumn("IdCliente", Type.GetType("System.Int32")))
        dt.Columns.Add(New DataColumn("Nombre", Type.GetType("System.String")))
        dt.Columns.Add(New DataColumn("NIF", Type.GetType("System.String")))
        dt.Columns.Add(New DataColumn("Domicilio", Type.GetType("System.String")))
        dt.Columns.Add(New DataColumn("Poblacion", Type.GetType("System.String")))
        dt.Columns.Add(New DataColumn("Provincia", Type.GetType("System.String")))
        dt.Columns.Add(New DataColumn("CodPostal", Type.GetType("System.String")))

        ' Creamos la clave principal del objeto DataTable.
        '

        Dim primaryKey() As DataColumn = {dt.Columns(0)}
        dt.PrimaryKey = primaryKey

        ' Añadimos cinco registros al objeto DataTable.
        '

        Dim row As DataRow = dt.NewRow()
        Dim data() As Object = {43001, "JOSE DELGADO RUIZ", "27029N", "CL CONSOLACION, 23", "JAEN", "JAEN", "23008"}
        row.ItemArray = data
        dt.Rows.Add(row)

        row = dt.NewRow()
        data = {43002, "CARMEN GARRIDO LOPEZ", "89234X", "CL GARCIA VAZQUEZ, 89", "LINARES", "JAEN", "23700"}
        row.ItemArray = data
        dt.Rows.Add(row)

        row = dt.NewRow()
        data = {43003, "RODRIGO GOMEZ CALLEJON", "14005L", "CL JULIO GARRIDO, 93", "MANCHA REAL", "JAEN", "23100"}
        row.ItemArray = data
        dt.Rows.Add(row)

        row = dt.NewRow()
        data = {43004, "FELIPE DIAZ BENITEZ", "82993Y", "CL FERNANDEZ FERNANDEZ, 134", "JAEN", "JAEN", "23005"}
        row.ItemArray = data
        dt.Rows.Add(row)

        row = dt.NewRow()
        data = {43005, "ROSARIO CANO MARTIN", "23344P", "AV CURRO SERRANO, 253", "JAEN", "JAEN", "23009"}
        row.ItemArray = data
        dt.Rows.Add(row)

        ' Devolvemos el objeto DataTable.
        '

        Return dt

    End Function

End Class

El formulario Form1 simplemente se encargará de rellenar el control DataGridView con los datos de una hipotética tabla de Clientes, y al hacer doble clic sobre una de sus filas, se llamará al formulario Form2 pasándole el objeto DataGridViewRow seleccionado.

El listado del formulario Form2 es el siguiente:

Public Class Form2

    Private m_row As DataGridViewRow

    Public Sub New(ByVal row As DataGridViewRow)

        InitializeComponent()

        ' En el caso improbable de que el valor sea Nothing,
        ' devolvemos la excepción al procedimiento llamador.
        '
       
If (row Is Nothing) Then _
            Throw New ArgumentNullException()

        ' Asignamos el valor del campo con el valor
        ' del parámetro pasado al constructor.
        '
       
m_row = row

    End Sub

    Private Sub Form2_Load(ByVal sender As Object, _
        ByVal e As EventArgs) Handles Me.Load

        ' Hacemos de sólo lectura el valor del campo IdCliente,
        ' ya que forma parte de la clave principal de la tabla.
        '

        TextBox1.ReadOnly = True
        TextBox1.TabStop = False

        Me.CancelButton = btnCancel

        ' Rellenamos los controles TextBox
        '
       
Dim value As Object = m_row.Cells(0).Value
        If (value IsNot DBNull.Value) Then
            TextBox1.Text = CStr(value)
        End If

        value = m_row.Cells(1).Value
        If (value IsNot DBNull.Value) Then
            TextBox2.Text = CStr(value)
        End If

        value = m_row.Cells(2).Value
        If (value IsNot DBNull.Value) Then
            TextBox3.Text = CStr(value)
        End If

        value = m_row.Cells(3).Value
        If (value IsNot DBNull.Value) Then
            TextBox4.Text = CStr(value)
        End If

        value = m_row.Cells(4).Value
        If (value IsNot DBNull.Value) Then
            TextBox5.Text = CStr(value)
        End If

        value = m_row.Cells(5).Value
        If (value IsNot DBNull.Value) Then
            TextBox6.Text = CStr(value)
        End If

        value = m_row.Cells(6).Value
        If (value IsNot DBNull.Value) Then
            TextBox7.Text = CStr(value)
        End If

    End Sub

    Private Sub btonOK_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnOK.Click

        ' Establecemos el valor de las distintas
        ' celdas del objeto DataGridViewRow.
        '
       
m_row.Cells(1).Value = TextBox2.Text
        m_row.Cells(2).Value = TextBox3.Text
        m_row.Cells(3).Value = TextBox4.Text
        m_row.Cells(4).Value = TextBox5.Text
        m_row.Cells(5).Value = TextBox6.Text
        m_row.Cells(6).Value = TextBox7.Text

        ' Cerramos el cuadro de diálogo
        DialogResult = DialogResult.OK

    End Sub

    Private Sub btnCancel_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnCancel.Click

        ' Cerramos el cuadro de diálogo
        DialogResult = DialogResult.Cancel

    End Sub

End Class

Observe cómo en el constructor de la clase Form2 se asigna el valor del parámetro al campo m_row de la clase. Es toda la información que necesita el formulario.

Como se está trabajando con un tipo de dato por referencia, en realidad se le está pasando al constructor un puntero a una instancia de la clase DataGridViewRow, por tanto, los cambios que se efectúen en el segundo formulario cuando se pulse el botón Aceptar, se verán reflejados automáticamente en el control DataGridView existente en el primer formulario. En el supuesto de Cancelar la operación se cerrará el formulario sin más sin que afecte a los valores actuamente existentes en la instancia de la clase DataGridViewRow pasada al segundo formulario.

 

Otros enlaces de interés:

Índice de la colección de ejemplos de las clases del marco de trabajo de .NET


Enrique Martínez Montejo - 2010

NOTA: La información contenida en este artículo, así como el código fuente incluido en el mismo, se proporciona COMO ESTÁ, sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo explicado, recomendado o sugerido en el presente artículo.

NOTE: The information contained in this article and source code included therein, is provided AS IS without warranty of any kind, and confers no rights. You assume any risk to implement, use or run it explained, recommended or suggested in this article.