Postal Navideña 2017. El “making of”

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 para que no dejen morir el foro de Velneo. Creo que es la mejor herramienta que tenemos para dejar nuestro conocimiento grabado y con acceso inmediato para 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.

 

Si te ha gustado el post, no dudes en compartirlo por tus redes sociales.

Además podéis dejar un comentario contándome vuestra experiencia usando los Gráficos nativos y vuestros avances con QML.

 

La entrada Postal Navideña 2017. El “making of” aparece primero en AyudaVelneo.

Sabías que … (5)

Nuestro “Papá Noel” Particular nos ha regalado una nueva entrega  “Sabías que“. En este post veremos curiosidades sobre funciones, contenidos iniciales, rejillas y personalización de gráficos nativos mediante CSS.

Por si te perdiste las cuatro primeras entregas del “juego”, no está de mas recordarlas:

¿Sabías que..?

Si no te fijas bien, la función getStringRegExp() te puede jugar una mala pasada

    • La función de cadena getStringRegExp(cadena, expresionRegular, posIni, caseSensitive, numSubExpresion) recibe como expresión regular una cadena.
    • En las expresiones regulares se usan metacaracteres, algunos de los cuales deben escaparse y ¿cuál es el caracter de escape?, pues precisamente el caracter \.
    • Por lo tanto, en Velneo debemos escapar los metacaracteres con doble caracter \ para que funcione correctamente la función getStringRegExp

Un ejemplo para obtener el código a la derecha de la palabra Informe:
 getStringRegExp(“Informe  233421122”, “Informe (\\d{1,}$)”, 0, 0, 1)  = 233421122

    • Cuando copiéis expresiones regulares de Internet, prestad atención a estos detalles, o padeceréis horas de desconcierto

Podemos recorrer los controles de una Caja de grupo (QGroupBox) para aplicar una determinada acción

    • El API de Velneo es una herramienta muy útil y este es un ejemplo de ello.
    • La clase QGroupBox (control caja de Grupo) dispone de 2 funciones, childWidgetAt(nIndex) y childWidget(nCount), que nos permiten recorrer todos los controles contenidos en la caja de grupo.
    • La función childWidgetAt devuelve un objeto Widget al que podremos aplicar una determinada acción.

Se puede establecer el tiempo que se muestran los caracteres en las cajas de edición que tienen la propiedad Contraseña activada

    • Debemos usar un par de propiedades CSS, lineedit-password-character y lineedit-password-mask-delay
    • La propiedad CSS lineedit-password-character determina código UNICODE elegido para ocultar la contraseña.
      Ejem:  ● 9679  * 42
    • La propiedad CSS lineedit-password-mask-delay establece el retardo en milisegundos
QLineEdit { 
   lineedit-password-character: 9679;
   lineedit-password-mask-delay: 300;
}

La columna de la rejilla puede mostrar directamente un icono

    • Si solo deseamos mostrar un icono en la columna de un rejilla no es necesario hacerlo en la parte reservada para ello.
    • Podemos hacerlo en la zona reservada para el contenido de la columna.
    • El editor de fórmulas no nos dejará insertar, por ejemplo, el campo ICONO de la tabla estática. Lo tecleamos manualmente.
      #BLOQUEADO_ICO.ICON

¿En qué plano se determinan los valores iniciales de los campos de la tabla?

    • El plano donde se ejecuta el comando Crear nueva ficha en memoria es en el que se determinan los valores iniciales.
    • Si queremos que siempre se determinen en 3P tendremos que calcular el valor inicial en el Trigger anterior al Alta.
    • En el API con el VRegister, los valores iniciales se calculan en la función setTable(), ¡¡ cuidado con esto !!.

¿Para qué sirve la función de campo getID()?

    • ¿Qué sentido tiene la función GetID() si devuelve el mismo ID que uso para llamarla?
    • Pues nos permite refactorizar los nombres de campo de una tabla cuando éstos son renombrados
      Por ejemplo:
      Hay que pasar a una función un string con los nombres de campo separados por comas.
      Si renombramos los campos AUTOR, CSS_FINAL o NOTAS, la aplicación seguirá funcionando correctamente.
Set (CLISTA_CAMPOS, "ID,NAME," + #AUTOR:getID() + "," + #CSS_FINAL:getID()  + "," + #NOTAS:getID()

Podemos usar el control Combobox en formularios sin Origen

    • El control combobox del formulario es muy cómodo de usar para el usuario si va a seleccionar de una lista corta, entre otras cosas, porque la lista se despliega directamente al pulsar con el ratón.
    • A veces tenemos un formulario sin origen y necesitamos desplegar los valores de una tabla maestra.
    • Una técnica habitual es crear una tabla temporal con todos los punteros a maestro que vayamos a usar en controles de selección
    • En el formulario sin origen añadimos una Ficha de Extensión FIEXT que se alimente por proceso, Alta, Baja y Modificación estarán a cero.
Crear nueva ficha en memoria (hMaestros, MAESTROS_TEMP@MiApp_app)
        Añadir ficha a la salida
    • Añadimos el control combobox y lo asociamos al campo puntero a maestro de la Ficha de Extensión FIEXT.
    • Ya sabemos que el valor seleccionado en el Combobox se lee mediante ##FIEXT.EMPRESA.ID.

Podemos personalizar los Gráficos nativos con CSS

    • El nuevo gráfico de Velneo es un control nativo LifeIsSoft y por lo tanto desesperadamente poco personalizable.
    • Sin embargo, teniendo en cuenta que los Títulos y Etiquetas son objetos Label de Qt, podemos usar tags HTML y código CSS.

Veamos 2 ejemplos que puedes probar en la aplicación del cloud vatp://pruebas:pruebas@c6.velneo.com:16400/0PS__MisCSS_iapp:

/* Gráfico nativo */
/* No existe selector de clase
Aunque podemos usar los tags HTML y código CSS en los Títulos y etiquetas del Gráfico
Pero limitados al subconjunto de HTML4 - http://doc.qt.io/qt-5/richtext-html-subset.html */

/* Cada etiqueta QLbel personaliza un elemento del gráfico.
Lo que hacemos es parsear el código CSS y aplicar un Atributo HTML style="font: bold 18px verdana; ...." mediante variables locales 
definidas en el Gráfico nativo que se añaden como prefijo a los Títulos y Etiquetas
*/

QLabel#GRAF_TITULO {
   font: bold 18px verdana;
   text-align: center;
   background-color: WhiteSmoke;
   color: DarkSlateGray;
   padding: 0 80 0 80;
}
QLabel#GRAF_TITULO_EJEY {
   font: bold 12px arial;
   text-align: center;
   color: DarkSlateGray;
}
QLabel#GRAF_TITULO_EJEX {
   font: bold 11px arial;
   text-align: center;
   color: DarkSlateGray;
}
QLabel#GRAF_ETIQUETAS_EJEY {
   font: bold 12px arial;
   text-align: center;
   color: DimGray;
}
QLabel#GRAF_ETIQUETAS_EJEX {
   font: bold 10px arial;
   background-color: IndianRed;
   color: Cornsilk;
   padding: 3 10 3 10;
}
QLabel#GRAF_ETIQUETAS_SERIE1 {
   font: bold 12px verdana;
   background-color: DarkGreen;
   color: HoneyDew;
   padding: 3 10 3 10;
}
QLabel#GRAF_ETIQUETAS_SERIE2 {
   font: bold 12px arial;
   color: white;
}

Las funciones CurrentDateTime() y CurrentUTCDateTime() tienen truco

    • Un día quise obtener el huso horario de vClient y como no me acordaba de la función getSysTimeZone(), hice lo siguiente:
set ( NDIF_HOR, secondsTo(currentDateTime(), currentUTCDateTime()))

Sin embargo, el resultado es cero.

    • Consultado a soporte, resulta que currentDateTime() y currentUTCDateTime() representan internamente la misma fecha, lo que ocurre es que las variables de tipo Tiempo guardan el huso horario en el que han sido creadas y Velneo no nos proporciona ese dato. Estas funciones devuelven fechas distintas en algunas operaciones y en otras actúan como valores iguales, lo que crea una gran confusión. Como muchas veces ocurre, no está documentado.
    • Así que tenerlo en cuenta:
dateTimeToTime(currentUTCDateTime()) es distinto de dateTimeToTime(currentDateTime())
dateTimeToString(currentUTCDateTime(), "hh:mm") es distinto de dateTimeToTime(currentDateTime(), "hh:mm")
pero
currentUTCDateTime() es igual a currentDateTime()
    • Si no existiera getSysTimeZone() la forma correcta de obtener el huso horario sería:
