24 enero 2006

Pruebas de binding, y estudio de filtrado

Después de un nutrido banco de pruebas realizado por Caña, nada menos que 10 pruebas, parece claro que la implementación de nuestra colección de negocio debe ser una BindingList. No directamente, sino una colección que herede de ella, para contar con su funcionalidad. En resumen, las pruebas realizadas consistieron en enlazar (FListado o FObjeto) -> [BindingSource] -> [List o BindingList] -> objeto(s) y las conclusiones son:
  • No importa de qué forma combinemos un BindingSource con un List, la inclusión de nuevos objetos en la colección no se refresca en el interfaz de usuario. Para esto, es necesario que la colección implemente IBindingList.
  • Si conectamos el interfaz de usuario a la colección directamente, esta debe ser IBindingList para refrescarse, en otro caso (que sea una List) el interfaz sólo se refresca manualmente.
  • En cambio, al conectar directamente el BindingSource al objeto omitiendo la colección, sí se refresca todo correctamente, por lo que es un método válido para las Fichas. No lo consideramos para listados, ya que el BindingSource es un componente de interfaz. ¿La colección de negocio podría heredar de BindingSource en lugar de hacerlo de BindingList? No es este el planteamiento de la librería .NET ni de los que diseñaron el enlace a datos.
Una vez que conseguimos mostrar datos en un DataGridView, ahora nos encaminamos a la realización de filtros y ordenaciones sobre dichos listados. En principio estamos construyendo la interfaz gráfica para esto, nuestra archiconocida línea de filtros, aunque ligeramente potenciada adaptándose al tipo de cada columna. Una vez desarrollado el interfaz, veremos quién se encarga de realizar el filtro. Hay una cadena de objetos (UCListadoFiltros -> FListadoBase -> DataGridView dgv -> BindingSource bind -> EEmpleadoCol col -> DEmpleado oad), y la responsabilidad puede caer en cualquiera de ellos. En el caso de la ordenación, es muy probable que la implemente el dgv (pendiente probarlo), lo que nos facilitaría enormemente el trabajo. El caso del filtrado es mucho más complejo, dudo que lo haga el dgv, por lo que tenemos otras opciones:
  1. El bind ofrece el interfaz para hacerlo (eso sí, muy sencillo, tipo cláusula WHERE de SQL), pero no ofrece su implementación, sino que confía en la posible implementación del DataSource del que bebe. En este caso dicho DataSource es nuestra colección, que en su diseño actual (heredando de BindingList) no ofrece esta funcionalidad. Si no encontramos clases en la librería que nos permitan realizar fácilmente esta funcionalidad, es muy posible que esta opción sea descartada.
  2. Pero aún en el caso de no implementarla, la colección ofrecerá la funcionalidad. ¿Qué quiero decir? Que si la colección no sabe filtrarse, tendrá que buscar otro sistema: realizar el filtro directamente sobre la base de datos, con el sobrecoste de rendimiento que supone, pero de forma transparente para el objeto llamante. Esto nos situaría en igualdad de rendimiento con la implementación actual de Motorwin en VB6, donde todo filtro es convertido en SQL y resuelto por la BD, aunque sea un subconjunto de los datos que antes estábamos mostrando. No nos gustaría tirar por este camino, así que pensamos en una tercera opción.
  3. Utilizar la funcionalidad de filtros que ofrece ADO.NET. No olvidemos que ahí está todo hecho (recordar el antiguo modelo de cursor del lado de cliente de ADO 2.0), aunque trabajando con una representación concreta de la base de datos, por lo que no nos sirve para tratar directamente con nuestros objetos de negocio. Así que ¿cómo podemos aprovechar esta funcionalidad? En lugar de llegar a repetir la consulta a la base de datos, ya en la DAL, si somos capaces de detectar que el nuevo filtro siempre será un subconjunto del anterior, podemos aplicar el filtro al DataSet (posiblemente usando un DataView) y refrescar la colección, quizá manteniendo 2 colecciones paralelas, una de objetos del filtro actual y otra con todos los objetos originalmente cargados. Este es más costoso de implementar, como vemos, pero por la mejora en el rendimiento es muy deseable. Y no debemos olvidar que el filtro sobre un filtro anterior para ir afinando una búsqueda es de lo más habitual que hace un usuario sobre un listado, por lo que cuanto más eficiente sea, mejor.
