Si no es por las buenas…

El otro día, intentando filtrar un DataView, me encontré con un problema pintoresco: quería filtrar los usuarios con determinados permisos en la aplicación, para lo cual necesitaba hacer una comparación bit a bit. Hacer esa comparación en SQL no supone problema (Permisos & PermisoPedido = PermisoPedido). En VB, tampoco (UsuarioActivo.Permisos AND PermisoPedido = PermisoPedido), pero la propiedad RowFilter del DataView es mucho más restrictiva. Usa sintaxis SQL, pero con muchas limitaciones.

En la Geekpedia encontré una solución al problema: pasar de las operaciones bit a bit y resolver el problema con operaciones decimales normales y corrientes:

(Convert((Permisos – Permisos % PermisoPedido) / PermisoPedido,’System.Int32′) % PermisoPedido = 1)

Donde PermisoPedido es el valor numérico buscado de nuestra enumeración (1, 2, 4, 8, 16…). El operador % es, en Visual Basic, el módulo (resto de la división entera).

Hola, si lo hacemos mal os las apañáis vosotros

Tengo un amigo que trabaja en un distribuidor de cierta compañía de telefonía. Oírle hablar de los programas informáticos que usan para hacer su trabajo me demuestra que no sólo es el trato al cliente lo que falla en las empresas de telefonía y que la externalización a bajo coste de los servicios incluye algunas áreas que debieran ser básicas para el funcionamiento de la empresa. La última que me ha contado va de un problema para dar de alta un servicio a un cliente. Un problema que, tras pruebas varias y la intervención del soporte técnico que, como distribuidor, tiene a su disposición, tiene todas y cada una de las papeletas de ser un bug del programa. Así que se abre una incidencia a los responsables del susodicho programa.

Que empiezan a marear la perdiz. Lo suficiente como para que la promoción caduque y se retire dicha opción del programa. Dado lo que sé del programa, me imagino una subcontrata con becarios y picacódigos con poca experiencia, mal pagados y echando horas a manta, con una motivación por los suelos y una productividad e interés por hacer bien su trabajo en consonancia (modificada a la baja por la falta de respeto que puedan recibir de sus jefes). Ante la queja del usuario y para dos días que faltan del tema, que le den a él y a su cliente. Y luego, todos, así te las apañes con tu cliente, que no es problema nuestro.

Ya la versión 2… cómo pasa el tiempo

La semana pasada cerré el diseño de la versión 2.0 (nombre clave «La Cigala») de nuestro gestor de incidencias, ahora ampliado también a gestor de proyectos. Dos meses de trabajo para una versión preliminar a la que aún tengo que echarle mucho trabajo. Pero ya casi está. Ahora tengo que preparar un par de programitas auxiliares para importar datos a cascoporro desde unos Excel que se usan ahora y a quienes mi criaturita va a jubilar.

Da gusto ver el trabajo de uno en funcionamiento y que los usuarios lo acepten y usen.

No debería haber pasado

—¡Permiso para ir a Donde-sale-el-sol y caparle, señor!

—Ya lo haré yo el lunes.

Los viernes son terroríficos. Siempre pasa algo. Que además de viernes sea el día en que sensei empieza vacaciones es tentar al diablo, así que senpai y yo estábamos con la mosca detrás de la oreja y esperando… Esperando…

A media mañana, el caos: los usuarios avisan de que no pueden entrar en SAP. Donde-sale-el-sol ha desaparecido del mapa. Y bien desaparecido: no responde ni por MPLS ni por ADSL. ¡Ni siquiera por teléfono! ¡Un meteorito ha arrasado la fábrica! ¡Nos invaden los extraterrestres!

La cara de sensei, al móvil, mientras le van dando partes, es todo un poema. Un lumbreras de mantenimiento, sin avisar, se ha puesto a hacer pruebas con las líneas eléctricas. Como resultado, ha tumbado la luz en la fábrica.

—No lo entiendo. No debería haber pasado.

Y la línea de los SAIs. Y con ella, los routers, los switches, el MPLS, la centralita… y todos y cada uno de los servidores.

—Pero si no debería haber pasado nada.

Y mientras intentábamos arrancar todo y lo chequeábamos, yo iba haciendo mentalmente la lista de cosas que necesitaba para un fin de semana de trabajo no planeado, por si las moscas. E incluía una escopeta.

Aghhhh, quiero morir

En algún momento, pareció buena idea. Desdoblar incidencias para cubrir también peticiones de servicio. Se trataba sólo de añadir un campo a tres tablas y luego gestionarlo en tres formularios.

Ya, claro.

Pero había cuatro vistas de esas tablas. Y luego procedimientos almacenados sobre esas vistas y esas tablas. Que eran llamados por los tableadapters de cinco datasets tipados, que los usaban para trabajar con esas vistas y esas tablas. Una docena de tableadapters, más o menos. Muchos métodos llamados (por fortuna) desde unos pocos sitios bien controlados.

Un horror de día.

Las datarow pasan a modificadas al navegar entre registros

