Funciona

Es una buena noticia: el formulario de tablas menores de mi nueva aplicación de Colección Anime funciona y no ha dado mucha guerra, salvo algunos problemas menores con la herencia visual en formularios y los problemas que da un MenuStrip heredado. Pero, por lo demás, todo ha funcionado desde el principio sin problemas. No está terminado, aún falta la opción de Eliminar y añadir un control por si las tablas están vacías, de forma que se deshabiliten las opciones de Modificar y Eliminar, pero el resto es funcional.

La razón para empezar por este formulario eran dos:

  1. La primera es que es el segundo formulario más complejo que tengo. Pese a que las tablas que maneja tienen sólo tres campos (un identificador numérico, el nombre y un campo para comentarios), el formulario debe gestionar 11 tablas, lo que me garantiza unos cuantos dolores de cabeza.

  2. La segunda es que, dada las tablas que maneja, necesito este formulario antes de ponerme con las series y releases, en el otro formulario grande. Y eso es lo principal para tener algo útil, que personajes y listas de episodios siempre puedo añadirlos después.

Pero la razón fundamental de haber hecho este formulario, antes incluso que el formulario de inicio o haber terminado las clases de acceso a datos, es para poder comprobar que lo que estoy haciendo es correcto y que no tengo graves errores de concepto. Mejor descubrirlo ahora que mil líneas después.

El formulario.

El formulario es muy simple: tiene dos cajas de texto, uno para el campo Nombre y otro para el campo Comentarios de las tablas. Tiene también un ListBox para navegación por los distintos registros de la tabla y un ComboBox que permite cambiar la tabla. Completa el formulario un MenuStrip para altas, bajas, modificaciones, etc., un GroupBox y varias etiquetas.

Parte del formulario de marras
Fig.1 El formulario de marras.

No me he complicado la vida y he usado un enlace de datos de tutorial básico. Es decir, tengo un DataSet que contendrá los datos y un BindingSource que los enlaza a los TextBoxes y al ListBox. El DataSet es genérico (no tipado) aunque realmente sí trabajo con datasets tipados, pero eso lo veremos luego. Para altas uso el método AddNew() del BindingSource y al Aceptar valido los datos con un Me.ValidateChildren (sólo se comprueba que el campo Nombre no quede en blanco, gestionando el evento Validating del TextBox y usando un ErrorProvider para indicárselo al usuario), llamamos al EndEdit del BindingSource y, finalmente, guardamos los datos.

Vamos, un tutorial de acceso a datos lo más simple posible. Si sólo hubiera una tabla, ya estaría hecho. El problema es que son once.

El combo.

Como dije, el formulario tiene un ComboBox para elegir la tabla. Para empezar con esto, hay que rellenar el combo. Necesito que cada elemento del combo guarde dos valores: el texto a mostrar (que no corresponderá necesariamente con el nombre de la tabla, por aquello de que el texto tendrá espacios y tildes) y la tabla a la que se refiere. Para esto último tengo creado una Enumeración con todas las tablas de la base de datos donde las 11 primeras entradas son las tablas que me interesan aquí.

Pues sabiendo esto, una clase:

Private Class csMisTablas
    Implements IComparable(Of csMisTablas)
    Private _IdTabla As enTablas
    Private _Nombre As String
    Public Sub New(ByVal tabla As enTablas, _
                   ByVal NombreTabla As String)
        _IdTabla = tabla
        _Nombre = NombreTabla
    End Sub
    Public ReadOnly Property Nombre() As String
        Get
            Return _Nombre
        End Get
    End Property
    Public ReadOnly Property IdTabla() As enTablas
        Get
            Return _IdTabla
        End Get
    End Property

    Public Function CompareTo(ByVal other As csMisTablas) _
            As Integer Implements System.IComparable(Of  _
            csMisTablas).CompareTo
        Return Me._Nombre.CompareTo(other.Nombre)
    End Function
End Class

Luego, en el constructor del formulario, me declaro una List(Of csMistablas), la relleno, la ordeno (de ahí que implemente IComparable(Of T)) y la uso de DataSource para el ComboBox. Las propiedades públicas son para usarlas como ValueMember y DisplayMember del combo.

Ya tenemos en el combo los valores que necesitamos. Lo siguiente es controlar el evento SelectedIndexChanged del ComboBox, averiguar la tabla elegida (enumeración enTablas) y mostrar los datos de esa tabla. Para mostrar los datos, primero desligamos los TextBox del Binding, cargamos los datos y volvemos a enlazarlos. Algo tal que así:

