Crear un DLL
En
anteriores ediciones de este boletín ya hemos hecho un repaso rápido, casi en
forma de lista, de las novedades que traerá consigo la versión 3.5 que
probablemente aparecerá a primeros de octubre, coincidiendo con la celebración
de la reunión anual de usuarios de AVR en Barcelona.
A
partir de este momento pretendemos meternos un poco en faena y explicar dichas
novedades ya no a grandes rasgos, sino con algo más de profundidad, con
ejemplos y código, de forma que cuando esta versión llegue a vuestras manos tengais el conocimiento suficiente de la misma como para
poder comenzar a aprovechar lo antes posible sus nuevas posibilidades.
Para
empezar os propongo un ejercicio relativamente sencillo mediante el cual
podemos empezar a descubrir tres de las novedades que en mi opinión más juego
van a aportar a nuestros desarrollos a partir de octubre. Se trata de la
capacidad para pasar controles como
parámetros de subrutinas, y del manejo del objeto Collection, el cual es un nuevo objeto
integrado en el AVR.
El
ejercicio que nos va a servir de guía para explicar estos conceptos y su
utilización va a consistir en la generación de una función en una clase de una
DLL mediante la cual vamos a poder clasificar cualquier subfichero de cualquier
form por una o varias columnas del subfichero.
Para
ello vamos a crear un nuevo proyecto de tipo ActiveX DLL en el que vamos a
incluir una clase (que ya nos aparece por defecto) y un form.
En
el Form vamos a incluir con combo box, al que daremos
el nombre cmbOrdenar y a cuya propiedad Sorted le cambiaremos el valor a True.
Además también cambiaremos el nombre del Form, al que
llamaremos frmTrabajo y en el que cambiaremos la
propiedad Visible para dejarla en False. Este combo
que hemos incluido en este form de trabajo no
visible, va a ser el que realmente nos ordene el contenido de las celdas que
seleccionemos del subfichero. Su tratamiento lo vamos a hacer desde la
subrutina correspondiente a la función que vamos a codificar en la clase por lo
que el form no llevará nada de código.
Figura 1. Form de trabajo con el combo box ordenado.
Hecho esto, nos centraremos en la clase. Lo primero que haremos será
modificar sus propiedades para darle un nombre y una descripción. Con el foco en
el código de la clase pulsamos F4 para ver sus propiedades y le ponemos el
nombre AVR35PruCls1 y la descripción AVR35 Pruebas Cls
1.
Ahora
comenzaremos la codificación de la función de ordenación de subficheros
propiamente dicha.
Esta
función nos va a servir para clasificar cualquier subfichero, y para lo cual
tenemos que recibir un parámetro con el subfichero que queremos
clasificar. Como resultado de la función
devolveremos un número entero con el que indicaremos si hemos detectado algún
error o si todo ha funcionado correctamente.
Algo
que tenemos que tener en cuenta es que los controles siempre se pasan por referencia. Mientras que el resto de los
parámetros se pueden pasar por valor (opción por defecto) o por referencia,
cuando una subrutina recibe un control como parámetro este simpre
se tratará por referencia, por lo que cualquier modificación que realicemos
sobre sus propiedades o cualquier método que ejecutemos dentro de la subrutina
que lo recibe como parámetro tendrá un efecto inmediato sobre el control que se
pasó como parámetro. Incluso si como resultado de la ejecución de un método o
de cualquier otra condición se activase un evento del control pasado como
parámetro, este evento será manejado por la subrutina de evento correspondiente
al control que fue pasado como parámetro, si es que tal subrutina estuviese
codificada.
Por
otra parte, el subfichero es un control muy especial, ya que normalmente lo
tratamos además de por sus propiedades, métodos y eventos, por los códigos de
operación CHAIN, READC, WRITE... Debemos ser conscientes de que en la subrutina
que recibe un subfichero como parámetro, en tiempo de compilación el compilador
no sabe qué es lo que se va recibir en dicho parámetro, ni mucho menos el
diseño del registro del subfichero (que puede ser cualquiera), y por lo tanto
si intentamos utilizar en la subrutina cualquiera de estos códigos de operación
para acceder al subfichero o modificarle recibiremos un error en tiempo de
compilación.
Hasta
ahora hemos recibido el subfichero, pero la función necesita saber qué columnas
del subfichero vamos a usar como criterio de clasificación. Para obtener esta
información recibiremos un segundo parámetro en el cual se especificarán cual o
cuales son estas columnas. Pero este segundo parámetro debe contener un número
indeterminado de valores que representen al número variable de columnas por las
que podemos querer hacer la clasificación. Por lo tanto, para este segundo
parámetro vamos a utilizar el nuevo objeto Collection
(colección) de AVR.
Podríamos decir que una colección es
una serie no homogénea de un número indeterminado de elementos, a los que
podemos acceder tanto por su índice como por una clave. A pesar de que en este
ejemplo no lo vayamos a utilizar, conviene destacar el carácter no homogéneo de
las colecciones, lo que hace que cada elemento pueda ser de un tipo distinto,
incluidos objetos como subficheros, combo boxes, otra colección... Dando a este nuevo objeto de AVR
una potencia que solo podremos ir descubriendo poco a poco según vayamos
trabajando y experimentando con ella. A pesar de esto, la colección es un
objeto de un manejo bastante sencillo, con un reducido número de métodos y
propiedades.
Figura 2. El objeto Collection
Con todo esto, la declaración de la función queda de la siguiente forma:
BegFunc OrdenarSbf *Integer Len(2) Scope(*Public)
Hoy
por hoy, aunque ya podemos pasar los controles como parámetros, siguen siendo
objetos que no se pueden crear en tiempo de ejecución, por lo que el parámetro
de la función lo creamos de tipo *Object y no de tipo
AvrCtlsLib.Subfile ni nada similar.
Declaramos las variables de trabajo que vamos a usar para esta función.
DclFld
srvX *Integer Len(4)
DclFld
srvZ *Integer Len(4)
DclFld
srvY *Integer Len(4)
DclFld
srvClave *String
Como
el parámetro en donde esperamos recibir el subfichero lo hemos tenido que
definir de tipo *Object, por un descuido del
programador por el nos podría llegar cualquier otro tipo de objeto que no fuese
un subfichero, por lo que es bueno comprobarlo. Para ello simplemente
asignaremos a una variable el valor de la propiedad RowCount.
Eval
F2(srvZ = srpSubfichero.RowCount) Err(*In99)
If *In99
LeaveSr
1
EndIf
Hacemos la asignación dentro de una sentencia EVAL de forma que si el objeto recibido no era un subfichero y por lo tanto no disponía de la propiedad RowCount en lugar de recibir un error en tiempo de ejecución se encienda el indicador de error *IN99 y podamos devolver un error controlado.
Si sí que se trataba de un subfichero pero no tenía ningún registro, finalizamos la ejecución de la función indicándo que no ha habido error.
If srvZ = 0
LeaveSr
0
EndIf
Otra
cosa que debemos comprobar es si la colección tiene algún criterio
seleccionado. La función Count de la colección nos devuelve el número de elementos
que contiene.
srvZ = srpCriterios.Count
If srvZ = 0
LeaveSr 3
EndIf
Limpiamos el Combo Box de trabajo.
frmTrabajo.cmbOrdenar.ClearObj()
Clasificamos
el subfichero
Vamos
recorriendo las filas del subfichero
DO 0 TOVAL(srpSubfichero.RowCount
- 1) srvZ
srpSubfichero.Row
= srvZ
Montamos la serie de caracteres por la que vamos a hacer la clasificación. Para ello concatenamos el contenido de las celdas correspondientes a las columnas seleccionadas para la clasificación.
srvClave
= *Blanks
En este caso vamos a recorrer la colección por el índice de sus elementos. El primer elemento de una colección siempre es el elemento 0 (igual que la primera fila del subfichero). Para recuperar el valor del elemento utilizamos la propiedad Items, pero como esta es la propiedad por defecto podemos omitirla y limitarnos a especificar el índice del elemento del que queremos saber el valor.
Para saber qué columnas se han seleccionado recorremos la colección que hemos recibido por parámetro.
Do 0 ToVal(srpCriterios.Count - 1) srvY
srpSubfichero.Col
= srpCriterios[srvY]
srvClave = srvClave + srpSubfichero.CellData
EndDo
Una vez montada la serie de caracteres, la añadimos al combo box ordenado y en la avariable srvX recuperamos la posición en la que se ha añadido el elemento.
srvX
= frmTrabajo.cmbOrdenar.AddItem(srvClave)
Insertamos una fila en el subfichero en la posición que nos ha marcado el combo box.
srpSubfichero.AddItem('
', srvX)
Copiamos
el contenido de las celdas desde la fila original a la fila recién insertada.
Para ello vamos recorriendo las celdas de todas las columnas de la fila
original y copiamos su contenido en las celdas correspondientes de la fila
insertada.
Do
0 ToVal(srpSubfichero.ColCount - 1) srvY
srpSubfichero.Col
= srvY
srpSubfichero.Row
= srvZ + 1
srvCelda = srpSubfichero.CellData
srpSubfichero.Row
= srvX
srpSubfichero.CellData
= srvCelda
EndDo
Finalmente
eliminamos la fila original.
srpSubfichero.RemoveItem(srvZ + 1)
ENDDO
Devolvemos
0 como resultado de la función indicando que la clasificación se ha finalizado
correctamente.
LeaveSr 0
EndFunc
El
evento Terminate de la clase se produce cuando se
descarga esta de la memoria, bien porque
finalice el programa en la que fue instanciada
o declarada, o bien porque se haya asignado el valor *NONE a la variable en la
que se la instanció.
En
este momento aprovechamos para descargar el form de
trabajo que hemos utilizado para ubicar el combo box ordenado.
BEGSR AVR35PruCls1
Terminate
Unload frmTrabajo
ENDSR
El parámetro Desc del código de operación DclMbrAttr nos permite dar una pequeña descripción de la propiedad, método o evento que sirva como una pequeña primera ayuda al desarrollador. Esta información se podrá ver al seleccionar el elemento en el examinador de objetos.
DclMbrAttr OrdenarSbf
Desc('Clasificación de un subfichero según múltiples
criterios.' + x'0d0a' + +
'Recibimos el subfichero a clasificar como
primer parámetro de la función y' + x'0d0a' + +
'una colección con las columnas
seleccionadas como segundo parámetro.' + x'0d0a' + +
'Como resultado devolvemos un entero que
puede tener los siguientes valores:' + x'0d0a' + +
x'0d0a' + x'09' +
'0 - Se ha realizado correctamente la clasificación.' + x'0d0a'
+ +
x'09' + '1 - El
primer parámetro recibido no era un subfichero.' + x'0d0a'
+ +
x'09' + '2 - El
segundo parámetro recibido no era una colección.' + x'0d0a'
+ +
x'09' + '3 - No se
ha seleccionado ningún criterio de clasificación.')
Con
esto ya hemos terminado la codificación en la DLL. Algo que nunca debemos
olvidar al crear una DLL es ir a las especificaciones del proyecto y darle un
nombre y una descripción al proyecto.

Figura 3. Diálogo de especificaciones
de proyecto del proyecto de la DLL
Ahora estamos en condiciones de guardar el proyecto y
generar la DLL.
Una vez generada la DLL comenzaremos la codificación de un
programa en el que usemos esta función. Podemos usar cualquier programa que ya
tengamos escrito y que tenga un subfichero y añadirle las líneas de código
necesarias para usar esta función.
Lo primero que tenemos que hacer es seleccionar en el cuadro
de diálogo de referencias que vamos a trabajar con la DLL que acabamos de
crear:

Figura 4. Cuadro de diálogo de selección de referencias
Una vez que hemos seleccionado nuestra DLL podemos verla en
el visor de objetos.


Figura 5. Vista de la clase y de la función en el visor de
objetos
En el código del form declararemos
dos variables, una para la clase de nuestra DLL y otra para la colección que
vamos a usar para pasar los criterios de clasificación.
DclFld lCritClas Type(AVRCTLSLib.Collection)
DclFld
lAvr35Pru1 Type(AVR35_Pruebas.AVR35PruCls1)
Ahora tenemos que decidir en qué momento vamos a añadir una
columna a la colección de criterios de clasificación. Podríamos hacerlo cuando
el usuario haga doble click en la cabecera de una columna del subfichero.
BegSr
sbfClientes DoubleClick
lCritClas.Add(sbfClientes.Col)
EndSr
Añadiremos un botón ordenar al form. Para clasificar el subfichero simplemente llamamos al método de clasificación en el evento click del botón pasándole como parámetros el subfichero y la colección con los criterios de clasificación. Si el proceso de la clasificación es correcto se recibe como resultado el valor 0, si se ha detectado algún error se recibe el código del error y mostramos el mensaje correspondiente en un msgbox. Por último limpiaremos la colección para dejarla disponible para una posible posterior clasificación por distintos criterios.
BEGSR btnOrdenar Click
DclFld srvRtnCod
*Integer Len(4)
*This.MousePointer
= 11
sbfClientes.Enabled
= *Off
btnOrdenar.Enabled
= *Off
srvRtnCod = gAvr35Pru1.OrdenarSbf(sbfClientes,
lCritClas)
Select
When srvRtnCod = 1
MsgBox
Msg('El
primer parámetro del método de clasificación esperaba un subfichero') +
Title(*this.Caption
+ ' - Error') Icon(*Stop)
When srvRtnCod = 2
MsgBox Msg('No
se ha seleccionado ningún criterio de clasificación') +
Title(*this.Caption
+ ' - Error') Icon(*Stop)
EndSl
LCritClas.RemoveAll
btnOrdenar.Enabled
= *On
sbfClientes.Enabled
= *On
*This.MousePointer
= 0
EndSr
Espero que esto que hemos comentado en este artículo haya
sido claro y sea de utilidad tanto para empezar a conocer la versión 3.5 como
por la utilidad que pueda aportar la función en sí.
Hasta el próximo boletín.