Por tercer año he publicado en el foro una postal navideña realizada con Velneo en la que, además de felicitar las fiestas, pongo en práctica mis conocimientos en aquellos aspectos que pueden dar cierto dinamismo gráfico a nuestras aplicaciones.

Aprovecho para hacer un llamamiento a todos los que lean este artículo, por favor, no dejéis morir el foro de Velneo. Creo que es la mejor herramienta que tenemos para que nuestro conocimiento quede grabado y con acceso inmediato a los nuevos discípulos de Velneo.

Este año tocaba desear felices fiestas empleando uno de los objetos largamente esperado por los programadores, el objeto nativo Gráfico.

Los señores de Velneo nos vienen negando desde hace años la disponibilidad de un objeto nativo Canvas que permita aportar a nuestras aplicaciones un punto de libertad a la hora de mostrar elementos gráficos. En su defecto debemos usar el Visor html o QML tal como vimos en la primera y segunda postal navideña respectivamente. Estas soluciones alternativas están muy bien, pero nos encontramos con problemas de rendimiento y multiplataforma, sin contar que debemos aprender lenguajes y técnicas de programación muy poco LifeIsSoft.

Como ya he dicho, este año había que hacer algo con el objeto Gráfico. Este nuevo objeto es el resultado de incorporar a Velneo el módulo QtCharts de Qt, lo que nos permite crear gráficos empresariales tanto en su modalidad de Widget nativo o en su modalidad de Tipo QML (con import QtCharts 2.x).

Teniendo en cuenta que el objeto Gráfico es en realidad un objeto Canvas al que se ha dado una funcionalidad específica, veamos como podemos usarlo para otros menesteres que espero te sorprendan.

Objetivo de este año: Postal navideña con gráficos nativos.

Este año había que mostrar algo dinámico usando el Gráfico en su forma nativa.

El elemento dinámico debía ser una frase de felicitación que contuviera el nombre del Usuario conectado y además hubiera un desplazamiento horizontal a través de la pantalla. Lo más parecido es un Rótulo de leds que vemos habitualmente en las estaciones de autobús o tren para informar de diferentes eventos.

El Rótulo led debe tener los siguientes elementos dinámicos:

  • La frase o mensaje de felicitación es configurable y debe contener el nombre del usuario conectado
  • El Rótulo desplaza horizontalmente la frase de derecha a izquierda y de forma cíclica
  • Los leds luminosos podrán variar de color o de brillo

Diseño del Rótulo LED para mostrar Mensajes

¿Qué tiene que ver un Rótulo led con un objeto Gráfico de Velneo? En realidad mucho.

Un Rótulo led no es más que una matrix de puntos de luz que podemos programar accediendo a ellos mediante sus coordenadas x,y. Por lo tanto, si disponemos de la posibilidad de crear gráficos XY, ya lo tenemos casi hecho. Nos faltará añadir el desplazamiento horizontal y el cambio de color.

Un Gráfico nativo de Velneo al igual que una Rejilla necesita una Lista como entrada para poder representar los datos correspondientes. El Gráfico, por lo tanto, se insertará siempre en los formularios como un control Vista de datos con el correspondiente Proceso que alimenta la Lista de datos a mostrar en el gráfico. Esta característica de Velneo es muy potente y lifeifsoft, ahorrándonos muchas líneas de código, pero también es su talón de aquiles en entornos de redes lentas como Internet.

En Velneo el tipo de gráfico se determina con la propiedad Tipo serie del subobjeto Serie. Para representar un gráfico XY debemos insertar en el Gráfico un subobjeto Serie de tipo Puntos. Los puntos del gráfico se muestran mediante las 2 coordenadas X,Y de los ejes horizontal y vertical respectivamente.

En realidad lo que vamos a hacer es usar el Gráfico nativo como un control Canvas mediante la gestión de coordenadas XY.

Diseño de los caracteres del MENSAJE PARA EL RÓTULO

Para poder construir una frase de forma dinámica debemos disponer del diseño de todos los caracteres posibles en formato de matrix X,Y.

Establecemos una matrix de 5×7 puntos para representar los caracteres.

Construimos una tabla LETRAS_LED con 2 campos, uno con el carácter a representar y otro con una cadena de longitud 7 caracteres con valores posibles de espacio y 1. El espacio indica led apagado y 1 indica led encendido.

En la imagen siguiente se muestran las matrices de las letras A, B, C y D. Cada línea es un registro de la tabla y hay 6 registros por letra, 5 registros de la matrix más 1 de separación entre letras.

La carga de la tabla LETRAS_LED se realiza una sola vez en el ON_INIT_SERVER.

