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

El valor no puede ser nulo. Nombre del parámetro: objectType

El error en cuestión me salía en el diseñador de Visual Basic Express 2010 de forma insistente. Una molestia a la hora de trabajar con el diseñador, más que nada, porque luego la aplicación funcionaba sin problemas. Ayer me dediqué a buscarle solución, tirando de Google.

El problema, apuntaban en varios sitios, se daba en formularios o controles de usuario heredados y estaba causado al gestionar eventos de objetos de alguna de las clases padre con la cláusula Handles en la clase hija. Tras investigar con calma mi usercontrol, encontré que el causante del desaguisado era, efectivamente, una cláusula handles que apuntaba a un evento de un BindingSource del abuelo del usercontrol. Quité el Handles y lo sustituí por un AddHandler en el constructor y todo funciona con suavidad.

Cualquiercosa x 1 = Cualquiercosa

Necesitamos un campo que siempre valga uno.

—¿Uno? ¿Por qué?

—Porque cualquier cosa multiplicada por uno es cualquier cosa. Y necesitamos pedir cualquiercosa al usuario y la única forma es ligarlo a un campo de una tabla. Con un campo que siempre valga uno podemos meterlo en un select que nos devuelva cualquiercosa * campo_que_vale_1 y meterlo en la variable con la que necesitamos trabajar.

Momento de silencio pensativo. Los engranajes mentales zumban.

—OITM. Uno de los Salesfactor. Por defecto, valen uno y sólo usamos el primero de ellos.

Viernes, 13 horas. Avanzando en una consulta puñetera en SAP B1.

MKV con subtítulos en TDT Easy Home HD10

El otro día me compré un TDT que reproducía matroskas, de la marca BestBuy. En fin, el caso es que monté el cacharro en casa, le puse un matroska (Macross Frontier, BluRay Rip 720p, versión de Nanikano, la comenté en su día) y se veía, sí, pero no los subtítulos. Mala cosa. Temiendo que el cacharro no entendiera más allá de los SRT, probé con una versión guiri de la 2ª temporada de Shana, que tengo por ahí para traducir y, efectivamente, me mostraba los subtítulos en SRT. Pero resulta que los MKV de Shana tienen dos pistas de subtítulos, SRT y SSA y reproducía ambas. Perplejo me hallaba.

Revisé otros MKV y nada, no conseguía reproducir nada. Incluso probé a extraer los subtítulos, grabarlos como SRT y ponerlos en el pendrive, pero tampoco. Así que pensé que igual con Shana funcionaba no por los formatos, sino por tener dos cadenas de subtítulos. Hice una prueba rápida: extraje los subtítulos de un episodio, los grabé como SRT y los volví a meter en el MKV. ¡Bingo! Toca jugar un poco con el botón de Subtitle del mando hasta que se da por aludido el muy hijo de su madre, pero funciona.

Me gustaría hacer un tutorial en condiciones, con capturas y tal, del proceso, por si a alguien le pasa algo similar, pero con el poco tiempo de que dispongo es imposible, así que es esta entrada en modo telegráfico o nada.

Necesitamos el MKVtoolnix tanto para sacar los subtítulos como para añadírselo de nuevo. Para sacarlos usaremos el MKVExtractGUI2, que descargaremos aparte. Para meterlos, el mmg.exe que ya viene de serie. Usaremos, si queremos, el Subtitle Workshop si queremos guardar los subtítulos en otro formato.

1) Extraer

Lo primero es instalar el MKVtoolnix. Luego, copiaremos los archivos que vienen en el zip del MKVExtractGUI2 en la carpeta del primero (lo de siempre, archivos de programa tal y tal). Ejecutamos el MKVExtractGUI2 y nos saldrá una ventana muy simple: pinchamos en el botón de Input y le indicamos el archivo que vamos a abrir. En la caja Tracks nos aparecerá el contenido del matroska. Seleccionamos los subtítulos y pulsamos en el botón Extract. Los subtítulos se habrán guardado en la carpeta indicada en Output.

2) Guardar como…

