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.