Lista de puntos X,Y de la Vista de datos del Gráfico

Para alimentar la Vista de datos del Gráfico XY necesitamos una tabla con las coordenadas XY ordenadas por el eje X. Estas coordenadas XY serán las que se corresponden con los leds iluminados del Rótulo.

Cada registro de la tabla LETRAS_LED nos proporciona un valor X y hasta 7 valores Y correspondientes a las posiciones de la cadena cuyos valores sean 1.

Por ejemplo la letra C tiene los siguientes valores X,Y que forman la matriz de leds en el rótulo:

eje Y
|               2,7  3,7  4,7
|        1,6                       5,6
|        1,5
|        1,4
|        1,3
|        1,2                       5,2
|               2,1  3,1  4,1
|_____________________ eje X

Necesitamos un proceso PRO_ROT_FRASE_3P que nos devuelva desde la tabla LETRAS_LED las coordenadas de cada una de las letras de la frase que vamos a mostrar en el Rótulo.

Al principio se pensó en rellenar con las coordenadas una tabla en memoria en primer plano, pero lamentablemente en cloud eso es inviable.

Velneo debería revisar el funcionamiento de las tablas en memoria para que en local fuera optativo el crear transacción en el servidor. Esto abriría una multitud de posibilidades en aplicaciones en cloud que necesitan generar listas personalizadas de datos temporales para alimentar objetos de Interfaz como por ejemplo Informes o Gráficos.

De momento se ha optado por una solución más óptima. Ejecutamos en tercer plano el proceso PRO_ROT_FRASE_3P para rellenar la tabla en disco  GRAFICO_XY_3P con las coordenadas de la frase que se mostrará en el Rótulo. Rellenar la tabla en tercer plano y cargar la Lista es mucho más rápido que hacerlo con una tabla local en memoria.