Si queremos guardar los subtítulos con otro formato, usaremos el Subtitle Workshop. Sin complicaciones: Archivo, Cargar subtítulo y Archivo, Guardar como. Lo complicado, para mí, acostumbrado a ver sólo las extensiones de los subtítulos, fue acertar con el formato. SRT es SubRip.

Un problema que podemos tener en Subtitle Workshop es la aparición de símbolos extraños en lugar de signos de puntuación, vocales con tildes y cosas así. Para evitarlo es tan simple como abrir primero el archivo de subtítulos con el venerable Bloc de Notas, Guardar como y en Codificación elegir ANSI.

3) Meter de nuevo

Con el mmg.exe (Mkvmerge GUI). En Archivos de entrada pinchamos en Añadir y elegimos los archivos con los que vamos a trabajar, el MKV y el de subtítulos. Por lo general, él solo ofrece añadir automáticamente el subtítulo. Indicamos el nombre del archivo de salida, pinchamos en Iniciar multiplexado y esperamos a que termine.

Con la última versión del programa puede haber problemas al reproducir el MKV resultante en reproductores de salón, como el TDT de marras para el que lié todo esto. La solución consiste en, antes de iniciar el multiplexado, seleccionar tanto el vídeo como el audio del archivo (en la sección de Pistas, capítulos y etiquetas), pinchar en la pestaña Opciones adicionales y, en Compresión, seleccionar explícitamente Ninguna, o bien ir a Archivo, Opciones, pestaña mmg y marcar o desmarcar (según funcione) «Deshabilitar eliminador de la compresión del encabezado para audio y vídeo predeterminadamente» (en inglés es bastante más corto).

Es un jaleo, pero realmente no lleva más de dos minutos por episodio. Es una pesadez, pero por lo menos puedo disfrutar de mis capítulos en la tele, mientras monto el sobremesa de nuevo.

En las garras del mal: Windows Phone

Estos días pasados me he hecho, by my beautiful face™, con un móvil nuevo con Windows Phone 7. Hace un tiempo comenté que pensaba hacerme de uno, pero que su falta de sincronización directa me hizo descartarlo. Con el tiempo, este descarte quedaba aún más obligado al no poder ver en directo un cacharro con el nuevo sistema de los de Redmond: está muy maltratado por las operadoras y las tiendas de telefonía. Supongo que cuando lleguen los modelos de Nokia lo veremos más, que los hierros finlandeses gozan de gran predicamento por estos lares.

Tengo móvil nuevo, decía. Un poco triste el sacarlo de la caja: el anterior que tuve, un Touch del mismo fabricante, venía en una caja negra de cartón duro y cierre magnético que aún conservo, con protector de pantalla, manual tocho, una buena funda y puntero de reserva. El nuevo, más grande y más caro, viene constreñido en una caja barata, con guía rápida y poco más. Tristes nuevos tiempos.

El cacharro es un señor cacharro, aunque no le pueda colgar la llave del Lancelot que llevo en el Touch. Mueve con soltura lo que le han metido, el nuevo invento de Spectra para dominar los móviles, después de que la malvada cool Apple cambiara las reglas hace ya su buen tiempo con el ay!fon. El mal aprende del mal y nos hace firmar un pacto con la manzana (iba a decir con el diablo, pero aquél se conformaba con el alma) limitando muy mucho lo que podemos meter en el sistema y cómo. En la semana que llevo con el cacharro ya hay algunas cosas que echo de menos del viejo, venerable y obsoleto Windows Mobile/CE.

Pero los sistemas como Mobile o el mismísimo Symbian se han quedado viejos. Ahora que vivimos (o vamos camino de vivir) en la nube (siempre que no nos preguntemos dónde están nuestros datos, no sea que nos dé un soponcio) y consumimos servicios en la nube y, en general, es moda no tener los pies en el suelo, estos viejos sistemas que nos daban el control sobre lo que sucedía en nuestro móvil se extinguen, apartados a puntapiés de la carrera evolutiva. Pasamos a darle el control de nuestro móvil a la operadora si nos daba un buen cacharro y un servicio medio decente. El siguiente paso ¿lógico? era dar el control total al fabricante. Así, leo en multitud de sitios que a Windows Phone le hace falta una buena tienda de aplicaciones, no el poder instalar esas aplicaciones sin tener que pasar por la dichosa tienda, y cosas así. Horror de los horrores. ¿Adónde hemos llegado?

