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:
-
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.
-
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.
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.