A menudo disponemos de un control ComboBox relleno de elementos, y queremos
que al seleccionar uno de ellos nos muestre, en otro control ComboBox diferente,
los registros que se encuentren relacionados con el elemento seleccionado en el
primer control ComboBox. Una especie de relación principal - secundaria
(o maestra - detalle), pero que se muestren los datos en controles
ComboBox, digamos que en controles ComboBox relacionados.
Esto se puede hacer fácilmente, siempre y cuando se encuentren previamente
relacionados los datos que se van a mostrar en los diferentes controles ComboBox
que se desean relacionar. Si no se encuentran relacionados los datos, va a ser
complicado establecer la relación, porque ésta no se haría de manera automática,
si no que la tendríamos que implementar a base de búsquedas.
Como personalmente no me gustan las cosas complicadas, procuro esforzarme para
hacerlas de una manera fácil y sencilla, de tal manera que con el mínimo esfuerzo obtenga el mejor resultado óptimo posible.
Como normalmente tenemos los datos almacenados en una base de datos, simplemente
sería cuestión de rellenar objetos DataTable, añadirlos a un objeto DataSet, y
establecer en éste último objeto la relación adecuada entre los objetos
DataTable. Pero en ésta ocasión, voy a suponer que el lector no tiene los datos
en una base de datos, si no que los tiene desperdigados por cualquier lado.
¿Habría una solución para poner orden a éste desorden? La solución se encuentra
en el objeto DataSet.
Muchos usuarios de Visual Basic, y de otros lenguajes .NET, creen que el objeto
DataSet únicamente se utiliza cuando tenemos los datos en una base de datos, motivo por el cual desconocen las posibilidades de éste objeto, que se
encuentra en lo más alto de la pirámide de lo que se conoce como la arquitectura
desconectada de ADO .NET.
Imagine un objeto DataSet como si fuera una base de datos, pero que en lugar de
estar localizada físicamente en un archivo de nuestro disco duro, se encuentra en la memoria de nuestro sistema, por lo que podemos guardar
los datos en objetos DataTable (al igual que lo almacenaríamos en una tabla de
una base de datos), y podemos relacionar los objetos DataTable entre sí (como
también haríamos con las tablas de una base de datos relacional).
El ejemplo que presento a continuación, enseña la manera de crear un objeto
DataSet, crear los objetos DataTable, rellenarlos de datos, y por último, relacionarlos entre sí. Para ello, vamos a mostrar en un primer control
ComboBox (digamos en el control maestro) el nombre de cinco continentes, y en
otro control ComboBox (el que actúa de detalle) los países que pertenecen al
continente seleccionado en el primer control ComboBox. Obviamente, no espere que
liste aquí todos los países de los cinco continentes, porque se trata solamente
de un ejemplo.
En primer lugar, vamos a comenzar por escribir el procedimiento que se encargará
de crear el objeto DataSet:
Private Function
CreateDataSet() As DataSet
Dim ds
As New
DataSet()
' Añadimos la tabla de Continentes.
'
ds.Tables.Add(CreateDataTableContinents())
'
Añadimos la tabla de Países.
'
ds.Tables.Add(CreateDataTableCountries())
'
Referenciamos las tablas maestra y detalle existentes
' en el
objeto DataSet.
'
Dim parentTable As
DataTable = ds.Tables("Continentes")
Dim childTable
As
DataTable = ds.Tables("Paises")
'
Creamos una relación entre los objetos DataTable a través
' del
campo IdContinente común en ambos objetos.
'
Dim rel
As New
DataRelation( _
"Continentes_Paises", _
parentTable.Columns("IdContinente"),
_
childTable.Columns("IdContinente"))
' Añadimos la relación al objeto DataSet.
'
ds.Relations.Add(rel)
' Devolvemos el objeto DataSet.
'
Return ds
End Function
Observará que desde el procedimiento CreateDataSet, se está llamando a
otros dos métodos, cada cual devolverá su respectivo objeto DataTable. Al
final del procedimiento, se establece la relación entre los objetos
DataTable, a través de un campo común, en el ejemplo, el campo
IdContinente
existente en los dos objetos DataTable. Esto no significa que los campos que
forman la relación, tengan idénticos nombres en los dos objetos DataTable;
mientras que los tipos de datos de los dos campos o columnas sean iguales,
no importa el nombre que tenga cada uno de ellos.
A continuación, éste será el procedimiento para crear el objeto DataTable de
Continentes:
Private Function
CreateDataTableContinents() As DataTable
Dim dt
As DataTable =
New
DataTable("Continentes")
' Añadimos la
columna que va a identificar de manera
' única a cada Continente.
'
Dim dc
As DataColumn =
New
DataColumn("IdContinente",
GetType(Integer))
'
Añadimos la columna al objeto DataTable.
'
dt.Columns.Add(dc)
'
Indicamos que ésta columna será la clave principal
' del objeto DataTable.
'
dt.PrimaryKey = {dc}
' Añadimos
otra columna para el nombre del Continente.
'
dc = New
DataColumn("Nombre",
GetType(String))
dt.Columns.Add(dc)
' Añadimos
los registros al objeto DataTable.
'
Dim
row As DataRow = dt.NewRow()
row("IdContinente")
= 1
row("Nombre")
= "Europa"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente")
= 2
row("Nombre")
= "América"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente")
= 3
row("Nombre")
= "África"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente")
= 4
row("Nombre")
= "Asia"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente")
= 5
row("Nombre")
= "Oceania"
dt.Rows.Add(row)
Return
dt
End Function
Fíjese que el objeto DataTable resultante dispone solamente de dos campos
o columnas: el campo IdContinente, que a su vez será la clave principal del
objeto DataTable, y el campo Nombre.
Continuamos con el procedimiento para crear el objeto DataTable
correspondiente a los países:
Private Function
CreateDataTableCountries() As
DataTable
Dim dt As DataTable
= New DataTable("Paises")
'
Añadimos una columna autonumérica que va a identificar de manera
' única a
cada País.
'
Dim dc As DataColumn
= New DataColumn("IdPais",
GetType(Integer))
dc.AutoIncrement = True
dc.AutoIncrementStep = 1
dc.AutoIncrementSeed = 1
' Añadimos la columna al objeto DataTable.
'
dt.Columns.Add(dc)
'
Indicamos que ésta columna será la clave principal
' del
objeto DataTable.
'
dt.PrimaryKey = {dc}
' Añadimos la columna que representará el identificador
del
'
continente al que pertenece el país.
'
dc = New DataColumn("IdContinente",
GetType(Integer))
dt.Columns.Add(dc)
'
Añadimos la columna para el nombre del país.
'
dc = New DataColumn("Nombre",
GetType(String))
dt.Columns.Add(dc)
'
Añadimos los registros al objeto DataTable. Observe que no es
'
necesario especificar los valores de la columna autonumérica.
'
Dim row As DataRow =
dt.NewRow()
row("IdContinente") = 1
row("Nombre") =
"España"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 1
row("Nombre") =
"Francia"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 1
row("Nombre") =
"Alemania"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 1
row("Nombre") =
"Italia"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 2
row("Nombre") =
"Brasil"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 2
row("Nombre") =
"Colombia"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 2
row("Nombre") =
"Estados
Unidos"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 2
row("Nombre") =
"Chile"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 2
row("Nombre") =
"México"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 3
row("Nombre") =
"Marruecos"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 3
row("Nombre") =
"Libia"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 3
row("Nombre") =
"Egipto"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 4
row("Nombre") =
"China"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 4
row("Nombre") =
"Japón"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 4
row("Nombre") =
"India"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 5
row("Nombre") = "Australia"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 5
row("Nombre") =
"Nueva
Zelanda"
dt.Rows.Add(row)
row = dt.NewRow()
row("IdContinente") = 5
row("Nombre") =
"Papúa
Nueva Guinea"
dt.Rows.Add(row)
Return dt
End Function
El objeto DataTable de Países tendrá tres campos: IdPais (que será un
campo autonumérico y actuará de clave principal de la tabla), IdContinente
(para relacionarlo con el mismo campo de la tabla de Continentes), y Nombre,
que como es de esperar, guardará el nombre del país.
¡Bueno! Pues ya estamos en condiciones de llamar al método CreateDataSet
desde el evento Load del formulario que contiene los dos objetos ComboBox,
para crear el objeto DataSet y establecer la relación entre ambos objetos
DataTable:
Private Sub
Form1_Load(ByVal sender
As Object,
ByVal e As
EventArgs) Handles MyBase.Load
' Obtenemos un objeto DataSet
'
Dim ds
As DataSet =
CreateDataSet()
' Configuramos el control BindingSource principal
'
Dim bsPrincipal
As New
BindingSource()
bsPrincipal.DataSource = ds
bsPrincipal.DataMember = "Continentes"
' Configuramos el control ComboBox
principal
'
With ComboBox1
.DataSource = bsPrincipal
.DisplayMember = "Nombre"
.ValueMember = "IdContinente"
End With
' Configuramos el control BindigSource secundario
'
Dim bsDetalle
As New
BindingSource()
bsDetalle.DataSource = bsPrincipal
bsDetalle.DataMember = "Continentes_Paises"
' Configuramos el control ComboBox secundario
'
With ComboBox2
.DataSource = bsDetalle
.DisplayMember = "Nombre"
.ValueMember = "IdPais"
End
With
' Instalamos el controlador para el evento SelectedValueChanged
' del control ComboBox de países.
'
AddHandler ComboBox2.SelectedValueChanged,
AddressOf ComboBoxOnSelectedValueChanged
End Sub
El mecanimo para que funcione la relación entre las tablas Continentes y Paises, se encuentra en la utilización de dos objetos BindingSource, los cuales se encargarán de encapsular los orígenes de datos de los dos controles ComboBox. La configuración de los controles BindingSource deberá de adaptarse al siguiente esquema:
Y el esquema de configuración de los controles ComboBox relacionados sería éste otro:
Por último, vamos a escribir el procedimiento que se utilizará para el controlador del evento SelectedValueChanged del control ComboBox de países, el cual se desencadenará cuando seleccionemos un elemento del citado control (cuando cambie el valor de su propiedad SelectedValue), teniendo la posibilidad de obtener el identificador del país seleccionado:
Private Sub ComboBoxOnSelectedValueChanged(ByVal
sender As Object,
ByVal
e As EventArgs)
' Referenciamos el control ComboBox que
ha
' desencadenado el evento.
'
Dim cb
As
ComboBox = DirectCast(sender, ComboBox)
Dim value
As
Object = cb.SelectedValue
If (TypeOf
value Is DataRowView)
Then
Return
' Obtenemos el identificador del país
seleccionado.
'
Dim idPais
As Integer = CInt(value)
MessageBox.Show(CStr(idPais))
End Sub
¡Eso es todo! Como habrá tenido ocasión de comprobar, mediante la utilización de una relación existente en un objeto DataSet, hemos sido capaces de relacionar de una manera automática, los elementos existentes en dos controles ComboBox diferentes.
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 - 2011
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.