En fin, podía hacer oídos sordos a los cantos de sirena de los tres diablos (el cool, el malomalo y el buenobueno) y seguir con mi viejo cacharro (SCUMMVM al poder), o pasarme a un Symbian. Pero el problema es que, aunque fui fuerte y resisit la tentación cool y la buenrollista, he terminado probando la opción maligna der tó. Y resulta que la maldita es buena de narices. Y las preocupaciones y quejas que tenía de antes (no sincroniza con Outlook, no deja instalar aplicaciones si no es a través de su tienda de aplicaciones, ¿qué pasa con mis datos?…) han pasado a un discreto segundo plano.

El infierno de los códigos de barras

Con motivo del día de Satán, también conocido como nuestro arranque con SAP B1, hemos tenido un fin de semana horroroso de preparativos que se alargó un día más de lo previsto, como colofón a una semana horrorosa y preludio a otra igual de terrible. Una de las cosas que más guerra nos ha dado la impresión de etiquetas de códigos de barra que antes las hacíamos con un programa y ahora teníamos pensado emplear informes de Crystal Reports. No había manera de conseguir imprimir un EAN14 válido. El problema añadido es que en Internet no encontrábamos mucha ayuda.

Al final, la solución vino de un foro del venerable Visual FoxPro, solución que resumo aquí como aviso para navegantes:

El EAN/DUN14 es un churro de números. Información sobre cómo componer un número válido es fácil de encontrar: que debe tener un número par de cifras, cómo se calcula el dígito de control, la norma para su composición… Pero averiguar cómo imprimir eso en un código de barras es harina de otro costal.

Para el código de barras se usa una codificación Interleaved 2 of 5, que representa dos números como un grupo de cinco líneas y espacios. Aquí viene nuestro problema, porque con una fuente de texto sola no hacemos nada: cada carácter de nuestro código de barras se corresponde con dos números de nuestro DUN14. O sea, nuestro código de barras tendrá la mitad de caracteres que nuestro DUN14. Esto nos obliga a usar un algoritmo que nos convierta nuestro chorro de números en esa cadena. Esta cadena, con un tipo de letra Interleaved 2 of 5, será nuestro código de barras.

Y aquí es donde se nos presenta el problema, porque el algoritmo depende de la fuente empleada (o al revés, vamos). De nada nos vale una fuente I2o5 si no tenemos el algoritmo. ¿Por qué? Porque los casos que he visto funcionan del siguiente modo:

Vamos recorriendo nuestro DUN14, cogiendo cada par de números. Al número que representan (por ejemplo, 58) se le suma un valor dado (digamos 52). Se obtiene el carácter correspondiente a ese código numérico (110) y ése, en nuestra fuente I2o5, se corresponderá con las cinco barras-espacios que necesitamos. Como se ve, si nuestra fuente no es la correspondiente a nuestro algoritmo, el carácter representado no tiene por qué coincidir (y no lo hará) con el buscado.

Al final, debemos tener una cadena de caracteres formada por un carácter de apertura, la cadena tratada y un carácter de fin, que nuestro lector será capaz de entender.

En el artículo que menciono antes encontramos código para generar una representación de nuestro DUN14 para una fuente I2o5 y, además, nos da la fuente. El código, pese a los nombres de las variables, es muy simple: la mayor parte de la función se dedica en calcular el dígito de control y en asegurarse que la longitud sea correcta. Si ya tenemos nuestro código válido, todo se reduce a lo siguiente:

1) Tomamos cada par de números de nuestro código (para 14 cifras tendremos, pues, 7 pares).

2) Si el número de dos cifras que componen es menor de 50, le sumamos 48. Si es mayor, 142.

