Mostrando entradas con la etiqueta Formulario. Mostrar todas las entradas
Mostrando entradas con la etiqueta Formulario. Mostrar todas las entradas

lunes, 20 de mayo de 2024

Interfaz. OOo Basic.

Formularios (c). Calc

También en LO.Calc podemos utilizar formularios sin necesidad de acceder a la capa gráfica (getDrawPage()). Esto es posible por permitir LibreOffice la vinculación de los controles de formulario (algunos) con las celdas.



Cuando implementamos un control en una hoja de cálculo podemos definir sus características desde Propiedades del control (1). Este menú consta de 2 ó 3 submenús, en el segundo de ellos (Datos) podemos incluir, si es que está disponible, la referencia a la celda (vg. A7) con la que vinculamos el control desde la opción Celda enlazadaEsta opción está disponible para los cuatro controles que vimos en la [entrada anterior], estos es, para los campos Cuadro de texto, Cuadro de lista, Botón de opción y Casilla.

Al establecerse esta asociación (2) podemos acceder al contenido creado por el usuario accediendo al contenido de la celda vinculada, evitando así el acceso a la capa gráfica del documento. Como sabemos (3) para acceder al contenido de una celda deberemos seguir lo pasos siguientes:

Dim oDocumento As Object, oHojaActiva As Object, oCelda As Object

Dim sContenido As String

oDocumento = ThisComponent 

oHojaActiva = oDocumento.getCurrentController.getActiveShee()

oCelda = oHojaActiva.getCellRangeByName("E3")

sContenido = oCelda.getString()