Rem PROCESO PRO_ROT_FRASE_3P
Rem ( Recibe la Frase y el Nombre para identificar las coordenadas en la Tabla )
Rem ( Insertamos NOMBRE, convertimos a mayúsculas, quitamos acentos y sustituimos los espacios por _ )
Set ( _CFRASE, replaceString(removeAccents(toUpper(replaceString(_CFRASE, "[[NOMBRE]]", _CNOMBRE))), " ", "_") )
Libre
Rem ( Rellenamos la Tabla del Gráfico XY desde la tabla LETRAS_LEDS )
For ( NLETRA, 0, NLETRA < len(_CFRASE), 1 )
        Set ( CLETRA, mid(_CFRASE, NLETRA, 1) )
        Rem ( Carga los registros que componen la Letra )
        Cargar lista ( LETRAS_LED@0PS_Navidad_dat, NAME, CLETRA, , , )
                Recorrer lista solo lectura
                        Set ( CLINEA, #LEDS )
                        Set ( NCON_X, NCON_X + 1 )
                        If ( ! isEmpty(CLINEA) )
                                For ( N, 0, N < len(CLINEA), 1 )
                                        If ( mid(CLINEA, N, 1) = "1" )
                                                Crear nueva ficha en memoria ( hGraf, GRAFICO_XY_3P@0PS_Navidad_dat )
                                                        Modificar campo ( NAME, _CNOMBRE )
                                                        Modificar campo ( VALOR_X, NCON_X )
                                                        Modificar campo ( VALOR_Y, N+1 )
                                                        Modificar campo ( TIPO, "M" )
                                                Alta de ficha ( hGraf )
                                                        Libre

Configuración del objeto Gráfico XY (tipo Puntos)

Una vez que ya tenemos diseñado el procedimiento de obtención de las coordenadas de la frase a mostrar en el Rótulo, ya podemos configurar el objeto Gráfico nativo de Velneo GRF_ROTULO_LEDS.

No seleccionamos ningún Tema. Desactivamos la Animación y Menú de contexto y ajustamos los márgenes para aprovechar el espacio de los ejes ya que no los vamos a mostrar. Se puede ocultar el Título si ponemos a cero el canal Alfa del Color.

Añadimos la Serie ROTULO de Tipo Puntos. El Eje X (Categoría) se rellena con el campo #VALOR_X y el Eje Y (Valor) con el campo #VALOR_Y. El Tipo de categoría (coordenadas X) es Numérico y ocultamos las Etiquetas de los valores X,Y. El color de la serie lo determina la variable local CCOLOR que usaremos para atenuar y aumentar el brillo de los leds.

Añadimos un subobjeto Eje de Tipo Eje categorías EJE_X. El Tipo de categoría Numérico, sin Título, sin Etiquetas y sin Línea. Los valores Mínimo y Máximo se determinan mediante las variables locales NEJEX_INI y NEJEX_FIN que como veremos luego son las que desencadenan el refresco del gráfico.

Añadimos un subobjeto Eje de Tipo Eje valores EJE_Y. Sin Título, sin Etiquetas y sin Línea. Los valores Mínimo y Máximo se ajustan manualmente para conseguir una altura correcta del Rótulo.

El diseñador nos muestra el siguiente gráfico. Los círculos representan las coordenadas X,Y de los leds.

Echamos en falta poder cambiar el tamaño de los Puntos para ajustar el tamaño del Rótulo (un despiste tonto del equipo de desarrollo). En la versión QML existe la propiedad markerSize y también se puede cambiar la forma con la propiedad markerShape.

Formulario con el Rótulo del mensaje

Ahora diseñemos un Formulario en el que podamos integrar toda la funcionalidad de este ejercicio.



    • En la parte superior introducimos la frase que deseamos mostrar en el Rótulo.
      El botón <Actualizar> ejecuta el proceso PRO_ROT_FRASE_3P y Recalcula la Vista de datos para obtener las coordenadas XY de la nueva Frase.
    • La Vista de datos GRF_ROTULO con el Gráfico XY (o de Puntos) debe tener una altura fija de 210px.
      Esto es debido a que los caracteres tienen una altura mínima limitada a 7 leds de tamaño fijo (ya que como hemos visto no disponemos de la propiedad markerSize).
      La anchura de los caracteres también debe ser fija y se ha establecido en 110px.
    • El control RUEDA desplaza en los dos sentidos la Frase dentro del Rótulo.
      En ambos lados de la Rueda se muestran las coordenadas del Eje X de la parte de la Frase NEJEX_VENTANA que aparece en el Rótulo .
      Estos valores son NEJEX_INI y NEJEX_INI + NEJEX_VENTANA que se corresponden con las propiedades NEJEX_INI y NEJEX_FIN del Gráfico.
      La Rueda cambia el valor de NEJEX_INI y mediante el manejador RESIZE_JS se calcula NEJEX_VENTANA.
      El siguiente proceso javascript es el más importante desde el punto de vista gráfico, porque permite mantener el ancho fijo de los caracteres cuando redimensionamos la Ventana en los equipos de escritorio o giramos la pantalla en los dispositivos móviles.
// Manejador RESIZE_JS 
// Tenemos que mantener la anchura de los caracteres fija aunque cambie el ancho del Rótulo
// Ancho del Rótulo
var nAnchoRotulo = theRoot.dataView().control("GRF_ROTULO").width
// Los caracteres deben tener un ancho en pixeles determinado para que sean legibles (110px)
// Calculamos entonces el Nº de caracteres que entran en el Rótulo
var nNumLetras = nAnchoRotulo / 110
// Calculamos también el valor X máximo a mostrar (el que está a la derecha de la rueda)
var nEjeX_Fin = nNumLetras * 6  // Cada letra son 6 puntos X
theRoot.setVar("NEJEX_VENTANA", Math.round(nEjeX_Fin))
theMainWindow.showMessageStatusBar("Ancho: " + theMainWindow.width() + "px", 0)
    • El manejador del evento RESIZE de la Vista de datos también recalcula los valores NEJEX_INI y NEJEX_VENTANA para reposicionar la Frase en el Rótulo.
    • El botón CMD_PLAY  pone en marcha el Timer del Formulario (otra ausencia imperdonable es el evento Timer en el objeto Gráfico nativo).
      El manejador TIMER incrementa el valor de NEJEX_INI para desplazar la Frase de derecha a izquierda dentro del Rótulo.
      También cambia el color de los Leds actualizando la propiedad CCOLOR del Gráfico.
    • Tanto manualmente, como a través del Timer o redimensionando el formulario, tan solo necesitamos actualizar las propiedades NEJEX_INI y NEJEX_FIN del Gráfico XY para desplazar la Frase.

Hagamos un Reloj con nuestro Gráfico XY

En un Reloj Digital tenemos siempre un texto de ancho fijo (8 caracteres) y solo necesitamos un Timer de 1 segundo para refrescar la Hora actual.



    • Para el reloj digital lo que hacemos es crear en la tabla de disco GRAFICO_XY_3P las coordenadas de todas las posibles horas del día, desde las 00:00:00 hasta las 23:59:59. Este proceso PRO_RELOJ_CARGAR solo debe ejecutarse una vez y por eso lo ejecutamos en el ON_INIT_SERVER.
    • En el arranque de la aplicación rellenamos mediante un Tubo de Lista la tabla en memoria RELOJ_XY_MEM con todas las coordenadas posibles del Reloj digital.
    • En cada evento TIMER la Frase del Rótulo será currentTime() y esta vez construimos hh:mm:ss de forma dinámica desde la tabla en memoria. La variable global APP_RELOJ_SEGUNDOS determina si se muestran los segundos o no. En el caso de que no se muestren los segundos hacemos que el separador de minutos se encienda y apague cada segundo.
Rem ( Proceso PRO_RELOJ para la Vista de datos del Reloj digital)

Rem ( Obtenemos las coordenadas XY de la Hora actual desde la Tabla en memoria )
Set ( CHORA, timeToString(currentTime(), "hhmmss") )

Rem ( DECENAS DE HORA )
Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "DECENA_HORA_" + mid(CHORA, 0, 1), , ,)
        Añadir lista a la salida