He tenido durante meses un problema pintoresco: en una aplicación de Windows Forms con enlace de datos (datasets tipados, BindingSource y demás) se me cambiaba el estado de las datarow a modificadas sólo con navegar entre los registros. Dado cómo se actualizaban los datos a la base de datos, resultó ser un problema menor, pero molesto. El problema parecía estar en el enlace de datos (binding) de usercontrols. En su día no encontré nada en MSDN y lo dejé para otra ocasión. Ayer, buscando otra cosa, encontré la solución al problema.

Resumiendo: si buscamos ayuda sobre cómo hacer un enlace de datos simple en nuestro control personalizado, nos dicen que usemos <BindableAttribute(True)>:

<BindableAttribute(True)>
Public Property IdAccion As Integer

Pero resulta que, con sólo eso, el BindingSource supone que siempre cambian los valores enlazados y actualiza el origen de datos. Resulta que además debemos implementar la interface INotifyPropertyChanged y notificar cuándo cambia el valor, tal que de esta forma:

<BindableAttribute(True)>
Public Property IdAccion As Integer
    Get
        Return Me._IdAccion
    End Get
    Set(value As Integer)
        If _IdAccion <> value Then
            Me._IdAccion = value
            RaiseEvent PropertyChanged(Me, _
                    New PropertyChangedEventArgs("IdAccion"))
            Me._IdAccion = value
        End If
    End Set
End Property

Sigo, eso sí, sin haberlo visto tan bien explicado en la documentación. Menos mal que existen los foros y los blogs.

Pero, ajá, no es la panacea. Ahora me encuentro con que, a veces, el valor no cambia aunque debería. Por ejemplo, al añadir un nuevo registro que tiene (o tendrá) el mismo valor de la propiedad que el anterior. Hay que revisar estos casos con cuidado para, o bien asegurarnos de que cambia el valor de la propiedad o bien provocar a mano el evento cuando lo creamos necesario. En fin, problemas del enlace a datos.

Datasets tipados, amor y odio

Llevo varios meses con un proyecto que empezó siendo algo pequeño (sustituir una aplicación Access de gestión de incidencias por un desarrollo en .NET, por temas de falta de licencias de Access para todos los usuarios) y ha ido creciendo hasta el punto que mataría por tener una semana para documentar con calma todo lo que llevo hecho. Este crecimiento, aunque lo tenía en mente (no tanto, claro) cuando diseñé la aplicación, me hace dudar de ciertas decisiones que tomé en su día, como el uso de datasets tipados.

Supongo que la idea de usar datasets tipados con VB2010 teniendo Entity Framework puede parecer rara o anacrónica, pero llevo usando datasets desde 2005 y tirar de ellos me ahorraba un tiempo de aprendizaje que no tenía. Ahora, con 17 datasets tipados (y creciendo) no sé hasta qué punto fue buena idea.

Entendámonos, me gustan porque el asistente es cómodo y puedo personalizarlos rápida y fácilmente. Se han convertido en poderosas (y pesadas, me temo) entidades que se mueven entre la aplicación tanto con los datos como con las órdenes de qué hacer con ellos.

El problema viene cuando tengo que reconfigurar alguno, porque se ha añadido algún campo a la base de datos o, como está pasando estos días, ha habido que hacer cirugía mayor y donde antes tiraban de una tabla ahora lo hacen de una vista. Esa forma de machacarse la configuración de los campos existentes, esos problemas con el AllowDBNull y con los NullValue…

A veces odio los datasets tipados.

El problema de la barra de progreso

El año pasado, intentando desembarazarme de una tarea tediosa que dejaba frito mi equipo, usé mi primera función recursiva con utilidad práctica. Como la tarea consumía su tiempo, tiré de un BackGroundWorker. Componente sencillo de usar y con ejemplos bastante claros en la ayuda de Visual Studio. No problem: tenía un valor de entrada pedido por el usuario, se realizaban n operaciones de forma recursiva, se obtenía un valor de salida.

Como el proceso era algo largo (mínimo unos diez segundos, pero podía llegar superar el minuto en la versión 2, que admitía de entrada una serie de valores), se me ocurrió usar una barra de progreso para mantener informado al usuario.

Entonces empezaron los problemas.

Todos los ejemplos que he visto que usan el evento ProgressChanged del BackGroundWorker para alimentar una barra de progreso presentan una serie de operaciones en secuencia con llamadas a ProgressChanged entre ellas, es decir: hago paso 1; progreso cambiado; hago paso 2; progreso cambiado… Sin embargo, mi problema era otro: tengo una instancia de mi clase de trabajo, cojo los valores del usuario (a través de DoWorkEventArgs.Argument), se los paso, y le digo «ea, majo. Tú puedes» llamando al método MiClase.HacerCurro. Ahí dentro se harán las n operaciones (recursivas), número desconocido hasta que se hagan.

Vale, pensé. Puedo provocar un evento OperaciónCompletada cada vez que complete una operación. Pero la instancia de MiClase está declarada dentro del DoWork del BackGroundWorker. No tengo acceso desde fuera. ¿Cómo lo hago?

