jueves, 27 de junio de 2024

Procedimientos. Gráficos

Creación de ítem de evaluación en Impress

También en Impress (servicio de creación de presentaciones de LibreOffice) es posible crear ítem de evaluación, y no me refiero al uso básico de las presentaciones como recurso para presentar la tarea, algo que ya [hemos visto] en este blog como soporte complementario de un docap. También hemos hablado de las peculiaridades del uso de  OOo Basic en Impress, por lo que [te remito a estas entradas] sobre estas cuestiones previas.


Lo que voy a tratar ahora es el proceso a seguir para transformar una diapositiva Impress en un recurso de evaluación. Se trata de una mera ilustración del modo de trabajo, en realidad de uno de los modos de trabajo, y en su forma más simple. La incorporación de recursos gráficos como los que proporciona Impress dentro de un docap complejo, requiere un desarrollo del contenido de esta entrada que posiblemente abordaré en otro momento.

Para contextualizar esta exposición, vamos a suponer una prueba de evaluación de un único ítem creado sobre presentación Impress, la cual diseñaremos para que el alumno responda directamente actuando sobre la pantalla. Se trata de que el funcionamiento del documento permita el uso directo por parte del alumno, aunque no resuelve todos los problemas que esto implica, sólo los más básicos.

Ya sabemos que Impress permite también (como todos los servicios LibreOffice) el uso de script (1), pero con ciertas limitaciones, incluyendo una determinante: no es posible utilizar la funcionalidad Grabar macro. También presenta ciertas peculiaridades que responden a los dos modos de trabajo con el servicio: en modo Diseño y en modo Presentación.
  • En modo Diseño podemos implementar controles de formulario, por ejemplo botones de comando, que asociados a script funcionan como se espera.
  • Pero algunos de esos controles, por ejemplo los citados botones de comando, no funcionan como desearíamos en el modo Presentación (2).
Estas limitaciones las podemos suplir implementando diálogos, pero también transformando los elementos gráficos en comandos, asociándolos a script. Esta será nuestra opción ahora. Paso a explicarte en detalle cómo hacerlo.

Te presento nuestra "presentación". Se trata de un ítem [tomando como modelo el test RIAS-MnV] que nos servirá de base para este trabajo.



Seguro que reconoces en este nuestro ejemplo el formato de presentación de los ítem del test, con lo que te será fácil trasladarte mentalmente a sus cuadernos de trabajo. Como puedes ver, nuestro ejemplo consta únicamente de dos diapositivas, aunque en un desarrollo real sería necesario repetir el mismo modelo tantas veces como ítem tiene la prueba: la primero contiene el estímulo y la segunda las opciones de respuesta. Utilizo la forma Flecha (Insertar | Forma | Flechas de bloques) para el desplazamiento por la presentación, si bien se podría utilizar el paso de diapositiva en función de un intervalo de tiempo (3)

