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 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.
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.
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.
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.