With Me
    'limpiamos los bindings
    .txtComentarios.DataBindings.Clear()
    .txtDescriptor.DataBindings.Clear()
    'leemos datos
    .dsTablas = .TablasMenores.LeerDatos(._TablaElegida)
    'ligamos los controles
    .bsTablas.DataSource = .dsTablas.Tables(0)
    .txtDescriptor.DataBindings.Add(New Binding( _
                        "text", .bsTablas, "Descripcion"))
    .txtComentarios.DataBindings.Add(New Binding( _
                        "text", .bsTablas, "Comentarios"))
    .lstNavegacion.DataSource = .bsTablas
    .lstNavegacion.DisplayMember = "Descripcion"
End With

Donde TablasMenores es una instancia de la clase de acceso a datos TablasMenoresAD. Aquí se usa su método LeerDatos. En la opción de Aceptar llamaríamos a su método GuardarDatos después del EndEdit del BindingSource.

Con esto, más algo de código de control y un par de cositas más y ya está hecho. Falta la opción de eliminar, donde habrá que comprobar relaciones y tal, pero eso tampoco es complejo y lo haré en cuanto tenga un rato.

Acceso a datos.

La clase de acceso a datos TablasMenoresAD es muy simple. Como se ha visto en la tabla anterior, tiene un método público llamado LeerDatos al que se le pasa por parámetro cuál es la tabla que se quiere leer (en este caso, un valor de una enumeración) y devuelve un DataSet relleno. Internamente la clase tiene una función privada LeerDatos para cada tabla en la que usa un DataSet tipado y su TableAdapter correspondiente. LeerDatos abre la conexión y llama a la función privada correspondiente (hacía tiempo que no usaba un Select Case, la verdad). La parte de GuardarDatos funciona prácticamente igual:

Private Function LeerArchivador() As ArchivadorDataSet
    Dim taArchivador As New  _
            ArchivadorDataSetTableAdapters.ArchivadorTableAdapter
    Dim dsArchivador As New ArchivadorDataSet
    Try
        taArchivador.Connection = Me.Conexion
        taArchivador.Fill(dsArchivador.Archivador)
    Catch ex As Exception
        Throw ex
    End Try
    Return dsArchivador
End Function

Y ya está. El resto de la aplicación seguirá estos pasos, así que este fin de semana le meteré caña.

La clave primaria.

Y, para terminar, un apunte: el campo que es clave primaria no se muestra en el formulario y su valor se calcula automáticamente (en este caso es una suerte de autonumérico). Esto me ha dado ciertos problemas porque, ¿cómo le paso un valor a un campo de la fila al ejecutar el AddNew del BindingSource? Sigo sin saberlo. Una opción habría sido usar una etiqueta oculta para guardar este valor, pero eso habría significado usar un control sólo para almacenar un valor que no voy a mostrar, lo que no es una buena opción (otra cosa es si hubiera tenido que mostrar ese valor; por ejemplo, un número de pedido que se genera automáticamente).

La opción que encontré es gestionar el evento TableNewRow y añadir ahí el nuevo valor. Algo tal que como esto:

Partial Public Class ArchivadorDataSet
    Partial Public Class ArchivadorDataTable
        Private Sub NuevaFila(ByVal sender As Object, _
                    ByVal e As DataTableNewRowEventArgs) _
                    Handles Me.TableNewRow
            Dim MiFila As ArchivadorRow = TryCast(e.Row, ArchivadorRow)
            Dim Id As Integer
            Id = Me.Rows.Count
            Dim FilasEncontradas = From Fila In Me _
                                   Where Fila.IdArchivador = Id _
                                   Select Fila.IdArchivador
            Do While FilasEncontradas.Count <> 0
                Id = Id + 1
            Loop
            MiFila.IdArchivador = Id
        End Sub
    End Class
End Class

Así, de paso, probaba LINQ.

Puede que no sea la forma más elegante de hacerlo (que no lo será, eso es algo que aprendí en Dibujo Técnico), pero, ¡joder, qué bien sienta resolver un problema y que la solución funcione!

O, usando una frase que resume estupendamente esto (dígase con un bolígrafo en la boca): Me encanta que los planes salgan bien.

Nos vemos en el Forlon.

 

 


Deja un comentario