3) Sacamos el carácter correspondiente a ese código.

4) Formamos una nueva cadena de caracteres con los que hemos obtenido, en orden.

5) Como cabecera le añadimos el carácter 40 y como cierre, el 41 (los paréntesis)

6) Aplicamos la fuente I2o5 que acompaña al ejemplo y listo.

La primera aplicación de esto la hice a mano, en Excel, para un código dado y la alegría de ver cómo el lector entendía el puñetero código después de tan horroroso y desesperante fin de semana es difícil de explicar.

Restaurar Menú Inicio Todos los programas en Windows 7

Si a raíz de una infección vírica se han borrado los accesos directos de Menú Inicio\Todos los programas en Windows 7/Vista y/o los iconos de acceso directo del escritorio de todos los usuarios (antiguo All Users, ahora Public), probar lo siguiente:

1) Que la carpeta AppData\Local\Temp\smtmp\1\ dentro de la cuenta de nuestro usuario (C:\Users\Usuario\AppData\Local\Temp\smtmp\1\) existe y tiene todas las carpetas y accesos directos del Todos los programas

2) Que la carpeta AppData\Local\Temp\smtmp\4\ dentro de la cuenta de nuestro usuario (C:\Users\<Usuario>\AppData\Local\Temp\smtmp\4\) existe y tiene los elementos del escritorio público.

Si es así, sólo queda restaurarlos. La forma más rápida es abrir una ventana de comando con permisos de Administrador (ojo con esto) y tirar de Xcopy:

xcopy %Temp%\smtmp\1 «C:\ProgramData\Microsoft\Windows\Start Menu» /H /I /S /Y /C

xcopy %Temp%\smtmp\4 «C:\Users\Public\Desktop» /H /I /S /Y /C

El bicho en cuestión también había hecho desaparecer todo lo existente en Users. En casos como ese es importante no dejarse llevar por el pánico. Puede haberlos ocultado o convertido en archivos de sistema. El comando Attrib es de gran ayuda en estos casos. Como antes, siempre ejecutando la consola en modo administrador.

Tocamiento de narices de un TextBox

Ando estos días programando una serie de plantillas y controles personalizados. He estado preparando un TextBox personalizado con posibilidad de validación y filtrado de datos de serie o mediante validadores/filtradores a medida que implementen cierta interfaz. Hasta ahí sin problema. Como el tipo de TextBox en cuestión va a ir montado, como regla general, sobre un formulario o un control de usuario que incorporan un sistema de gestión del estado (VerDatos, Modificar, Nuevo, etc.) se me ha ocurrido que el TextBox ajuste su estado ReadOnly automáticamente. Vale, sin problema. Ha funcionado a la primera. Salvo por un pequeño detalle visual.

Al indicar ReadOnly=True el color de fondo del TextBox cambia.

Pensé en controlar el evento ReadOnlyChanged, pero no conozco el color de fondo anterior al cambio y, francamente, saberlo complicaría más las cosas (reescribir la propiedad BackColor y el método OnReadOnlyChanged). Reescribir el método OnPaint, que de todas formas ni se me pasaba por la cabeza, no serviría porque, leído aquí, el cambio de color provocado por el cambio de ReadOnly se hace «por su cuenta» (Actually, .Net TextBox is a simple wrapper of Win32 Edit control, and it passes the painting issue to the Win32 Edit control). En el mensaje en cuestión se propone sobreescribir la propiedad ReadOnly del TextBox, algo que parece sencillo, simple y razonablemente elegante.

Salvo por otro pequeño problema: ReadOnly es una palabra reservada. Todo intento de crear una propiedad que se llame ReadOnly es frustrada por el propio IDE. ¿Cómo se hace para llamar a una propiedad como una palabra reservada?

Este pequeño problema me ha llevado bastante rato solucionarlo, así que lo comparto. Es una tontería y tiene su lógica, pero me ha hecho perder un tiempo precioso.

Usar corchetes:

Public Overloads Property [ReadOnly] as Boolean

En fin, sigamos…