// Creamos 2 variables Tiempo nuevas a partir de información devuelta por las funciones currentDateTime y currentUTCDateTime
Set (THORA_LOCAL, setDateTime(dateTimeToDate(currentDateTime()), dateTimeToTime(currentDateTime()))
Set (THORA_UTC, setDateTime(dateTimeToDate(currentUTCDateTime()), dateTimeToTime(currentUTCDateTime()))
// Ya podemos operar con las variables Tiempo
Set (NDIF_HORARIA , secondsTo(THORA_UTC, THORA_LOCAL) / 3600)
Mensaje ("Hora local: " + dateTimeToString(currentDateTime(), "dd/MM/yyyy hh:mm") + "<br>" + 
"Hora UTC: " + dateTimeToString(currentUTCDateTime(), "dd/MM/yyyy hh:mm") + "<br>" +
"Diferencia horaria: " + numberToString(NDIF_HORARIA, "L", 0)

Los formularios del Bloc de Formularios no ejecutan el evento PRE_INI

    • Los formularios pueden ser los contenedores de los registros en algunos controles de Vista de datos de tipo Lista como el Bloc de formularios.
    • En este caso hay que tener en cuenta lo siguiente:
      • El evento Pre-inizialización del formulario solo se ejecutará cuando se muestra el Bloc de formularios, es decir, solo se ejecuta para el formulario mostrado en ese momento. Cuando navegamos por la Lista de registros no se dispara este evento.
      • Aparece un evento Item: pre cambio de seleccionado que funciona al contrario que el evento Pre-inizialización. En este caso se ejecuta solo cuando navegamos por la LIsta de registros.
      • En el Bloc de formularios no podemos usar en el manejador PRE_INI código que dependa del registro de la tabla, sobre todo el código que determina el nivel de acceso del Usuario a los registros. 

Y ahora confiesa… ¿cuántas de estas curiosidades sobre gráficos, rejillas y funciones sabías? 

Déjame un comentario mas abajo y comenzamos el debate.

La entrada Sabías que … (5) aparece primero en AyudaVelneo.

Sabías que … (5)

Nuestro “Papá Noel” Particular nos ha regalado una nueva entrega  “Sabías que“. En este post veremos curiosidades sobre funciones, contenidos iniciales, rejillas y personalización de gráficos nativos mediante CSS.

Por si te perdiste las cuatro primeras entregas del “juego”, no está de mas recordarlas:

¿Sabías que..?

Si no te fijas bien, la función getStringRegExp() te puede jugar una mala pasada

    • La función de cadena getStringRegExp(cadena, expresionRegular, posIni, caseSensitive, numSubExpresion) recibe como expresión regular una cadena.
    • En las expresiones regulares se usan metacaracteres, algunos de los cuales deben escaparse y ¿cuál es el caracter de escape?, pues precisamente el caracter \.
    • Por lo tanto, en Velneo debemos escapar los metacaracteres con doble caracter \ para que funcione correctamente la función getStringRegExp

Un ejemplo para obtener el código a la derecha de la palabra Informe:
 getStringRegExp(“Informe  233421122”, “Informe (\\d{1,}$)”, 0, 0, 1)  = 233421122

    • Cuando copiéis expresiones regulares de Internet, prestad atención a estos detalles, o padeceréis horas de desconcierto

Podemos recorrer los controles de una Caja de grupo (QGroupBox) para aplicar una determinada acción

    • El API de Velneo es una herramienta muy útil y este es un ejemplo de ello.
    • La clase QGroupBox (control caja de Grupo) dispone de 2 funciones, childWidgetAt(nIndex) y childWidget(nCount), que nos permiten recorrer todos los controles contenidos en la caja de grupo.
    • La función childWidgetAt devuelve un objeto Widget al que podremos aplicar una determinada acción.

Se puede establecer el tiempo que se muestran los caracteres en las cajas de edición que tienen la propiedad Contraseña activada

    • Debemos usar un par de propiedades CSS, lineedit-password-character y lineedit-password-mask-delay
    • La propiedad CSS lineedit-password-character determina código UNICODE elegido para ocultar la contraseña.
      Ejem:  ● 9679  * 42
    • La propiedad CSS lineedit-password-mask-delay establece el retardo en milisegundos
QLineEdit { 
   lineedit-password-character: 9679;
   lineedit-password-mask-delay: 300;
}

La columna de la rejilla puede mostrar directamente un icono

    • Si solo deseamos mostrar un icono en la columna de un rejilla no es necesario hacerlo en la parte reservada para ello.
    • Podemos hacerlo en la zona reservada para el contenido de la columna.
    • El editor de fórmulas no nos dejará insertar, por ejemplo, el campo ICONO de la tabla estática. Lo tecleamos manualmente.
      #BLOQUEADO_ICO.ICON

¿En qué plano se determinan los valores iniciales de los campos de la tabla?

    • El plano donde se ejecuta el comando Crear nueva ficha en memoria es en el que se determinan los valores iniciales.
    • Si queremos que siempre se determinen en 3P tendremos que calcular el valor inicial en el Trigger anterior al Alta.
    • En el API con el VRegister, los valores iniciales se calculan en la función setTable(), ¡¡ cuidado con esto !!.

¿Para qué sirve la función de campo getID()?

    • ¿Qué sentido tiene la función GetID() si devuelve el mismo ID que uso para llamarla?
    • Pues nos permite refactorizar los nombres de campo de una tabla cuando éstos son renombrados
      Por ejemplo:
      Hay que pasar a una función un string con los nombres de campo separados por comas.
      Si renombramos los campos AUTOR, CSS_FINAL o NOTAS, la aplicación seguirá funcionando correctamente.
Set (CLISTA_CAMPOS, "ID,NAME," + #AUTOR:getID() + "," + #CSS_FINAL:getID()  + "," + #NOTAS:getID()

Podemos usar el control Combobox en formularios sin Origen

    • El control combobox del formulario es muy cómodo de usar para el usuario si va a seleccionar de una lista corta, entre otras cosas, porque la lista se despliega directamente al pulsar con el ratón.
    • A veces tenemos un formulario sin origen y necesitamos desplegar los valores de una tabla maestra.
    • Una técnica habitual es crear una tabla temporal con todos los punteros a maestro que vayamos a usar en controles de selección
    • En el formulario sin origen añadimos una Ficha de Extensión FIEXT que se alimente por proceso, Alta, Baja y Modificación estarán a cero.
Crear nueva ficha en memoria (hMaestros, MAESTROS_TEMP@MiApp_app)
        Añadir ficha a la salida
    • Añadimos el control combobox y lo asociamos al campo puntero a maestro de la Ficha de Extensión FIEXT.
    • Ya sabemos que el valor seleccionado en el Combobox se lee mediante ##FIEXT.EMPRESA.ID.

Podemos personalizar los Gráficos nativos con CSS

    • El nuevo gráfico de Velneo es un control nativo LifeIsSoft y por lo tanto desesperadamente poco personalizable.
    • Sin embargo, teniendo en cuenta que los Títulos y Etiquetas son objetos Label de Qt, podemos usar tags HTML y código CSS.

Veamos 2 ejemplos que puedes probar en la aplicación del cloud vatp://pruebas:pruebas@c6.velneo.com:16400/0PS__MisCSS_iapp:

/* Gráfico nativo */
/* No existe selector de clase
Aunque podemos usar los tags HTML y código CSS en los Títulos y etiquetas del Gráfico
Pero limitados al subconjunto de HTML4 - http://doc.qt.io/qt-5/richtext-html-subset.html */

/* Cada etiqueta QLbel personaliza un elemento del gráfico.
Lo que hacemos es parsear el código CSS y aplicar un Atributo HTML style="font: bold 18px verdana; ...." mediante variables locales 
definidas en el Gráfico nativo que se añaden como prefijo a los Títulos y Etiquetas
*/

QLabel#GRAF_TITULO {
   font: bold 18px verdana;
   text-align: center;
   background-color: WhiteSmoke;
   color: DarkSlateGray;
   padding: 0 80 0 80;
}
QLabel#GRAF_TITULO_EJEY {
   font: bold 12px arial;
   text-align: center;
   color: DarkSlateGray;
}
QLabel#GRAF_TITULO_EJEX {
   font: bold 11px arial;
   text-align: center;
   color: DarkSlateGray;
}
QLabel#GRAF_ETIQUETAS_EJEY {
   font: bold 12px arial;
   text-align: center;
   color: DimGray;
}
QLabel#GRAF_ETIQUETAS_EJEX {
   font: bold 10px arial;
   background-color: IndianRed;
   color: Cornsilk;
   padding: 3 10 3 10;
}
QLabel#GRAF_ETIQUETAS_SERIE1 {
   font: bold 12px verdana;
   background-color: DarkGreen;
   color: HoneyDew;
   padding: 3 10 3 10;
}
QLabel#GRAF_ETIQUETAS_SERIE2 {
   font: bold 12px arial;
   color: white;
}

Las funciones CurrentDateTime() y CurrentUTCDateTime() tienen truco

    • Un día quise obtener el huso horario de vClient y como no me acordaba de la función getSysTimeZone(), hice lo siguiente:
set ( NDIF_HOR, secondsTo(currentDateTime(), currentUTCDateTime()))

Sin embargo, el resultado es cero.

    • Consultado a soporte, resulta que currentDateTime() y currentUTCDateTime() representan internamente la misma fecha, lo que ocurre es que las variables de tipo Tiempo guardan el huso horario en el que han sido creadas y Velneo no nos proporciona ese dato. Estas funciones devuelven fechas distintas en algunas operaciones y en otras actúan como valores iguales, lo que crea una gran confusión. Como muchas veces ocurre, no está documentado.
    • Así que tenerlo en cuenta:
dateTimeToTime(currentUTCDateTime()) es distinto de dateTimeToTime(currentDateTime())
dateTimeToString(currentUTCDateTime(), "hh:mm") es distinto de dateTimeToTime(currentDateTime(), "hh:mm")
pero
currentUTCDateTime() es igual a currentDateTime()
    • Si no existiera getSysTimeZone() la forma correcta de obtener el huso horario sería:
// Creamos 2 variables Tiempo nuevas a partir de información devuelta por las funciones currentDateTime y currentUTCDateTime
Set (THORA_LOCAL, setDateTime(dateTimeToDate(currentDateTime()), dateTimeToTime(currentDateTime()))
Set (THORA_UTC, setDateTime(dateTimeToDate(currentUTCDateTime()), dateTimeToTime(currentUTCDateTime()))
// Ya podemos operar con las variables Tiempo
Set (NDIF_HORARIA , secondsTo(THORA_UTC, THORA_LOCAL) / 3600)
Mensaje ("Hora local: " + dateTimeToString(currentDateTime(), "dd/MM/yyyy hh:mm") + "<br>" + 
"Hora UTC: " + dateTimeToString(currentUTCDateTime(), "dd/MM/yyyy hh:mm") + "<br>" +
"Diferencia horaria: " + numberToString(NDIF_HORARIA, "L", 0)

Los formularios del Bloc de Formularios no ejecutan el evento PRE_INI

    • Los formularios pueden ser los contenedores de los registros en algunos controles de Vista de datos de tipo Lista como el Bloc de formularios.
    • En este caso hay que tener en cuenta lo siguiente:
      • El evento Pre-inizialización del formulario solo se ejecutará cuando se muestra el Bloc de formularios, es decir, solo se ejecuta para el formulario mostrado en ese momento. Cuando navegamos por la Lista de registros no se dispara este evento.
      • Aparece un evento Item: pre cambio de seleccionado que funciona al contrario que el evento Pre-inizialización. En este caso se ejecuta solo cuando navegamos por la LIsta de registros.
      • En el Bloc de formularios no podemos usar en el manejador PRE_INI código que dependa del registro de la tabla, sobre todo el código que determina el nivel de acceso del Usuario a los registros. 

Y ahora confiesa… ¿cuántas de estas curiosidades sobre gráficos, rejillas y funciones sabías? 

Déjame un comentario mas abajo y comenzamos el debate.

La entrada Sabías que … (5) aparece primero en AyudaVelneo.

La Tabla estática en un Combo sin Origen

La Tabla estática es un objeto del proyecto de datos cuyos registros se pueden crear y editar en tiempo de diseño. En tiempo de ejecución solo podemos mostrar los registros de la Tabla estática sin posibilidad de filtrarlos (siempre se muestran todos) o modificarlos.

Seguramente alguna vez has tenido la necesidad de mostrar los Items de una Tabla estática en un formulario sin Origen o filtrar los registros de la Tabla estática de forma dinámica. En estos casos el API de Velneo viene en nuestra ayuda, veamos cómo hacerlo.

tabla estática

 

Para definir la tabla estática tenemos que crear registros o Items con 3 propiedades:

    • Identificador de un solo caracter alfanumérico que identifica de forma unívoca el registro
    • Nombre para mostrar en el Interface de usuario
    • Dibujo que se corresponde con un Objeto dibujo del proyecto para mostrar también en el interface de usuario

El uso más común de la Tabla estática consiste en enlazar un campo a dicha tabla mediante un enlace de Tipo estática. Lo que se guarda en el campo enlazada es el Identificador del Item seleccionado.

En tiempo de ejecución el usuario podrá desplegar un control Combobox en el formulario con todos los elementos de la Tabla estática y cambiar el Identificador guardado en el campo indicado en la propiedad Contenido.

Como vemos en la siguiente imagen tenemos un formulario de configuración con un Combobox CBO_LOGNIVEL mostrando los 5 Nombres de una Tabla estática con el Dibujo a la izquierda. 

Los Identificadores son los caracteres 1, 2 ,3 4 y 5.

Este formulario no tiene Origen de tabla y por lo tanto no podemos fijar la propiedad “Contenido” del Combobox a un campo de dicha tabla.

El valor del Identificador seleccionado en el Combobox se guardará en una variable global numérica G_APP_LOG_LEVEL.

En el POS_INI del formulario ejecutamos el manejador javascript LOGNIVEL_RELLENAR_JS que nos permite acceder a los Items de la Tabla estática y rellenar con ellos el Combobox. Como la propiedad Contenido del Combobox ya nos es operativa debemos gestionar la selección y lectura del Item, algo que ocurre de forma automática cuando el contenido es un campo.

// Formulario sin Origen
// Manejador de Evento JS - LOGNIVEL_RELLENAR_JS
// Rellena el Combobox con los Items de la tabla estática

var cAliasAPP = theApp.mainProjectInfo().alias()
var cAliasDAT = cAliasAPP.replace("app","dat")
var oCbo = theRoot.dataView().control("CBO_LOGNIVEL")
// Obtenemos el número de Items de la Tabla estática
var nNumNiv = theApp.staticTableItemCount(cAliasDAT + "/APP_LOG_LEVEL")

// Rellenamos el Combo desde la Tabla estática con Dibujo, Nombre e Identificador
if (nNumNiv > 0) {
        for (var i=0 ; i<nNumNiv ; i++) {
                oCbo.addItem(
                        theApp.staticTableItemImage(cAliasDAT + "/APP_LOG_LEVEL",i),
                        theApp.staticTableItemName(cAliasDAT + "/APP_LOG_LEVEL",i), 
                        parseInt(theApp.staticTableItemId(cAliasDAT + "/APP_LOG_LEVEL",i)))
        }
}

// Establecemos el Item seleccionado inicialmente desde la variable Global
var nNivel = theApp.globalVarToInt(cAliasDAT + "/G_APP_LOG_LEVEL")
var nIndex = oCbo.findData(nNivel)
if (nIndex > -1) {
        oCbo.setCurrentIndex(nIndex)
}

Cuando el usuario seleccione un nuevo Item del Combo tenemos que guardar el Identificador seleccionado en la variable global G_APP_LOG_LEVEL.

Desde el evento Item: Cambio de seleccionado del Combobox ejecutamos el manejador javascript LOGNIVEL_CAMBIO_JS

// Formulario sin Origen
// Manejador de Evento JS - LOGNIVEL_CAMBIO_JS

var cAliasAPP = theApp.mainProjectInfo().alias()
var cAliasDAT = cAliasAPP.replace("app","dat")
var oCbo = theRoot.dataView().control("CBO_LOGNIVEL")

// Obtenemos el Identificador del Item seleccionado y lo guardamos en la variable global
var nNivel = oCbo.itemData(oCbo.currentIndex)
theApp.setGlobalVar(cAliasDAT + "/APP_LOG_LEVEL", nNivel)

Y esto es todo, el API ha aportado a la programación del Combobox ese grado dinámico que no tenemos en Velneo nativo. Sin embargo perdemos la potencia de la refactorización por lo que tendremos que ser muy cuidadosos con el código.

Hagamos un componente de todo esto

Quizás quieras darle un poco de abstracción a la resolución de este ejercicio y te planteas el diseño de un Combobox personalizable. Un control Combobox que sirva para cualquier Tabla estática del proyecto y tenga los manejadores RELLENAR y CAMBIO incluidos en el componente.

El control Vista de Datos del formulario nos permite incrustar componentes del proyecto e interactuar con él mediante comandos de Velneo.

Veamos como crear un Combobox genérico para mostrar cualquier Tabla estática del proyecto en un formulario sin Origen.

Diseñamos un formulario FRM_SIS_COMBO_ESTATICA sin Origen con un Combobox CBO_ESTATICA y un Label para mostrar la variable local CID_ELEMENTO que guarda el identificador del Item seleccionado.

El formulario FRM_SIS_COMBO_ESTATICA va a ser nuestro componente que insertamos en un formulario sin Origen mediante una Vista de datos.

Este componente necesita 2 valores, el Alias del proyecto de datos CALIAS y el Identificativo de la Tabla estática CTABLA_ESTATICA.

Un manejador de evento javascript CBO_RELLENAR_JS es ejecutado desde el ON_SHOW del formulario. Rellenamos el Combobox en el evento On_Show porque es la única manera de asegurarnos que las variables locales CALIAS y CTABLA_ESTATICA han sido asignadas correctamente a la Vista de datos desde el evento POS_INI del formulario principal.

// Componente Combo de Tabla estática
// Manejador de Evento JS - CBO_RELLENAR_JS
// Rellena el Combobox con los Items de la tabla estática

// El componente necesita personalizar 2 valores: Alias del proyecto y Tabla estática
var cAliasDAT = theRoot.varToString("CALIAS")
var cTablaEstatica = theRoot.varToString("CTABLA_ESTATICA")
var oCbo = theRoot.dataView().control("CBO_ESTATICA")

// Solo rellenamos si el combo está vacío (recordad que estamos en el evento On_Show)
if (oCbo.count == 0) {
        // Rellenamos el Combo desde la Tabla estática
        var nNumNiv = theApp.staticTableItemCount(cAliasDAT + "/ " + cTablaEstatica)
        if (nNumNiv > 0) {
                for (var i=0 ; i<nNumNiv ; i++) {
                        oCbo.addItem(
                                theApp.staticTableItemImage(cAliasDAT + "/ " + cTablaEstatica,i),
                                theApp.staticTableItemName(cAliasDAT + "/ " + cTablaEstatica,i), 
                                theApp.staticTableItemId(cAliasDAT + "/ " + cTablaEstatica,i)
                        )
                }
        }
        // Establecemos el valor inicial
        var cID = theRoot.varToString("CID_ELEMENTO")
        var nIndex = oCbo.findData(cID)
        if (nIndex > -1) {
                oCbo.setCurrentIndex(nIndex)
        }
}

Desde el evento Item: Cambio de seleccionado del Combobox ejecutamos el manejador javascript CBO_CAMBIO_JS

// Componente Combo de Tabla estática
// Manejador de Evento JS - CBO_CAMBIO_JS
// Obtiene el ID seleccionado en el Combobox

var oCbo = theRoot.dataView().control("CBO_ESTATICA")

// Obtenemos el ID seleccionado
theRoot.setVar("CID_ELEMENTO", oCbo.itemData(oCbo.currentIndex))
theRoot.dataView().updateControls()

Con esto ya tenemos el componente terminado y listo para insertarlo como Vista de datos en cualquier formulario sin Origen.

En el manejador POS_INI del formulario principal establecemos la personalización del componente con los valores del Alias del proyecto de datos, nombre de la Tabla estática e Identificador inicialmente seleccionado.

Para recoger el Identificativo seleccionado en el Combobox podemos usar un manejador LOGNIVEL_CAMBIO que se dispare con el evento Item: Cambio de seleccionado del Combobox de la Vista de datos.

Rem (Manejador LOGNIVEL_CAMBIO para obtener el identificativo seleccionado)
Interfaz: Get variable local de vista de datos ( CTR_LOG_NIVEL, CID_ELEMENTO, CID_LOGNIVEL )
Modificar variable global ( APP_LOG_LEVEL@MiProyecto_dat, CID_LOGNIVEL, LOK )

Aquí vemos un ejemplo de la secuencia de eventos en Velneo. Cuando en el Combobox se cambia el elemento seleccionado, primero se dispara el evento Item: Cambio de seleccionado del componente incrustado en la Vista de datos y a continuación se dispara el evento del control Vista de datos definido en el formulario principal.

 

¿Qué pasa con los formularios con Origen si quiero insertar una Vista de datos sin Origen?

Pues también tiene solución, pero eso lo dejo como ejercicio y pones tu solución en los comentarios.

La entrada La Tabla estática en un Combo sin Origen aparece primero en AyudaVelneo.

¿Cómo accedo a la Lista o Ficha de cualquier Vista de datos de mi Aplicación?

Una de las tareas que inicialmente más me costó asimilar fue cómo se podía acceder a los registros de un objeto Vista de datos.

Sigue leyendo para averiguar el porqué.

Cuando uno descubre Velneo, viniendo de un entorno de programación totalmente diferente, debe asumir que va a tener que aprender un nuevo paradigma de programación y esto conlleva algunas dificultades para entender algunos aspectos esenciales del nuevo lenguaje.

vista de datos

 

Como ya sabemos un objeto Vista de datos es un objeto del Proyecto de aplicación que nos permite mostrar en el Interfaz un conjunto de registros de una tabla del Proyecto de datos.

En Velneo se denomina Lista al conjunto de registros y Ficha a un solo registro.

Primero me hice una serie de preguntas en lo que respecta al acceso y gestión de las Listas y Fichas que están contenidas en los objetos gráficos de la Aplicación.

Respondiendo a estas preguntas entenderemos el mecanismo de Velneo y quedará resuelto el problema planteado.

1ª ¿Dónde puedo colocar el objeto Vista de datos en el Interfaz de mi Aplicación?

Los objetos de Vista de datos normalmente irán insertados en un formulario a través de un control de tipo Vista de datos.

También podemos colocar un objeto Vista de datos en un formulario de un Dock del objeto autoexec.

Las Cestas globales también contienen una Lista que podemos mostrar como una Rejilla en un Dock del autoexec.

2ª ¿Desde dónde puedo acceder a la Lista o Ficha de una determinada Vista de datos?

Si no usamos el API, en Velneo solo tenemos acceso a los controles de un formulario desde los manejadores de evento del propio formulario.

Desde un proceso solo tenemos acceso a los controles de objeto autoexec.

3ª ¿Qué comando o comandos me permiten dicho acceso?

En Velneo se entiende por Procesar como la acción de acceder a la Lista o Ficha de la Vista de datos.

Disponemos del comando Interfaz:Procesar que, tal como dice la ayuda, permite acceder a los datos de un control de tipo objeto usado en un formulario o en cualquiera de los subformularios del mismo.

En un control de tipo objeto podremos presentar una ficha o una lista de registros de una tabla.

Mediante este comando podremos acceder a los datos de dicho control e interactuar con ellos, ya sea para leerlos, modificarlos, borrarlos, etc.

En un proceso usaremos este comando para acceder a los objetos Vista de datos de un Dock del autoexec.

Para procesar la Lista de una Cesta global disponemos del comando Cesta:Procesar.

4ª ¿Tiene algo que ver esto con los conceptos de Origen y Destino de los procesos en Velneo?

Por supuesto, si entendemos los conceptos de Origen y Destino, entenderemos porqué necesitamos el comando Procesar.

Cuando ejecutamos el comando Interfaz:Procesar o Cesta:Procesar se crea un nuevo subproceso dentro del manejador de evento o proceso actual. El subproceso cambiará el Origen a la Ficha o la Lista de la Vista de datos que hayamos seleccionado y de esa forma tenemos acceso inmediato.

Cuando finaliza el subproceso, se vuelve a recuperar el Origen que había antes de ejecutar el comando Procesar.

 

Para resumir:

  • En Velneo programamos mediante manejadores de evento y procesos cuyo Origen determina a qué Lista o Ficha tenemos acceso.
  • El comando Procesar crea un nuevo proceso (en realidad es un subproceso) que cambia el Origen a la Lista o Ficha de la Vista de datos.
  • La única condición a tener en cuenta es que la Vista de datos sea accesible desde el manejador de evento o proceso.

 

Una vez entendido cómo funciona el comando Procesar, nos daremos cuenta de la gran potencia de esta forma de programar.

¿Alguna duda en la sala sobre la vista de datos y el comando procesar?

Si es así, cuéntamela dejándome un comentario mas abajo.

La entrada ¿Cómo accedo a la Lista o Ficha de cualquier Vista de datos de mi Aplicación? aparece primero en AyudaVelneo.

Sabías que … (4)

Sabías que … (4)

Se puede desactivar la ordenación de las columnas de las Rejillas de 2 formas …

    • Pero ninguna de ellas con Velneo nativo.
    • La primera usando el API mediante la función setSortingEnabled(false) de la clase VGridListDataView.
    • La segunda se consigue usando una regla CSS sobre el control Rejilla.

Interfaz: Establecer hoja de estilo CSS ( GRD_REJILLA, “QTableView { qproperty-sortingEnabled: false}”)

Se puede ACELERAR LA CARGA DE LOS REGISTROS DESDE SERVIDORES REMOTOS

    • En nuestras aplicaciones necesitamos consultar a menudo tablas con muchos registros (artículos, clientes, …).
    • En Cloud la primera consulta necesita un tiempo extra para guardar los registros en la caché del cliente.
    • Ese tiempo extra puede resultar en una mala experiencia para el usuario.
    • Podemos minimizar ese tiempo extra precargando los registros en el autoexec de la aplicación mediante un proceso en 2º plano y el comando Cargar lista
    • El comando Cargar lista rellenará la caché con los registros de la tabla y de esta forma la primera consulta que realice el usuario se hará desde la caché en lugar del servidor remoto.

Se puede fijar el tamaño de un Dock cuyo Objeto incrustado sea un formulario

    • Muchas veces nos interesa bloquear el tamaño de un Dock de tipo formulario, aparte de fijarlo en un sitio de la pantalla.
    • Usaremos el CSS siguiente en el que establecemos el mismo valor para las propiedades min-height/max-height o min-width/max-width:

QDockWidget {
       /* Los Docks son hijos de la Ventana principal QMainWindow y heredan los valores de min-width y min-height */
       /* Por eso se sobreescriben con la clase QDockWidget */
       min-width: 300px;
       /* Haciendo el min=max conseguimos que el Dock no sea modificable en tamaño */
       min-height: 100px;
       max-height: 100px;
}

    • Hay que tener en cuenta que el CSS afectará a todos los Docks de tipo formulario que tenga la Aplicación, por lo tanto, esto solo es válido cuando existe un solo Dock. (Los Docks del editor de Informes personalizados también se verán afectados).

Ordenar una Rejilla sin tener en cuenta ni mayúsculas ni acentos

    • La ordenación que tenemos en la Rejilla es alfabética y tendrá en cuenta las mayúsculas y acentos.
    • Para disponer de una Ordenación independiente de mayúsculas y acentos lo que debemos hacer es añadir un nuevo campo #NOMBRE_64 a la tabla que sea de tipo Fórmula alfabética. El contenido de la fórmula será stringToAlpha64(#NOMBRE). El campo NOMBRE es el que queremos ordenar.
    • Con el comando Ordenar lista (#NOMBRE_64) conseguiremos el resultado deseado.

El comando Set retorno de proceso = NO en procesos de 3P inhabilita el comando Get variable local de objeto ejecutado desde 1P

    • Mucho cuidado con el comando Set retorno de proceso = NO en procesos ejecutados en 3P y que deban devolver valores en variables locales
    • El comando Get variable local de objeto no es funcional cuando el proceso en 3P ha terminado con un Set retorno de proceso = NO
    • Por lo tanto, cuando un proceso vaya a ser ejecutado en 3P lo mejor es no usar este comando y sustituir el retorno por una variable local que leeremos desde 1P

La carpeta base en el Visor HTML es siempre la caché de vClient

    • La carpeta base del código que ejecutamos en el Visor HTML es siempre la caché de vClient
    • Tenemos que tenerlo en cuenta si queremos usar links en el código que hagan referencia directamente al fichero
    • Para cambiar la carpeta base tenemos que usar la función del API VCWebView.setSourceCode(html, pathBase).

Un proceso en 4P ejecutado desde vClient no contempla la herencia hacia arriba

    • Los procesos en 4P se ejecutan siempre en una copia de la instancia del proyecto al que pertenecen
    • Lo podemos comprobar con el siguiente ejemplo:

Tenemos un proceso PRO_VERALIAS javascript en el proyecto de datos,

var cAliasAPP = theApp.mainProjectInfo().alias();
// Muestra en vAdmin el Alias
alert (“El valor de theApp.mainProjectInfo().alias() es: ” + cAliasAPP)

Ejecutamos el proceso PRO_VERALIAS desde el proyecto de aplicación en 3P y 4P.

Comprobamos que en 3P devuelve el alias del proyecto de aplicación y en 4P sin embargo devuelve el alias del proyecto de datos.

    • Por lo tanto, habrá que tener en cuenta este comportamiento en aquellos scripts javascript que vayamos a ejecutar tanto en 3P (síncrono) como en 4P (asíncrono).
      El resultado no será el mismo cuando en el código se vaya a contemplar la cadena de herencia, por ejemplo la función VProjectInfo.allTableCount().

QML solo funciona en 1P

    • El código QML se ha incorporado a Velneo para mejorar nuestros interfaces, sobre todo en dispositivos móviles.
    • Los scripts QML siempre van asociados a un formulario.
    • Así que QML solo tiene sentido en 1P y el código estará siempre descargado en la caché de vClient.

En tiempo de diseño el editor de fórmulas javascript evalúa las expresiones introducidas

    • El botón Verificar (F9) no solo comprueba la sintaxis del código javascript sino que además evalúa la expresión.
    • Este comportamiento en tiempo de diseño puede tener consecuencias curiosas o incluso peligrosas.

Dos ejemplos:

SET(LOK, /*JAVASCRIPT*/theApp.setOverrideCursor(17))
SET(LOK, /*JAVASCRIPT*/theApp.removeFile(“d:/este_fichero_se_va_a_borrar.txt”))

    • Para evitar que se evalúe el código en tiempo de diseño podemos usar el siguiente comando:

if (theApp.exeName() != “vDevelop”) { <expresión javascript> }

La entrada Sabías que … (4) aparece primero en AyudaVelneo.

Sabías que … (3)

Vamos con una nueva entrega de nuestro mítico juego… “Sabías que” en este post veremos curiosidades sobre búsquedas, cestas y listas.

Por si te perdiste las dos primeras entregas del “juego”, no está de mas recordarlas:

¿Sabías que..?

En los objetos Búsqueda no sabemos a priori el campo por el que va a estar ordenada la lista de salida

    • Aunque esto ya está documentado en la ayuda, no está de más recordarlo. Para ordenar la salida de un objeto Búsqueda tendremos que usar siempre un comando Ordenar lista.
    • En procesos, funciones o manejadores de evento que disparen búsquedas, usaremos el comando Ordenar lista, en acciones que disparen búsquedas, incluiremos entre la Búsqueda y el objeto de lista de la salida un proceso, con origen y destino lista de la tabla de la búsqueda, que ordene y añada la lista a la salida, .

Las Cestas pierden la ordenación que tuviera establecida la Lista Origen

    • Si ordenamos la Lista de salida de una Búsqueda y la copiamos a una Cesta para poder volcarla a una Rejilla, entonces perderemos el orden establecido.
    • Por lo tanto, el comando Ordenar lista deberá ejecutarse después de copiar la Cesta a la Rejilla. Veamos un ejemplo:
Rem ( Ejecución de una Búsqueda para alimentar la Rejilla del formulario )
Rem ( Utilizamos la Cesta como bufer intermedio para rellenar la Rejilla con el resultado de la Búsqueda )
Cesta: Crear cesta local ( MI_BUSQUEDA@MiApp_app, oCesta )
Crear manejador de objeto ( oBuscar, Proceso PRO_BUSCAR@MiApp_app )
Libre
Set variable local de objeto ( oBuscar, DFECHA_INI, DFECHA_DES_INI )
Set variable local de objeto ( oBuscar, DFECHA_FIN, DFECHA_DES_FIN )
Libre
Disparar objeto ( oBuscar, 3º plano: Servidor (síncrono), )
   Set ( NNUM_REGISTROS, sysListSize )
   Rem ( ¡OJO! La Cesta pierde la ordenación que tuviera establecida la Lista Origen )
   Cesta: Agregar lista a la cesta ( oCesta )
Interfaz: Procesar ( GRD_DOCUMENTOS, Todas )
   Cortar lista ( 0, )
   Cesta: Agregar a la lista en curso ( oCesta )
   Rem ( Tenemos que ordenar la lista obtenida después de pasar por la Cesta porque ésta deshace la ordenación )
   Ordenar lista ( #F_REGISTRO, #ID, , , , )
   Invertir lista
   Seleccionar ficha por posición ( 1 )
Interfaz: Establecer foco ( GRD_DOCUMENTOS )

El comando “Modificar ficha de maestro” FUERZA EL refresco DE los campos DEL MAESTRO

    • En los controles del formulario, después de haber modificado la ficha del Maestro desde un proceso independiente, los campos de dicho maestro no refrescan los cambios porque el puntero a maestro no ha cambiado.
    • Para forzar la lectura de la ficha del maestro desde el servidor y actualizar la caché es suficiente con ejecutar el comando Modificar ficha de maestro desde un manejador de evento.
    • Este comando produce un refresco en el formulario de todos los campos del maestro.

El ancho y alto mínimos de los controles Caja de texto vienen determinados por la clase VMainWindow y las propiedades CSS min-weight y min-height

    • Si has fijado a un valor determinado las propiedades CSS min-weight y min-height de la clase VMainWindow comprobarás que los controles Cajas de texto de los formularios en modo Vista también se verán afectados.

El comando Set dato de retorno no funciona en los procesos, aunque hay una excepción

    • En los procesos que van a ser usados desde la web (con VModApache) la forma de devolver el dato al servicio de Apache es mediante el comando Set dato de retorno.
    • Esto ocurre cuando el Estilo del proceso lo fijamos a “Accesible Web“.

La propiedad Valor del control Botón de radio debemos expresarla como una Constante

    • El control Botón de radio tiene la propiedad Contenido (expresión de fórmula) y la propiedad Valor (valor constante).
    • Por lo tanto, si el contenido es numérico pondremos Valor = 9 y si es de tipo carácter pondremos Valor = A, sin las comillas.

La función HomePath() en Cloud nos permite crear directorios en tercer plano

    • Crear directorio” es un comando de Velneo que actúa a nivel de sistema operativo. Por esa razón en Cloud tenemos que acceder con al ruta completa homePath() + “/Velneo/datos/<<archivo.ext>>” que es la ruta del sistema Linux en el vServer.
    • SDV: Subir fichero al servidor” es un comando de Velneo de tipo Cliente/Servidor, por lo tanto la ruta donde guardamos el fichero es una ruta Virtual. Esa ruta Virtual se define en vAdmin en la opción de menú Datos. Por defecto disponemos de la ruta Virtual “datos/” que equivale a la ruta física homePath() + “/Velneo/datos/ en el vServer.

Si cruzamos 2 listas del mismo tamaño el resultado se ordena igual que la lista cargada en primer lugar

    • Ya sabemos que cuando cruzamos 2 listas, la lista resultante tendrá el mismo orden que la lista de mayor tamaño
    • Cuando cruzamos 2 listas, si éstas tienen el mismo tamaño, el orden de la lista resultante es el mismo que la lista que hayamos cargado primero

Por ejemplo:  Cargo la Lista1 y se añade a la Cesta1. Cargo nuevamente la Lista1 con el mismo número de registros pero en distinto orden y cruzo la Cesta1 con la Lista1.
El orden que prevalece es el de la Cesta1.

Podemos usar el selector CSS CLASE[objectName|=valor] para filtrar los controles de nuestra aplicación

    • Cuando queremos aplicar un determinado CSS a un objeto concreto de nuestra aplicación usamos el selector CLASE#IDENTIFICADOR
    • Sin embargo, podemos usar también un Selector que nos aporta una gran flexibilidad, es el selector CLASE[objectName|=<valor>] que funciona seleccionando los controles cuya propiedad Identificador comienza con <valor>.
      Veamos un ejemplo:

Queremos que el usuario identifique visualmente los campos obligatorios dentro de los formularios. Para ello usaremos un fondo amarillo brillante.
La selección en el CSS será QDoubleSpinBox[objectName|=NUM_O_], QDateEdit[objectName|=DAT_O_], QDateTimeEdit[objectName|=DAT_O_], QTimeEdit[objectName|=TIM_O_], QLineEdit[objectName|=TXT_O_] {background: yellow;}

De esta forma es suficiente con cambiar el Identificador de un control para que tenga fondo amarillo, sin tocar el CSS de la aplicación.

Otros ejemplos:

QPushButton:flat[objectName|=CMD_IMP_] {background-color: orange;}    /* Botones naranjas */
QLabel[objectName|=LBL_AZUL_] {color: SteelBlue;}                     /* Etiquetas azules */

Y ahora confiesa… ¿cuántas sabías? 

Déjame un comentario mas abajo y comenzamos el debate.

 

La entrada Sabías que … (3) aparece primero en AyudaVelneo.

Sabías que (2) …

Vamos con una nueva entrega de nuestro mítico juego… “Sabías que“.

Por si te perdiste la primera parte del juego y quieres ganar el primer “quesito” del trivial, aquí te dejo el enlace ¿Sabías que?… Parte 1

¿Sabías que..?

¿Sabías que se puede ocultar la Barra de Estado con un comando nativo de Velneo?

    • Usar el comando Interfaz: Ocultar (STATUS_BAR).
    • Podemos usarlo al inicio de la Aplicación en el evento POS_INI del marco Autoexec.
    • También disponemos de la función del API theMainWindow.hideStatusBar().

¿Sabías que a veces los comandos Else y Else if no funcionan correctamente porque …?

    • Los comandos de instrucción Else y Else if siempre deben estar precedidos de un comando If o Else if. Los comandos If y Else if deben estar situados al mismo nivel y no puede haber ninguna otra línea al mismo nivel entre ambos, ni siquiera un Rem.

¿Sabías que se puede convertir un control de puntero a maestro en un control de edición alfabética?

    • El control de edición del puntero a maestro tiene un comportamiento especial para permitir el Autocompletado o la selección de un registro desde la Vista de datos.
    • Si solo nos interesa mostrar el Nombre del maestro como un control de edición alfabética añadimos el string vacío “” al contenido del campo.
      Por ejemplo el contenido   “” + #PUNTERO_MAESTRO.NAME  hará que las propiedades Autocompletar y Tipo de botones desaparezcan.
      Fijando a 1 el valor de la propiedad Solo lectura conseguimos un control no editable.
    • Recordad que la clase VBoundFieldEdit representa al control de edición de puntero a maestro y la clase VLineEdit al control de edición alfabética.

¿Sabías que se puede determinar el tamaño de los iconos del objeto de lista ListView?

    • El tamaño de los Iconos en un objeto de lista ListView se determina con el tamaño del Dibujo del proyecto que asignamos a la propiedad Icono nulo.
    • Añade al proyecto un objeto Dibujo y ponle un tamaño con el editor (por ejemplo 150x100px) y lo asignas a la propiedad Icono nulo del ListView.

¿Sabes cuándo se calculan por primera vez los valores iniciales de los campos de una Tabla?

    • Desde un formulario se calculan siempre en primer plano.
    • En el plano que corresponda cuando se ejecuta el comando Crear nueva ficha en memoria.
    • La función del API VRegister.setTable() es la que ejecuta los valores iniciales. Tenedlo en cuenta en los bucles que añaden registros a una tabla.
    • Crear Copia de ficha en memoria no ejecuta valores iniciales. Podemos usar el comando Calcula campos dependientes, pero solo funciona con fórmulas que contienen campos de la tabla.
    • Los Tubos de ficha o lista no ejecutan los valores iniciales. Solamente se ejecutan los valores iniciales que dependan de otros campos de la tabla de destino

¿Sabes el orden de las señales cuando se han definido mas de una vez sobre el mismo control?

    • Un objeto de lista del proyecto con una conexión de evento Cambio de seleccionado se incrusta en un control Vista de datos del formulario.
    • En el control Vista de datos definimos la misma conexión de evento Cambio de seleccionado.
    • Se ejecutarán los 2 manejadores de evento, pero siempre primero el manejador de evento del objeto del proyecto.
    • De esta forma podemos tener funcionalidad común en el objeto del proyecto (por ejemplo guardar el ID seleccionado) y en los distintos formularios disparar una conexión de evento con un manejador de evento personalizado cuya primera línea podrá ser

Interfaz: Get variable local de vista de datos (CTR_LISTA, NID_FICHA, NID_FICHA_LISTA)

La variable local de la Vista de datos NID_FICHA tendrá un valor fijado previamente por la conexión de evento del objeto de lista.

¿Sabías que VReport SOLO tiene acceso al proyecto de datos?

    • Desde el editor de fórmulas de VReport solo tenemos acceso a los controles del proyecto de datos de la tabla de entrada (funciones, constantes, variables globales, …)

¿Sabías que un proceso ejecutado desde un formulario recibe como entrada la ficha en memoria que está en Alta o Modificación?

    • El comando Ejecutar proceso ejecutado desde un manejador de evento o desde un botón del formulario pasa como entrada la Ficha que tenemos en memoria en Alta o Modificación.
    • Por lo tanto podemos modificar los campos de la Ficha en memoria sin generar nueva transacción (equivale al comando Pedir formulario).
    • ¡¡Ojo!! no es lo mismo que ejecutar los comandos Crear manejador de objeto, Añadir ficha al objeto y Disparar objeto. En este caso lo que pasamos al proceso es la ficha en disco.

¿Sabías que puedes aplicar formato HTML/CSS a los controles de Texto estático en los formularios?

    • La propiedad Nombre de los controles de tipo Texto estático aceptan código HTML/CSS que se interpreta en tiempo de diseño y en ejecución.
      Un ejemplo de lo que se puede conseguir se muestra en la captura siguiente. Encima de cada Texto estático aparece el contenido de la propiedad Nombre.

VAF_S2_HTML

    • Como se puede ver podemos usar directamente Titulares H1, H2, …, colorear texto, crear líneas horizontales con un simple <hr>, insertar imágenes desde la carpeta caché o directorio por defecto, subíndices y superíndices.
    • No es una funcionalidad documentada, por lo tanto puede desaparecer en futuras versiones.

Y ahora confiesa… ¿cuántas sabías? 

Déjame un comentario mas abajo y comenzamos el debate.

La entrada Sabías que (2) … aparece primero en AyudaVelneo.

Comandos de Configuración del sistema

Velneo proporciona un conjunto de comandos que permiten escribir y leer entradas en el fichero de configuración del sistema operativo de una máquina. 

Aprovecharemos esta funcionalidad, al igual que lo hace Velneo, para guardar y recuperar información útil de nuestras aplicaciones.

configuración del sistema

En el sistema operativo Windows los valores de configuración del sistema se guardan en el registro accesible mediante el comando regedit.exe. En Linux, OS X, iOS y Android se guardan en ficheros de texto. En este artículo vamos a centrarnos en Windows, aunque en el resto de plataformas el funcionamiento es el mismo.

Para acceder al registro de Windows tenemos que ejecutar el comando regedit desde la ventana Ejecutar (Tecla Windows+R).

Desde nuestras aplicaciones solo tenemos acceso a la información del registro que cuelga de la rama HKEY_CURRENT_USER y concretamente Velneo guardará siempre todos los valores de configuración en la rama HKEY_CURRENT_USER\SOFTWARE\Velneo.

Rama HKEY_CURRENT_USER (HKCU)
En esta rama se encuentra la configuración del usuario que está actualmente usando el equipo. Los cambios que hagamos en esta rama afectarán solo al usuario actual. Las carpetas del usuario, los colores de la pantalla y la configuración del Panel de control se almacenan aquí. Esta información está asociada al perfil del usuario. Esta clave a veces aparece abreviada como “HKCU”.

En la imagen siguiente se muestra un ejemplo de los valores de configuración en el Editor del Registro.

VAF_04_REGEDIT

Cuando usamos los comandos de Configuración del sistema, debemos tener en cuenta lo siguiente:

    • El ámbito de estos comandos de instrucción es la rama HKEY_CURRENT_USER\Software por lo tanto esta parte de la senda de una entrada del registro debemos omitirla en los parámetros.
    • El parámetro Fórmula de clave raíz es una subrama de Software. Normalmente valdrá Velneo.
    • En el parámetro Fórmula de senda de sección se especifican una o múltiples subcarpetas de la clave raíz, separadas por doble slash \\.
    • El parámetro Fórmula de entrada contiene el nombre de la clave que vamos a leer o escribir
    • La Fórmula de dato contiene la expresión de tipo fórmula que devuelve el valor de cadena o número que vamos a excribir.

Ejemplos:

  • Configuración del sistema: Escribir cadena de texto ( “Velneo”, “Mis_APPS”, “Path_Librerías”, $APP_INFO_CARPETA_LIBRERIAS@MiAplicacion.dat )
  • Configuración del sistema: Leer cadena de texto ( “Velneo”, “Mis_APPS”, “Parametro1”, CPARAMETRO_1 )
  • Configuración del sistema: Escribir cadena de texto ( “Velneo”, “ExtReport”, “zintdir”, sysCacheClientPath)
  • Configuración del sistema: Escribir cadena de texto ( “Velneo”, “WebBrowser\\websettings”, “enableJavascript”, “true” )

¿Qué secciones de la clave Velneo nos pueden interesar para nuestras aplicaciones?

Si revisamos por orden alfabético algunas de las secciones que utiliza Velneo, podemos destacar las siguientes entradas:

HKEY_CURRENT_USER\Software\Velneo\ExtReport\zintdir

Esta configuración es utilizada por VReport para localizar la utilidad de creación de códigos de barras Zint (zint.exe, zint.dll y zlib1.dll).
Podemos adjuntar en nuestro proyecto los 3 ficheros de la utilidad Zint y en el Pre_Init del marco Autoexec ejecutar el comando

Configuración del sistema: Escribir cadena de texto ( “Velneo”, “ExtReport“, “zintdir“, sysCacheClientPath)

HKEY_CURRENT_USER\Software\Velneo\SerialPorts\

En esta sección se guarda la configuración de los Dispositivos series que el usuario utiliza desde la aplicación mediante el comando Puerto serie: Configurar dispositivo.
Por ejemplo, la configuración del objeto Dispositivo serie ARDUINO_PLACA del proyecto de aplicación se guardará en la clave siguiente:

HKEY_CURRENT_USER\SOFTWARE\Velneo\SerialPorts\41mcn2yd.vca\ARDUINO_PLACA

La clave se creará únicamente cuando el usuario cambia la configuración del Dispositivo serie que viene por defecto con la aplicación.

HKEY_CURRENT_USER\SOFTWARE\Velneo\vAdmin\

El componente vAdmin.exe lee las claves LastServer, Servers y LastUser de la sección vAdmin para usarlas como valores iniciales en la ventana de Login.

HKEY_CURRENT_USER\SOFTWARE\Velneo\vClient

El componente vClient.exe también dispone de las claves LastServer, Servers y LastUser para inicializar la ventana de Login.

Las secciones DebuggerV7 y DebuggerJS guardan algunos aspectos del interface de ambos depuradores de código. Si eliminamos ambas secciones al inicio de nuestra aplicación conseguiremos tener siempre el depurador con el mismo aspecto.

La sección EditorFormulasDlg determinará el aspecto del Editor de fórmulas en tiempo de ejecución.

La sección MainWindow guarda para cada usuario/servidor/instancia/ el aspecto de la ventana principal de la aplicación (posición, tamaño, maximizada, …).

Si nuestra aplicación accede a Internet a través de un Servidor proxy con o sin seguridad, la sección proxy contendrá los parámetros de acceso.

HKEY_CURRENT_USER\SOFTWARE\Velneo\vDataClient

El componente vDataClient.exe dispone también de claves para configurar la ventana de login, tamaño de la ventana principal y el aspecto visual del interface.

HKEY_CURRENT_USER\SOFTWARE\Velneo\WebBrowser

Esta sección se corresponde con la configuración del Visor HTML de Velneo accesible a través del botón de la Barra de Herramientas.

Hay que tener en cuenta que esta configuración es común a todos los componentes de Velneo que usen el Visor HTML en la misma máquina y con el mismo usuario conectado. Esto quiere decir que si desde vDevelop se cambia la configuración del Visor HTML, ésta afectará a los Visores HTML de todas las aplicaciones que ejecutemos con el componente vClient. Si desde una aplicación, por ejemplo, cambiamos la clave enableJava a false, el resto de aplicaciones que ejecutamos con el mismo vClient tendrán también el java desactivado en el Visor HTML.

VAF_04_WEBBROWSER

Así que esto es lo mismo que si configuramos el explorador web de la máquina que usarán todas las aplicaciones del usuario conectado.

HKEY_CURRENT_USER\SOFTWARE\Velneo\Mis_APPS

Por supuesto, nosotros también podemos tener nuestra propia sección dentro de la raíz Velneo, por ejemplo Mis_APPS.

Algunos ejemplos de uso:

  • Path de la carpeta con nuestras librerías javascript que usaremos desde el Visor HTML: Mis_APPS\Path_Librerias = D:/MisLibrerias
  • Path del ejecutable del visor para ficheros PDF: Mis_APPS\Path_VisorPDF = C:\Program Files (x86)\Adobe\Acrobat 11.0\Acrobat\Acrobat.exe
  • Valor devuelto por una utilidad externa: Mis_APPS\Resultado_AutoIt3 = resultado
  • Valor del parámetro pasado a la Aplicación: Mis_APPS\Parametro1 = valor del parámetro

Para pasar un parámetro a nuestra aplicación Velneo creamos el siguiente archivo de comandos miAPP_parametros.cmd

@ECHO OFF
 REM Comprueba si hay parámetros
 IF %1.==. GOTO INICIAR
REM Escribe el parámetro en el registro para que lo lea vClient
 REG ADD HKCU\Software\Velneo\Mis_APPS /v Parametro1 /t REG_SZ /d %1 /f
REM Inicia la Aplicación Velneo y cierra el batch
 :INICIAR
 START "VELNEO" "C:\Program Files\Velneo\vClient.exe" vatp://Mi_Usuario:@127.0.0.1/Mi_Aplicacion
 EXIT

Conclusiones

    • A través de las opciones de Configuración del sistema podemos establecer y reiniciar el aspecto visual de los componentes de Velneo.
    • Es también una forma de comunicarnos con aplicaciones de terceros recibiendo parámetros o resultados de otros cálculos.
    • Todas las opciones deben colgar siempre de la rama HKEY_CURRENT_USER\Software\Velneo a la que el usuario tiene siempre acceso total.
    • Hay que tener en cuenta que solo podemos escribir y leer valores de texto o números enteros (ver los comandos de Configuración).
    • Aunque hemos visto el detalle en el sistema operativo Windows, los comandos de Configuración del sistema son multiplataforma.

¿Preparado para utilizar los comandos de configuración del sistema? 

Me gustaría escuchar tu opinión. Déjame un comentario más abajo.

La entrada Comandos de Configuración del sistema aparece primero en AyudaVelneo.

Introducción a los objetos de lista

En el proyecto de aplicación de nuestra solución disponemos de un conjunto de objetos con los que podemos gestionar listas de registros de una determinada tabla. El objeto más conocido es la Rejilla porque su funcionalidad es la más extensa, aunque existen otros objetos de lista visualmente más atractivos para mostrar los datos al usuario.

Los objetos de lista

objetos de lista

Hagamos un repaso de todos los objetos de lista, analizando a fondo aquellos aspectos más interesantes para el programador.

Enumeramos los objetos de lista disponibles:

    1. Rejilla: Conjunto de celdas distribuidas en filas y columnas. Una fila se corresponde con un registro de la tabla y una columna con un campo de dicha tabla.
    2. Árbol visor de tablas: Presenta, en distintos niveles, la información de una tabla con clave arbolada.
    3. Bloc de formularios: Permite editar en un formulario cada uno de los registros de una tabla.
    4. Casillero: Está compuesto por una serie de celdas (o casillas) distribuidas en filas y columnas. Cada celda permitirá mostrar un formulario de un registro de la tabla. Cada celda a su vez es un botón que permite disparar un comando al hacer clic sobre él.
    5. ComboView: Presenta una lista desplegable de registros de la tabla.
    6. Informe: Se utilizarán para enviar información de los registros de una lista de la tabla a una impresora o a un documento PDF en disco.
    7. ListView: Presenta una lista de registros de la tabla en una zona con scroll.
    8. Lista QML: Aprovecha toda la potencia gráfica de QML para mostrar listas de registros de una o varias tablas.
    9. ViewFlow: Usa un modo gráfico a modo de presentación de diapositivas para mostrar un formulario o campo dibujo por cada registro de la tabla.
    10. Rejilla avanzada: Partiendo de una Rejilla normal podemos crear otro Rejilla con opciones avanzadas, disponibles por el usuario en tiempo de ejecución.
    11. Alternador de lista: En este objeto podemos declarar múltiples objeto de salida (rejilla, informe, casillero, …) entre los que el usuario final podrá alternar en tiempo de ejecución.
    12. MultiVista: Permite presentar varios objetos sincronizados en función de su entrada y salida.

Características comunes a los objetos de lista

Algunas características comunes a todos ellos son:

    • Los objetos de lista implementan el esquema Modelo/Vista (Model/View) a la hora de mostrar los datos de una tabla. El Modelo se corresponde con la Lista de entrada y la Vista es el propio objeto que proporciona el interface para que el usuario pueda interactuar con los datos de entrada. Por lo tanto, el objeto de lista necesita siempre una Lista de entrada para que pueda mostrar registros de la tabla. La propiedad Tabla asociada determina a qué tabla del proyecto pertenece la Lista de entrada.
    • En la mayoría de objetos de lista podemos añadir, modificar o eliminar fichas o registros de la lista de entrada. Para ello especificaremos el Formulario de alta, modificación y baja, lo cual activa automáticamente las teclas Ins para mostrar el formulario de Inserción y Supr para el formulario de eliminación de la ficha seleccionada. El formulario de modificación se muestra haciendo doble click sobre la ficha seleccionada o con las teclas Enter/Return/Barra espacio. Existe la opción de mostrar un menú contextual por defecto que mostrará las opciones de Alta, Modificación y Baja de ficha. En este caso existe un problema, el menú por defecto seguirá mostrando todas las opciones aunque no hayamos especificado algún formulario de alta, modificación o baja. En ese caso usaremos un Menú personalizado.
    • En todos los objetos de lista se pueden definir los subobjetos Conexión de evento, Manejador de evento, Variable y en la mayoría el Drop. Estos subobjetos permiten encapsular código en los objetos de lista y de esta forma darles funcionalidad que podremos aprovechar en múltiples lugares de nuestra aplicación.

El evento de drag&drop permite seleccionar registros en un objeto de lista para soltarlos en otro objeto de lista. Consultar los siguientes enlaces: qué es drag&drop, ejemplo drag&drop de ayudavelneo y tutor de drag&drop.

    • Los objetos de lista se pueden mostrar en el interface de 3 formas:
      • Como un control “Vista de datos incrustado en el formulario.
      • Como un objeto incrustado directamente en una ventana independiente (en modo Vista o Modal) mediante un objeto “Acción“.
      • Disparar el objeto desde un proceso con el comando “Crear manejador de objeto(), en este último caso la ventana siempre es Modal, es decir, la ejecución del proceso se interrumpe hasta que se cierra la ventana contenedora del objeto de lista.
    • Además desde el API podemos disparar una Acción con theMainWindow.runAction() o instanciar el objeto mediante la clase VDataViewDialog.

Veamos un ejemplo:

Queremos mostrar un Casillero CAS_ARTICULOS con datos de la tabla ARTICULOS. Se ha preparado un formulario FRM_ARTICULOS_CAS con cuatro campos de la tabla y lo asignaremos a la propiedad Formulario del objeto CAS_ARTICULOS.

vaf_03_cas_frm

Necesitamos un proceso que proporcione la Lista de entrada para el objeto CAS_ARTICULOS.

Rem ( Proceso PRO_LISTA_ARTICULOS - Entrada ninguna y Salida la Lista de Artículos)
Rem ( Obtenemos la Lista de Artículos que alimentará nuestro objeto de lista)
Cargar lista ( ARTICULOS@facturas_dat, ID, , , , )
        Añadir lista a la salida

Para insertar el control Vista de datos en un formulario que muestre el objeto CAS_ARTICULOS nevesitamos 2 objetos:
       Objeto 1: PRO_LISTA_ARTICULOS@facturas_app
       Objeto 2: CAS_ARTICULOS@facturas_app

Para mostrar el casillero en una ventana independiente en modo Vista necesitamos un objeto Acción ACC_ARTICULOS_CAS que nos permita encadenar el proceso y el objeto de lista tal como hemos visto en el control Vista de datos. En el objeto ACC_ARTICULOS_CAS seleccionamos el comando Disparar objetos y elegimos el proceso PRO_LISTA_ARTICULOS y el Casillero CAS_ARTICULOS.

Una Acción es el objeto del proyecto de aplicación que permite disparar un comando. Éste puede ser un comando en stock (preprogramado) o un comando programado que, en general, disparará objetos.

Rem ( Mostrar el Casillero en modo Vista )
Interfaz: Ejecutar acción ( ACC_ARTICULOS_CAS@facturas_app )

El comando Crear manejador de objeto muestra el objeto de lista en una ventana Modal o en cuadro de diálogo.

Rem ( Mostrar el Casillero en una Ventana Modal )
Crear manejador de objeto ( hCasillero, Casillero CAS_ARTICULOS@facturas_app )
Rem ( Obtenemos la lista de entrada y la añadimos al objeto de lista)
Cargar lista ( ARTICULOS@facturas_dat, NAME, , , , )
        Añadir lista al objeto ( hCasillero )
Rem ( Mostramos la ventana Modal. El proceso se detiene hasta que se cierra la ventana )
Disparar objeto ( hCasillero, No aplicable, LOK )

Con el API y la clase VDataViewDialog también podemos hacer algo equivalente:

// Instanciamos objeto Lista en modo Vista ejecutando una Acción del proyecto
theMainWindow.runAction("facturas_app/ACC_ARTICULOS_CAS")

// Usando la clase VDataViewDialog para mostrar el Casillero en Ventana Modal o cuadro de Diálogo
// Obtenemos la lista de entrada
var oLista = new VRegisterList(theRoot)
if (oLista.setTable("facturas_dat/ARTICULOS")) {
        if (oLista.load("ID", [])) {
                // Instanciamos el Casillero
                var oCasillero = new VDataViewDialog(theRoot)
                oCasillero.setDataView(17, "facturas_app/CAS_ARTICULOS")
                // Alimentamos la entrada
                if (oCasillero.setRegisterList(oLista)) {
                        // Ejecutamos el objeto. El script se detiene hasta que cerramos la ventana modal.
                        oCasillero.exec()
                }
        }
}

vaf_03_cas_final

    • A todos los objetos de lista se les puede asociar una Barra de herramientas (Toolbar) que irá situada en la parte superior o inferior.

Una Toolbar o Barra de herramientas es un objeto de interfaz gráfica que contiene botones que al ser presionados activan ciertas funciones de una aplicación. Cada botón de la toolbar disparará un objeto (Acción o Menú) declarado en el proyecto en curso o en los proyectos heredados o un menú en stock.

Los botones de la Barra de herramientas solo puede disparar Acciones o menús.
El uso más común es mostrar al Usuario de forma gráfica las opciones de Añadir, Modificar y Eliminar.

Las 3 Acciones son:

      • Ficha: Formulario de alta – Muestra el formulario indicado en la Acción o en su defecto el indicado en el objeto de lista.
      • Ficha: Formulario de modificación – Muestra el formulario indicado en el objeto de lista.
      • Ficha: Formulario de baja – Muestra el formulario indicado en el objeto de lista.

vaf_03_toolbar

Estas tres Acciones refrescan automáticamente la Ficha afectada del objeto de lista.
Si queremos refrescar la lista del objeto de lista no funcionará el comando Interfaz: Recalcular(), éste solo se puede aplicar a un control Vista de datos ya que necesitamos ejecutar de nuevo el proceso que alimenta la lista de entrada del objeto de lista.

¿Cómo podemos refrescar la lista del objeto de lista desde su propio manejador de evento?
Creamos un botón en la Toolbar cuya Acción ejecuta el comando Disparar señal. En el objeto de lista una conexión de evento captura la señal Acción disparada y ejecuta el siguiente manejador:

Rem ( El comando Recalcular() no funciona en el contexto del objeto de lista )
// Interfaz: Recalcular ( )
Libre
Rem ( Usamos una Cesta local para recargar la lista en el objeto de lista )
Cesta: Crear cesta local ( ARTICULOS@facturas_dat, cesArt )
Cargar lista ( ARTICULOS@facturas_dat, NAME, , , , )
Cesta: Agregar lista a la cesta ( cesArt )
Cortar lista ( 0, )
Cesta: Agregar a la lista en curso ( cesArt )
Seleccionar ficha por posición ( 1 )
    • La clase del API VAbstractListDataView es la clase base de todos los objetos de lista. Con VAbstractListDataView tendremos acceso a funciones relacionadas con la lista asociada al objeto de lista. Para cada tipo de objeto de lista (rejilla, casillero, …) disponemos de la correspondiente clase que hereda de VAbstractListDataView que proporciona funciones específicas para la interfaz.

Veamos un ejemplo de un manejador de evento javascript que se dispara en el objeto de lista mediante una Acción que ejecuta el comando Disparar señal. Es un código genérico que podemos usar en cualquier objeto de lista independientemente de su tipo y de la tabla asociada.

// Manejador BUSCAR_LISTA_JS genérico que sirve para cualquier objeto de lista
// Obtenemos el objeto de lista
var oObjetoLista = theRoot.dataView()

// Obtenemos la Lista asociada al objeto de lista
var oLista = new VRegisterList(theRoot)
if (oObjetoLista.getList(oLista)) {
        // Tabla de la lista (alias/idTabla)
        var cTabla = oLista.tableInfo().idRef()
        // Instanciamos un registro de la Tabla para seleccionar una Ficha en la Lista
        var oRegistro = new VRegister(theRoot)
        if (oRegistro.setTable(cTabla)) {
                // Buscamos un registro por el índice ID único
                var cIDArt = prompt("ID del elemento de la Lista", "", "Búsqueda")
                if (cIDArt.length > 0) {
                        // Localizamos un registro por el Índice único
                        if (oRegistro.readRegister("ID", [cIDArt], VRegister.SearchThis)) {
                                oObjetoLista.setCurrentSelect(oRegistro)
                                // Edita el registro de la Lista
                                oObjetoLista.execEditForm()
                        }
                        else alert("No existe el elemento en la Lista", "Búsqueda")
                }
        }
}

Estas son algunas de las funcionalidades comunes a los objetos de lista.

En un próximo artículo repasaremos las peculiaridades de cada tipo de los objetos de lista y comprobaremos lo sencillo que resulta con Velneo mostrar una lista de registros de múltiples maneras solo con cambiar el objeto de lista, recuerda el esquema Modelo/Vista, un solo modelo y múltiples vistas.

¿Qué objetos de lista utilizas tú? 

Me gustaría escuchar tu opinión. Déjame un comentario más abajo.

La entrada Introducción a los objetos de lista aparece primero en AyudaVelneo.