¿Y si meto alguien que escuche en el DoWork? De esta forma tendría mi «escuchador» de eventos en el mismo hilo. Es decir, una clase, digamos Escuchante, a la que le paso mi instancia de MiClase y la del BackGroundWorker. Escuchante gestiona el evento OperacionCompletada provocando a su vez el ProgressChanged, que actualiza la barra de progreso.

No sé si será una solución muy limpia, pero funcionó.

En resumen, algo así:

Public Class Escuchante

Private WithEvents _MiClase as MiClase

Private _MiBGW as BackGroundWorker

Public Sub New(mc as MiClase, bw as BackGroundWorker)

_MiClase=mc

_MiBGW=bw

End Sub

Private Sub OpCompletada(sender as Object, e as OperacionCompletadaEventArgs) _

        Handles _MiClase.OperacionCompletada

_MiBGW.ReportProgress=e.NumOp

End Sub

End Class

Calc es para los pobres

Este año he sido especialmente parco en entradas en este blog. Lo tengo casi abandonado, con entradas a medio redactar, distintos temas por probar para cambiar el que tiene… Ha sido un año lleno de historias y de material que publicar, no ha faltado el trabajo: una migración a SAP B1, nuevos equipos, programación a saco, anécdotas varias… Un año completo y divertido en el curro. También muy cansado, que me dejaba sin ganas de tocar otro ordenador al llegar a casa. Intentaré estos pocos días que tengo de vacaciones contar algunas y que no parezca Quemando Cromo un blog abandonado.

El título de la entrada de hoy es una frase lapidaria de mi jefe de departamento cuyo origen hay que buscarlo en un problema de licencias que nos encontramos a principios de año: los puestos de trabajo crecían y faltaban licencias de Microsoft Office (el 2000 teníamos). Teníamos entonces una gran dependencia de Outlook, Excel y Access, pero el precio de las licencias de Office resultaba prohibitivo para todos los puestos que teníamos, así que estudiamos alternativas.

Outlook fue sustituido sin más problemas por Thunderbird + Lightning. Necesitábamos un cliente de correo con calendario y tareas y el pájaro de trueno cumplió con creces. Con Access el problema desapareció por sí solo: había muchos aplicativos en Access que atacaban la base de datos del ERP. Pero este iba a ser sustituido por SAP B1, así que muerto el perro…

El tercer problema era Excel. Probamos tanto OpenOffice.org como LibreOffice como Lotus Symphony. Elegimos finalmente LibreOffice confiando en su futuro frente a OpenOffice.org y tras tener graves problemas de estabilidad con Lotus Symphony. Migramos con ilusión intentando contagiar nuestro entusiasmo para cortar las quejas tontas (tal opción se llama distinta, los botones no son iguales, el desplegable de colores es peor porque es distinto…).

Después de varios desesperantes meses, hubo que claudicar, re-evaluar y adquirir cierto número de licencias para algunos puestos claves. Simplemente, Calc no está a la altura. Tuvimos unos meses de horror, plagados de archivos corruptos al guardar y de pérdida de información (hiperenlaces y vínculos externos). A la hora de buscar cómo migrar ciertos libros de Excel complejos a Calc nos topamos con la falta de documentación, falta de movimiento en los foros oficiales… Cuando llegamos a las tablas dinámicas y las conexiones a SQL Server, tiramos la toalla. Salía más caro intentar usar Calc (tiempo perdido en varios departamentos, falta de información en el momento clave o inconsistencia de la misma) que volver a Excel.

A fin de cuentas, Excel es Dios.

PD: como yo no ocupo un puesto clave, uso Calc. Total, para lo que sé y necesito hacer me basta. Voy más lento de lo que podría ir con Excel 2007-2010 (falta de herramientas, están más ocultas), pero para una vez al mes que lo uso me vale.

Quiero mi Date Null, gracias

Quiero mi bo cadillo decía ese gran filósofo moderno que es Homer Simpson. Algo parecido he estado yo pidiendo estos días. El problema se me ha presentado en una aplicación con acceso a datos, con un montaje convencional: dataset tipado + bindingsource + controles en el formulario. Uno de los campos era una fecha y podía tener valores nulos. Hasta ahí bien. El problema es que en ediciones podía recuperar el valor nulo. En esos casos, fallaba la validación de datos y no se guardaba nada, aunque tampoco conseguía saber por qué: tuve que echar mano del evento BindingComplete del BindingSource para enterarme que se producía un error en la conversión de tipos. El tipo Date no acepta nulos.

Tras una larga búsqueda en internet, casi todo en inglés, averigüé dos cosas: que era un problema común y muy molesto y que no tenía solución fácil. También hallé unas pistas que me indicaron el camino. Un trabajo no muy limpio, pero que funciona, dividido en dos partes: el enlace al control y la datatable correspondiente.

En enlace, usando el diseñador (DataBindings, Avanzado) para asignar un formato de fecha corta, queda así:

Sigue leyendo