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.