miércoles, 23 de agosto de 2023

Usos. Datos.

 Calc. Acceder a celdas.

Una vez que ya sabemos movernos por las hojas de un libro Calc, el siguiente paso consiste en acceder a las celdas individuales para leer su contenido y/o escribir datos en ellas. Este será el objetivo de esta entrada.



Para acceder desde código a una celda de una hoja, lo primero es acceder efectivamente a la hoja. En una entrada anterior aprendimos cómo resolver esta cuestión, tanto a la hoja activa como a cualquier otra hoja del libro. Si recuerdas, incluso avanzamos en nuestro objetivo actual, aunque sin centrar la explicación en él. Es ahora cuando retomaremos ese mismo script, pero para trabajar el acceso a las celdas. Pero antes vamos a pensar con qué objetivo.

De momento se me ocurren dos objetivos inmediatos que justifican sobradamente el contenido de esta entrada: leer y escribir en las celdas mediante código.

Efectivamente, en Calc el posicionamiento de los datos viene dado por el marco que define la estructura básica de una hoja de cálculo: la celda. Sin celdas no se entiende una hoja de cálculo, que no es otra cosa que una colección de celdas organizadas en una matriz de doble entrada (fila-columna). Desde esta perspectiva, un documento Calc no es otra cosa que un conjunto n de hojas, que son, a su vez, un conjunto n de celdas.

Pero dejemos esta cuestión, aunque que, no obstante, relevante para entender Calc como gestor de datos, y centrémonos en lo que nos demanda la práctica: el acceso a la celda.

Si recuerdas el script, una vez posicionados en la hoja (en ese caso, no en la hoja activa, aunque esto es ahora irrelevante)...

oHoja =ThisComponent.getSheets().getByName(sHoja)

... tomando como referencia esa hoja, nos ubicábamos en una celda determinada por su "nombre", siendo éste el identificador con que ubicamos la celda en la hoja, esto es: la referencia a su posición en la matriz Columna-Fila (vg. "B4")...

oDatosId(0) = oHoja.getCellRangeByName( "B2" )

... y mediante el método o función setString() escribíamos en la celda el contenido del parámetro de dicha función:

oDatosId(0).setString(sNombre)

Este es, en resumen, el procedimiento básico para acceder a una celda (posicionarnos en ella) y manipularla (en este caso escribir en ella). Evidentemente disponemos de otras opciones de acceso y podemos ejecutar la maniobra contraria a la desarrollada (leer vs. escribir), pero el problema fundamental está resuelto.

Vamos a completar la explicación dada en esta entrada con esos detalles "menores" por ampliar nuestros conocimientos y contar con los necesarios para idear procedimientos de trabajo funcionales con Calc (yo prefiero decir "docap basados en Calc").

Para acceder a una celda por su posición ( también podría entenderse como su índice) como alternativa al posicionamiento por nombre (que en realidad se expresa como un índice), utilizaremos el función alternativa getCellByPosition() que cuenta con un par de argumentos numéricos que se expresan separados por una coma:

oDatosId(0) = oHoja.getCellByPosition(1,1 )

NOTA. Dada la posible confusión entre B2 como índice vs. nombre, la función complementaria se denomina posición, resultando las dos funciones complementarias:

getCellRangeByName( "B2" )     -> Posicionamiento por nombre del rango

getCellByPosition( Col,Fil )         -> Posicionamiento por posición en la matriz Colum-Fila

Ahora que queremos leer el contenido de la celda en lugar de escribir en ella. Lo que debemos hacer es cambiar la función setString(sNombre) que hemos asociado a la variable que identifica el acceso a la celda, por la función getString()asociada a la misma variable. También deberemos modificar el modo de uso de las variables de "contenido", ya que mientras que en setString() esa variable ocupa el lugar del parámetro (lo que escribe la función), en getString()  es la variable a la que asignamos el dato que devuelve la función, esto es:

sNombre=oDatosId(0).getString() -> Captura en sNombre el contenido de la celda asociada   
                                                        oDatosId(0)

 ... frente a 