Rem ( UNIDADES DE HORA )
Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "UNIDAD_HORA_" + mid(CHORA, 1, 1), , ,)
        Añadir lista a la salida
If ( $APP_RELOJ_SEGUNDOS@0PS_Rotulo_LED_dat.dat | ((second(currentTime()) % 2) = 0) )
        Rem ( SEPARADOR DE MINUTOS )
        Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "SEP_MINUTOS", , , )
                Añadir lista a la salida
Rem ( DECENAS DE MINUTOS )
Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "DECENA_MINUTO_" + mid(CHORA, 2, 1), , , )
        Añadir lista a la salida
Rem ( UNIDADES DE MINUTOS )
Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "UNIDAD_MINUTO_" + mid(CHORA, 3, 1), , , )
        Añadir lista a la salida
If ( $APP_RELOJ_SEGUNDOS@0PS_Rotulo_LED_dat.dat )
        Rem ( SEPARADOR DE SEGUNDOS )
        Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "SEP_SEGUNDOS", , , )
                Añadir lista a la salida
        Rem ( DECENAS DE SEGUNDOS )
        Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "DECENA_SEGUNDO_" + mid(CHORA, 4, 1), , , )
                Añadir lista a la salida
        Rem ( UNIDADES DE SEGUNDOS )
        Cargar lista ( RELOJ_XY_MEM@0PS_Rotulo_LED_dat, NAME, "UNIDAD_SEGUNDO_" + mid(CHORA, 5, 1), , , )
                Añadir lista a la salida
        Libre
    • El manejador del evento RESIZE ocultará los segundos cuando el ancho del Rótulo muestre menos de 28 coordenadas (4 caracteres x 6 + 4 del separador).

Conclusiones

Con estos 2 ejercicios hemos comprobado que, sin quererlo, Velneo nos ha proporcionado un objeto Canvas nativo para realizar pequeños retos que necesitan algo de dinamismo y potencia gráfica.

Que el objeto Gráfico nativo sea una Vista de datos es por un lado una gran ventaja LifeIsSoft, pero por otro es un handicap en redes lentas debido a la dependencia de Velneo con el hilo de conexión con del vServer, sobre todo en tablas temporales o en memoria.

El uso de Timers en el hilo principal es otro problema con Velneo debido a su bajo rendimiento gráfico. Estos ejercicios en equipos de escritorio se ejecutan con cierta agilidad pero en dispositivos móviles la experiencia gráfica deja mucho que desear. En el evento ON_HIDE de los formularios de ambos ejercicios ejecuto un stopTimer para que no perjudique un formulario en el otro.

En cualquier caso que estos ejercicios os sirvan para aprender sobre las posibilidades de los objetos de Velneo y al mismo tiempo divertirnos con ello. También una forma de hacer ver a los desarrolladores de Velneo las posibilidades que daría un objeto nativo Canvas, junto con tablas en memoria locales y no dependientes del gestor de transacciones.

Podéis probar el ejercicio en el Cloud en la url habitual vatp://pruebas:pruebas@c6.velneo.com:16400/0PS_Rotulo_LED_iapp. En principio es funcional en todas las plataformas.

Finalmente he preparado la versión QML para comparar. En principio podemos pensar que va a ser mucho más ágil el funcionamiento porque tendremos los Timers aislados y los modelos de datos pueden estar totalmente desconectados del servidor. He usado un modelo de datos procedente de un string json.

En los ejercicios del Cloud vatp://pruebas:pruebas@c6.velneo.com:16400/0PS_Varios_iapp tenéis también el código QML (rama Tipos QML de QtChart/Rótulo LEDS – TabView).
Es un buen ejercicio para probar algunos objetos QML interesantes para construir interfaces en dispositivos móviles.