Solución: Utilizar clases genéricas y enlazarlos a los grid usando Linq.
Las clases genéricas es un nuevo tipo de elemento que viene en Visual Studio. Se lo puede encontrar al dar click en el menú Proyecto y luego en Agregar Nuevo Elemento. En la ventana de Agregar Nuevo Elemento, en la Categoría Datos, se encuentra la plantilla "Clases de LINQ to SQL", el cual crea un archivo con la extensión .dbml.
Al cargar este archivo aparecerá un apartado en blanco en donde se podrán ir diseñando las clases con sus propiedades, como quien añade tablas y campos. Dos clases se pueden relacionar por medio de una asociación o por herencia, pero realmente se puede controlar el detalle por medio de sentencias Linq.
Para entender un poco mejor, pongo un ejemplo.
Tengo un contenedor de clases llamado DataClasses1DataContext, el cual contiene dentro dos clases "tipadas" llamadas Class1 y Class2. La clase Class1 tiene dos propiedades Property1 (tipo Integer) y Property2 (tipo String), y la clase Class2 tiene tres propiedades Property1 (tipo Integer), Property2 (tipo Integer) y Property3 (tipo String).
Tengo un formulario llamado Form1, el cual contiene dos grid llamados DataGridView1 y DataGridView2.
El código que se utiliza en el formulario principal es el siguiente.
Public Class Form1 Private Class1 As New List(Of Class1) Private Class2 As New List(Of Class2) Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Se llenan los datos llenar_clase1() llenar_clase2() 'Se asignan los datos al grid DataGridView1.DataSource = Class1 'Para que se seleccione toda la fila DataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect 'Se obtiene la primera fila del grid principal y se lo setea 'al grid detalle por medio de linq Dim Clase2 = From clase In Class2 Where clase.Property2 = 1 Dim clases2 = Clase2.ToList DataGridView2.DataSource = clases2 End Sub Private Sub llenar_clase1() 'Aqui se debe utilizar una funcion para llenar los datos 'Esta funcion se llena con datos de prueba Dim dt_table As New DataTable dt_table = dt_clase1() 'Recorremos los datos temporales para llenar la clase For Each dt As DataRow In dt_table.Rows 'Se crea una instancia nueva de la clase 'para llenarlo en la lista Dim clase1 As Class1 = New Class1 'Se pasan los datos clase1.Property1 = dt.Item("col1") clase1.Property2 = dt.Item("col2") 'Se llena la lista principal Class1.Add(clase1) Next End Sub Private Function dt_clase1() As DataTable Dim new_DataTable As New DataTable 'Creando las columnas new_DataTable.Columns.Add("col1") new_DataTable.Columns.Add("col2") 'Crando las filas o datos new_DataTable.Rows.Add(1, "AAA") new_DataTable.Rows.Add(2, "BBB") new_DataTable.Rows.Add(3, "CCC") dt_clase1 = new_DataTable End Function Private Sub llenar_clase2() 'Aqui se debe utilizar una funcion para llenar los datos 'Esta funcion se llena con datos de prueba Dim dt_table As New DataTable dt_table = dt_clase2() 'Recorremos los datos temporales para llenar la clase For Each dt As DataRow In dt_table.Rows 'Se crea una instancia nueva de la clase 'para llenarlo en la lista Dim clase2 As Class2 = New Class2 'Se pasan los datos clase2.Property1 = dt.Item("col1") clase2.Property2 = dt.Item("col2") clase2.Property3 = dt.Item("col3") 'Se llena la lista principal Class2.Add(clase2) Next End Sub Private Function dt_clase2() As DataTable Dim new_DataTable As New DataTable 'Creando las columnas new_DataTable.Columns.Add("col1") new_DataTable.Columns.Add("col2") new_DataTable.Columns.Add("col3") 'Crando las filas o datos new_DataTable.Rows.Add(1, 1, "AAA-AAA") new_DataTable.Rows.Add(1, 2, "AAA-BBB") new_DataTable.Rows.Add(1, 3, "AAA-CCC") new_DataTable.Rows.Add(2, 1, "BBB-AAA") new_DataTable.Rows.Add(2, 2, "BBB-BBB") new_DataTable.Rows.Add(2, 3, "BBB-CCC") new_DataTable.Rows.Add(3, 1, "CCC-AAA") new_DataTable.Rows.Add(3, 2, "CCC-BBB") new_DataTable.Rows.Add(3, 3, "CCC-CCC") dt_clase2 = new_DataTable End Function Private Sub DataGridView1_CellClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) _ Handles DataGridView1.CellClick 'Cada vez que cambie de fila, se actualiza el grid de detalle Dim Clase2 = From clase In Class2 Where clase.Property2 = e.RowIndex + 1 Dim clases2 = Clase2.ToList DataGridView2.DataSource = clases2 End Sub End Class
El Linq es parecido a las sentencias de SQL y se basan en la misma lógica, pero no en el mismo orden.
Una sentencia Linq es parecido a lo siguiente.
Dim valor = From variable In contenedor Where variable = objeto Select variable
valor es el resultado y lo que contiene la sentencia devuelta.
variable es una temporal en donde se procesará la setencia dependiendo de la condición.
contenedor es la clase o el objeto que pueda utilizar una sentencia Linq.
objeto es la restricción que se necesita para obtener un conjunto determinado de datos.
Quienes hayan usado sentencias SQL podrán fácilmente adaptarse al Linq.
Usar clases genéricas o tipadas es muy útil y versatil, sobre todo cuando se necesita trabajar con diferentes fuentes de datos y/o no se puede utilizar los orígenes estándares como DataTables o DataSets.
Su poder se ve apreciado cuando se tiene enlazados más de un maestro - detalle, o se cuenta con una cadena de detalles. Por ejemplo, una cotización que tiene varias facturas y cada factura tiene su cabecera y detalle, esta complejidad se ahorra mucho tiempo de código al "dibujar" las clases y relacionarlas entre sí, siendo el Linq el que facilita la tarea del filtrado de los datos.