La implementación de la colección de negocio queda pendiente, actualmente vamos a trabajar en el interfaz con una colección poco dinámica (que no notifica sus cambios), no necesitamos más para probar.

20 enero 2006

Aprendemos a mostrar listados

Estamos comenzando a mostrar listados en pantalla. De primera mano conseguimos conectar un DataGridView con una colección de objetos de negocio mediante un BindingSource que hace de puente (aunque no es necesario ese puente ya que un IList puede usarse como DataSource). Comentario al canto: para conectar controles individuales (de una ficha de detalles) con propiedades de un objeto de negocio sí necesitamos usar un BindingSource intermedio, ya que el objeto de negocio en sí no vale como DataSource. En cambio, si el objeto de negocio está contenido (sólo o acompañado) en una colección, dicha colección puede ser un DataSource. Pero ojito con todo esto: esta conexión directa puede ser suficiente en algunos casos, pero no propaga cambios desde el negocio, me explico: se actualiza el control a partir del objeto de negocio, manualmente, y los cambios en los controles son realizados automáticamente en el negocio; pero si se modifica el objeto por código, el control no es actualizado automáticamente. Para conseguir esto, son necesarias 2 cosas: que el objeto de negocio implemente INotifyPropertyChanged disparando un evento en cada Set, y que los objetos de negocio sean contenidos en una BindingList para que procese esos eventos hacia arriba (hacia el DataGridView). No sé si cualquier IList procesa esos eventos, pero creo que no porque si no no tendría sentido que existiera BindingList. Está pendiente de probar esto.
Uno de los ejemplos (buscado por Caña,
DataGridView App using BackgroundWorker for Async Data Load de Mark Rideout) nos servirá para realizar la carga asíncronamente, en segundo plano. En el ejemplo, se trabaja sobre una BindingList que va creciendo leyendo archivos del disco duro, lo que refuerza la teoría de que nuestras colecciones de objetos de negocio deben implementar BindingList.
Por último, investigando en otra dirección: la configuración automática del DataGridView a partir de metadatos del objeto de negocio. Una primera aproximación, asignar el BindingSource directamente al DataGridView en tiempo de ejecución, funciona si tenemos activo el atributo AutoGenerateColumns del grid, pero muestra todas las propiedades (no nos permite seleccionar). Hemos encontrado un ejemplo bastante completo, de nuevo usando BindingList (la panacea), en este caso heredando de dicha clase para personalizar la lista de columnas que se ofrece. Esto nos permite crear vistas personalizadas de las clases de negocio, mostrando o ocultando ciertas columnas. Además aprendemos a utilizar el atributo Browsable para evitar que la propiedad Id se vea nunca. Y también descubrimos el atributo DisplayName para poner un nombre de columna a cada propiedad (que también puede usarse para los nombres de las etiquetas en los formularios de detalles). Sólo hay una cuestión abierta: al cargar automáticamente las propiedades como columnas, las devuelve en un orden aleatorio, no en el que están escritas en la clase. La documentación dice que esto es así (incluso puede cambiar entre 2 llamadas a GetProperties), que no puede hacerse nada para solucionarlo. La opción de incluir más metainformación con el orden de las propiedades en el objeto de negocio es fea, ya que para ordenar tenemos que numerar las propiedades, y esto dificulta la inserción de nuevas propiedades. Así que de momentos ignoramos este problema, ya que con las vistas del ejemplo anterior sí podemos definir en qué orden se verán las columnas.
Por último, y volviendo al DataGridView, un capítulo de ejemplo donde se enseña a configurar su apariencia. Y una pregunta al aire: en VS2003, el DataGrid tenía la posibilidad de cambiar de estilos mediante
DataGridTableStyle; parece que no hay nada similar para el DataGridView de VS2005. ¿Cómo se consigue esa funcionalidad? ¿O es que en Microsoft andan como los cangrejos?

05 enero 2006

