Trucos y técnicas

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)

DclSrParm srpSubfichero *Object

DclSrParm srpCriterios AVRCTLSLib.Collection

 

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

DclFld srvCelda *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.