Introducción
Es muy común cuando se desarrolla con formularios tener la necesidad de comunicarlos de pasar información entre ellos, pudiendo trabajar con la selección de ciertos datos, o especificar algún valor que se asignara en algún control, etc
Este articulo intentara explicar cuales son las mejores formas de realizar estas operaciones.
En artículos previos se ha comentado como pasar información de un formulario hijo al padre, o aquel que realizo la apertura.
En esta oportunidad se atacara la comunicación del padre al hijo, o sea se pasara información al momento de realizar la apertura del formulario.
Existen dos técnicas que pueden aplicarse en este caso:
- haciendo uso del constructor del formulario
- por medio de propiedades
1 – Uso del Constructor del formulario
Una de las opciones pasa pasar información entre formularios es por medio del constructor.
Debe tenerse en cuenta que en .net los formularios son en definitiva clases, por lo tanto puede aplicarse los mismo conceptos orientados a objetos.
EJEMPLO 1
Empezaremos por un ejemplo simple, en donde solo se pasara un valor del formulario denominado “Principal”, a otro formulario denominado “Detalle” que tomara este valor y lo mostrara en un TextBox.
Formulario Detalle
[C#]
public partial class frmDetalle : Form
{
private int? idbusqueda = null;
public frmDetalle()
{
InitializeComponent();
}
public frmDetalle(int idbusqueda) : this()
{
this.idbusqueda = idbusqueda;
}
private void frmDetalle_Load(object sender, EventArgs e)
{
if(idbusqueda.HasValue)
txtIdBusqueda.Text = Convert.ToString(idbusqueda);
}
}
[VB.NET]
Public Partial Class frmDetalle
Inherits Form
Private _idbusqueda As System.Nullable(Of Integer) = Nothing
Public Sub New()
InitializeComponent()
End Sub
Public Sub New(idbusqueda As Integer)
Me.New()
Me._idbusqueda = idbusqueda
End Sub
Private Sub frmDetalle_Load(sender As Object, e As EventArgs)
If _idbusqueda.HasValue Then
txtIdBusqueda.Text = Convert.ToString(_idbusqueda)
End If
End Sub
End Class
En este código se puede apreciar como se ha creado un nuevo constructor en la clase del formulario agregando al mismo un parámetro en donde se indica el id que debe trabajarse.
Un punto importante a destacar es que al pasar la información en el constructor no se realice la asignación directamente en un control del formulario, sino que recomendable hacer uso variable privadas para contener valor que luego en el evento Load o en algún otro evento se haga efectiva la asignación si es necesaria.
Esto es así porque en la etapa de construcción de la clase del formulario los controles aun no están inicializados, por lo tanto una asignación en ese punto puede traer problemas.
En el caso de este ejemplo se veras que la variable privada ha sido declarada para soportar valores nulos, se realizo de esta forma ya que se ha dejado el constructor del formulario sin parámetros, en caso de que no quiera especificarse valor de búsqueda al momento de instanciar el formulario.
En caso de darse la situación en que sea necesario usar el formulario sin pasar un valor determinado, esta situación es verificada en el evento Load y así determinar la asignación un valor a la variable o no.
Formulario Principal
[C#]
public partial class frmPrincipal : Form
{
public frmPrincipal()
{
InitializeComponent();
}
private void btnBuscar_Click(object sender, EventArgs e)
{
int idbusqueda = Convert.ToInt32(txtBusqueda.Text);
frmDetalle form = new frmDetalle(idbusqueda);
form.Show();
}
}
[VB.NET]
Public Partial Class frmPrincipal
Inherits Form
Public Sub New()
InitializeComponent()
End Sub
Private Sub btnBuscar_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim idbusqueda As Integer = Convert.ToInt32(txtBusqueda.Text)
Dim form As New frmDetalle(idbusqueda)
form.Show()
End Sub
End Class
El formulario denominado Padre o Principal encargado de hacer la llamada y de pasa el valor al formulario Hijo es bastante simple, solamente cuenta con un botón el cual al ejecutarse crea una instancia del formulario a invocar pasándole allí mismo el valor que el usuario ingreso en el TextBox, y por ultimo invoca al método Show() para que se desplegué el formulario.
| [C#]
|
[VB.NET]
|
EJEMPLO 2
En este punto aplicaremos el mismo concepto que el ejemplo anterior solo que lo haremos algo mas complejo.
No solo vamos a enviar como valor al segundo formulario un simple dato, en este caso será toda una colección de ítems seleccionados en un DataGridView,
Formulario Detalle
[C#]
public partial class frmDetalle : Form
{
private int _maxPrecio;
private List<DataRow> _listDataRow = null;
private List<Productos> _listProducto = null;
private frmDetalle()
{
InitializeComponent();
}
public frmDetalle(List<DataRow> items)
: this(items, 3200)
{
}
public frmDetalle(List<Productos> items)
: this(items, 3200)
{
}
public frmDetalle(List<DataRow> items, int maxPrecio)
: this()
{
this._listDataRow = items;
this._maxPrecio = maxPrecio;
}
public frmDetalle(List<Productos> items, int maxPrecio)
: this()
{
this._listProducto = items;
this._maxPrecio = maxPrecio;
}
private void frmDetalle_Load(object sender, EventArgs e)
{
//
// Cargo los items provenientes del constructor con DataRows
//
if (_listDataRow != null)
{
foreach (DataRow row in _listDataRow)
{
dtgDetalles.Rows.Add(row.ItemArray);
}
}
//
// Cargo los items provenientes del constructor
// con entidades de la clase Producto
//
if (_listProducto != null)
{
dtgDetalles.AutoGenerateColumns = false;
dtgDetalles.DataSource = _listProducto;
}
}
private void dtgDetalles_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (dtgDetalles.Columns[e.ColumnIndex].Name == "Precio")
{
if (Convert.ToInt32(e.Value) > _maxPrecio)
dtgDetalles.Rows[e.RowIndex].DefaultCellStyle.ForeColor = Color.Red;
}
}
public class Productos
{
public int IdProducto { get; set; }
public string Descripcion { get; set; }
public int Precio { get; set; }
}
}
[VB.NET]
Public Partial Class frmDetalle
Inherits Form
Private _maxPrecio As Integer
Private _listDataRow As List(Of DataRow) = Nothing
Private _listProducto As List(Of Productos) = Nothing
Private Sub New()
InitializeComponent()
End Sub
Public Sub New(items As List(Of DataRow))
Me.New(items, 3200)
End Sub
Public Sub New(items As List(Of Productos))
Me.New(items, 3200)
End Sub
Public Sub New(items As List(Of DataRow), maxPrecio As Integer)
Me.New()
Me._listDataRow = items
Me._maxPrecio = maxPrecio
End Sub
Public Sub New(items As List(Of Productos), maxPrecio As Integer)
Me.New()
Me._listProducto = items
Me._maxPrecio = maxPrecio
End Sub
Private Sub frmDetalle_Load(sender As Object, e As EventArgs)
'
' Cargo los items provenientes del constructor con DataRows
'
If _listDataRow IsNot Nothing Then
For Each row As DataRow In _listDataRow
dtgDetalles.Rows.Add(row.ItemArray)
Next
End If
'
' Cargo los items provenientes del constructor
' con entidades de la clase Producto
'
If _listProducto IsNot Nothing Then
dtgDetalles.AutoGenerateColumns = False
dtgDetalles.DataSource = _listProducto
End If
End Sub
Private Sub dtgDetalles_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs)
If dtgDetalles.Columns(e.ColumnIndex).Name = "Precio" Then
If Convert.ToInt32(e.Value) > _maxPrecio Then
dtgDetalles.Rows(e.RowIndex).DefaultCellStyle.ForeColor = Color.Red
End If
End If
End Sub
Public Class Productos
Private _IdProducto As Integer
Public Property IdProducto() As Integer
Get
Return _IdProducto
End Get
Set(ByVal value As Integer)
_IdProducto = value
End Set
End Property
Private _Descripcion As String
Public Property Descripcion() As String
Get
Return _Descripcion
End Get
Set(ByVal value As String)
_Descripcion = value
End Set
End Property
Private _Precio As Integer
Public Property Precio() As Integer
Get
Return _Precio
End Get
Set(ByVal value As Integer)
_Precio = value
End Set
End Property
End Class
End Class
Este código tiene bastante para comentar, en primer lugar es interesante analizar como se han definido varios constructores para el formulario, los cuales abarcan distintos tipos de colecciones, que podrá ser enviada al mismo para ser desplegada en el formulario de detalle.
Entre las opciones disponibles esta una lista de DataRow, o la carga de una clase custom que representa a los productos. Además en los constructores esta la posibilidad de definir un valor de precio máximo, el cual cumplirá la función de definir que registros de la grilla aparecerán con letras rojas según se haya excedido ese monto, sino se especifica valor (al optar el uso del constructor sin este parámetro), se define un valor por defecto de 3200.
Algo importante a marcar es la definición del constructor sin parámetros como privado, esto permite que sea invocado desde dentro de la clase, pero no desde fuera, es por eso que los constructores con parámetros lo terminan utilizan, cuando haces :this(), para inicializar los controles del formulario, pero desde fuera quien cree la instancia deberá obligativamente definir los valores de los ítems a cargar en la grilla.
Dentro del evento Load del formulario se valida que lista de datos se ha cargado y se procede con la carga del mismo, como se vera cada lista tiene su particularidad en el método con que carga la información en el DataGridView.
Además se hizo uso del evento CellFormatting, y como juega el valor de precio máximo para definir que filas de la grilla deben ir en color, en este caso definido con rojo.
Formulario Principal
[C#]
private void btnMostrarSeleccionDataRow_Click(object sender, EventArgs e)
{
List<DataRow> items = new List<DataRow>();
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
DataRowView dataItem = row.DataBoundItem as DataRowView;
items.Add(dataItem.Row);
}
frmDetalle form = new frmDetalle(items);
form.Show();
}
private void btnMostrarSeleccionEntidades_Click(object sender, EventArgs e)
{
List<frmDetalle.Productos> items = new List<frmDetalle.Productos>();
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
frmDetalle.Productos producto = new frmDetalle.Productos()
{
IdProducto = Convert.ToInt32(row.Cells["IdProducto"].Value),
Descripcion = Convert.ToString(row.Cells["Descripcion"].Value),
Precio = Convert.ToInt32(row.Cells["Precio"].Value),
};
items.Add(producto);
}
frmDetalle form = new frmDetalle(items);
form.Show();
}
[VB.NET]
Private Sub btnMostrarSeleccionDataRow_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim items As New List(Of DataRow)()
For Each row As DataGridViewRow In dataGridView1.SelectedRows
Dim dataItem As DataRowView = TryCast(row.DataBoundItem, DataRowView)
items.Add(dataItem.Row)
Next
Dim form As New frmDetalle(items)
form.Show()
End Sub
Private Sub btnMostrarSeleccionEntidades_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim items As New List(Of frmDetalle.Productos)()
For Each row As DataGridViewRow In dataGridView1.SelectedRows
Dim producto As New frmDetalle.Productos()
producto.IdProducto = CInt(row.Cells("IdProducto").Value)
producto.Descripcion = CStr(row.Cells("Descripcion").Value)
producto.Precio = CInt(row.Cells("Precio").Value)
items.Add(producto)
Next
Dim form As New frmDetalle(items)
form.Show()
End Sub
Del formulario Padre o Principal que realiza la llamada, es importante notar los dos métodos utilizados para pasar la mismos registros seleccionados, pero utilizando distintos constructores del formulario Hijo o Detalle.
En el primero se toma la información usada como origen de datos al DataGridView, transformado este en un DataRow, en el segundo método se construye una entidad independiente en base a la información e la misma.
| [C#]
|
[VB.NET]
|
2 – Uso de Propiedades
Esta es otra de las técnicas que pueden utilizarse para pasar información entre formularios, por ahí no es tan restrictivo al momento de inicializar el formulario como el uso de constructores, ya que en este método el constructor será único y sin parámetros, las propiedades definirán los atributos que podrán asignarse.
Para este caso se implementaran los mismo ejemplos anteriores pero con el uso de propiedades esto permitirá la comparación y así analizar que técnica resulta mas conveniente, como en todo desarrollo no hay una regla que diga que esta bien y que mal, será cuestión de aplicar los criterios que uno crea mas razonables y seleccionar que método aplicar.
EJEMPLO 1
Formulario Detalle
[C#]
public partial class frmDetalle : Form
{
private int? _idbusqueda = null;
public int IdBusqueda
{
set
{
_idbusqueda = value;
}
}
public frmDetalle()
{
InitializeComponent();
}
private void frmDetalle_Load(object sender, EventArgs e)
{
if (_idbusqueda.HasValue)
txtIdBusqueda.Text = Convert.ToString(_idbusqueda);
}
}
[VB.NET]
Public Partial Class frmDetalle
Inherits Form
Private _idbusqueda As System.Nullable(Of Integer) = Nothing
Public WriteOnly Property IdBusqueda() As Integer
Set
_idbusqueda = value
End Set
End Property
Public Sub New()
InitializeComponent()
End Sub
Private Sub frmDetalle_Load(sender As Object, e As EventArgs)
If _idbusqueda.HasValue Then
txtIdBusqueda.Text = Convert.ToString(_idbusqueda)
End If
End Sub
End Class
En este caso se ha creado una propiedad de nombre IdBusqueda, definida para que solo pueda ser establecido su valor, pero no su lectura, esto es así porque la idea es luego usar la variable privada para la lógica interna.
Si se compara con el método anterior del constructor el código no ha cambiado mucho, solo se ha dejado un constructor sin parámetros, y se agrego la propiedad.
Formulario Principal
[C#]
public partial class frmPrincipal : Form
{
public frmPrincipal()
{
InitializeComponent();
}
private void btnBuscar_Click(object sender, EventArgs e)
{
int idbusqueda = Convert.ToInt32(txtBusqueda.Text);
frmDetalle form = new frmDetalle();
form.IdBusqueda = idbusqueda;
form.Show();
}
}
[VB.NET]
Public Partial Class frmPrincipal
Inherits Form
Public Sub New()
InitializeComponent()
End Sub
Private Sub btnBuscar_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim idbusqueda As Integer = Convert.ToInt32(txtBusqueda.Text)
Dim form As New frmDetalle()
form.IdBusqueda = idbusqueda
form.Show()
End Sub
End Class
Al crear la instancia ahora se puede hacer libremente sin necesidad de establecer allí mismo un valor como parámetro, pues esto ha sido reemplazado por la línea contigua en donde se utiliza la propiedad creada.
| [C#]
|
[VB.NET]
|
EJEMPLO 2
Formulario Detalle
[C#]
public partial class frmDetalle : Form
{
private int _maxPrecio;
private List<DataRow> _listDataRow = null;
private List<Productos> _listProducto = null;
public List<DataRow> ListaDataRow
{
set
{
_listDataRow = value;
}
}
public List<Productos> ListaProducto
{
set
{
_listProducto = value;
}
}
public int PrecioMaximo
{
set
{
_maxPrecio = value;
}
}
public frmDetalle()
{
InitializeComponent();
this._maxPrecio = 3200;
}
private void frmDetalle_Load(object sender, EventArgs e)
{
//
// Cargo los items provenientes del constructor con DataRows
//
if (_listDataRow != null)
{
foreach (DataRow row in _listDataRow)
{
dtgDetalles.Rows.Add(row.ItemArray);
}
}
//
// Cargo los items provenientes del constructor
// con entidades de la clase Producto
//
if (_listProducto != null)
{
dtgDetalles.AutoGenerateColumns = false;
dtgDetalles.DataSource = _listProducto;
}
}
private void dtgDetalles_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (dtgDetalles.Columns[e.ColumnIndex].Name == "Precio")
{
if (Convert.ToInt32(e.Value) > _maxPrecio)
dtgDetalles.Rows[e.RowIndex].DefaultCellStyle.ForeColor = Color.Red;
}
}
public class Productos
{
public int IdProducto { get; set; }
public string Descripcion { get; set; }
public int Precio { get; set; }
}
}
[VB.NET]
Public Partial Class frmDetalle
Inherits Form
Private _maxPrecio As Integer
Private _listDataRow As List(Of DataRow) = Nothing
Private _listProducto As List(Of Productos) = Nothing
Public WriteOnly Property ListaDataRow() As List(Of DataRow)
Set
_listDataRow = value
End Set
End Property
Public WriteOnly Property ListaProducto() As List(Of Productos)
Set
_listProducto = value
End Set
End Property
Public WriteOnly Property PrecioMaximo() As Integer
Set
_maxPrecio = value
End Set
End Property
Public Sub New()
InitializeComponent()
Me._maxPrecio = 3200
End Sub
Private Sub frmDetalle_Load(sender As Object, e As EventArgs)
'
' Cargo los items provenientes del constructor con DataRows
'
If _listDataRow IsNot Nothing Then
For Each row As DataRow In _listDataRow
dtgDetalles.Rows.Add(row.ItemArray)
Next
End If
'
' Cargo los items provenientes del constructor
' con entidades de la clase Producto
'
If _listProducto IsNot Nothing Then
dtgDetalles.AutoGenerateColumns = False
dtgDetalles.DataSource = _listProducto
End If
End Sub
Private Sub dtgDetalles_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs)
If dtgDetalles.Columns(e.ColumnIndex).Name = "Precio" Then
If Convert.ToInt32(e.Value) > _maxPrecio Then
dtgDetalles.Rows(e.RowIndex).DefaultCellStyle.ForeColor = Color.Red
End If
End If
End Sub
Public Class Productos
Private _IdProducto As Integer
Public Property IdProducto() As Integer
Get
Return _IdProducto
End Get
Set(ByVal value As Integer)
_IdProducto = value
End Set
End Property
Private _Descripcion As String
Public Property Descripcion() As String
Get
Return _Descripcion
End Get
Set(ByVal value As String)
_Descripcion = value
End Set
End Property
Private _Precio As Integer
Public Property Precio() As Integer
Get
Return _Precio
End Get
Set(ByVal value As Integer)
_Precio = value
End Set
End Property
End Class
End Class
La sección donde se aplica la lógica del formulario si se compara con los ejemplo que usaban un constructor para pasar los valores, no ha cambiado en nada, la única parte que si ha recibido los cambios es en la definición del método en que se asignan los valores a las variables privadas del formulario.
En este caso el constructor sin parámetro vuelve a ser publico, y dentro de este se define valor por defecto para el precio máximo, usado para asignar los colores a las filas de la grilla.
Formulario Principal
[C#]
private void btnMostrarSeleccionDataRow_Click(object sender, EventArgs e)
{
List<DataRow> items = new List<DataRow>();
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
DataRowView dataItem = row.DataBoundItem as DataRowView;
items.Add(dataItem.Row);
}
frmDetalle form = new frmDetalle();
form.ListaDataRow = items;
form.Show();
}
private void btnMostrarSeleccionEntidades_Click(object sender, EventArgs e)
{
List<frmDetalle.Productos> items = new List<frmDetalle.Productos>();
foreach (DataGridViewRow row in dataGridView1.SelectedRows)
{
frmDetalle.Productos producto = new frmDetalle.Productos()
{
IdProducto = Convert.ToInt32(row.Cells["IdProducto"].Value),
Descripcion = Convert.ToString(row.Cells["Descripcion"].Value),
Precio = Convert.ToInt32(row.Cells["Precio"].Value),
};
items.Add(producto);
}
frmDetalle form = new frmDetalle();
form.ListaProducto = items;
form.Show();
}
[VB.NET]
Private Sub btnMostrarSeleccionDataRow_Click(sender As Object, e As EventArgs)
Dim items As New List(Of DataRow)()
For Each row As DataGridViewRow In dataGridView1.SelectedRows
Dim dataItem As DataRowView = TryCast(row.DataBoundItem, DataRowView)
items.Add(dataItem.Row)
Next
Dim form As New frmDetalle()
form.ListaDataRow = items
form.Show()
End Sub
Private Sub btnMostrarSeleccionEntidades_Click(sender As Object, e As EventArgs)
Dim items As New List(Of frmDetalle.Productos)()
For Each row As DataGridViewRow In dataGridView1.SelectedRows
Dim producto As New frmDetalle.Productos()
producto.IdProducto = CInt(row.Cells("IdProducto").Value)
producto.Descripcion = CStr(row.Cells("Descripcion").Value)
producto.Precio = CInt(row.Cells("Precio").Value)
items.Add(producto)
Next
Dim form As New frmDetalle()
form.ListaProducto = items
form.Show()
End Sub
| [C#]
|
[VB.NET]
|
Conclusión
Si bien en este articulo se han explicado dos técnicas para pasar información de un formulario Padre a su Hijo, esto no quiere decir que deba usarse una u otra, por el contrario estas podrían ser complementadas entre si.
En el constructor se podría definir información que es requería para el funcionamiento del formulario que se este invocando, mientras que en las propiedades podría especificarse información opcional.