Imprimir objetos de negocio

Para imprimir tenemos varias alternativas, así que como introducción voy a comentarlas todas:
- Impresión matricial (o para ticket): consiste en usar la API de Windows para enviar líneas de texto directamente a la impresora. En .NET debe haber alguna clase en la jerarquía para conseguir esto, así que quien se anime, a buscarlas.
- Impresión matricial según plantilla XML (también válida para ticket): es un refinamiento de la anterior, en el XML definimos qué poner en cada línea (qué campos, qué etiquetas, con qué longitud...) y una clase propia se encargaría de ir procesando el XML e ir generando las líneas concretas con datos reales procedentes de un objeto de negocio, de un XML, etc.

- Impresión de calidad en HTML. Posiblemente usando XSLT sobre un XML, todas tecnologías conocidas.
- Impresión usando Word. Muy útil para documentos de tamaño fijo (como contratos).
- Impresión usando Excel. Muy útil para formularios prediseñados como una orden de trabajo. Ofrece una paginación muy difícil, para mostrar cabeceras y pies en todas las páginas del documento.

- Impresión mediante sistema de informes (como Crystal Reports). Este es el más potente, ya que incluye la posibilidad de agrupaciones y fórmulas en el propio informe, cosa que ninguno de los anteriores incluye, y una paginación muy sencilla por secciones. Pero tradicionalmente ha estado diseñado para conexión directa a la base de datos. Como no queremos que sea así, las nuevas versiones incluyen la posibilidad de conectarse a objetos de negocio, tal como explica el enlace de este post, y como explica también el Walkthroughs de Crystal para VS2005, a partir de la página 354 (a un nivel muy básico, con ejemplo de cómo crear los objetos de negocio, cómo diseñar el informe y cómo pasar después los datos reales).
La alternativa a Crystal es ActiveReports.NET 2.0 SP2, que permite algo similar a esto: enlazar un informe a cualquier colección de objetos que implemente IList. Tenemos que valorar qué funcionalidad ofrece el Crystal Reports que viene con VS2005, si nos es suficiente o necesitamos más, y si necesitamos más, si está mejor en Crystal o en ActiveReports.
Otra cuestión a considerar es el editor de informes para el usuario. Ambos lo incluyen (aunque no creo que venga en la edición de Crystal de VS2005), así que habrá que probar si son viables, fáciles de usar y demás.
Y por último, respuesta a eventos del informe: capturar cuando el usuario haga clic (o doble clic) en un dato del informe, para mostrarle el origen de datos real (por ejemplo, abrir el albarán, o la ficha de la pieza) o la lista de elementos que sumados producen ese valor (esto puede ser complejo, primero vamos a centrarnos en lo del evento y más adelante veremos cómo justificar un total en un informe de la forma más genérica posible).

03 enero 2006

Disparar excepciones por el binding

Lo siguiente que pensé tras realizar bien y fácilmente el Binding entre un formulario y un objeto de negocio fue propagar los errores del objeto como excepciones que capture el formulario (a través de eventos del bind). Pues bien, además de la poca documentación que existe por Internet y en la ayuda sobre esto, sobre el evento DataError que no hay manera de disparar, hemos encontrado este artículo, donde queda bien claro que no es posible (aunque no porqué, eso deberá aclararlo Microsoft). Así que de momento tenemos una aproximación usando el evento BindingComplete, que se dispara a punta de pala, pero creo que nos hará el avío.

Otra cuestión abierta es el tratamiento de las propiedades de un objeto de negocio. Si antes teníamos un enumerado de NP, ¿tenemos que mantener algo parecido? ¿Nos vamos a referir a las propiedades por su nombre directamente (usando reflexión cuando sea posible)? Esto impide algunas cosas, por ejemplo antes teníamos un Select Case, pero en C# no es posible implementar un switch de una variable string, sólo numéricas. A pensar se ha dicho.

// Aportación de Berny, que no se acostumbra todavía a escribir aquí, y que encima no recibirá nunca un aviso de este blog porque esta registrado con el email bernardo@nobisoft.com (a ver si lo puedes arreglar en tus datos de usuario de Blogger).