No tenía intención de volver sobre este tema pero, la verdad, tampoco tengo nada más que escribir. Y, al final, fue un problema curioso cuya resolución no me quedó tan elegante como a mí me hubiera gustado. El primer problema que me dio la solución que comentaba fue cuando intenté ordenar el contenido de la BindingList. Mi gozo en un pozo: la BindingList no ordena. Me resulta chocante que la BindingList, que se supone va mejor para enlace a datos, no ordene ni busque, y una List sí. De hecho, por internet e incluso en la misma documentación de MSDN encontramos clases derivadas de BindingList que ordenan y/o buscan. De hecho, encontré una para un caso general que me ha gustado mucho y me la he guardado, pero para esta aplicación he preferido usar una clase preparada para la ocasión.
Partíamos, recuerdo, de una clase muy sencilla, IdMasDescripcion, con dos propiedades, Id de tipo Integer que se corresponde a la clave primaria, y Descripcion de tipo String. La clase, además, implementa la interfaz IComparable(Of T), y el método CompareTo lo que hace es comparar las descripciones. Lo que voy a hacer, entonces, es una BindingList(Of IdMasDescripcion) personalizada que sea capaz de ordenarse. Pero no siempre va a estar ordenada (depende de qué tabla muestre) y la ordenación puede ser alfabética o por resoluciones (de momento; en un futuro puede que me haga falta un tercer tipo). Voy a necesitar un método para ordenar (Sort), y dos campos que me indiquen si la colección está ordenada (IsSorted) y si hay que ordenarla (ToSort):
Imports System.ComponentModel Imports CdA.Comun Public Class ReleasesCombosBinding Inherits System.ComponentModel.BindingList(Of IdMasDescripcion) Public Enum TipoOrden As Integer Alfabetico Resolucion End Enum Private _IsSorted As Boolean = False Private _ToSort As Boolean = False Private _TipoSort As TipoOrden = TipoOrden.Alfabetico Protected Overrides Sub ClearItems() Me.IsSorted = False MyBase.ClearItems() End Sub ''' <summary> ''' Ordena la lista subyacente de forma ascendente ''' </summary> Public Sub Sort() Dim items As List(Of IdMasDescripcion) = TryCast(Me.Items, _ List(Of IdMasDescripcion)) If items IsNot Nothing Then Select Case Me._TipoSort Case TipoOrden.Alfabetico items.Sort() Case TipoOrden.Resolucion '(...) End Select Me.IsSorted = True Else End If Me.OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, -1)) End Sub #Region " Propiedades públicas " ''' <summary> ''' Devuelve si la lista está ordenada o no ''' </summary> Public Property IsSorted() As Boolean Get Return Me._IsSorted End Get Protected Set(ByVal value As Boolean) Me._IsSorted = value End Set End Property ''' <summary> ''' Devuelve o establece si la lista ha de ser ordenada. ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public Property ToSort() As Boolean Get Return Me._ToSort End Get Set(ByVal value As Boolean) Me._ToSort = value End Set End Property Public Property TipoSort() As TipoOrden Get Return Me._TipoSort End Get Set(ByVal value As TipoOrden) Me._TipoSort = value End Set End Property #End Region #Region " Métodos privados " Private Function ValorNumL(ByVal Resolucion As String) As Integer End Function Private Function ValorNumA(ByVal Resolucion As String) As Integer End Function #End Region End Class |
El código que falta, marcado con (…) en el método Sort y las dos funciones en blanco, se corresponden con la ordenación por resoluciones. De la clase en sí, tenemos que la propiedad IsSorted sólo puede cambiar su valor desde dentro de la clase: al ordenar la colección o al vaciarla. Por otra parte, el método Sort termina provocando el evento ListChanged, necesario para que todo el mundo que trabaje con esta colección se entere de que ha cambiado.
Vayamos al formulario de Series, que es para el que nos hace falta esta clase. Vamos a tener una ReleasesCombosBinding por cada combo que hay en una pestaña, y que se corresponde con una tabla de la base de datos. Para tenerlo más ordenado, voy a aprovechar una enumeración con toda la lista de tablas de la base de datos, llamada enTablas y definir lo siguiente:
Private CombosReleases As New Dictionary(Of enTablas, ReleasesCombosBinding) |
El primer «llenado» lo hago en la capa inferior. El método recibe el diccionario y se ocupa de llenarlo para cada tabla. Como la aplicación no es muy grande, en su día la diseñé en dos capas y el método en cuestión queda embutido en la clase de acceso a datos de una forma un poco extraña pero (ojo, no está todo el código):
Public Function LeerDatosCombosReleases() As Dictionary(Of enTablas, _ ReleasesCombosBinding) Dim dicCombos As New Dictionary(Of enTablas, ReleasesCombosBinding) Dim HeConectado As Boolean = False Try If Me.Conexion.State = ConnectionState.Closed Then HeConectado = Me.Conectar() End If 'Y ahora el lío padre. Necesitamos una variante de LlenarCombo 'a la que, pasándole el nombre de la tabla, nos devuelva un 'ReleasesComboBinding de esos con todos los valores. Llamaremos 'a esa función para cada puñetera tabla para rellenar el dictionary. 'Las tablas: 'Archivador dicCombos.Add(enTablas.Archivador, _ Me.LlenarLista(enTablas.Archivador.ToString, True)) 'Audio dicCombos.Add(enTablas.Audio, Me.LlenarLista(enTablas.Audio.ToString, _ True)) 'Calidad dicCombos.Add(enTablas.Calidad, Me.LlenarLista(enTablas.Calidad.ToString)) 'Fansub dicCombos.Add(enTablas.Fansub, Me.LlenarLista(enTablas.Fansub.ToString, _ True)) 'Resolucion dicCombos.Add(enTablas.Resolucion, _ Me.LlenarLista(enTablas.Resolucion.ToString, _ True, ReleasesCombosBinding.TipoOrden.Resolucion)) 'Soporte dicCombos.Add(enTablas.Soporte, Me.LlenarLista(enTablas.Soporte.ToString)) '(...) Finally If HeConectado Then Me.Desconectar() End If End Try Return dicCombos End Function |
Donde LlenarLista lo que hace es crear el nuevo ReleasesComboBinding y rellenarla, ordenando o no según se le indique. El método recibe el nombre de las tablas como parámetro, que, casualmente, coincide con el nombre dado a cada elemento de la enumeración de tablas. Realmente hay dos métodos LlenarLista, una función y un procedimiento. La función crea la colección y la devuelve en su nombre, mientras que el procedimiento recibe la colección como parámetro, lo limpia, rellena y ordena si debe. Este procedimiento será al que llame a la hora de actualizar cada ReleasesComboBinding.
Aquí tenemos la función:
''' <summary> ''' Vuelca el contenido de la tabla indicada en el parámetro ''' a una lista que guarda el campo identificador y el descriptor ''' (los dos primeros) de esa tabla, creando una nueva lista. ''' </summary> ''' <param name=Tabla">Nombre de la tabla</param>" Public Function LlenarLista(ByVal Tabla As String, _ Optional ByVal Ordenar As Boolean = False, _ Optional ByVal Como As ReleasesCombosBinding.TipoOrden = _ ReleasesCombosBinding.TipoOrden.Alfabetico) As ReleasesCombosBinding Dim MisDatos As New ReleasesCombosBinding MisDatos.ToSort = Ordenar MisDatos.TipoSort = Como Try Me.LlenarLista(Tabla, MisDatos) Catch ex As Exception Throw ex End Try Return MisDatos End Function |
Y aquí el procedimiento:
Public Sub LlenarLista(ByVal Tabla As String, _ ByVal MisDatos As ReleasesCombosBinding) Dim drDatos As SqlServerCe.SqlCeDataReader Dim Comando As SqlServerCe.SqlCeCommand Dim tbTabla As DataTable Dim HeConectado As Boolean = False If MisDatos Is Nothing Then Exit Sub End If Comando = New SqlServerCe.SqlCeCommand("SELECT * FROM " & Tabla, _ Me.Conexion) tbTabla = New DataTable MisDatos.Clear() Try If Me.Conexion.State = ConnectionState.Closed Then HeConectado = Me.Conectar() End If drDatos = Comando.ExecuteReader() tbTabla.Load(drDatos, LoadOption.OverwriteChanges) For Each Fila As DataRow In tbTabla.Rows MisDatos.Add(New IdMasDescripcion(CInt(Fila(0)), CStr(Fila(1)))) Next drDatos.Close() If MisDatos.ToSort Then MisDatos.Sort() End If Catch NoConexion As InvalidOperationException Throw NoConexion Catch NoConexion As Exception Throw NoConexion Finally Comando = Nothing tbTabla = Nothing If HeConectado Then Me.Desconectar() End If End Try End Sub |
Y por hoy, ya me he cansado. La próxima cuento cómo trabajan el formulario y los combos de las pestañas con el diccionario. Lo que me recuerda que los chavales a los que di clase en primavera andaban algo liados con los arrays de estructuras. ¡y yo tengo aquí, básicamente, un array de arrays! La verdad es que cuando pienso en ello empieza a dolerme la cabeza.