Mantener actualizados unos ComboBox (II)

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.

Deja un comentario