oDatosId(0).setString(sNombre) -> Escribe en la celda asociada a oDatosId(0) el contenido                                                         de sNombre

martes, 22 de agosto de 2023

Usos. Datos.

Calc. Acceder a una hoja.

Una vez que sabemos crear y copiar hojas mediante código, deberemos aprender a acceder a ellas. Hacerlo manualmente no supone ninguna complicación, pero muchas veces necesitaremos hacerlo mediante código.



Efectivamente, acceder a cualquiera de las hojas de un libro Calc desde el propio servicio es muy sencillo: es suficiente con hacer clic en la pestaña que la identifica, la cual está situada e identificada por sus nombre en el navegador situado al final de la pantalla del libro.


Otra opción, más enrevesada pero igualmente útil, pasa por usar el menú Hoja/Navegar/Ir a la hoja


... que activa un menú emergente que presenta la lista de las hojas del libro para que elijamos la que deseemos.


Explico esto para que antes de lanzarnos a crear un script pensemos si con los recursos de Calc no es suficiente, aunque está claro que frecuentemente no será una solución satisfactoria. En ese caso debemos saber que contamos con tres posibles alternativas basadas en la creación de un script:
  • Posicionarnos "manualmente" en la hoja deseada mediante los procedimientos antes explicados y trabajar con ella como hoja activa mediante un script.
  • Trabajar con la hoja deseada mediante script pero sin desplazarnos visualmente a ella. Seguiremos viendo la hoja en la que nos encontremos.
  • Convertir la hoja seleccionada en hoja activa mediante código y, a continuación, trabajar con ella como hoja activa. 
Empezaré por la que considero la forma más simple de acceder a una hoja: hacerlo con y desde la hoja que estamos viendo en pantalla, considerándola como la hoja activa. Para ello el primer paso consiste en ubicarnos en esa hoja (por ejemplo Exped2 del ejemplo anterior) haciendo clic en su pestaña desde el navegador de Calc.

Una vez en ella, y más por asegurarnos de estar en la hoja activa y que ésta es la deseada, solicitaremos al script que nos devuelva su nombre:

Sub HojaActiva


Dim oHoja As Object

oHoja = ThisComponent.getCurrentController.getActiveSheet()
Msgbox oHoja.getName()

End Sub