Puedes comprobar que es posible implementar botones de comando [Ver | Barra de herramientas | Controles de formularioy también su correcto funcionamiento en el modo Diseño, (una vez que lo asocies a un script), pero cuando trates de activarlos desde el modo Presentación observarás que no funciona.

Después de hacer esta prueba (4), y antes de implementar nuestro "botones", deberemos habilitar nuestra presentación para que contenga script. Para ello procedemos como en el resto de los servicios LibreOffice:

Herramientas | Macros | Organizar macros | BASIC | Organizador ....

... para crear el módulo (o los módulos) en el que escribiremos nuestros script. Te recomiendo escribir estos antes de implementar los comandos, así que aquí tienes los que yo planteo:

Dim mRespuestas(43) As String
Dim mPuntos(43) As Integer

Sub cmd11

mRespuestas(0) = "A"
mPuntos(0) = 2

Info
End Sub

Sub cmd12

mRespuestas(0) = "B"
mPuntos(0) = 0

Info
End Sub

Sub cmd13

mRespuestas(0) = "C"
mPuntos(0) = 0

Info
End Sub

Sub cmd14

mRespuestas(0) = "D"
mPuntos(0) = 0

Info
End Sub

Sub cmd15

mRespuestas(0) = "E"
mPuntos(0) = 0

Info
End Sub

Sub cmd16

mRespuestas(0) = "F"
mPuntos(0) = 0

Info
End Sub

Sub Info
MsgBox "Respuesta: " & mRespuestas(0)
MsgBox "Puntuación " & mPuntos(0)
End Sub
 
A destacar:
  • Las matrices que contienen las respuestas (mRespuestas(43) - mPuntos(43) (5)) se declaran como públicas, fuera de los script, para que sean accesibles desde todos ellos.
  • Creo tantos script como botones necesito implementar en la diapositiva de respuesta (contiene 6 opciones), todos ellos con la misma estructura, por lo que el procedimiento, aunque parece costoso, en realidad es muy simple: asignación al primer elemento de cada una de las dos matrices un contenido. En el de la matriz mRespuestas(0) se asignará una letra que corresponde con la opción seleccionada, y en el de la matriz mPuntos(0), obviamente la puntuación (1 para la opción A y 0 para el resto).
Queda claro que no es un código precisamente muy elaborado, lo admito, pero en esta entrada de lo que se trata es de ver cómo podemos implementar controles de formulario (o similares) para asociarlos a OOo Basic y hacer que una presentación funcione como docap, así que, de momento, no nos detendremos en el código para centrar la atención sobre los procedimientos de acceso y la forma de implementar controles funcionales en la presentación. De eso va la parte que sigue de esta entrada.

Dado que el funcionamiento de los controles de formulario parece que no son de mucha ayuda (en el modo Presentación, que es el que nos interesa) (6), tendremos que buscar una alternativa. Afortunadamente la hay, y se llama uso de las formas básicas y su asociación a macros (script), posibilidad esta prevista LO-Impress como funcionalidad dentro de las opciones de funcionamiento que se asignan a esos objetos. Veamos cómo hacerlo.

En primer lugar tenemos que considerar dos posibles situaciones, ya que la forma de trabajo dependerá de con cual de las dos situaciones nos encontremos:
  • Que en la diapositiva de respuestas cada imagen sea un elemento diferente. En ese caso, nuestro caso tendíamos un total de seis elementos.
  • O que se trate de un dibujo único (una lámina con seis dibujos). Ahora sólo habrá un único elemento (7).
En el primer caso, directamente podremos convertir cada uno de los elementos en un comando que se asocia con un script. Para ello seguiremos estos pasos:
    1.  Seleccionamos el objeto-dibujo.
    2. Clic derecho sobre el objeto seleccionado
    3. En el menú de opciones que se despliega seleccionamos Interacción
    4. Activamos el desplegable de opciones (Acción al pulsar sobre el ratón)
    5. Y seleccionamos la opción Ejecutar macro
  • Una vez que aceptamos esta selección (botón inferior Aceptar) aparece un TextBox (Macro) que nos facilita acceder a IDE haciendo clic en el botón de comando Examinar.
  •  LO que tenemos que hacer a continuación es lo mismo que hacemos cuando queremos vincular un botón de comando a un script: seleccionar el directorio donde se encuentra el script, en caso de que el que se nos muestra no sea el que nos interesa (que normalmente lo será, con lo que nos ahorraremos este paso)
  •  Y hacer clic sobre el script que nos interese.

De este modo tan simple hemos convertido una imagen (o una forma Impress) en un comando que activa un script. Al contrario de lo que sucede con el control Botón de comando de formulario, nuestra imagen-comando no funciona como tal en el modo Diseño, pero sí en el modo Presentación, y la primera muestra de ello es que cuando pasamos el ratón por la imagen-comando, el puntero cambia de forma (Flecha -> Mano) y si hacemos clic sobre ella se desarrollará la secuencia de acciones que está diseñada en el script asociado.

Cuando se trata de una lámina con varias imágenes, tenemos que cambiar de estrategia, ya que ahora no existe un objeto individualizado sobre el que actuar; por eso deberemos utilizar las formas disponibles en Impress  (por ejemplo (8)), pero el proceso a seguir para transformar esas formas en comandos sigue siendo el mismo de antes.

Ahora bien, para resolver esta segunda situación (lámina única) tenemos dos opciones de configurar la presentación:

  • Crear una forma-comando añadida a cada elemento de la imagen, como es el caso de la imagen que te muestro a continuación, en la que bajo cada figura he situado una forma (pequeño círculo rojo), la cual transformo en comando siguiendo el método explicado antes...

  • O superponer a cada imagen de la lámina una forma transparente, siendo ésta la que se convierta en comando (siguiendo el mismo procedimiento. En este caso no se observa aparentemente nada diferente de lo que es la propia lámina, pero cada una de sus imágenes aparentemente se ha convertido en un comando; en realidad es la forma superpuesta la que actúa como comando, pero al ser transparente no se aprecia.

Veamos como proceder para desarrollar esta posibilidad:

  • Seleccionamos la figura que vamos a superponer a cada uno de los dibujos de la lámina, de modo que lo cubra por completo.
  • Seleccionamos y copiamos esta figura y la desplazamos hasta la imagen siguiente, así hasta cubrir todos las imágenes (o todas las que deseemos (9))
  • Ahora puedes convertir cada figura en comando, siguiendo el procedimiento ya explicado (10)
  • A continuación te recomiendo modificar la línea exterior de todas esas formar para hacer que desaparezca. Para ello, con la forma seleccionada hacemos clic derecho y en el menú desplegable elegimos la opción Línea y como estilo Ninguno.

  • Ahora es el momento de trabajar de nuevo con cada figura, pero no ya con la línea, sino con el Área 


... y dentro de las opciones posibles, con Transparencia, seleccionando la segunda opción (Con transparencia) y como valor el 100%.


La imagen que sigue muestra diferentes grados de transparencia para que aprecies mejor el proceso, aunque el objetivo final es que todas se presenten igual que la primera, sin línea exterior y totalmente transparentes.


De este modo, la imagen de la lámina queda a la vista, la figura superpuesta es invisible y el comando funciona sin problemas en el modo Presentación.  El usuario tendrá la sensación de hacer clic sobre el dibujo de su elección igual que si trabajáramos con imágenes individuales.

Documento

Desde [este enlace] puedes descargar la presentación que nos ha servido de ejemplo. Consta de cuatro diapositivas, el estímulo y tres más de respuesta, una con cada una de las opciones de trabajo he henos visto. 

Debes descargar el archivo, abrirlo desde LibreOffice y como se trata de un archivo con macros, habilitarlas.

El código que contiene es el mismo que te muestro en la entrada, pero te sugiero que accedas a él desde el IDE y que introduzcas los cambios y mejoras que te parezcan oportunos.

NOTAS

(1) En este momento nos limitamos al uso de OOo Basic como lenguaje de programación, pero ya sabes que en LibreOffice hay otras opciones, incluyendo Python.

(2) Otros controles se pueden implementar en el modo diseño, pero no funcionan ni en este modo ni en el modo presentación. Además, el funcionamiento de la presentación depende del modo en que ésta esté configurada. Todas estas cuestiones no serán tratadas en esta entrada, salvo lo que resulte de inmediato interés.

(3) En RIAS-MnV se plantean tiempos de presentación del estímulo (5") y de máximo de exposición de la lámina de respuesta: 20" en primera instancia y, en caso de fallo, 10" más. Estos requisitos se ajustan bien al uso de intervalos de tiempo como criterio de paso de las diapositivas; pero aquí asumo que es el profesional quien controla los tiempos de exposición, lo que, aunque es factible con un único monitor, se beneficia del uso de dos: el primero es controlado por el OE y en el segundo se muestra la presentación al alumno.

(4) No es obligatoria, ya te he contado yo lo que sucede, pero tampoco está de más comprobarlo personalmente.

(5) Su dimensión (43) se explica por contener RIAS-MnV 44 ítem.

(6) No puedo descartar que esto sea debido a muy escaso conocimiento del servicio Impress, pero en todo caso sí está claro que el comportamiento de los controles de formulario no es tan funcional y sencillo como en Calc o en Writer, lo que genera problemas que, hoy por hoy, no estoy en condiciones de resolver. De ahí la necesidad de trabajar desde otra perspectiva.

(7) También podemos importar imágenes si consideramos que se ajustan mejor a lo que nos interesa.

(8) Aunque según qué casos, ésta puede ser una solución menos "elegante" que presentar cada imagen de forma independiente, la lámina única ahorra mucho trabajo de preparación de materiales e incluso puede ser la mejor opción según para qué tareas, como la de localizar determinados elementos en un mapa, fotografía o dibujo.

(9) Para un soporte de evaluación como el que estamos creando es necesario que todas la posibles respuestas estén cubiertas por este tipo de comandos, ya que todas son opciones de respuesta. En otros casos se deberá valorar lo que resulta más fiable y conveniente, ya que, como dije, en el modo Presentación el cursor cambia de forma, lo que es una pista de cuáles son las imágenes que están asociadas a script. 

(10) Aunque puedes convertir cada figura en comando cuando lo desees, yo te recomiendo que lo hagas bien en este momento o, como mucho, tras el siguiente (eliminar la línea externa de la forma), pero siempre antes de trabajar con la transparencia de la figura. De este modo te ahorras problemas para seleccionarla, ya que una vez que es transparente te va a costar que se active para convertirla en comando.  Cierto que puedes utilizar un truco, como seleccionar la lámina de fondo y desplazarla para que las figuras transparentes queden identificadas, pero tendrás que volver a colocar de nuevo la lámina en su sitio exacto sin poder ver cómo queda la superposición lámina-figuras, lo que no siempre es fácil.

domingo, 16 de junio de 2024

Procedimientos. Datos

Borrar celdas

Cuando  trabajas con una Hc-Calc como soporte para, por ejemplo, crear un listado, si usas el mismo instrumento de forma sucesiva, puede ser un engorro tener que borrar el contenido introducido antes para introducir nuevo contenido. Este problema se acentúa cuando el número de celdas a borrar es elevado o las celdas están dispersas y te interesa que se borren unas pero no otros, por contener, por ejemplo, textos que te interesa mantener. 



En esos y otros casos similares, disponer de una macro o de un script que te ahorre trabajo no vendría nada mal, ¿no te parece?. Vamos a ver qué podemos hacer.

En primer lugar contamos con una solución basada en el código-macro: grabaremos una macro de borrado de celda y multiplicaremos esta acción tantas veces como celdas deseemos borrar.

Sub mcrBorrarCelda

dim document   as object
dim dispatcher as object

document   = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")

dim args1(0) as new com.sun.star.beans.PropertyValue
args1(0).Name = "ToPoint"
args1(0).Value = "$A$3"
dispatcher.executeDispatch(document, ".uno:GoToCell", "", 0, args1())

dispatcher.executeDispatch(document, ".uno:ClearContents", "", 0, Array())

End Sub

Eliminados los comentarios que ahora no son necesario, podemos ver cómo funciona esta macro:

  • Primero se crea las variables de la macro, ambas de tipo Object (document y dispatcher)
  • Después se asigna a ambos los objetos correspondientes, el objeto documento activo (document   = ThisComponent.CurrentController.Frame) y el objeto Dispatcher (dispatcher = createUnoService("com.sun.star.frame.DispatchHelper"))
  • A continuación se construye la estructura-matriz que identifica el objeto celda sobre el que actuamos y se concreta el código que identifica la celda-diana ($A$3") sobre la que se realizarán las acciones posteriores.
dim args1(0) as new com.sun.star.beans.PropertyValue
args1(0).Name = "ToPoint"
args1(0).Value = "$A$3"

  • ... y se ejecuta el salto a dicha celda (dispatcher.executeDispatch(document, ".uno:GoToCell", "", 0, args1()))
  • Por último se ejecuta la instrucción de borrado de la celda mediante una instrucción de sintaxis similar a la anterior (dispatcher.executeDispatch(document, ".uno:ClearContents", "", 0, Array()))
Para trabajar sobre cualquier otra celda es suficiente con sustituir el valor String que la identifica ("$A$3") por una variable a la que asignar el identificador o nombre (formato String) de la nueva celda.

Y para generalizar este procedimiento, sólo tenemos que repetirlo tantas veces como deseemos, aunque caben otras opciones, como veremos más abajo.

Supongamos que hemos creado un docap para gestionar la creación de una base de datos en la que se recogen los que constan en el expediente de resultados académicos del alumnado de EP. Para ello hemos creado una Hc-Gestor a modo de ficha personal como la que se ilustra (1)



Podemos ver que el número de celdas a borrar (manualmente) es suficientemente grande como para que ir de una en una o por pequeños grupos no sea una solución eficiente; además el posicionamiento de las celdas y la existencia de "etiquetas" nos impide optar por el "borrón y cuenta nueva". Estas mismas circunstancias hacen que tampoco sea funcional utilizar repetidamente una macro simple de borrado como la anterior.

En casos, una primera opción consiste en seleccionar las celdas sobre las que actuar (borrar contenido) y proceder posteriormente a su borrado. La orden de borrado es la misma que la que aplicamos a una celda individual (dispatcher.executeDispatch(document, ".uno:ClearContents", "", 0, Array()), pero la selección de las celdas puede realizarse de dos modos diferentes:
  • Desplazando el cursor de modo que se seleccione una a una cada una de las celdas de la tabla. Ver mcrBorrarTabla1 (en el IDE). En este se crea una matriz-base que identifica la celda origen (del proceso) y tantas matriz de selección de celdas como movimientos de selección se realicen, con la doble peculiaridad de que el args().Name se identifica como "By" y la acción-movimiento (dispatcher) como instrucción derecha-Izquierda-Arriba-Abajo (ver macro) (2) -> vg. ".uno:GoRightSel"
  • O arrastrando el cursos mediante el ratón desde la celda superior-izquierda hasta la celda inferior-derecha del espacio-tabla. Ver mcrBorrarTabla32. En este caso se omiten las matrices de selección celda-a-celda y se genera una en el primer argumento (args().Name) y la acción (dispatcher) son de posicionamiento ("ToPoint") y el segundo argumento (args().Value) contiene el intervalo de celdas sobre las que se actuará ("$G$6:$J$11").
La segunda opción  pasa por emplear código OOo Basic no procedente de Grabar macro. Este cambio nos permite simplificar el script y consiste, fundamentalmente en hacer uso de la instrucción (método) clearContents(), que recibe una clave numérica como parámetro, el cual identifica el tipo de dato a borrar:
  •  1 -> Borra datos numéricos, manteniendo el formato.
  • 4 -> Borra datos alfanuméricos (String), manteniendo el formato.
  • 1023 -> Borra todo tipo de contenido, incluido formato.
Este método lo aplicamos al objeto celda, referenciado por el nombre de su variable (oCelda). Un ejemplo:

Sub BorrarCelda

Dim oDoc As Object, oHoja As Object, oCelda As Object
Dim sNombreHoja As String

sNombreHoja = "Expediente"

oDoc = ThisComponent
oHoja = oDoc.getSheets.getByName(sNombreHoja)
oCelda = oHoja.getCellRangeByName("C6")

oCelda.clearContents(4)  'Borra contenido alfanumérico sin borrar formato

End Sub 

También podemos borrar un conjunto de celdas (rango) de una vez, sustituyendo el nombre de la celda por la referencia al rango sin hacer otra modificación (3). Así que el mismo script de antes, con algunos cambios, sirve también para borrar un rango de celdas (vg "G6:J11" borra el contenido de la tabla de calificaciones). Los cambios son de denominación de variable y sus implicaciones (4)...
oRango As Object en sustitución de oCelda As Object
... y otro de parámetro
oRango = oHoja.getCellRangeByName("G6:J11") en sustitución de oCelda = oHoja.getCellRangeByName("C6")

El uso de instrucciones OOo Basic, además de script de borrado directo de celdas o de rangos de celda, también nos permite trabajar con subrutinas, como en el ejemplo que muestro a continuación y que nos permite borrar todas las celdas de contenido de nuestro ejemplo:

Script

Sub BorrarCeldas

Dim mCeldas(3) As String
Dim sNombreHoja As String
Dim i As Integer

mCeldas = Array ("C6:D6","C10:D10","C12:D12","G6:J11")
sNombreHoja = "Expediente"

Borrar(sNombreHoja,mCeldas())

End Sub

Subrutina

Sub Borrar(nHoja As String, mC() As Variant)

Dim oHoja As Object, oCelda As Object
Dim i As Integer

oHoja = ThisComponent.getSheets.getByName(nHoja)

For i = LBound(mC()) To UBound(mC())
oCelda = oHoja.getCellRangeByName(mC(i))
oCelda.clearContents(4)
Next

End Sub

Documento. [BorrarCeldas.ods]


NOTAS

(1) No viene al caso ahora crear este docap ni explicar nada más sobre su funcionamiento y posibles usos. Dado que el objetivo de esta entrada es explicar cómo borrar celdas (y rangos del celdas) me limitaré a dar respuesta a este objetivo.

(2) Podemos reducir las líneas de código dando al parámetro args().Value el valor numérico que se ajuste a número de movimientos a realizar. Esto simplifica significativamente la macro. Ver mcrBorrarTabla2.

(3) Si te fijas, el método que se usa para identificar la celda es en realidad un método relativo al rango de celda única: getCellRangeByName()

(4) Con las implicaciones de denominación de la variable en el resto de la sintaxis del script en la instrucción de identificación del rango y en la llamada al método de borrado (oRango.clearContents(4))

viernes, 14 de junio de 2024

OOo Basic. Programación.

Definición de programación

Programar es ordenar una secuencia de pasos para conseguir el funcionamiento esperado en un ordenador. También se puede definir como la acción de crear programas o aplicaciones mediante el uso de un lenguaje de programación.


Existen múltiples lenguajes de programación, pero todos comparten características comunes, en su definición, en sus componentes y en la forma en que podemos esperar que funcionen.

Un lenguaje de programación es un lenguaje artificial que contiene signos, términos y estructuras, se rige por unas reglas sintácticas y produce unos resultados que son consecuencia del procesamiento de los datos con los que trabaja.

Los lenguajes de programación son lenguajes intermediarios entre el programador y el ordenador. Éste traduce a lo que se denomina "código máquina" las instrucciones que el programador escribe en determinado lenguaje de programación. El ordenador interpreta esas instrucciones, traduciéndolas a su lenguaje y ejecutándolas.

Lo interesante de la programación es que podemos adaptar el funcionamiento del ordenador a nuestras necesidades. Lenguajes como OOo Basic, por sus características especiales, nos permiten adaptar el funcionamiento de servicios de uso común en nuestro trabajo, como el procesador de texto, la hoja de cálculo o el programa de creación de presentaciones, para que se adapten a necesidades concretas y específicas de funcionamiento, lo que conlleva un ahorro de tiempo de trabajo.

No obstante también exige que aprendamos en qué consisten y cómo se usan esos lenguajes, lo que supone un tiempo de aprendizaje y una práctica. Pero podemos contar con la existencia de ciertas funcionalidades, como Crear macro, que nos facilitan el primer acercamiento al uso del lenguaje de programación. Gracias a ellas podemos empezar a "crear programas" sin otras exigencias de las que conlleva aprender a usar esas funcionalidades. No son suficientes para aprender a programar, pero ayudan a iniciar el camino.

Este blog está pensado para ayudar a que ese proceso de aprendizaje sea más llevadero, pero sobre todo que sea más funcional, al estar centrado en la solución de necesidades profesionales concretas. No es, por tanto, un blog para aprender a programar, ni tampoco para aprender a programar en OOo Basic; se trata (eso pretendo) de un blog para ayudar a los orientadores educativos en concreto, pero en general a los diferentes componentes de los SEO, a crear soluciones informáticas, funcionales y ajustadas a sus necesidades profesionales concretas. Y por tales entiendo el manejo de la documentación prescriptiva y la creación de la que resulta de la práctica profesional.

Otra forma de entender el para qué de este blog, es la de verlo como un recurso para el desarrollo de la competencia digital de los componentes de los SEO, la cual presenta ciertas particularidades que hace que, a mi juicio, las propuestas y los planes de la Administración educativa resulten demasiado genéricos ( o se se prefiere, poco específicos), dejando sin cubrir lo que deriva de la especificidad de estos (y otros) servicios educativos.

jueves, 13 de junio de 2024

OOo Basic. Interface.

Diálogo (e) Casilla de verificación y botón de opción (1).

Ambos controles presentan una apariencia similar, pero se diferencian en su forma de funcionamiento y también en el modo en que podemos acceder al resultado de su manipulación por el usuario. Veámoslo con más detalle sobre un ejemplo de uso de Diálogo sobre Writer, aunque ya sabes que sería igual si trabajáramos con un documento Calc, por lo que esta explicación sirve para trabajar sobre ambos servicios.


Por lo que conocemos de los controles de un Formulario, ya sabemos que la diferencia entre una casilla de verificación (CheckBox) y un botón de opción (OptionButton) es que en la primera podemos seleccionar todas las casillas presentes en el Dialogo, pero sólo uno de los n botones de que esté compuesto el conjunto de opciones. De ahí que su funcionalidad dentro de la interfaz sea diferente, tal y como expresan los términos que les acompaña.

Voy a ejemplificar el uso y acceso a Casilla y botones. sobre un docap-simple basado en Writer que, como en el [caso anterior] permite crear un listado simple, al que ahora añadiremos...
  • Mediante una casilla la especificación de si se trata de un alumno con PTI-ACS
  • Y mediante botones si se trata de un alumno con apoyos de AL, PT o ambos.
Las diferencias entre Casilla y Botón se observan más en la construcción de ambas y en las peculiaridades de cada una de ellas como control que en el modo de trabajar con ambas. El primer aspecto no se trata en esta entrada, aunque algo se ha dicho antes sobre el funcionamiento diferenciado de estos dos controles, pero sí expongo a continuación el modo de acceder al contenido seleccionado por el usuario. Dado que es igual para cada Botón o Casilla, lo que explico para uno de estos elementos se debe entender aplicable al resto.

Iniciamos con la declaración de tres variables, por ejemplo, 
  • Dim obtnPT As Object, para la asignación o referenciar el objeto Botón 
  • Dim bPT As Boolean, para contener el valor del objeto (2)
  • Dim sApoyo As String, para contener la implicación o resultado que deriva de dicho valor.
La declaración de las variables antecede a la apertura del diálogo, que en el ejemplo que desarrollo se concreta como llamada a la función  Abrir() que cuenta con un parámetro (nDlg), variable String en la que referenciar el nombre del Dialogo, tal y como se establece en la construcción de éste (Dialog1).

El segundo paso consiste en acceder al objeto Botón (o Casilla), lo que implica conocer el nombre del control, como sucede en el resto de los elementos del Diálogo. La función o método requerido para ello es getControl() y el parámetro requerido es el nombre del control ("btnPT")
  • obtnPT = oDialogo.getControl("btnPT")
Esta segunda fase se desarrolla tras la apertura algorítmica del Diálogo y antes de visibilizarlo (ejecutarlo), por lo que anteceden a la instrucción oDialogo.execute().

A continuación, tras ejecutar el Diálogo, asigno a la variable Boolean  bPT el valor (True/False) resultante de la acción del usuario sobre el control, y que está contenido en el atributo State propio de los objetos Botón y Casilla.
  • bPT = obtnPT.State
Este paso no es estrictamente necesario, ya que podemos utilizar directamente la expresión referenciada (obtnPT.State) en el proceso posterior, pero resulta de utilidad didáctica para entender mejor cómo se desarrolla el proceso. 

Finalmente hacemos uso de una estructura condicional (If) para explicitar lo que la elección del usuario implica cuando selecciona o no el control Botón. Aquí se impone la lógica del algoritmo, que es la que da sentido a la acción del usuario. Por ejemplo, en nuestro caso, si el usuario hace clic (selecciona) la opción que representa nuestro botón btnPT, lo que se expresa es que el alumno cuenta con apoyo de PT, luego, si hace esta elección, el State del control será True. En caso contrario será False. En función de esto, la variable que expresa en forma de texto esa implicación tendrá un valor u otro. Todo ello se expresa como sigue (3):

If bPT= True Then
sApoyo = "Apoyo especializado de PT"
ElseIf bAL = True Then
sApoyo = "Apoyo especializado de AL"
ElseIf bAmbos = True Then
sApoyo = "Apoyos especializados de PT y de AL"
Else
sApoyo = ""
End If

La pulsación de cada uno de los tres botones (activando a True su State) (4) se traduce en un valor (que se expresa como String) que se asigna a la variable de respuesta (sApoyo). En caso de no activarse ningún botónsApoyo recibe como valor una cadena vacía.

Documento
En el IDE de este documento puedes encontrar el código que incluye el acceso a los controles Botón y Casilla implementados en el diálogo que se activa mediante el comando Dialogo Lista.

NOTAS

(1) Esta entrada resulta de la la actualización y modificación de una anterior publicada el 4/07/2023, a la que sustituye.

(2) Como veremos a continuación, el valor, que depende de que el usuario seleccione (True) o no (False) el botón (o la casilla) es, en consecuencia un valor booleano. Como se explica en nota 4, estas variables no son imprescindibles, pero permiten explicar mejor el procedimiento, de ahí que haga uso de ellas.

(3) Utilizo la formulación completa del condicional, tal y como se emplea para dar cuenta del conjunto de botones, no sólo del control que centra la explicación. De este modo se comprende mejor el procedimiento. En esto hay diferencia respecto al control Casilla, ya que al ser cada una independiente del resto, es necesario utilizar una estructura condicional independiente para cada una de ellas. No obstante, esta diferencia, aunque coherente con la lógica del funcionamiento diferencial de ambos controles, es más formal que real, puesto que también es posible (aunque no conveniente) utilizar una estructura condicional para cada botón.

(4En el condicional If bPT= True Then se puede sustituir la variable bPT por la expresión que ésta referencia (obtnPT.State), con la que la expresión quedaría como sigue:  If obtnPT.State= True Then. De este modo se simplifica el algoritmo y se ahorra memoria RAM al eliminar la duplicidad espacio de memoria reservado para el mismo dato. No obstante prefiero (en este ejemplo y en función del carácter didáctico de esta la entrada) ser menos eficiente el el uso de RAM y más claro en la explicación del proceso. 

domingo, 9 de junio de 2024

Procedimientos. Datos

Calc. Crear listados.

Un listado y más aun una tabla son conjuntos o colecciones de datos organizados en formato fila-columna, lo que equivale a decir que equivale a una base de datos (Bd) simple (en realidad, su forma más básica), en la que se pueden identificar entradas y campos: las filas equivalen a las entradas o registros de la Bd y las columnas identifican los campos que componen dicha Bd. Desde esta perspectiva, el servicio Calc, en cuanto que trabaja con hojas de cálculo (Hc) es un recurso que nos permite crear y trabajar con Bd simples (1). 


La
primera y necesaria forma de trabajo dentro desde esta perspectiva es crear una Bd, algo que podemos hacer manualmente, pero también automatizar mediante
OOo Basic. Este será el tema de esta entrada.

Para entender mejor l desarrollo de esta entrada es necesario contextualizarlo. Con este fin, y a modo de ejemplo, vamos a partir de una posible situación o problemática: deseamos generar una tabla o Bd simple en soporte Calc en la que almacenaremos los resultados de la aplicación de una prueba para su posterior análisis. Para ello, en primer lugar, crearemos la estructura de campos (columnas) que contiene la Bd y lo haremos sobre una Hc.Calc específica, esto es: diferenciada del soporte (también Calc, pero no necesariamente) sobre el que construimos el núcleo del docap (2). 
  • La primera implicación de esta decisión es la creación del documento Hc.Calc de Bd.
  • La segunda es construir sobre esta Hc la estructura de campos de la Bd, que no es otra cosa que identificar en las columnas las cabeceras de dichos campos, configurando así una tabla de doble entrada: Filas->Registros; Columnas->Campos.



Esta imagen muestra un ejemplo sencillo de la configuración de una Bd (tabla, si se prefiere) en la que se identifican un total de 18 campos (3):

    • Seis de identificación del alumno
    • Diez para la PD del test
    • Dos para los cálculos (sumatorio PD y valoración del resultado)
  • La tercera es que el docap queda determinado en cuanto al procedimiento de ejecución: desde el gestor de entrada de datos (SoportePrueba.ods) (4) se accede al documento Bd (BDPrueba.ods), con todo lo que esto implica: acceso al documento Hc previamente creado y trabajo con dicho documento. 
El primer paso consiste en especificar el modo de acceso a dicho documento desde el script principal. Para ello, desde éste ejecutaremos una subrutina (AccesoBD(mDatos()) que llama desde el script principal (Main, en nuestro caso) al scritp (subrutina) que gestiona el trabajo con la Hc.Bd (BDPrueba.ods)

El script de trabajo con el documento Bd se constituye como subrutina, por lo que contiene un parámetro (Sub AccesoBD(Datos() As Variant)) que traslada a la subrutina el contenido de la matriz de datos (originariamente, esto es, en el script Main, la matriz mdatos(), que como parámetro de la subrutina equivale a la matriz Datos()) resultantes de la fase input y de procesamiento (que se desarrolla en el script Main) (5).

Pasemos a explicar el funcionamiento de la subrutina AccesoBD(mDatos(), que es la que nos interesa ahora, y en la que se diferencian 3 fases o subprocesos, los cuales especificaré a continuación:

Sub AccesoBD(Datos() As Variant)

Dim sRuta As String
Dim mArg()
Dim oBD As Object, oHojaBD As Object, oCeldaCampo As Object, oCeldaNuevoRegistro As Object
Dim i As Integer, a As Integer, b As Integer
Dim sEntrada1 As String

 Fase 1.  Acceso al documento externo

'Acceso a los objetos documento (BDPruebas.ods) y Hoja (DatosPrueba)

sRuta = ConvertToUrl("D:\Pruebas\BDPrueba.ods")
oBD = StarDesktop.loadComponentFromURL(sRuta, "_blank", 0, mArg())
oHojaBD = oBD.getCurrentController.getActiveSheet()

Fase 2. Trabajo con la Bd. Registro de datos

'Búsqueda del primer registro vacío

For i = 0 To 100
oCeldaCampo = oHojaBD.getCellRangeByName("A" & CStr(i+1))
sEntrada1 = oCeldaCampo.getString()
If sEntrada1 = "" Then
a = i
Exit For
End If
Next

'Escritura de los datos en el registro vacío

For b = 0 To UBound(Datos())
oCeldaNuevoRegistro = oHojaBD.getCellByPosition(b,a)
oCeldaNuevoRegistro.setString(Datos(b))
Next

Fase 3.  Almacenamiento de los datos del nuevo registro

'Guardar cambios

oBD.store()

'Cerrar BDPruebas.ods

oBD.close(True)

End Sub

  • Tras la declaración de las variables necesarias para el algoritmo específico de la subrutina...
Dim sRuta As String
Dim mArg()
Dim oBD As Object, oHojaBD As Object, oCeldaCampo As Object, oCeldaNuevoRegistro As Object
Dim i As Integer, a As Integer, b As Integer
Dim sEntrada1 As String
  • ... viene la primera fase, que consiste en el acceso y apertura del documento Hc.Bd...
sRuta = ConvertToUrl("D:\Pruebas\BDPrueba.ods")
oBD = StarDesktop.loadComponentFromURL(sRuta, "_blank", 0, mArg())

... y a la hoja donde está construida la Bd (6

oHojaBD = oBD.getCurrentController.getActiveSheet()

  • La segunda fase es la más compleja y en ella se desarrollan dos subprocesos: la identificación del primer registro sin datos (el que sigue al último que sí contiene datos), lo que equivale y se concreta como búsqueda de la primera fila vacía y el traslado de los datos enviados desde el script principal a la subrutina mediante la asociación de parámetros (mDatos() -> Datos()). Dada la complejidad (e importancia) de estos dos subprocesos, paso a explicar detenidamente cada uno de ello.

Identificación de la primera fila (registro) vacía. Consiste en buscar mediante un bucle que recorre las filas de la hoja aquella (primera) que no contiene aun datos. Para ello creamos una estructura For...Next ...

For i = 0 To 100
oCeldaCampo = oHojaBD.getCellRangeByName("A" & CStr(i+1))
sEntrada1 = oCeldaCampo.getString()
If sEntrada1 = "" Then
a = i
Exit For
End If

Next

... con la que inicialmente repetimos el proceso un número determinado de veces (For i = 0 To 100) (7). Dicho proceso consiste en acceder al contenido de la celda (campo 1 del registro) de forma secuencial...

 oCeldaCampo = oHojaBD.getCellRangeByName("A" & CStr(i+1))

...  asignar ese contenido a una variable de gestión del dato...

sEntrada1 = oCeldaCampo.getString()

... y someter el mismo (presencia vs. ausencia de contenido) a un condicional If simple...

 If sEntrada1 = "" Then

a = i
Exit For
End If

... en el que si se cumple la condición de ausencia de contenido (celda vacía).. 

Si sEntrada1 = "" -> Es True

... entonces a la variable a (que se empleará en el el subproceso siguiente como referencia de la fila sobre la que trabajar) se le asigna el valor correspondiente de la variable i, que equivale al identificador numérico de la celda vacía de la primera columna ("A" & CStr(i+1)). De este modo, la variable a contendrá el valor que identifica la primera fila son datos de la tabla.

Dentro de esta estructura condicional incluimos la sentencia de cierre/cese del bucle (Exit For)  para ajustar éste a la solución del problema (encontrar la celda vacía), acortando el tiempo de ejecución de algoritmo a lo estrictamente necesario (8)

Inclusión de datos en el registro. Consiste en pasar los datos contenidos en la matriz-parámetro Datos() en el registro de la Bd (en la fila de celdas vacía de contenido localizada gracias al proceso anterior)

For b = 0 To UBound(Datos())
oCeldaNuevoRegistro = oHojaBD.getCellByPosition(b,a)
oCeldaNuevoRegistro.setString(Datos(b))

Next

Empleamos para ello un segundo bucle For que trabajará con dicha matriz (9). En cada ciclo asignamos a la variable objeto oCeldaNuevoRegistro las distintas posiciones (objetos celda) de la fila (oHojaBD.getCellByPosition(b,a)) empleado como referencias las posiciones que ocupan en el entramado columna-fila (que no el nombre, como hicimos en el proceso anterior) mediante la función getCellByPosition(b,a). Observa que hace referencia a dos variables ((b,a)): primera (b) referencia cada una de las columnas que recorremos mediante el bucle y la segunda (a) referencia la fila (la primera fila vacía) (10).

Una vez identificada-seleccionada la celda, pasamos el contenido de la matriz a la celda mediante la función-método setString()) (setString(Datos(b))).

  • Finalmente, en la tercera fase procedemos a grabar y cerrar el soporte.Hc (BDPrueba.ods) que hemos referenciado-asociado a la variable-objeto oBD (11). La grabación del documento lo realizamos mediante la función store() (oBD.store()) y el cierre mediante la función close() (oBD.close(True)) con valor True (12). 
Documentos. Para acceder a los dos documentos indicados en esta entrada, [consulta la siguiente]. En ella se explica con detalle el funcionamiento de un docap-modelo que contiene (entre otras funcionalidades) el procedimiento que se explica en la actual.

NOTAS

(1) De hecho, LO.Calc cuenta con funciones que nos permiten trabajar con Bd creadas en formato listado-tabla. Ver Asistente para funciones | Categoría -> Base de datos.
(2) Podemos crear la Bd en una hoja diferente de las de entrada de datos pero perteneciente al mismo libro en el que creamos el soporte.gestor del docap. No obstante, con frecuencia (y es especialmente si deseamos trabajar de forma independiente con la colección de datos resultante de la aplicación de la prueba, es preferible que la Bd y el soporte.gestor del docap sean documentos diferenciados. Cuanto más específico y complejo sea el tratamiento que queramos dar a los datos recopilados, más conveniente es que la Bd se cree en un documento Calc específico.
(3) Huelga decir que se trata de un ejemplo concreto y que para cada Bd la estructura de campos se deberá planificar previamente a su configuración en formato documental. Dicha configuración forma parte del proceso de planificación previo y afecta al diseño del docap en todas sus dimensiones. De ahí su gran importancia.
(4) En este caso o ejemplo, desde el documento.gestor del docap se requerirá especificar el código que facilite el acceso al documento externo que contiene la Bd.
(5) Este script principal Main no es objeto de trabajo en esta entrada, ya que en ella nos centramos en el acceso y el trabajo con el documento.Hc de Bd. Para una mejor comprensión del funcionamiento global del algoritmo del que aquí se explica sólo una parte, [ver esta entrada].
(6) En este caso el documento Hc.Bd sólo consta de una hoja, pero es preferible asegurar el posicionamiento en ella como posicionamiento específico, identificado por el nombre de la hoja, y no como posicionamiento genérico (esto es, como hoja activa).
(7) Bucle de 100 repeticiones según concreto en este ejemplo, pero puede ser cualquier otro número de repeticiones. Se aconseja, no obstante, ser "generosos" al respecto, aunque el criterio de referencia para definir el tamaño del bucle es la previsión del número de registro que se espera contenga la Bd.
(8) Este sencillo procedimiento resulta de mucha utilidad: acorta el desarrollo del bucle al número de repeticiones necesario y nos da libertad para establecer éstas de forma aproximada y con el suficiente margen de seguridad, sin que la posible sobredimensión del bucle tenga un coste de tiempo de ejecución. No obstante, esto no implica que no tomemos referencias a la hora de determinar el número de repeticiones del bucle (ver nota 7)
(9) Para asegurarnos de ello utilizamos como referencia superior del recorrido el resultado de la función UBound() que identifica el último elemento de la matriz (UBound(Datos()))
(10) Recuerda que deriva del proceso previo en el que la asociamos al valor que alcanza la variable-contador i cuando se cumple el criterio que buscamos (If sEntrada1 = "" Then)
(11) En la primera fase hemos asociado dicho archivo a esta variable mediante la instrucción oBD = StarDesktop.loadComponentFromURL(sRuta, "_blank", 0, mArg()) que a su vez contiene como parámetro  la variable (sRutaque asociamos en la instrucción inmediatamente anterior al procedimiento de acceso al documento (sRuta = ConvertToUrl("D:\Pruebas\BDPrueba.ods"))
(12) Interesa destacar aquí el hecho de que ambas son funciones-métodos aplicadas sobre el objeto-documento con el que trabajamos (BDPrueba.ods), el cual, como tal, es un objeto de la clase Hoja-de-cálculo dentro del programa LibreOffice. El uso de la variable asociada a dicho documento (oBD.) es lo que nos asegura actuar sobre ese documento y no sobre (por ejemplo) la Hc.Gestor que contiene y desde la que actúa el script-subrutina AccesoBD()