La repetición de las líneas 5-6 de este script (con la creación de tantas variables Object y String como controles-celdas tenga nuestro formulario, nos permitirá acceder al conjunto de datos introducidos por el usuario.

Aunque el procedimiento básico es así de simple, deberemos considerar, no obstante, las peculiaridades y/o posibilidades que presentan los distintos tipos de control, ya que lo anterior es válido para el control Cuadro de texto, y también para el resto, pero cada uno de los restantes presenta alguna peculiaridad que es necesario conocer.

El control Cuadro de lista permite en su configuración seleccionar, además de la celda enlazada, el contenido que mostrará dicha celda. Esta opción está disponible desde Datos | Contenido de la celda enlazada, pudiendo establecer bien La entrada seleccionada o bien la Posición de la entrada seleccionada (4). Aunque en este ejemplo y en la mayoría de los formularios con los que trabajemos, la opción preferente será la primera (La entrada seleccionada), tanto por funcionalidad como por facilidad de uso en el script (5), habrá ocasiones en las que sea conveniente trabajar con Posición de la entrada seleccionada precisamente por tratarse de un valor numérico (6). En este caso tanto la variable que asociemos al contenido de la celda, como el tratamiento posterior de esta variable (y su contenido) será muy diferente del que normalmente haremos con el texto resultante de la opción La entrada seleccionada (ver nota 4).

El o mejor aun, los controles Botón de opción y Casilla presentan también peculiaridades  que es necesario conocer, aunque en este caso se trata tanto de la vinculación a las celdas como del tratamiento del resultado de la elección del usuario y su constatación en el contenido de las celdas.

Es necesario saber, en primer lugar, que cada botón o cada casilla se debe asociar con una celda diferente. Esto tiene sentido para las casillas, pero puede parecer paradójico para los botones, ya que éstos están vinculados y la elección de uno implica necesariamente la no elección del resto (7). No obstante, la lógica de la asociación control-celda impone que se vincule cada botón a una celda, ya que si vinculamos todos los botones a la misma celda lo que hacemos (por la bidireccionalidad explicada en nota 2) es precisamente equivalente a seleccionar todos los botones.

Lo segundo que debemos saber es que, en coherencia con la naturaleza de los botones  y las casillas, lo que ambas devuelven (y queda escrito en las celdas enlazadas) en un valor booleano ("VERADERO" | "FALSO"), lo que conlleva repercusiones tanto para el acceso al contenido de dichas celdas como para el posterior tratamiento de estos datos. Se abren además diferentes opciones de trabajo para el desarrollo de este tratamiento. Veamos las implicaciones de lo anterior en nuestro formulario de ejemplo.

En  él contamos con dos botones para la elección del sexo del alumno (por ejemplo), cada uno de ellos asociado a una celda (por las razones antes dichas), concretamente btnM está vinculado a F15 y btnF a F16. En ambos casos, en dichas celdas se escribe de forma simultánea VERDADERO y FALSO (o viceversa) según pulsemos en el primer o en el segundo botón (8). Ahora bien, a nosotros lo que nos interesa es lo que deriva de la elección del usuario, esto es: conocer si el sujeto referenciado es niño o niña (9) y nada de eso nos dice el contenido de F15 o de F16 (10). Para convertir VERDADERO | FALSO en niño | niña necesitamos emplear una estructura o función condicional, pudiendo optar por emplear las funciones Calc o por el tratamiento condicional (if) desde OOo Basic.

En el primer caso (que es lo que hacemos en nuestro ejemplo) construimos sobre la celda G15 la siguiente función: =SI(F15=1,"niño";"niña) (11). En el segundo accederemos mediante código al contenido de la celda F15 y construiremos una estructura condicional como se ejemplifica a continuación, y que cumple la misma función (12):

Dim oCeldaSexo As Objet

Dim sSexo As String

oCeldaSexo = oHojaActiva.getCellRangeByName("F15")

If oCeldaSexo.getValue = 1 Then (Ver nota 11)

sSexo = "niño"

Else

sSexo = "niña"

End If

También en Casilla sucede algo similar. En nuestro ejemplo se utilizan tres casillas, cada una de ellas asociada a una celda (F19, F20 y F21 respectivamente) que contienen valores booleanos VERADERO | FALSO. Y también en este caso disponemos de dos opciones para convertir estos valores en el resultado que nos interesa (en este caso la puntuación del ítem).

La opción basada en las funciones Calc se desarrolla en dos fases:

  • En la primera puntuamos cada ítem mediante una función condicional (=SI(F19="";"";SI(F19=0;1;0))) sobre las celdas G19, G20 y G21.
  • Y en la segunda calculamos el total de puntuación del ítem mediante la función Calc Suma() (=SUMA(G19:G21))
Dadas las características propias de Casilla tenemos que tomar en cuenta la acción del usuario sobre cada una de las casillas que componen este elemento del formulario, siendo esta una diferencia con el tratamiento del control Botón. Por ese mismo motivo necesitamos realizar la segunda fase (el sumatorio), ya que el análisis de la casilla individual no nos permite acceder al resultado del conjunto. Esto también es válido para la opción basada en OOo Basic, como veremos a continuación.

Dim oCeldaItem1 As Object,oCeldaItem2 As Object,oCeldaItem3 As Object

Dim iPuntos As Integer

iPuntos = 0

If oCeldaItem1.getValue = 0 Then

    iPuntos = iPuntos + 1

End If

If oCeldaItem2.getValue = 1 Then

    iPuntos = iPuntos + 1

End If

If oCeldaItem3.getValue = 1 Then

    iPuntos = iPuntos + 1

End If

Puedes observar como, en esta modalidad u opción necesitamos tres variables Object, una para cada Casilla (oCeldaItem1, oCeldaItem2 oCeldaItem3), además de una variable Integer para el sumatorio de la puntuación. Además, cada variable Ítem debe ser sometida al análisis condicional (vg. If oCeldaItem1.getValue = 0) y el cálculo de la puntuación total (el sumatorio) se realiza asignando repetidamente (tantas veces con ítem y condicionales) la propia variable a si misma más el punto que corresponde por acierto (iPuntos = iPuntos + 1) (13)

Documento.

NOTAS

(1) Sobre la implementación manual de un formulario en un documento ver [esta entrada]

(2) La asociación o vinculación entre el control y la celda es bidireccional; en consecuencia, lo que escribamos o seleccionemos en el control se escribe en la celda y viceversa. Lógicamente también lo que se modifique o se borre.

(3) Ver procedimiento en [esta entrada]

(4) En el primer caso mostrará en la celda asociada el texto seleccionado de la lista de opciones que tenga establecido Cuadro de lista (vg. 1º EP), y en el segundo, para esta misma elección del usuario, mostrará en la celda el valor numérico 2, ya que se trata del 2º elemento de la lista de opciones. La consecuencia es tanto para la visualización del resultado como para el procedimiento de acceso al contenido de la celda enlazada, ya que la variable receptora deberá ser (preferiblemente) de tipo numérico; pero también puede tener consecuencias para el posterior tratamiento del contenido de la variable con la que asociemos este dato.

(5) Esta opción tiene la ventaja de permitir un tratamiento directo del contenido textual, pero también la total similitud del proceso con el que realizamos con Cuadro de texto

(6) En ejemplo de esto es cuando la elección del usuario implique valor cardinal u ordinal, como en el caso de las escalas tipo Likert. Estas escalas pueden construirse utilizando diferentes controles; Cuadro de lista es uno de ellos. 

(7) Cosa que, como sabemos, no ocurre con las casillas, dado que es es posible seleccionar cada casilla de forma independiente de lo que hagamos con el resto. En este caso es lógico que cada casilla deba estar asociada a una celda distinta, pero el valor final de una elección entre varias opciones excluyente de un botón (entre varios), sólo puede arrojar un único dato.

(8) Esta simultaneidad es resultado de la vinculación entre ambos botones; es coherente con su lógica de funcionamiento y, en consecuencia, síntoma de una correcta configuración del conjunto. 

(9) En el supuesto, por ejemplo, de que tal categorización tiene efectos en las formas gramaticales de un texto que describa el resultado de un análisis de datos o una comunicación personalizada con la familia, etc. 

(10) Obviamente, a excepción de lo que nos permite la visualización directa del formulario; pero estamos hablando aquí de la automatización del análisis de los resultados, no del que podemos realizar manualmente visualizando los datos que se nos muestran en la hoja de cálculo.

(11) No es necesario trabajar con el contenido de la celda F16 dada la asociación que se establece entre los botones de opción. Ver explicación en el texto de la entrada, contenidos asociados a las notas y 8, y en las propias notas. Observa que la expresión "VERADERO" es sustituida por 1. Esto es debido a la equivalencia entre 1 -> VERADERO y 0 -> FALSO cuando se trata de datos booleanos. Lo mismo sucede en el código alternativo OOo Basic (If oCeldaSexo.getValue = 1 Then)

(12Queda a la tu decisión optar por una u otra opción, pero es necesario que conozcas ambas para que estés en posición de elegir. En ambos casos el resultado es el mismo. Mi recomendación es que, dado que optamos en este caso por acceder a los resultados mediante el acceso a las celdas (y no directamente a los controles), continúes con esa misma lógica y utilices las funciones Calc todo lo que te sea posible. Es necesario saber utilizar estas funciones y en este caso hacerlo no sólo mantiene la coherencia con el planteamiento de trabajo, además supone un ahorro de tiempo.

(13) Este proceso puede resolverse de otras formas, pero para los que nos interesa ahora es suficiente con la forma en que aquí se desarrolla. Respecto a la opción a elegir (el uso de funciones Calc vs. código OOo Basic remito a lo dicho en la nota 12.

domingo, 19 de mayo de 2024

Interfaz. OOo Basic.

Formularios (b)


Analizo ahora el acceso a los controles de uso más frecuente en un formulario desde OOo Basic, esté creado éste sobre un documento Writer o sobre una hoja de cálculo Calc. Esta entrada complementa, pues, la [entrada anterior] en la que explico la creación del formulario sobre el documento y el acceso a éste mediante código OOo Basic, este sí diferentes según el servicio (Calc o Writer) sobre el que esté implementado.




Los controles de uso más frecuente en un formulario son cuatro (1): Caja de texto, Cuadro de lista, Botón de opción y Casilla (de verificación). Los dos primeros para la entrada de datos y los dos últimos para facilitar la elección de una posible opción de respuesta.

Como ya sabemos, todos los controles de formulario deben ser implementados en el documento-soporte de forma manual (ver [aquísobre la implementación de un formulario Formulario), pero además necesario (o la menos conveniente) conformar la apariencia y forma de funcionamiento de cada uno de esos controles de forma específica. Para ello deberemos acceder a Propiedades del control, opción disponible a partir de la ventana emergente que activamos mediante Selección del control -> Clic derecho sobre el control seleccionado. Una vez dentro de este menú de opciones, podremos configurar nuestro control.

El control Cuadro de texto se comporta funcionalmente como un InputBox(); no obstante presenta características y posibilidades de uso diferentes que las que ofrece InputBox() (2). Para acceder a este control y a su contenido crearemos el código siguiente:

Dim oTxtNombre As Object

Dim sDato As String

 oTxtNombre = oFormulario.getByNa.me("txtAlNombre")

 sDato = oTxtNombre.Text

  • Primero creamos dos variables, la primera (oTxtNombre ) de tipo Object (recuerda que un control es un objeto en LibreOffice) y la segunda (sDato) de tipo String.
  • El paso siguiente es acceder al objeto control (en este caso) por su nombre (una de las opciones que tenemos cuando configuramos el control) mediante la siguiente instrucción: oTxtNombre = oFormulario.getByName("txtAlNombre")
  •  Finalmente, recurriendo al atributo Text del control, accedemos al contenido introducido por el usuario. Asignamos este contenido a la variable String creada al inicio de del código (sDato = oTxtNombre.Text)

El control Cuadro de lista se diferencia del anterior por sustituir la entrada de texto de escritura libre por una lista de datos, presentados como opciones de respuesta para la elección del usuario. Este listado puede crearse directamente desde Propiedades del control -> General -> Entradas de la lista o desde Propiedades del control -> Datos -> Tipos de contenido de la lista opción Lista de valores. Esta segunda forma también permite asociar el control a una tabla, una consulta o a otras opciones de base de datos.

Para acceder al control Cuadro de lista se sigue el mismo procedimiento que para hacerlo a Cuadro de texto, pero no así para el acceso al dato seleccionado por el usuario. Observa el código siguiente:

Dim oCurso As Object, oCursoVista As Object
Dim sCurso As String

oCurso =  oFormulario.getByName("curso")
oCursoVista = ThisComponent.getCurrentController.getControl( oCurso )
sCurso = oCursoVista.getSelectedItem()
  • Primero declaramos dos variables Object para acceder a los objetos control (oCurso) y listado del control (oCursoVista) y una tercera variable, esta de tipo String (sCurso)
  • El uso de las dos variables Object es el siguiente:
    • Mediante la primera (oCurso) accedemos al control como tal (oCurso =  oForm.getByName("curso"))
    • Y mediante la segunda (oCursoVista) a su listado de opciones (oCursoVista = ThisComponent.getCurrentController.getControl( oCurso ))
  • Posteriormente asignamos la opción seleccionada (método getSelectedItem()) a la variable String, previamente declarada (Dim sCurso As String) haciendo uso de ese método (sCurso = oCursoVista.getSelectedItem())
El control Botón de opción es en realidad un conjunto (de al menos dos controles del mismo tipo vinculados entre sí; ambos ofrecen al usuario la posibilidad de elegir uno de ellos como opción seleccionada (el o los otros se descartan), por lo que el conjunto presenta un comportamiento similar al del Cuadro de lista en el que el usuario opta por una de las opciones que se le presentan), aunque en su apariencia formal y su funcionamiento son muy diferentes.

También es diferente el modo en que se accede a la opción seleccionada, requiriendo Botón de opción un manejo similar al que hacemos del valor que devuelve la función MsgBox() (3).

        Dim oBtn1 As Boject, oBtn2 As Object
Dim sSexo As String
oBtn1 = oFormulario.getByName("btn1")
oBtn2 = oFormulario.getByName("btn2")
If oBtn1.State() Then
sSexo = "Niño"
End If
If oBtn2.State() Then
sSexo = "Niña"
End If
MsgBox "Sexo": " & sSexo

El acceso al "contenido" en Botón de opción es en realidad la captura de su pulsación por el usuario (oBtn1 = oFormulario.getByName("btn1")), y se requieren tantas variables como botones de opción contenga nuestro conjunto de opciones. Por ejemplo, en este caso, sólo dos (oBtn1 y oBtn2): el primero capturar la pulsación del botón btn1 (oBtn1 = oFormulario.getByName("btn1")) y el segundo la del botón btn2 (oBtn2 = oFormulario.getByName("btn2"))

Al hacerlo capturamos también sus métodos y propiedades, en concreto el método  State(), que presenta dos opciones (0|1), dado que es de tipo Boolean. Por ello es posible valorar el estado del botón mediante el condicional If. En función del resultado del análisis del estado (State()) de los diferentes botones que componen ese conjunto, asignamos un valor u otro a una segunda variable (en nuestro caso sSexo que es de tipo String) que es la que contendrá la información relevante para el desarrollo del script. En nuestro ejemplo se trata de determinar el sexo del alumno (sSexo="Niño" vs. sSexo="Niña"y de informar de ello mediante MsgBox (MsgBox "Sexo": " & sSexo)

Finalmente el control Casilla (de selección o de verificación) se diferencia de Botón de opción en su forma externa (se representa como un cuadradito en vez como círculo) pero se asemeja en el código necesario para acceder al control y a su estado (de nuevo el método State()). 

Otra diferencia entre ambos es que Casilla no es opcional-excluyente cuando se presenta en agrupamientos, lo que implica que el usuario puede marcar cada uno de estos controles de forma independiente, cosa que no sucede con los botones. De hecho una presentación posible de Casilla es como control único de confirmación del cumplimiento de una condición (Vg. ¿Es correcta esta contraseña?). Otra es, como dije, formando agrupamientos, como en el ejemplo que sigue (4).

        Dim oCasilla1 As Object, oCasilla2 As Object, oCasilla3 As Object
        Dim iPuntos As Integer

iPuntos = 0 

        oCasilla1 = oFormulario.getByName("Casilla1")

If oCasilla1.State() = 0 Then

 iPuntos = iPuntos + 1

End If

En este script, las tres casillas pueden ser seleccionadas independientemente una de otra. De hecho la respuesta correcta implica no seleccionar la primera y sí las otras dos. 

Por lo que se refiere al código de acceso al objeto Casilla como el tratamiento de su estado (State()) es igual que el Botón de opción, como puedes observar comparando ambas instrucciones.

Documentos. Ambos enlaces permiten la descarga del formulario sobre Writer y sobre Calc. Se trata de ejemplificar el uso y [acceso a los formularios], diferente en función del servicio, y a los controles de uso más frecuente (los tratados en esta entrada).


NOTAS 

(1) LibreOffice cuenta con un listado de controles de formulario mucho más amplio, pero estos cuatro cubren la mayoría de las necesidades que buscamos satisfacer con un formulario, de ahí que sean los de uso más frecuente y, por tanto, los que más interés tienen para la creación de un docap basado en Formulario.

(2El manejo de Cuadro de texto requiere el mismo procedimiento de acceso que cualquier otro control de formulario, por lo que no es factible trabajar con él como con InputBox(). En términos de funcionalidad, Cuadro de texto permite opciones de configuración que no están disponibles en InputBox() (que en esto es muy limitado); destaco por su utilidad la posibilidad de configurarlo multirrenglón, además de los formatos de texto (incluido tamaño de letra). Estas opciones de configuración permiten un uso muchos más amigable y funcional como interface, si bien requiere un trabajo previo de configuración desde Propiedades del control.

(3) También en este caso deberemos tratar el valor devuelto mediante un condicional If.

(4) Se trata de un fragmento del script real. En él se ejemplifica el acceso a uno de los tres controles Casilla y a su posterior tratamiento mediante el condicional If. El script completo está disponible en el documento que ejemplifica el uso de los controles que se explican en esta entrada.

jueves, 16 de mayo de 2024

Interfaz. OOo Basic.

Formularios (a)

Además de las formas simples de interface (MsgBox e InputBox), LibreOffice cuenta con otros dos recursos de interface: los formularios y los cuadros de diálogo




Los formularios son medios para facilitar la interacción usuario-programa, empleados fundamentalmente en la fase Input. A diferencia del cuadro de diálogo (en adelante, Diálogo, por brevedad), el formulario se puede emplear tanto como funcionalidad (1como asociado a a un script; en ambos casos, el primer paso consiste en crear el formulario (funcionalidad) desde el documento, sea este un documento Writer o desde Calc.




En esta entrada trataré sobre el acceso al formulario mediante código OOo Basic, útil para trabajar con Writer, Calc e Impress. Los formularios de Base (base de datos) quedan fuera de nuestros objetivos por su especificidad.

El primer paso es, como dije antes, es crear el formulario desde el documento. Es posible que esta funcionalidad no está directamente disponible (2), por lo que necesitaremos activar las opciones FormularioVer | Barra de herramientas | Diseño de formularios (que muestra sus controles) y Ver | Barra de herramientas | Controles de formularios, que muestra las herramientas de los Controles. A partir de aquí lo que debemos hacer es seleccionar el icono Modo diseño... (3)



Una vez creado el formulario, para trabajar sobre él mediante OOo Basic deberemos trabajar desde el IDE para acceder a la capa gráfica (getDrawPage()), que es donde se sitúan los formularios (4). 

El recorrido que deberemos hacer para acceder a un formulario depende del soporte (o servicio) en el que esté creado: no es lo mismo trabajar desde Writer que hacerlo desde Calc.

Un script que nos permita acceder a un formulario creado en un documento Writer requiere realizar el siguiente recorrido, en el que se diferencian cuatro pasos, que precisan contar con una serie de variables de tipo Object (5): 

Dim oDocumento As Object, oCapaDib As Object, oForms As Object, oForm As Object

  • Paso 1. Acceso al documento: oDocumento = ThisComponent
  • Paso 2. Acceso a la capa gráfica: oCapaDib = oDocument.getDrawPage()
  • Paso 3. Acceso al conjunto de formularios: oForms = oCapaDib.getForms()
  • Paso 4. Acceso al formulario concreto por el índice o por el nombre: 
oFormulario = oFormularios.getByIndex()               -> Acceso por el índice

oFormulario = oFormularios.getByName("frmActua")  -> Acceso por el nombre

El acceso a un formulario creado sobre Calc requiere también seguir una serie de pasos (cinco) que se basan en un conjunto de variables tipo Object por las mismas razones que expusimos antes (ver nota 5)

Dim oDocumento As Object, oHojaActiva As Object, oPaginaDibujo As Object
Dim oFormularios As Object, oFormulario As Object

 Los pasos a seguir son los siguientes:
  • Paso 1. Acceso al documento: oDocumento = ThisDocument
  • Paso 2. Acceso a la hoja activa: 
oHojaActiva = oDocumento.getCurrentController.getActiveSheet()

  • Paso 3. Acceso a la capa gráfica: oPaginaDibujo = oHojaActiva.getDrawPage()
  • Paso 4. Acceso al conjunto de formularios: oFormularios = oPaginaDibujo.getForms()
  • Paso 5. Acceso al formulario concreto, el cual puede ser por índice o por el nombre:

oFormulario = oFormularios.getByIndex()                   -> Acceso por el índice 
oFormulario = oFormularios.getByName(""frmCalcEj")      -> Acceso por el nombre


Podemos 
 considerar lo anterior como primera fase del proceso, que finaliza con el acceso a los contenidos que introduce el usuario en los controles del formulario. Dedicaremos a ello la entrada siguiente.

NOTAS

(1) Por funcionalidad entiendo cualquier recurso que puede ser desarrollado desde la interface general del servicio (Writer, por ejemplo), sin necesidad de acceder al IDE. En Writer los formularios se crean en la capa "Dibujo" del documento directamente desde éste. Para crear un Diálogo es necesario acceder al IDE y crearlo específicamente de forma directa. Desde esta perspectiva el formulario es un objeto más accesible y simple que un diálogo.

(2) Es posible que esta sea una de las causas de su escaso uso. Contribuye a ello que en los documentos prescriptivos (que no hacen otra cosa que seguir la costumbre consolidad por el uso) se empleen tablas como base para simular la apariencia de formulario.

(3) Este procedimiento implica activar el icono de la barra Controles de formulario. Este mismo icono está también disponible en la barra del Diseño de formulario. Activarlo también supone acceder a la definición del formulario y a otras utilidades, incluyendo la introducción de campos (comando Añadir campo), que también activa la barra Controles de formulario.

(4) Tanto en Writer como en Calc, los formularios se ubican en la capa gráfica ("de dibujo"), que se superpone a la capa en la que escribimos en el documento (en Writer) o en la que se encuentran las celdas (en Calc)

(5) Tanto el documento, como la capa gráfica y los formularios son objetos, dado que LibreOffice es un programa creado dentro del paradigma de la Programación Orientada a Objetos (POO)

martes, 7 de mayo de 2024

Interfaz. OOo Basic

Campo de elección


Último campo que vamos a tratar en esta serie de entradas. Es un tipo de campo que normalmente se resuelve como campo simple, solicitando la entrada de datos al usuario, pero que presenta como peculiaridad que en ciertas circunstancias disponemos de información previa que podemos tener recogida como listado o base de datos. Es el caso de los listados de alumnos con sus datos básicos, por ejemplo: NIE, nombre, apellidos y fecha de nacimiento (1).


Tanto en el informe como en el dictamen nos encontramos con campos aparentemente sencillos e inevitablemente considerados de entrada directa y simple de datos. Un ejemplo (aunque no el único) es el que representa esta imagen: Dictamen de escolarización, datos personales del alumno (2)


En este caso, y a modo de ejemplo, me limitaré a los campos de esta tabla del dictamen de la que he eliminado el campo Edad ya tratado en la [entrada anterior]. Se trata de los campos NIE, Nombre y apellidos del alumno y Fecha de nacimiento y voy a presuponer que ya disponemos de información sobre esos datos puesto que contamos con una sencilla base de datos creada en Calc (3).


Como en el resto de los campos, al tratarse de un documento prescriptivo (dictamen) crearemos previamente los marcadores necesarios (3 marcadores) que usaremos en el procedimiento de escritura del documento que, en esta ocasión, resolveremos mediante bucle que llama a una función específica de posicionamiento (o escritura)

For i = 0 To 2:
sVar = mDatos(i)
Posicionamiento(sVar,"mcd",i)
Next
... habiendo asociado los contenidos (los datos a incluir en los campos del documento) a una matriz de datos (mDatos()) (4) la cual cumplimentaremos mediante la asociación de sus componentes a funciones...

mDatos(0) = NIE(CodAl)

... aunque por seguridad demos opción a modificar o corregir los datos que esa función devuelve en función del dato que le pasamos como parámetro (NIE(CodAl))

mDatos(0) = InputBox("NIE:","ALUMNADO", mDatos(0))

En este planteamiento, en el script principal es fundamental el papel que juega el procedimiento de obtención de la variable parámetro que se incluye en la llamada a las funciones (que veremos después). En esta ocasión he utilizado un método simple basado en InputBox():

CodAl = InputBox("Introduce el código de identificación del alumno: " & Chr(13) &_
"1. Roberto Marcial Pérez" & Chr(13) &_
"2. María Martínez Sanz" & Chr(13) &_
"3. Adela Decastro Morán" & Chr(13) &_
"4. Antonio Heernández Jiménez" & Chr(13) &_
"5. Matias Luján López" & Chr(13) &_
"6. Amalia Desantos Melquiades" & Chr(13), "ALUMNADO","1-2-3-4-5-6")

Este código nos permite asignar a la variable-parámetro CodAl el valor de posición que ocupan los datos de cada alumno en cada una de las matrices que se explicitan en las funciones de las cuales CodAl es parámetro, previa corrección de su valor (CodAl = CodAl-1) para asegurar la coincidencia (5).

Queda ahora analizar el funcionamiento de las funciones que devuelven esos datos (6) y como todas ellas (NIE(), Nombres(), Apellidos() y FechaNacimiento()) son iguales en su funcionamiento, me limitaré a explicar una de ellas como ejemplo.

La función NIE() se define con un parámetro (ID As Integer) y de tipo String (esto es: devuelve un dato texto) (7).

Dentro de la función se declara una matriz (Dim mNIE(5) As String), una variable (Dim AlNIE As String) y una variable contador (Dim i As Integer). A la primera se le asignan los datos de los NIE conocidos (v.g. mNIE(0) = "123456") y la variable nos sirve para hacer más claro el procedimiento de selección y la devolución de valor pertinente asignándola el valor correspondiente de la matriz anterior (AlNIE = mNIE(i)) y, posteriormente, asignando su contenido a la función (NIE = AlNIE).

Resulta clave comprender el procedimiento que permite ejecutar la búsqueda e identificación del valor de interés que se resuelve mediante este código. Muestro y explico:

For i = LBound(mNIE()) To UBound(mNIE()):
mNIE(i)
If i = Id Then
AlNIE = mNIE(i)
End If
Next
  • Un bucle For recorre la matriz de datos NIE asociando su índice con el valor de la variable contador i.
  • Se condiciona (If) la asociación del contenido del NIE que se desea localizar al cumplimiento de la condición  i = Id, siendo id (como sabemos) el parámetro que traslada a la función el valor seleccionado por el usuario en el script principal.
  • En el momento en que la condición i = Id -> True, entonces se realiza la asociación del valor de la matriz a la variable (AlNIE) que devuelve el resultado de la función.
Finalizo esta entrada [aportando un documento] que contiene el código explicado en ella (8) con el mismo objetivo de otras veces: facilitar su comprensión y las propuestas de mejora que se quieran aportar. Me planteo, no obstante, retomar este procedimiento ya que es mejorable desde dos punto de vista: el uso de las matrices y el acceso a la base de datos que contiene la información que aquí manejo mediante las funciones y las matrices que contienen.

NOTAS

(1) Podemos ampliar este listado de campos mucho más, incluyendo datos familiares y escolares. Sería de desear que se trabajar más con bases de datos (con independencia del soporte), tanto para registro como para consulta, pero no suele ser lo habitual en el ámbito de los SEO. Por desgracia y sin una razón clara, a parte del poco conocimiento de estas herramientas, es una práctica poco frecuente.
(2) Del mismo tipo son los datos familiares del dictamen y del informe. Tanto unos (los personales del alumno) como otros (los familiares) pueden ser conocidos a priori, como resultado del análisis del expediente del alumno o de la información aportada por otros servicios (en caso de nuevas escolarizaciones). Sería deseable que así fuera, como también que el SEO tuviera actualizadas sus bases de datos sobre esta información y sobre otras, pero...
(3) De no contar con estos datos ya elaborados, deberíamos aplicar a estos campos el procedimiento básico de solicitud de información al usuario del docap mediante InputBox(). Incluso en caso de nuevas escolarizaciones (EI 3 años) es posible contar con esta información en formato tabla Excel. De ella podemos partir para construir la base de datos que necesitamos. Existen, como sabemos, diferentes modo de acceder y utilizar estos datos. En esta entrada trataremos la forma más simple que se puede concretar en forma similar a como propusimos en [esta entrada]. Este será nuestro punto de partida.
(4) En el caso de nombre y apellidos he optado por emplear, como paso previo, dos variables (AlNombre y AlApellidos) para mejor manejo de los datos al estar estos diferenciados en la base de datos de origen, posteriormente se asignan mediante concatenación al elemento de la matriz (mDatos(1) = AlNombre & " " & AlApellidos)
(5) Recuerda que las matrices se inician con el elemento 0, no con el elemento 1
(6) Además contamos con una subrutina (Posicionamiento()) cuyo cometido es facilitar la escritura de los datos en la posición de los marcadores. No la explico ya que el funcionamiento de este código ha sido explicado en entradas anteriores, aunque integrado dentro del script principal. debido a ello, la única novedad es que aquí se emplea como subrutina y que cuenta con tres parámetros: dos para identificar el marcador y uno para recibir el dato o contenido a escribir (Posicionamiento (sDatos As String,MrN As String, i As Integer))  
(7) Dado que el NIE es en realidad un código de identificación, no es necesario considerarlo un valor numérico.
(8) Si en todos los casos este paso es conveniente, en este lo es especialmente. Aunque el documento cuenta con un comando que facilita el uso del script, el mero uso de código no es el objetivo, por lo que insisto en la necesidad de acceder al IDE y conocer el código de primera mano.

lunes, 6 de mayo de 2024

Interfaz. OOo Basic

Campo calculado


Entiendo por campo calculado aquel que resulta de la aplicación de al menos una operación aritmética. Este tipo de campo es más propio de un docap de evaluación y está muy escasamente representado en los documentos prescriptivos, pero no ausente.


En efecto, tanto el dictamen como el informe contiene uno de esos campos, el mismo en realidad: el del cálculo de la edad del sujeto.


En la segunda tabla (1. Datos personales del alumno o alumna) del dictamen encontramos este tipo de campo. Como recordarás, en un primer momento lo tratamos como campo simple (introducir el dato directamente mediante InputBox()), trasladando al usuario la realización del calculo con o sin la ayuda de alguna herramienta de cálculo(1); pero en esta entrada vamos aprender a ejecutar el cálculo desde un script, lo que equivale a tratar dicho campo como campo calculado.

Como en todo cálculo son necesario datos y operadores, lo que nos lleva al necesario conocimiento de ambos en el lenguaje OOo Basic (en esta [entrada] sobre variables y operadores numéricos). Aquí encontramos los datos en dos campos: el tercero de la primera tabla (Fecha del dictamen) y en el tercero de la segunda (Fecha de nacimiento), ya que el cálculo de la edad resulta de:

Edad = Fecha del dictamen - Fecha de nacimiento

Este es un cálculo sencillo en cuanto a operaciones, pero no carente de cierta complejidad al constar una fecha de diferentes unidades de tiempo (año-mes-día). El procedimiento de la operación queda expresado (frecuentemente) en las carátulas de las pruebas de evaluación...


... que es una forma gráfica de expresar la siguiente igualdad:

e(a:m:d) = f(a*x + m*y + d) - i(a*x + m*y + d)
  • siendo e = edad, f = fecha final o actual e i = fecha inicial o de nacimiento
  • siendo a = años, m = meses y d = días
  • Siendo x = 365 e y = 30 (esto es, año standard de 365 días y mes ajustado a 30 días) 
Aunque en un campo calculado el procedimiento a seguir está en función de las operaciones (cálculos), en términos generales, y no con independencia de la complejidad del cálculo, se nos presentan dos posibilidades:
  • Incorporar el cálculo al script principal
  • O extraerlo del script principal y derivarlo a una función a la que asociar a una variable del script principal.
Ambas opciones son válidas en cualquier caso, pero la primera es aceptable cuando las operaciones son pocas (al menos pocas) y la segunda es aconsejable cuando debemos aplicar un número de operaciones que pueden afectar a la inteligibilidad del script principal.

Volviendo al cálculo de la edad, que es al que nos enfrentamos en el caso de los documentos prescriptivos tratados (dictamen e informe), el modo en que planteemos el script va a depender de la forma en que nos planteemos resolver el cálculo. De hecho podemos utilizar al menos tres procedimientos diferentes.
  • El primero y más sencillo consiste, básicamente y en primer lugar, en calcular la diferencia (en días) entre la fecha final y la inicial mediante la fórmula...
Edad = FechaFinal - FechaInicial

... factible siempre y cuando FechaFinal y FechaInicial hayan sido definidas como variables tipo Date, lo que requiere el uso de la función de conversión CDate() a la solicitud de datos al usuario mediante InputBox().

  • El segundo es más complejo puesto que no realizamos el cálculo de forma directa, sino que descomponemos cada una de las variables de fecha haciendo uso de tres funciones disponibles en Calc (year(), Month() y Day()) que devuelven respectivamente el año, mes y día de una fecha expresado en formato fecha (v.g., dd/mm/aaaa)
La primera fórmula es, en principio, perfectamente asumible desde el script principal (por lo que no sería necesario derivar el cálculo a una función), pero la segunda ya resulta suficientemente compleja como para que valoremos la pertinencia de usar una función.

No obstante, en ambas opciones, y hasta ese momento solamente hemos obtenido el número de días transcurridos desde la fecha inicial (nacimiento) hasta la actual...

iDias = fFinal - fInicial

... pero no la edad del sujeto tal y como se expresa normalmente (años, meses y, en su caso, días). Para ello necesitamos conocer, como los valores meses transcurridos y los años transcurridos (2) como paso previo para saber la edad.

iMeses = iDias\30
iAnnos = iDias\365

A partir de estos cálculos podremos obtener los valores edad en años y en meses. En realidad el de los años (de edad) resulta de la operación previa (iAnnos = iDias\365), pero el de meses (de edad) requiere restar el total de meses menos los que suponen los años.

iMesesEdad = iMeses -(iAnnos*12)

Es para el cálculo de los días de la expresión edad donde podemos optar por dos formas diferentes de realizar el cálculo:
  • Operando con los datos disponibles: iDiasEdad = iDias - ((iAnnos*365)+(iMesesEdad*30))
  • Utilizando el operador aritmético Mod para calcular el resto de la división de los días transcurridos entre 30: iDiasEdad = iDias Mod 30
Dado que con ambas operaciones se obtiene el mismo resultado, parece lógico que optemos por el operador Mod.

La tercera opción, que se ajusta claramente al uso de una función, reproduce el procedimiento empleado en el cálculo manual de la edad que vimos representado en la última imagen. Incluye el uso de funciones de identificación de años, mes y días de ambas fechas (funciones Calc Year(), Month() y Day()) y procedimientos de corrección para casos particulares. Pero detengo aquí la explicación porque esta función ya ha sido explicada en esta [entrada correspondiente] a la que remito para más detalle (3)

Analizo a continuación una de las formulaciones dentro de las posibles antes comentadas.

 Sub Edad2

'Variables

Dim oMarca As Object
Dim mMarcadores(2) As String
Dim mDatos(2) As String
Dim fDictamen As Date, fNacimiento As Date
Dim sEdad As String
Dim i As Integer

'Asignación de contenido

mMarcadores() = Array("d0","d1","d2")
fDictamen = CDate(InputBox("Fecha del dictamen:","DICTAMEN DE ESCOLARIZACIÓN"))
fNacimiento = CDate(INputBox("Fecha de nacimiento:","ALUMNO/A. DATOS PERSONALES"))
sEdad= EdadA(fNacimiento,fDictamen)
mDatos(0) = CStr(fDictamen)
mDatos(1) = CStr( fNacimiento)
mDatos(2) = sEdad

'Escritura de datos en el documento

For i=LBound(mMarcadores()) To UBound(mMarcadores())
oMarca = ThisComponent.getBookmarks().getByName(mMarcadores(i))
oMarca.getAnchor.setString(mDatos(i))
Next

End Sub

'Función para cálculo de edad

Function EdadA(fInicial As Date, fFinal As Date) As String

'Calculo de duración de los periodos temporales (en días, meses y años)

Dim iDias As Integer, iMeses As Integer, iAnnos As Integer

iDias = fFinal - fInicial
iMeses = iDias\30
iAnnos = iDias\365

'Cálculo de valores

Dim iMesesEdad As Integer, iDiasEdad As Integer

iMesesEdad = iMeses -(iAnnos*12)
iDiasEdad = iDias - ((iAnnos*365)+(iMesesEdad*30))

'Expresión edad

Dim sEdad As string

sEdad = CStr(iAnnos) & " años, " & Cstr(iMesesEdad) & " meses y " & CStr(iDiasEdad) & " días."

EdadA = sEdad

End Function

  • Dividimos el procedimiento en dos partes: un script (principal) y una función (para el cálculo de la edad). 
  • Desde el script se llama a la función para asignar el resultado obtenido mediante la variable sEdad
  • El script es el responsable de la asignación de datos y de su escritura en el documento.
  • En él, por claridad expositiva, diferencio entre las variables para asignación de datos...
Dim fDictamen As Date, fNacimiento As Date
Dim sEdad As String

  •  ... una matriz para su escritura en el documento (Dim mDatos(2) As String) (4) y una segunda para contener los nombres de los marcadores. De este modo podemos resolver la escritura de los datos en los marcadores del documento de forma eficiente mediante un bucle.

For i=LBound(mMarcadores()) To UBound(mMarcadores())
oMarca = ThisComponent.getBookmarks().getByName(mMarcadores(i))
oMarca.getAnchor.setString(mDatos(i))
Next
  • Centrándonos en la función, primeramente calculamos la diferencia en días entre los valores pasados como atributos desde el script.
  • Primero creo tres variables para el cálculo del tiempo transcurrido desde la fecha de nacimiento hasta la actual (Dim iDias As Integer, iMeses As Integer, iAnnos As Integer) y realizo los cálculos pertinentes, empezando por el datos de los días (iDias = fFinal - fInicial) y, a partir de éste, los de los meses (iMeses = iDias\30) y años (iAnnos = iDias\365)
  • El valor años es útil directamente, pero no así el de los meses y días transcurridos. Por ello necesitamos dos variables (Dim iMesesEdad As Integer, iDiasEdad As Integer) que nos permitan asignar a ellas el resultados de las operaciones requeridas.
  • El cálculo del valor meses de edad (iMesesEdad = iMeses -(iAnnos*12)) requiere descontar del total de meses los contenidos en los años de vida del sujeto...
  • Y el cálculo de los días de la edad, como explicamos antes, puede ser resultante de dos operaciones diferentes: en este script utilizo la forma básica, que no la mejor (iDiasEdad = iDias - ((iAnnos*365)+(iMesesEdad*30))), como ya expliqué antes.
  •  Finalmente componemos la expresión que informa sobre la edad del sujeto sobre una variable (Dim sEdad As string) y creo dicha expresión mediante la concatenación de variables y cadenas de texto (sEdad = CStr(iAnnos) & " años, " & Cstr(iMesesEdad) & " meses y " & CStr(iDiasEdad) & " días."
  • Finalmente se asigna al identificador o nombre de la función (EdadA) la variable anterior (EdadA = sEdad), con lo que se entrega al script que llama esta función el dato (string) asociado (el contenido de sEdad)
Para facilitar el análisis de las diferentes opciones dejo [acceso] al sencillo docap resultante.

NOTAS

(1) Además de no ser un cálculo de especial dificultad, que bien puede resolverse manualmente, existen en la Web calculadoras de edad, como la que pone a disposición de todos la editorial Pearson. También podemos crear una [calculadora de edad] mediante las funciones integradas en Calc.
(2) Obsérvese que el operador utilizado en ambos cálculos es el de la división entera (\), no el de la división general (/).
(3) En realidad la diferencia pequeña diferencia entre las dos primeras opciones y la tercera tampoco no es tan importante para el objetivo que se plantea en los documentos prescriptivos, especialmente si limitados los datos de edad a años y meses, datos normalmente suficientes para los objetivos que se persiguen en ambos documentos. Únicamente en caso de nuevas escolarizaciones (en 1º de E. Infantil) y cuando el número de días se aproxima al mes se puede considerar de cierta relevancia el número de días de edad; en el resto de las circunstancias, conocer los años y meses de edad será suficiente.
(4) En realidad nos podríamos ahorrar las variables y utilizar los elementos de la matriz mDatos() para asignar directamente los valores. Con ello nos ahorraríamos algunas líneas de código (la reasignación de los datos de las variables a los elementos de la matriz, por ejemplo), pero deberíamos hacer otros cambios en el script y en la función. En este caso deberíamos optar por declarar la matriz de tipo Variant o de tipo String, pero de tomar esta segunda decisión, tendríamos que modificar la categoría de las variables argumento de la función y realizar dentro de ésta un cambio de tipología (de String a Date) mediante la función CDate(). Como podemos ver, se comprueba de nuevo esa máxima de que en programación existen múltiples soluciones posible.