Lo bueno que tiene este modo de proceder (la función getActiveSheet()es que es sumamente sencillo de implementar y muy versátil, motivo por el que se pueden encontrar muchos script que hacen uso de esta función.

Como ejemplo de versatilidad se me ocurre recurrir al script mediante el cual copiamos una hoja y repetimos esta operación a discreción. Si esa hoja contara con un comando de acceso a un script que desarrollara x acciones en ella mediante getActiveSheet(), este comando y también su capacidad de lanzar el o los script asociados estaría disponible tantas veces como quisiéramos sin necesidad de modificar el código original, ya que se ejecuta siempre sobre la hoja activa, sea ésta la que sea.

En nuestro script de prueba, activarlo desde cualquiera de las hojas del libro no afecta a su funcionamiento, si bien la respuesta es diferente, dado que se ajusta a la hoja que en ese momento se considere hoja activa. Esto es: si estamos (físicamente) en Hoja1MsgBox devuelve Hoja1; si nos ubicamos en Exped5, ese será el nombre que devuelva MsgBox.

A pesar de la versatilidad de esta primera opción, el hecho de depender de las funcionalidades de Calc puede suponer ciertas limitaciones, siendo necesario acceder a una hoja sin que estemos físicamente en ella. En este caso no podemos usar las funciones asociadas a ActiveSheet: necesitamos otra alternativa como es acceder a la hoja elegida utilizando como referencia o identificador su índice o posición en el libro, o su nombre. Veamos como:

Sub HojaSelect

Dim oHoja As Object
Dim sHoja As String
Dim oDatosId (1) As Object
Dim sNombre As String, sApellidos As String
sHoja = "Exped1"
sNombre = "Macario"
sApellidos = "Rodríguez López"
oHoja =ThisComponent.getSheets().getByName(sHoja)
 
oDatosId(0) = oHoja.getCellRangeByName( "B2" )
oDatosId(1) = oHoja.getCellRangeByName( "B3" )

oDatosId(0).setString(sNombre)
oDatosId(1).setString(sApellidos) 
End Sub
Seguro que no se te escapa que aquí hay más (mucho más) código que el necesario para acceder a una hoja, y no te equivocas, pero me vas a permitir que por ahora me limite a explicar lo justo y necesario: cómo consigo que el script escriba lo que deseo (tema que ahora no interesa) en la hoja en que quiero que lo escriba (de esto sí va esta entrada): el "secreto" está en...

oHoja =ThisComponent.getSheets().getByName(sHoja)

... y más concretamente la función getByName(), cuyo único argumento (sHoja) aquí se concreta como Exped1 porque así lo pedimos en la asignación de valor a la variable...

sHoja = "Exped1"

...aunque que podría ser cualquier otra hoja, al igual que podría ser identificada por el valor-índice mediante la función getByIndex() contando que para ello y en ese caso, la variable sHoja mejor debería llamarse iHoja (variable Integer), ya que el índice es un valor numérico, no un string (1)

Con lo que sabemos hasta ahora ya disponemos de los recursos necesarios para que el acceso a una hoja no suponga un problema, debiendo elegir el procedimiento que mejor se ajuste a nuestras necesidades. Pero aun disponemos de una tercera opción: convertir una hoja en hoja activa y desplazarnos visualmente a ella, obviamente sin estar previamente situados físicamente en ella. Veamos este último script:

Sub ActivaPorIndice

Dim Doc As Object, sheet As Object, oHoja As Object

doc = ThisComponent

sheet = doc.Sheets.getByIndex(5)

doc.CurrentController.setActiveSheet(sheet)

oHoja = ThisComponent.getCurrentController.getActiveSheet()

Msgbox oHoja.getName()

End sub

Creo que ya adivinaste en qué consiste este script: efectivamente, convierte en hoja activa la hoja que ocupa la posición 5 (esto es: la sexta hoja del libro) y nos desplaza hasta ella (efecto de convertirla en la hoja activa). Esto es posible gracias a la combinación de estas dos acciones:

  1. Identificamos la hoja que deseamos convertir en activa: doc.Sheets.getByIndex(5)
  2. Pasamos el control (foco o cursor) a la hoja definida como hoja activa: doc.CurrentController.setActiveSheet(sheet). 
  3. Finalmente definimos como la hoja como activa: ThisComponent.getCurrentController.getActiveSheet()... (2)
  4. ... y para asegurarnos de estar donde queremos solicitamos información mediante MsgBox: Msgbox oHoja.getName() (3)

NOTAS

(1) Dada la simplicidad del cambio propuesto no me ha parecido necesario reproducir aquí el nuevo script, pero te animo a que "juegues" a trabajar con las diferentes opciones que supone este modo de trabajo.

(2) También es este caso podemos trabajar con el nombre en lugar de hacerlo con el índice. Para ello utilizaríamos la función alternativa getByName("Exped1") en sustitución de getByIndex() La misma recomendación que antes.

(3) Realmente este último paso no es necesario, aunque aquí sea conveniente a efectos didácticos.

Usos. Datos.

Calc. Crear hoja

Trabajando en Calc con frecuencia necesitamos crear nuevas hojas para ampliar las opciones de trabajo con nuestro documento-libro. Para ello no necesitamos más que hacer clic en el signo + que se localiza en el pie del libro, antes de las pestañas de las hojas disponibles. Pero hay ocasiones en las que esto es insuficiente. es aquí donde interesa crear hojas mediante OOo Basic.



Efectivamente, la mayoría de las veces, cuando necesitamos crear una nueva hoja para ampliar las disponibles, utilizar el procedimiento que implemente Calc es la mejor solución: accesible, simple y segura...


... disponible al pie del libro, después del navegador y antes de la primera hoja visible, o desde el menú Hoja, aquí con varias opciones.


Estos procedimientos suelen ser suficientes la mayoría de las veces y no hay motivo para complicar las cosas introduciendo opciones que resultan más complejas y no más funcionales, como es el caso de añadir nueva hoja mediante un script OOo Basic... sólo que, a veces, esa es la opción más recomendable...

Un ejemplo de ello es cuando nuestra nueva hoja va a repetirse un número elevado de veces y no es plan de estar creando; pongamos por ejemplo... 200 hojas manualmente. Otro ejemplo es cuando queremos que se cree una nueva hoja con un contenido básico predefinido; por ejemplo, un formato de "ficha" a modo de formulario.

En ambos casos, disponer de un script que automatice el o los procesos (la combinación de los dos anteriores sería un tercer ejemplo) puede considerarse una opción interesante. Este script sería su formulación básica:

Sub InsertarNuevaHoja

Dim oHojas As Object

oHojas = ThisComponent.getSheets()
oHojas.insertNewByName("Hoja creada", 2)

End Sub

  •  La función insertNewByName() cuenta con dos parámetros: nombre de la nueva hoja y posición-índice. 
  • En este caso, el primer parámetro se implementa directamente ("Hoja creada"), pero podríamos hacerlo de forma interactiva. Para ello deberemos crear una variable y asociarla a un InputBox para que sea el usuario quien introduzca el nombre que desee. Esto reformula el script inicial como sigue:

Sub InsertarNuevaHoja

Dim oHojas As Object

Dim sNombreHoja As String

sNombreHoja = InputBox("Nombre de la nueva hoja")

oHojas = ThisComponent.getSheets()

oHojas.insertNewByName(sNombreHoja, 2)

End Sub

NOTA 1. Observa que ahora la variable sNombreHoja  se posiciona como primer parámetro en la función insertNewByName()

NOTA 2. Esta segunda versión del script evita además una limitación del funcionamiento del primero: sólo sirve para utilizarlo una vez, ya que no puede haber dos hojas con el mismo nombre.

  • Siguiendo con la descripción del primer script, el segundo parámetro, el referido a la posición en función del índice, se resuelve mediante un valor numérico, que puede ser desde 0 hasta el valor que deseemos. 
  • El valor 0 es admisible al iniciar con ese valor el conteo interno del número de hoja disponibles en un libro Calc.

NOTA 3. Aunque en la generación de nuevas hojas mediante las utilidades de Calc, éstas se enumeren sucesivamente como Hoja + numeral, y se inicie (primero hoja) como "Hoja1", en realidad, la matriz interna se inicia en la posición 0.

  •  También podemos permitir al usuario que posiciona la nueva hoja donde desee, aunque posiblemente esta opción sea poco interesante. Más puede serlo utilizar la función getCount() que posiciona la nueva hoja al final del listado de las actuales.

NOTA 4La función getCount() devuelve el número total de hojas que contiene el libro Calc. Al utilizarlo como posición-índice, lo que hacemos es indicar el valor-índice que ocupa la última hoja creada. Nuestro script ubicará la nueva hoja tras la última que haya sido creada. De este modo ésta no se posicionará invariablemente en la posición numérica x, tal y como sucede en las formulaciones anteriores del script en relación con la posición 2 (como sabemos, en realidad la posición 3)

  • De hecho, podemos usar getCount() para saber el número de hojas de un libro Calc...

MsgBox oHojas.getCount()

 ... y utilizarlo como referencia para tomar decisiones sobre, por ejemplo, la creación o no de más hojas, pero seguramente utilizaremos con las frecuencia la función getCount() como referencia posicionar las nuevas hojas, lo que supone usarla como segundo parámetro de la función insertNewByName()

Sub InsertarNuevaHoja

Dim oHojas As Object
Dim sNombreHoja As String
sNombreHoja = InputBox("Nombre de la nueva hoja")

oHojas = ThisComponent.getSheets()
oHojas.insertNewByName(sNombreHoja, oHojas.getCount())
End Sub

Aunque las opciones son múltiples como puedes ver, me gustaría mostrar cómo automatizar la creación de un número de hojas decidido por el usuario haciendo uso de lo ya expuesto en los script anteriores. Para ello necesitamos únicamente utilizar una estructura de iteración (un For...Next, vamos)

Sub InsertarNuevaHoja

Dim oHojas As Object

Dim sNombreHoja As String, sNumHojas As String

Dim i As Integer

sNombreHoja = InputBox("Nombre identificativo de la hoja")

sNumHojas= InputBox("Número de hojas a crear")

oHojas = ThisComponent.getSheets()

For i=1 To CINt(sNumHojas)

oHojas.insertNewByName(sNombreHoja+i, oHojas.getCount())

Next

End Sub

En base a lo trabajado hasta este momento y lo que ya sabemos sobre el bucle For no creo que te resulte difícil entender este script y su funcionamiento. Si tienes alguna duda ya sabes dónde me puedes encontrar dispuesto a ayudarte a resolverla.

lunes, 21 de agosto de 2023

Usos. Datos.

Calc. Copiar una hoja

De las diversas cosas que podemos automatizar en Calc mediante OOo Basic, la creación sucesiva de hojas basadas en un modelo previamente creado es una opción interesante para generalizar un modelo de tratamiento de datos, algo parecido a la creación de fichas de expediente, por ejemplo.


Crear un modelo de presentación y recogida de datos y utilizarlo de forma "masiva" como recurso de gestión de la información suele ser un procedimiento común de trabajo, más frecuente en soporte textual (un formulario), pero posible también sobre una hoja de cálculo, lo que permite, además, utilizar las funciones disponibles en Calc.

De hecho, que esta opción no sea un procedimiento que los SEO utilicemos con más frecuencia se debe al escaso conocimiento que tenemos de las posibilidades que ofrece Calc para el trabajo con datos. Calc, Excel, Base, Access..., pero este es un tema que daría para mucho, así que mejor volvamos a lo que nos importa ahora.

La forma de acceder a una hoja (hoja-modelo, para que tenga más sentido, pero eso ahora es secundario), copiarla y posicionarla al final del libro Calc se resuelve, por ejemplo, mediante el siguiente script:

Sub CopiarModelo

Dim oHojas As Object
Dim sHModelo as string, sHNueva as String

oHojas = ThisComponent.getSheets() 
sHModelo = "Modelo"
sHNueva = Trim(InputBox("Nombre del alumno"))

If oHojas.hasByName(sHModelo) And (Not oHojas.hasByName(sHNueva)) Then
oHojas.copyByName( sHModelo, sHNueva, oHojas.getCount() )
Else
MsgBox "No se copio la hoja." & Chr(13) &_
  "Posiblemente esto se deba a que ya existe" & Chr(13) &_
  "el expediente " & sNombreNuevo  & "." & Chr(13) &_
  "Compruébelo, por favor."
End If

End Sub

  • Primero accedemos mediante variable al archivo Calc activo, asignando este objeto a una variable:
Dim oHojas As Object
oHojas = ThisComponent.getSheets()  

  • En segundo lugar, identificamos la hoja a copiar y el nombre que daremos a la hoja copiada, empleando para ello dos variables de tipo texto: 

Dim sHModelo as string, sHNueva as String
sHModelo = "Modelo"
sHNueva = Trim(InputBox("Nombre del alumno")) (este valor puede ser establecido por el usuario)

  • Y finalmente copiamos la hoja (identificada por su nombre mediante la función copyByName()): 
oHojas.copyByName( sHModelo, sHNueva, oHojas.getCount())

Esta función cuenta con tres parámetros:
    • el nombre de la hoja a copiar (sHModelo)
    • el nombre de la nueva hoja (sHNueva
    • y la posición en la que ubicar esa nueva hoja (oHoja.getCount()). 
NOTA 1. De este tercer parámetro, esto es, de la función getCount()ya hablamos en otra entrada.
NOTA 2. En realidad este tercer paso lo hacemos de forma condicional para evitar errores (control de errores) empleando una estructura condicional (if... else...end if), pero realmente esto ahora es secundario en cuanto a cumplir el objetivo que perseguimos con este script.

Un uso más eficiente de este script pasaría por convertirlo en subrutina, pero tal y como está definido ahora es suficiente para alcanzar el objetivo planteado en esta entrada, así que de momento es suficiente.