Etiqueta

19 enero 2011

Creación y uso de una sesión con Oracle



- Instalación del cliente:

1) Sobre la instalación del cliente Oracle en Mac OS X, consulte la nota técnica Cliente Oracle
 

2) Crearemos los parámetros necesarios en el archivo "tnsnames.ora" a fin de que el cliente Orcale pueda conectarse con la base de datos (en nuestro ejemplo la llamaremos "BASEORA"

- Creación de la biblioteca:

1) Crearemos una nueva biblioteca Omnis, a la que llamaremos "Sesión"


2) Añadiremos una clase "schema" en correspondencia con una tabla existente en nuestra base de datos Orcale (para nuestro ejemplo será "BDIREC"


3) Añadiremos una clase "table" (en nuestro ejemplo la llamaremos "Tabla") y asignaremos en su propiedad "sqlclassname" el esquema "BDIREC"

En "Starup_Task" crearemos las siguientes variables:

4) La variable Task, "taSesion" de tipo "Object" y con subtipo "ORACLE8SESS" y las variables Class, "clEsquema" y "clTabla" de tipo "List", a ésta última le añadiremos el "subtype" "Tabla".
 

- Conexión simple:

Añadiremos (bajo el método $construct, del "Starup_Task")


5) Do taSesion.$logon('BASEORA','usuario','contraseña','nombresesión')


- Si deseamos usar un "pool" de conexiones:

5) Do $extobjects.ORACLE8DAM.$objects.ORACLE8SESS.$makepool('Sesiones',10,'BASEORA','usuario',contraseña')

Cada vez que deseemos obtener una nueva conexión de entre las 10 disponibles usaremos:

6) Do clEsquemaPrueba.$sessionobject.$assign($sessionpools.Sesiones.$new())

- Uso de una clase esquema (schema)

6/7) Do clEsquema.$sessionobject.$assign(taSesion)
7/8) Do clEsquema.$select()
8/9) Do clEsquema.$fetch(kFetchAll)


- Uso de una clase tabla (table)

6/7) Do clTabla.$sessionobject.$assign(taSesion)
7/8) Calculate $clib.$tables.Tabla.$sqlclassname as 'BDIREC'
8/9) Do clTabla.$carga
9/10) Do clTabla.$sort(clTabla.NOMBREC)

En este último caso el método "$carga" es el encargado de leer la tabla y guardar su contenido en la lista "clTabla", tal y como sigue:

El método "$carga", se creará en la clase "Tabla", con el siguiente contenido:


1) Do $cinst.$select()
2) Do $cinst.$fetch(kFetchAll)

18 enero 2011

Uso del comando “SMTPSend”, caso práctico.


Partimos de la existencia de una tabla Oracle, que contiene las direcciones e-mail de posibles destinatarios del mensaje a enviar, lo haremos en sólo tres sencillos pasos.
Paso I.
Creamos una ventana, desde donde el usuario podrá seleccionar cada uno de los elementos del mensaje a enviar:

Las variables de entorno que usaremos serán las siguientes:
Class Variable                 Type                 Subtype
FileLog                           Object              FileOps
cListaDestinatarios         List                   
cAsunto                          Character           50
cMensaje                         Character           1000
cOrderAdjuntar               Boolean            

cAdjunto                         Character           100
Sentencia                         Object


Paso II.
El método $construct de la ventana:
Local Variable                 Type                                      Subtype
Condicion                        Character                                1000 
lStatus                             Short integer (0 to 255)         

;  Carga las direcciones e-mail de los posibles destinatarios DB-ORACLE

Do $sessions.[$sessions.$first().$name].$newstatement() Returns Sentencia

;  La variable Condicion contendrá la clausula where con las condiciones de la sentencia select de Oracle, en nuestro ejemplo, tomamos dicha clausula de la ya consignada en una queri denominada Bus_Alumnos.

Calculate Condicion as $queries.Bus_Alumnos.$extraquerytext
Do Sentencia.$execdirect(con("Select unique(EMAIL) from BALUMNOS ",Condicion)) Returns lStatus
Do Sentencia.$fetch(cListaDestinatarios,kFetchAll) Returns lStatus
Do cListaDestinatarios.$sendall($ref.$selected.$assign(kTrue))

;  Una vez cargada la lista cListaDestinatarios con las direcciones de e-mail, candidatas, nos aseguramos de tener la primera línea de la lista seleccionada, y las variables usadas para adjuntar un documento en sus valores iniciales.

Do cListaDestinatarios.$line.$assign(0)
Calculate cOrdenAdjuntar as 0
Calculate cAdjunto as ''


Paso III.
Crearemos un control de eventos para el botón: Adjuntar archivo
On evClick
Do FileOps.$getfilename(cAdjunto) Returns #F
If flag false
Calculate cOrdenAdjuntar as 0
Calculate cAdjunto as ''
Do $cobj.$redraw()
End If

Otro para el botón: Cancelar

On evClick
Close top window

Y finalmente



El evento para el botón: Enviar

Local Variable                 Type                                      Subtype
lArchivoBin                    Binary                                    n/a
lBinData                          Binary                                    n/a
lCharData                        Character                                1000000000
lCharSet                          Character                                50
lContentSubType           Character                                50
lContentType                 Character                                50
lEncoding                        Character                                1000000000
lFileName                        Character                                50
lLevel                              Short integer (0 to 255)
lNimeList                        List
lNombreArchivo             Character                                50
                                                          
On evClick

;  Primero eliminaremos de la lista las direcciones e-mail, que el usuario a des-seleccionada sobre la lista de candidatos.

Set current list cListaDestinatarios
For each line in list from 1 to #LN step 1
If cListaDestinatarios.$selected=kFalse
Delete line in list
End If
End For

;  Comprobaremos si el usuario ha seleccionado (o no) un documento para adjuntar al e-mail, pues el comando SMTPSend cambiará dependiendo de ésto.

If cAdjunto<>''
Do lNimeList.$define(lLevel,lContentType,lContentSubType,lFileName,lCharData,lBinData,lCharSet,lEncoding)
Do lNimeList.$add(0,'multipart','mixed')
Do lNimeList.$add(1,'text','plain',,cMensaje,,,)
Calculate lNombreArchivo as mid(cAdjunto,rpos(':',cAdjunto)+1,50)
Do FileLog.$openfile(cAdjunto)
Do FileLog.$readfile(lArchivoBin)
Do FileLog.$closefile()
Do lNimeList.$add(1,'application','octet-stream',lNombreArchivo,,lArchivoBin,,,)
SMTPSend ('smtp.miservidor.local','mi.direccion@ miservidor.es',cListaDestinatarios,cAsunto,lNimeList,,,'Universidad de Salamanca',,'1') Returns Estado
Else
SMTPSend ('smtp. miservidor.local',mi.direccion@ miservidor.es',cListaDestinatarios,cAsunto,cMensaje,,,'Universidad de Salamanca',,'1') Returns Estado
End If
Close top window

Y eso habrá sido todo, sólo indicar que el objeto, que muestra los destinatarios en la ventana de composición del mensaje, es de tipo kCheckBoxList, para facilitar al usuario, des-seleccionar direcciones e-mail.

Un cordial saludo, en especial a Case Software, Victor y Pepe, quienes se manifiestan como seguidores de éste blog.

Manual de programación en castellano


Manual de Programación Omnis Studio en Castellano. Mediante el primer enlace podrá obtener una edición en formato PDF, mediante el segundo, podrá optar por la edición impresa.



Recuerde que:  Adquiriendo una licencia o una actualización de cualquier producto Omnis Studio a través del distribuidor oficial (http://www.softpi.com), recibirá una copia en formato PDF.

17 enero 2011

Favoritos para "consultas sql dinamicas"

Hace algún tiempo que publiqué en este "blog" un ejemplo para crear consultas sql, ahora veremos como añadir a la ventana de resultados una nueva funcionalidad que nos permita guardar a modo de "favoritos" las búsquedas realizadas, para usarlas en otras ocasiones sin tener que reescribirlas.

El primer paso será crear un nuevo objeto sobre la ventana de resultados, un menú desplegable cuyo aspecto sería el siguiente:


En el procedimiento $construct de la ventana resultados añadiremos la siguiente secuencia:

; Carga menú de favoritos
Do cFavoritos.$define(cFavNombre,cFavConsulta)
Test if file exists {[con(mid(sys(115),1,pos(':',sys(115))),iBusqueda,'.txt')]}
If flag true
Set import file name {[con(mid(sys(115),1,pos(':',sys(115))),iBusqueda,'.txt')]}
Prepare for import from file {Delimited (tabs)}
Import data cFavoritos
Close import file
End If

Do cFavoritos.$addbefore(1,'Favoritos')
Do cFavoritos.$add('Añadir...')
Do cFavoritos.$add('Examinar...')
Do cFavoritos.$line.$assign(1)


Cuando el usuario seleccione una opción de éste menú podrá suceder lo siguiente:

On evClick
Switch cFavoritos.cFavNombre

Case 'Añadir...'
Prompt for input De nombre a la selección actual/Favoritos/3005/50 Returns cFavNombre (Cancel button,Prompt above entry)
If flag true
Do cFavoritos.$addafter(1,cFavNombre,$clib.$queries.[iBusqueda].$extraquerytext)
; Guarda favoritos en el disco
Set current list cFavoritos
If #LN>3
Set print or export file name {[con(mid(sys(115),1,pos(':',sys(115)))
,iBusqueda,'.txt')]}
For each line in list from 2 to #LN-2 step 1
Transmit text to print file {[cFavoritos.cFavNombre][chr(9)][cFavoritos.cFavConsulta][chr(9)][chr(13)]}
End For
Close print or export file
End If
End If
Case 'Examinar...'
Open window instance Favoritos (iBusqueda)

; Recarga favoritos
Set import file name {[con(mid(sys(115),1,pos(':',sys(115))),iBusqueda,'.txt')]}
Prepare for import from file {Delimited (tabs)}
Import data cFavoritos
Close import file
Do cFavoritos.$addbefore(1,'Favoritos')
Do cFavoritos.$add('Añadir...')
Do cFavoritos.$add('Examinar...')
Do cFavoritos.$line.$assign(1)
Do $root.$redraw()

Default
Do $clib.$queries.[iBusqueda].$extraquerytext.$assign(cFavoritos.cFavConsulta)
Do iVerList.$definefromsqlclass(iBusqueda)
Do iVerList.$sessionobject.$assign(iSessionObj) Returns #F
Do iVerList.$select() Returns lStatus
Do iVerList.$fetch(kFetchAll) Returns lStatus
Do iVerList.$line.$assign(1)
Calculate cRecordList as con(iVerList.$linecount,' registros en seleción')
Do $root.$redraw()
End Switch

Do cFavoritos.$line.$assign(1)
Do $cwind.$objs.Favoritos.$redraw()


La ventana para la gestión de favoritos (opción examinar) mostraría el siguiente aspecto:


La programación para esta ventana sería como sigue:

En el $construct de la ventana:

Do iListFavor.$define(iFavNombre,iFavConsulta)
Set current list iListFavor
Set import file name {[con(mid(sys(115),1,pos(':',sys(115))),pBusqueda,'.txt')]}
Prepare for import from file {Delimited (tabs)}
Import data iListFavor
Close import file
Enter data
If flag true
; Guarda favoritos en el disco
Set print or export file name {[con(mid(sys(115),1,pos(':',sys(115))),pBusqueda,'.txt')]}
For each line in list from 1 to #LN step 1
Transmit text to print file {[iListFavor.iFavNombre][chr(9)][iListFavor.iFavConsulta][chr(9)][chr(13)]}
End For
Close print or export file
End If
Close top window


Para la opción "mover arriba":

On evClick ;; Event Parameters - pRow( Itemreference )
Calculate iListFavor.$line as $cwind.$objs.favoritos_datagrid_1016.$gridvcell-1
If iListFavor.$line>1&iListFavor.$linecount>1
Load from list
Do iListFavor.$addbefore(iListFavor.$line-1,iFavNombre,iFavConsulta)
Do iListFavor.$remove(iListFavor.$line+1)
Do $cwind.$objs.favoritos_datagrid_1016.$redraw()
End If


Para la opción "mover abajo":

On evClick
Calculate iListFavor.$line as $cwind.$objs.favoritos_datagrid_1016.$gridvcell-1
If iListFavor.$line
Load from list
Do iListFavor.$addafter(iListFavor.$line+1,iFavNombre,iFavConsulta)
Do iListFavor.$remove(iListFavor.$line)
Do $cwind.$objs.favoritos_datagrid_1016.$redraw()
End If


Y por último para la opción "eliminar":

On evClick
Calculate iListFavor.$line as $cwind.$objs.favoritos_datagrid_1016.$gridvcell-1
If iListFavor.$line>0
Do iListFavor.$remove(iListFavor.$line)
Do $cwind.$objs.favoritos_datagrid_1016.$redraw()
End If


Y eso será todo, el sistema contendrá en archivos de texto plano, las preferencias o favoritos de las búsquedas seleccionadas permitiendo su posterior uso, con tan sólo seleccionarlas desde el menú, cada búsqueda diferente tendrá su propio archivo de texto plano.

13 enero 2011

Herramienta "ad hoc"

La herramienta de informes "ad hoc "

Se trata de una versión localizada al castellano de esta herramienta proporcionada por Omnis, para la generación rápida de informes y consulta de datos, mediante SQL.

El acceso a la base de datos se realiza mediante la DAM Omnis SQL para tener acceso a un archivo de datos de Omnis. La utilidad comprueba las sesiones abiertas con el servidor de datos y utiliza esta (independientemente de que servicio se trate Oracle, MySQL, etc) para presentar las tablas de datos que contenga.

Los informes que se generan son iguales que los presentados por la aplicación estándar "ad hoc" por lo que su manejo es idéntico al que se describe en el manual de Omnis para esta herramienta. Para usarla deberá colocar la librería adjunta en la carpeta de inicio (Startup).

Descargar librería

12 enero 2011

Aplicaciones modulares

Al diseñar una aplicación, es posible que deseemos modularla mediante la creación de una biblioteca que contenga todos los módulos de ventanas, informes y métodos.
Cada módulo puede tener sus propios menús y barras de herramientas. Un ejemplo podría ser una aplicación de gestión académica, con las preinscripciones, matriculas y expedientes.
En una aplicación totalmente modular, donde el usuario cambia constantemente entre módulos, no es fácil asegurarse de que el usuario vea correctamente los menús y herramientas para el módulo en uso. En un entorno, multi-ventana, el control de esta a veces puede ser difícil. Afortunadamente Omnis Studio permite la creación de múltiples “tasks” o tareas que permiten automatizar el proceso de creación de aplicaciones modulares.
Observe el ejemplo siguiente en el que, en una sola biblioteca se están ejecutando tres tareas: La “Startup_Task” y dos tareas más “Task1” y “Task2”. La tarea de inicio, que se abre automáticamente cuando se abre la biblioteca, contiene una ventana “Acerca de”. Las otras dos tareas contienen cada una, una ventana, un menú y una barra de herramientas. Cuando el usuario selecciona una ventana de “Task1” o de “Task2”, Omnis mostrará automáticamente las herramientas (toolbar) y los menús que correspondan a cada tarea.

Cuando la biblioteca se abre, la tarea inicial se ejecuta (Startup_Task) para mostrar la ventana “Acerca de” entonces se abren las demás tareas, cada una de los cuales abre su ventana, instala su menú y su barra de herramientas. La tarea de inicio concluye una vez que la propia ventana “Acerca de” se cierre.
Para abrir las dos tareas, usted deberá introducir la siguiente secuencia de instrucciones en el método $construct() de la tarea inicial.
Open window instance AboutWindow
Open task instance MyTaskClass1/Task1
Open task instance MyTaskClass2/Task2
Close task instance LibraryName ;; cierra la instancia Startup_Task

Cada tarea tiene una propiedad $autoactivate, que permite a la misma tomar el control, cuando el usuario actúa sobre la ventana que la contiene. Si la propiedad se establece en “false”, la ventana no llegará a mostrarse. Para que se active de manera automática, será necesario fijar la siguiente secuencia en el $construct() de cada tarea.
Do $ctask.$autoactivate.$assign(kTrue)
Para asegurarse de que los menús y barras de herramientas se muestren y oculten al cambiar de tarea, necesitará establecer la propiedad $local en cada clase. Al activar el “modo local” para cada menú y barra de herramientas que posee la tarea, Omnis los esconderá y mostrará automáticamente.
El ejemplo que se muestra a continuación, indica como establecer la propiedad $local desde el procedimiento $construct() de cada tarea:
; Método $construct() para la task1...
Do $menus.MyMenuClass1.$open(‘Menu1’) Returns iMenuRef
Do iMenuRef.$local.$assign(kTrue)
Do $toolbars.MyToolbarClass1.$open(‘Toolbar1’) Returns iToolRef
Do iToolRef.$local.$assign(kTrue)
Do $windows.MyWindowClass1.$open(‘Window1’) Returns iWinRef
Y exactamente lo mismo para la otra tarea.
; Método $construct() para la task2...
Do $menus.MyMenuClass2.$open(‘Menu1’) Returns iMenuRef
Do iMenuRef.$local.$assign(kTrue)
Do $toolbars.MyToolbarClass2.$open(‘Toolbar1’) Returns iToolRef
Do iToolRef.$local.$assign(kTrue)
Do $windows.MyWindowClass2.$open(‘Window1’) Returns iWinRef
Esta funcionalidad hará que los menús y barras de herramientas cambien automáticamente al cambiar de tarea.

¿Cómo permitir al usuario eliminar filas en un "Data Grid"/"Smart List"?

Descripción del problema:

Pongamos como ejemplo la siguiente tabla de datos, representada en un objeto tipo "kdatagrid":

Do cTablaEdit.$definefromsqlclass(iTituVen)
Do cTablaEdit.$sessionobject.$assign(iSessionObj) Returns #F
Do cTablaEdit.$select() Returns lStatus
Do cTablaEdit.$fetch(kFetchAll) Returns lStatus
Do cTablaEdit.$line.$assign(1)
Do cTablaEdit.$smartlist.$assign(kTrue)
If cTablaEdit.$linecount=0
Do cTablaEdit.$add()
End If

El problema parte de que mientras el usuario se mueve entre las celdas de la tabla, el atributo "cTablaEdit.$line" no cambia. En el ejemplo deseamos que el usuario pueda seleccionar la fila a borrar pulsando la tecla "F5", el asunto entonces es: ¿Cómo saber que número de fila (cTablaEdit.$line) debemos eliminar?

Solución:

On evCellChanged
Do cTablaEdit.$line.$assign(pVertCell)
On evKey
If pSystemKey=5
Yes/No message Eliminar registros {¿Desea realmente eliminar la línea de la tabla?}
If flag true
Do cTablaEdit.$remove(cTablaEdit.$line)
Do $root.$redraw()
End If
Process event and continue (Discard event)
End If

El evento evCellChanged, mantendrá seleccionada la línea o fila a borrar, de tal modo que el prodedimiento Do cTablaEdit.$dowork() actualizará finalmente la tabla en el servidor eliminando las filas marcadas para borrar.

No he encontrado un método mejor para solucionar este problema, no obstante (si usted querido lector lo tiene a bien), tal vez pueda sugerir algún otro procedimiento, de ser así lo ánimo a dejar su comentario en este blog.

Gracias por su atención.

11 enero 2011

Ajuste de la resolución de pantalla

Descripción del problema:

A menudo nos encontramos con diferentes monitores y diferentes configuraciones de resolución, además algunos usuarios prefieren una resolución menor de pantalla a fin de aumentar el tamaño de los objetos representados (no sólo el texto), especialmente aquellos que tengan alguna dificultad de visión.

Para resolver esta cuestión las aplicaciones desarrolladas con Omnis Studio deberían ser capaces de permitir al usuario elegir la resolución que deseen. Afortunadamente Omnis Studo incorpora los elementos necesarios para realizar este proceso, tan sólo se debe tener en cuenta que cuando se asignen estas preferencias se debe cerrar y reabrir la biblioteca para que surtan efecto.

A continuación describo en detalle un procedimiento que podría incorporarse a las opciones de preferencias de nuestra aplicación Omnis Studio, el ejemplo muestra un objeto visual que permitirá al usuario hacerse una idea del tamaño que tendrán sus ventanas tras aumentar o disminuir la resolución:


La ventana de preferencias podría ser de un estilo similar a este, mostrando (por ejemplo) un cuadro he indicadores de grado (Slider Control) para que el usuario modifique la resolución ha obtener según lo desee, no obstante tendremos que prefijar las resoluciones máximas y mínimas posibles, en nuestro caso las resoluciones posibles estarán entre 1.680x1.050 y 1.360x850. Para la resolución máxima (1.680x1.050) el rectángulo guía tendrá un tamaño de 130x220 ($height=130, $width=220).

Usaremos dos variables numéricas definidas para la clase "ventana de preferencias", las llamaremos “ZoomHori” y “ZoomVert”.

A continuación añadiremos al procedimiento $construct las siguientes líneas de código:

; Recoge el valor actual de resolución en pantalla
Do $cwind.$objs.ZoomHorizontal.$val.$assign($clib.$prefs.$hscale)
Calculate ZoomHori as $clib.$prefs.$hscale
Do $cwind.$objs.ZoomVertical.$val.$assign($clib.$prefs.$vscale)
Calculate ZoomVert as $clib.$prefs.$vscale
Do $cwind.$objs.NaviElement.$val.$assign($clib.$userinfo)
Calculate $cwind.$objs.ZoomHorizontal.$::min as 1360
Calculate $cwind.$objs.ZoomVertical.$::min as 850
Do $cwind.$bobjs.1053.$height.$assign(int((1680-$clib.$prefs.$hscale)/'6,40')+130)
Do $cwind.$bobjs.1053.$width.$assign(int((1050-$clib.$prefs.$vscale)/'3,33')+220)

Esto fijará la escala (o resolución) a los valores actuales y ajustará el tamaño del cuadro (ID=1053), lo que permitirá al usuario hacerse una idea aproximada de como se verán las ventanas en su pantalla.

Ahora debemos fijar los procedimientos asociados a las barras "Slider Control"

Para ZoomHorizontal:

On evNewValue
Calculate ZoomHori as pNewVal
Do $cwind.$bobjs.1053.$height.$assign(int((1680-ZoomHori)/'6,40')+130)

Para ZoomVertical:

On evNewValue
Calculate ZoomVert as pNewVal
Do $cwind.$bobjs.1053.$width.$assign(int((1050-ZoomVert)/'3,33')+220)

Finalmente debemos dejar ajustada la resolución que el usuario ha seleccionado, esto lo haremos al abandonar la clase "ventana de preferencias", (procedimiento $destruct) tal y como sigue:

; Establece nuevos valores de resolución en pantalla
Do $clib.$prefs.$hscale.$assign(ZoomHori)
Do $clib.$prefs.$vscale.$assign(ZoomVert)

Suelo incorporar este procedimiento en todas las aplicaciones que realizo, los usuarios agradecen disponer de un ajuste personalizado para cada aplicación.

Si encontráis útil la idea o tenéis otra mejor, os animo a que dejéis vuestros comentarios en el "blog", por mi parte seguiré incorporando alguna otra idea de interés general.

Un cordial saludo a todos los desarrolladores de Omnis Studio y en especial a los participantes del grupo programomnis.

10 enero 2011

ORACLE: Convertir vocales acentuadas

Problemática

Cuando realizamos consultas sql a la base de datos, (para, por ejemplo buscar por nombre de persona) podemos usar la función UPPER a fin de que la comparación no distinga entre mayúsculas y minúsculas, pero no ocurre lo mismo en el caso de las vocales acentuadas, a fin de solventar este problema podríamos implementar la siguiente función:

CREATE OR REPLACE function acentos(wcadena in varchar2) RETURN varchar2 is cadena varchar2(200);

BEGIN

cadena := replace(wcadena,'Á','A');
cadena := replace(cadena,'É','E');
cadena := replace(cadena,'Í','I');
cadena := replace(cadena,'Ó','O');
cadena := replace(cadena,'Ú','U');

return(cadena);

END;

06 enero 2011

Limpiar una variable List o Row eliminado los valores NULL

Limpiar una variable de tipo "list" o "Row", realemente es una tarea sencilla con Omnis Studio bastará con el siguiente comando:

iSqlRow.$clear()

El único problema que esto plantea es que deja las columnas con valores NULL, con todos los problemas que esto conlleva, como (por ejemplo) al realizar operaciones matemáticas .

La solución es la siguiente:

iSqlRow.$cols.$sendall($ref.$clear())

Esto causará que cada columna quede correctamente iniciada llenando con valores "0" las variables numéricas.

En el caso de variables de tipo "list" utilizadas en un objeto de tipo "complex grid", permitirá que el usuario coloque el cursor sobre ella para entrar datos, sin necesidad de añadir una primera línea vacía a la lista.

Existe algo publicado sobre esto en la nota técnica de Omnis: TNNO0016 (http://www.omnis.net/support/index.html?detail=technotes) pero esta solución me parece más simple y elegante.

Saludos a todos los lectores de este "blog".

05 enero 2011

Actualización automática de bibliotecas a los usuarios finales

Problemática

A menudo los desarrolladores realizamos cambios sobre las bibliotecas Omnis Studio para adaptarlas a las necesidades de los usuarios o para corregir posibles deficiencias, esto obliga a actualizar las versiones que estos tiene instaladas. Se necesita por tanto un procedimiento que actualice de forma automática los ordenadores de los usuarios.

Escenario previo (Requerimientos):

Partimos de la base de que disponemos de un servidor FTP, donde existe un directorio llamado “ForUpdate”, que contendrá una copia de las bibliotecas a actualizar, es el lugar donde el desarrollador dejará la nueva biblioteca, cada vez que se produzca un cambio en la misma y por tanto debe ser actualizada al usuario.

Resolución:

Ahora construiremos una nueva biblioteca llamada “Actualiza” en nuestro ejemplo y que una vez terminada situaremos en la carpeta “startup” de Omnis Studio, la idea es que sea la primera biblioteca que Omnis Studio ejecutará.

Todos los procedimientos descritos pertenecen a la clase Task “Startup_Task”

Las variables del caso son las siguientes:

No. Instance Variable Type Subtype Init.Val/Calc

1 iActualizado Boolean
2 iArchivo Character 100
3 iDirArchi Character 100
4 iDirFTP List
5 iDirLocal List
6 iFTPSocket Long integer
7 iPassword Character 100 ‘******’
8 iServerDirec Character 100 '192.33.20.20'
9 iServerError Character 500
10 iUsuario Character 100 'librerias'

En primer lugar construiremos el método $construct de la librería “Actualiza”, todos los procesos que tendrán lugar se ejecutarán desde este procedimiento.

Después de haber definido las variables del caso, debemos hacer lo mismo con las siguientes variables locales:


No. Local Variable Type Subtype

1 lErrCode Short integer (0 to 255)
2 lExportPath Character 100
3 ok Boolean

Además del método público $construct, crearemos un total de seis métodos privados: EnFTP, EnUsuario, Guardarropa, Compara, SolicitaCopia y CopiaFTP.

El método $construct se compondrá finalmente de las siguientes líneas de código:

;; obtiene el contenido del directorio FTP a actualizar
1 Do method EnFTP Returns ok
2 If ok
;; obtiene el contenido del directorio correspondiente a la última actualización
3 Do method EnUsuario
;; selecciona de la lista FTP los archivos que necesitarán actualizarse
4 Do method Compara
;; pregunta al usuario y permite la copia de la nueva versión de la biblioteca
5 Do method SolicitaCopia
6 If iActualizado
;; guarda la lista de archivos FTP en un documento local denominado UltimoUpdate.txt
7 Do method Guardaropa
8 End If
9 End If

El procedimiento “EnFTP”, tiene como objetivo leer la entrada del directorio de los archivos contenidos en el servidor FTP y guardarlo sobre la lista “iDirFTP”, para posteriormente compararla con las entradas de directorio correspondientes a la última actualización que realizo el usuario.


No. Local Variable Type Subtype

1 lErrCode Short integer (0 to 255)

1 ; establece la conexión con el servidor FTP
2 FTPConnect (iServerDirec,iUsuario,iPassword) Returns iFTPSocket
3 If iFTPSocket; entra en el directorio "ForUpdate"
8 FTPCwd (iFTPSocket,'ForUpdate') Returns lErrCode
9 If lErrCode
10 OK message Error FTP {[con("Error al acceder al directorio FTP de
actualización ",kCr,"Código : ",lErrCode)]}
11 Else
12 Do iDirFTP.$define(iDirArchi)
13 ; devulve la lista de archivos en el directorio FTP sobre la variable iDirFTP en formato extendido
14 FTPList (iFTPSocket,iDirFTP,,1) Returns lErrCode
15 If lErrCode
16 FTPGetLastStatus (iServerError) Returns lErrCode
17 OK message Error FTP {[con("Error obteniendo la lista de archivos del
servidor FTP",kCr,"Devolvio: ",kCr,iServerError)]}
18 Quit method kFalse
19 Else
20 Quit method kTrue
21 End If
22 End If
23 End If

El procedimiento “EnUsuario”, la idea es compara la lista de los archivos a actualizar (servidor FTP), con los de la última actualización realizada, pero note que en caso de tratarse de la primera actualización dicha lista no existirá, por lo que deberá ser creada.

Finalmente la lista se guardará en un archivos de texto local denominado “UltimoUpdate.txt” y es desde este archivo que se cargará la lista de archivos para su comparación, la lista de archivos contenida en “UltimoUpdate.txt” se guardará en la variable de lista “iDirLocal”.

Finalmente y a estas alturas del proceso abremos obtenido dos listas, “iDirFTP” (con el directorio del servidor FTP) e “iDirLocal” (con el directorio correspondiente a la última actualización)
No. Local Variable Type Subtype

1 lExportPath Character 100

1 ; confecciona una lista con los últimos archivos actualizados
2 Calculate lExportPath as con(sys(115),'UltimoUpdate.txt')
3 Test if file exists {[lExportPath]}
4 If flag false
5 Do method Guardaropa ;; guarda la lista de archivos FTP en un documento
local denominado UltimoUpdate.txt
6 End If
7 Do iDirLocal.$define(iDirArchi)
8 Set import file name {[lExportPath]}
9 Prepare for import from file {Delimited (commas)}
10 Import data iDirLocal
11 End import
12 Close import file
El procedimiento “Guardarropa”, tiene como único objetivo actualizar el archivo local “UltimoUpdate.txt”, que como ya sabe el lector, contiene las entradas del directorio FTP, correspondientes a la última actualización.

No. Local Variable Type Subtype

1 lExportPath Character 100

1 Calculate lExportPath as con(sys(115),'UltimoUpdate.txt')
2 Set print or export file name {[lExportPath]}
3 Prepare for export to file {Delimited (commas)}
4 Export data iDirFTP
5 End export
6 Close print or export file
El procedimiento “Compara”, halla las diferencias entra las listas “iDirFTP” e “iDisLocal”, el resultado final es que únicamente quedan seleccionadas las entradas del directorio FTP, que deberán ser actualizadas (“iDirFTP”)

No. Local Variable Type Subtype

1 lComapara Character 100

1 Set current list iDirLocal
2 For each line in list from 1 to #LN step 1
3 Load from list
4 Calculate lComapara as iDirArchi
5 Do iDirFTP.$search(iDirArchi=lComapara,kTrue,kFalse,kTrue,kFalse)
6 End For
7 Set current list iDirFTP
8 Invert selection for line(s) (All lines)
A continuación describimos el procedimiento “SolicitaCopia”, cuya única misión es solicitar al usuario el lugar de destino para albergar la nueva biblioteca actualizada.

1 Calculate iActualizado as kFalse
2 For each line in list (Selected lines only) from 1 to #LN step 1
3 Load from list
4 Calculate iArchivo as right(iDirArchi,len(iDirArchi)-pos(':',iDirArchi)-3)
5 Yes/No message ¡Nueva versión! (Icon,Sound bell) {Existe una nueva versión
de [iArchivo][kCr]¿Desea instalarla ahora?}
6 If flag true
7 Do method CopiaFTP
8 End If
9 End For
Finalmente tan sólo nos queda describir el último de los procedimientos “CopiaFTP”, que es donde finalmente se copiaran los archivos o biblioteca a actualizar desde el servidor FTP al ordenador del usuario. La variable “iActualizado”, nos permitirá saber si todo el proceso termino con éxito.

Note que sólo si todo el proceso termina con éxito se actualizará el archivo “UltimoUpdate.txt”, de este modo se podría volver a intentar la actualización tantas veces como se requiera.


No. Local Variable Type Subtype

1 lError Short integer (0 to 255)
2 lPath Character 500

1 Do FileOps.$selectdirectory(lPath,con('Elija destino para ',iArchivo)) Returns
lError
2 If lError=1
3 FTPType (iFTPSocket,1) Returns iServerError
4 If not(iServerError)
5 Calculate lPath as con(lPath,':',iArchivo)
6 Working message Descargando.../-1073735809,-1073735805;50;0;60 {Copiando
librería [iArchivo]...}
7 FTPGet (iFTPSocket,iArchivo,lPath) Returns iServerError
8 If iServerError
9 OK message Error FTP {[con("Error al copiar el archivo
",upp(iArchivo),kCr,"Código de error : ",iServerError)]}
10 Calculate iActualizado as kFalse
11 Else
12 Calculate iActualizado as kTrue
13 End If
14 Else
15 Calculate iActualizado as kFalse
16 End If
17 Else
18 Calculate iActualizado as kFalse
19 End If
Bueno… esto ha sido todo, se trata de la primera entrega de las muchas cosas que deseo compartir con todos los desarrolladores de Omnis Studio.

Cuento con vuestro ánimo para seguir poniendo mis experiencias, en este blog.

Migrando de Omnis 7 a Omnis Studio

Desde la aparición 1997 de Omnis Studio, Raining Data ha esperado que los desarrolladores dejaran Omnis 7 y adaptaran sus aplicaciones al nuevo Omnis Studio, pero realmente se trataba de una labor ardua, puesto que representaba todo un cambio en la filosofía de programación (Programación OO).
Seguramente debido en parte a esto y en otra al desembolso económico necesario, muchos desarrolladores continuaron manteniendo sus aplicaciones en Omnis 7, alentados también por el compromiso de Raining Data a dar continuidad a dicho producto.
Pero con la llegada de los Mac Intel, Raining Data anuncia que no seguirá dando soporte a Omnis 7 para la nueva plataforma Mac, es decir que no hay actualización para Mac OS X, ni la habrá.
¿Qué haremos ahora con nuestras actuales aplicaciones en Omnis 7, bajo plataforma Mac?
Posibilidades:
1) Cambiar de plataforma, es decir pasar de Mac OS X a Windows
2) Convertir la aplicación a Omnis Studio
La primera opción puede que le atraiga a usted si es desarrollador, pero no creo que resulte muy popular entre los usuarios de ordenadores Macintosh, entre los cuales me incluyo.
La segunda opción parece la más razonable y cómoda para el usuario, pues aunque suponga un esfuerzo adicional por parte del desarrollador, el resultado final será de mayor agrado al usuario, además Omnis Studio proporciona mayor funcionalidad y flexibilidad, ahorrando en tiempo de desarrollo y costes, además de ser la herramienta idónea, para entrar en el llamado “e-business”, Internet e Intranets.
¿Cómo migraremos nuestra antigua aplicación Omnis 7 a Omnis Studio?
Raining Data asegura que usted puede realizar esta tarea con no mucho esfuerzo de su parte (ver http://www.omnis.net/products/omnis7/conversion.html) pero mi experiencia es que se trata de una ingente y eso sólo para que funcione. Si lo que queremos es que además aproveche toda la funcionalidad de Omnis Studio, la tarea puede llevar hasta semanas de arduo trabajo.
¿Qué otra opción tengo?
Por supuesto usted puede solicitar ayuda externa, migrar de Omnis 7 a Omnis Studio bajo plataforma Mac OS X o Windows, incluso migrar de Mac OS X a Windows o de Windows a Mac OS X, puede ser mucho más sencillo para usted.
Pídame presupuesto sin compromiso alguno, usted sólo tendrá que enviarme su librería Omnis 7 y tendrá a vuelta de correo una nueva librería Omnis Studio completamente funcional y adaptada a las nuevas funcionalidades y filosofía de trabajo en Omnis Studio.
Póngase en contacto conmigo para más información.

04 enero 2011

¿Cómo crear, mantener y visualizar un archivo "log"?

Casi todas las aplicaciones cuentan con un archivo "log", donde guardar las incidencias que se vayan produciendo durante su ejecución, normalmente se trata de un archivo de texto "txt" con varias columnas tabuladas.

A continuación se muestra como crear, mantener y visualizar un archivo "log" para nuestra librería Omnis Studio, se describen un total de tres procedimientos:

1) Crear archivo 2) Añadir entradas 3) Visualizar contenido.

1) Método para crear el archivo, (toma su nombre del que ya tiene la librería)

Calculate ArchivoLog as con(mid(sys(10),1,len(sys(10))-3),'log')
Do FileOps.$doesfileexist(ArchivoLog) Returns ExisteLog
If not(ExisteLog)
Do FileOps.$createFile(ArchivoLog)
End If
2) Método para añadir entradas al archivo (Denominado: $grabalog)

Variable de parámetro

"PTextoLog" de tipo Carácter (Contiene el texto a grabar en el archivo de log)

; Inserta una línea en el archivo de log, con el texto contenido en "PTextoLog"Calculate lPathname as con(mid(sys(10),1,len(sys(10))-3),'log')Do FileLog.$openfile(lPathname)Do FileLog.$getfilesize() Returns FileEndDo FileLog.$writefile(con(#D,' ',tim(#T,'H:N:S'),chr(9),PTextoLog,chr(9), iUserName,chr(13)),FileEnd)Do FileLog.$closefile()

Ejemplo de llamada al método "$grabalog"

Do method $grabalog (con('Sesión iniciada con éxito en ',iSessionName))
Esto añadiría una nueva entrada (el texto se pasa en el parámetro "PTextoLog")

3) El siguiente procedimiento transfiere el contenido del archivo de log a una lista.

Set current list lListaLogDefine list {IFechaLog,ITextoLog,IUsuarioLog}Calculate lPathname as con(mid(sys(10),1,len(sys(10))-3),'log')Set import file name {[lPathname]}Prepare for import from file {Delimited (tabs)}Import data lListaLogEnd importClose import file
Y esto es todo, sencillo pero eficaz, sigo animando a todos los lectores de este blog a que dejéis vuestros animadores comentarios, eso permitirá que continúe añadiendo mis experiencias al mismo.

Ya sabéis que también podéis contactar conmigo a través de Skipe e incluso (si así lo deseáis) podríais colaborar con la publicación de vuestras propias experiencias, sería estupendo que este blog se convirtiera en un punto de encuentro para todos los que “nos pegamos” con el desarrollo de aplicaciones en Omnis Studio.

03 enero 2011

Consultas (SQL) dinámicas y sencillas

La capacidad que Omnis Studio nos proporciona para trabajar con objetos, será usada en este práctico ejemplo, para que el programador pueda construir nuevas consultas (query’s) sobre la base de datos, sin que sea necesario alterar el código de la aplicación, al añadir o modificar una consulta.

Puede descargar la librería Ejemplo.lib en este link.

Para nuestro ejemplo partimos de una tabla o esquema ALUMNOS, para el cual creamos un objeto de tipo “Query”, este será la base que usará la aplicación para construir la “interface” de búsqueda al usuario.

Posteriormente el programador se limitará a crear y/o modificar nuevos objetos “Query”, pero no necesitará escribir nuevo código.

En nuestro ejemplo crearemos la “Query” “Bus_Alumno” como sigue:

Detalle de la consulta: Bus_Alumno

Esquema Columna....Alias
ALUMNOS NUMEXPALU..Expediente
ALUMNOS APEL1......Primer apellido
ALUMNOS APEL2......Segundo apellido
ALUMNOS NOMBRE.....Nombre
ALUMNOS DNI........DNI
ALUMNOS FALTAN.....Faltan
ALUMNOS AVISO......Aviso
ALUMNOS ROWID......Puntero

Extensión: (Almacena la última consulta efectuada por el usuario)

La columna “Alias” contendrá la denominación que será usada para construir el “pop-menú” desde donde el usuario seleccionará el campo de comparación.

Además de las columnas o datos seleccionados del esquema “ALUMNOS”, se incluye la columna “ROWID”, para permitir la selección de una fila única por parte del usuario (Véase ventana de “Resultados”)

La ventana de búsqueda es auto-ampliable, permitiendo la inclusión de hasta 10 valores de comparación, su aspecto totalmente desplegada sería el que se muestra más bajo. Cabe destacar que el objeto “Bus_Alumno” guardará siempre la última consulta realizada por el usuario, incluso aunque abandone la aplicación.


A continuación mostramos las variables asociadas a la clase (Ventana “Buscar”) y los métodos asociados:
Variables, tipo y uso
bCampos1…bCampos10, tipo: Lista, Uso: Lista de los datos objeto de comparación (Nombres alias)
bCompara1…bCompara10, tipo: Lista, Uso: Lista de opciones de comparación
Valor1…Valor10, tipo: Carácter, Uso: Guarda el primer posible valor de comparación establecido

pQuery, tipo: Carácter Contiene una copia de las condiciones de búsqueda establecidas
Método: $construct
Variables locales
CuentaClon, tipo: Entero, Uso: Usado para crear el duplicado de las listas "bCampos" y "bCompara"
Item, tipo: Referencia, Uso: Referencia al primer dibujo a fijar en el fondo de la ventana
Ref_Col, tipo: Referencia, Uso: Referencia a cada columna en la "query" de búsqueda para la vista en selección
Variables parámetros
Busqueda, tipo: Carácter, Uso: Contiene el nombre de la "query" de búsqueda para la vista en selección
; Crea desplegables para los campos de búsqueda
Do bCampos1.$define(#S1,#S2)
Do bCampos1.$clear()
Do bCampos1.$add($clib.$queries.[Busqueda].$objs.$first().$alias,$clib.
$queries.[Busqueda].$objs.$first().$colname)
Set reference Ref_Col to $clib.$queries.[Busqueda].$objs.$first()
While Ref_Col
Set reference Ref_Col to $clib.$queries.[Busqueda].$objs.$next(Ref_Col)
If Ref_Col.$alias<>''&Ref_Col.$alias<>'Puntero'
Do bCampos1.$add(Ref_Col.$alias,Ref_Col.$colname)

End If
End While
Do bCampos1.$line.$assign(1)
For CuentaClon from 2 to 10 step 1
Do bCampos[CuentaClon].$copydefinition(bCampos1)
Do bCampos[CuentaClon].$merge(bCampos1,,,kTrue)
Do bCampos[CuentaClon].$line.$assign(1)
End For
; Crea desplegables para las comparaciones de búsqueda
Do bCompara1.$define(#S1)
Do bCompara1.$clear()
Do bCompara1.$add('contiene')
Do bCompara1.$add('empieza por')
Do bCompara1.$add('es')
Do bCompara1.$add('no es')
Do bCompara1.$add('mayor o igual a')
Do bCompara1.$add('menor o igual a')
Do bCompara1.$line.$assign(1)
For CuentaClon from 2 to 10 step 1
Do bCompara[CuentaClon].$copydefinition(bCompara1)
Do bCompara[CuentaClon].$merge(bCompara1,,,kTrue)
Do bCompara[CuentaClon].$line.$assign(1)
End For
Clear range of fields Valor1 to Valor10
Do $cwind.$objs.Opciones.$objs.Valor1.$enabled.$assign(kTrue)
; Pasa el parámetro de búsqueda a la variable global "pQuery" para no perderlo
Calculate pQuery as Busqueda
Método: PasaValor
Variables parámetros
Valor, tipo: Carácter, Uso: Contiene el valor a comparar
; Pasa el símbolo de consulta
Switch lst(#S1)
Case 'contiene'
Calculate bCondicion as con(bCondicion," LIKE '%",Valor,"%' ")
Case 'empieza por'
Calculate bCondicion as con(bCondicion," LIKE '",Valor,"%' ")
Case 'es'
Calculate bCondicion as con(bCondicion," = '",Valor,"' ")
Case 'no es'
Calculate bCondicion as con(bCondicion," <> '",Valor,"' ")
Case 'mayor o igual a'
Calculate bCondicion as con(bCondicion," >= '",Valor,"' ")
Case 'menor o igual a'
Calculate bCondicion as con(bCondicion," <= '",Valor,"' ")
End Switch
Botón “Más opciones”: $event
On evClick ;; Amplia el número de opciones en la ventana
If $cwind.$objs.Opciones.$height<>345
Do $cwind.$objs.Opciones.$height.$assign($cwind.$objs.Opciones.$height+33)
Do $cwind.$height.$assign($cwind.$height+33)
Switch $cwind.$objs.Opciones.$height
Case 81
Do $cwind.$objs.Opciones.$objs.Valor2.$enabled.$assign(kTrue)
Case 114
Do $cwind.$objs.Opciones.$objs.Valor3.$enabled.$assign(kTrue)
Case 147
Do $cwind.$objs.Opciones.$objs.Valor4.$enabled.$assign(kTrue)
Case 180
Do $cwind.$objs.Opciones.$objs.Valor5.$enabled.$assign(kTrue)
Case 213
Do $cwind.$objs.Opciones.$objs.Valor6.$enabled.$assign(kTrue)
Case 246
Do $cwind.$objs.Opciones.$objs.Valor7.$enabled.$assign(kTrue)
Case 279
Do $cwind.$objs.Opciones.$objs.Valor8.$enabled.$assign(kTrue)
Case 312
Do $cwind.$objs.Opciones.$objs.Valor9.$enabled.$assign(kTrue)
Case 345
Do $cwind.$objs.Opciones.$objs.Valor10.$enabled.$assign(kTrue)
End Switch
End If
Botón “Menos opciones”: $event
On evClick ;; Reduce el número de opciones en ventana
If $cwind.$objs.Opciones.$height<>48
Do $cwind.$objs.Opciones.$height.$assign($cwind.$objs.Opciones.$height-33)
Do $cwind.$height.$assign($cwind.$height-33)
Switch $cwind.$objs.Opciones.$height
Case 48
Do $cwind.$objs.Opciones.$objs.Valor2.$enabled.$assign(kFalse)
Calculate Valor2 as ''
Case 81
Do $cwind.$objs.Opciones.$objs.Valor3.$enabled.$assign(kFalse)
Calculate Valor3 as ''
Case 114
Do $cwind.$objs.Opciones.$objs.Valor4.$enabled.$assign(kFalse)
Calculate Valor4 as ''
Case 147
Do $cwind.$objs.Opciones.$objs.Valor5.$enabled.$assign(kFalse)
Calculate Valor5 as ''
Case 180
Do $cwind.$objs.Opciones.$objs.Valor6.$enabled.$assign(kFalse)
Calculate Valor6 as ''
Case 213
Do $cwind.$objs.Opciones.$objs.Valor7.$enabled.$assign(kFalse)
Calculate Valor7 as ''
Case 246
Do $cwind.$objs.Opciones.$objs.Valor8.$enabled.$assign(kFalse)
Calculate Valor8 as ''
Case 279
Do $cwind.$objs.Opciones.$objs.Valor9.$enabled.$assign(kFalse)
Calculate Valor9 as ''
Case 312
Do $cwind.$objs.Opciones.$objs.Valor10.$enabled.$assign(kFalse)
Calculate Valor10 as ''
End Switch
Do $redraw()
End If
Botón “Buscar”: $event
Variables locales
ItemAnterior, tipo: Entero, Uso: Contiene el nombre del campo a comparar, seleccionado en la línea anterior
Replica, tipo: Entero, Uso: Usado para recorrer cada una de las 10 líneas posibles de comparación
On evClick ;; Compone la consulta sobre la variable "bCondicion"
Calculate bCondicion as ''
If Valor1<>''
Calculate bCondicion as 'where '
Set current list bCampos1
Calculate ItemAnterior as bCampos1.$line
Calculate bCondicion as con(bCondicion,bCampos1.#S2)
Set current list bCompara1
Do method PasaValor (Valor1)
End If
For Replica from 2 to 10 step 1
If Valor[Replica]<>''
Set current list bCampos[Replica]
If bCampos[Replica].$line=ItemAnterior
Calculate bCondicion as con(bCondicion,' OR ')
Else
Calculate bCondicion as con(bCondicion,' AND ')
End If
Calculate ItemAnterior as bCampos[Replica].$line
Calculate bCondicion as con(bCondicion,bCampos[Replica].#S2)
Set current list bCompara[Replica]
Do method PasaValor (Valor[Replica])
End If
End For
; Pasa la misma consulta a la ventana de vista
Calculate $clib.$queries.[pQuery].$extraquerytext as bCondicion
Do $topwind.$close()
Esto será todo lo necesario para que el usuario construya una búsqueda, el resultado será una consulta SQL, preparada para ser lanzada a la base de datos.
Ahora nos centraremos en como presentar el resultado de la petición al usuario, para esto usaremos la clase ventana “Resultados”, a continuación mostramos el aspecto de esta ventana, así como sus variables y procedimientos.



Esta ventana tiene como finalidad mostrar los resultados de la última búsqueda realizada por el usuario. En la parte superior se recuerda la consulta realizada.
Variables asociadas a esta clase
iBusqueda, tipo: Carácter, Uso: Contiene una copia del nombre de la "query" en selección
Método: $construct
Variables locales
Item, tipo: Referencia, Uso: Referencia al primer dibujo a fijar en el fondo de la ventana
lStatus, tipo: Entero, Uso: Recoge el resultado de la sentencia "fetch"
Nom_Col, tipo: Carácter, Uso: Contiene los nombres alias de las columnas en la "query" de búsqueda en selección
Ref_Col, tipo: Referencia, Uso: Referencia a cada columna en la "query" de búsqueda para la vista en selección
Variables parámetros
Busqueda, tipo: Carácter, Uso: Contiene una el nombre de la "query" en selección
Calculate Nom_Col as $clib.$queries.[Busqueda].$objs.$first().$alias
Set reference Ref_Col to $clib.$queries.[Busqueda].$objs.$first()

For #1 from 2 to $clib.$queries.[Busqueda].$objs.$count()-1 step 1

Set reference Ref_Col to $clib.$queries.[Busqueda].$objs.$next(Ref_Col)

Calculate Nom_Col as con(Nom_Col,",",Ref_Col.$alias)

End For

Calculate $cwind.$objs.Resultados_headedlistbox_1023.$columnnames as Nom_Col

Calculate $cwind.$objs.Resultados_headedlistbox_1023.$colcount as
$clib.$queries.[Busqueda].$objs.$count()-1
Do iVerList.$definefromsqlclass(Busqueda)

Do iVerList.$sessionobject.$assign(iSessionObj) Returns #F

Do iVerList.$select() Returns lStatus

Do iVerList.$fetch(kFetchAll) Returns lStatus

Do iVerList.$line.$assign(1)
; Pasa el parámetro de busqueda a la variable de clase "iBusqueda" para no perderlo
Calculate iBusqueda as Busqueda
Do $root.$redraw()
Acciones sobre la lista: $event
Variables locales
Nom_Col, tipo: Carácter, Uso: Contiene el nombre de la columna seleccionada
Ref_Col, tipo: Referencia, Uso: Referencia a cada columna de la "query" en selección
On evDoubleClick
Do $topwind.$close()
On evHeaderClick ;; Clasifica los elementos según la columna seleccionada
Switch pColumnNumber
Case 1
Calculate Nom_Col as con("iVerList.",$clib.$queries.[iBusqueda].$objs.$first().$alias)
Do iVerList.$sort([Nom_Col])
Default
Set reference Ref_Col to $clib.$queries.[iBusqueda].$objs.$first()
For #1 from 2 to pColumnNumber step 1
Set reference Ref_Col to $clib.$queries.[iBusqueda].$objs.$next(Ref_Col)
Calculate Nom_Col as con("iVerList.",Ref_Col.$alias)
Do iVerList.$sort([Nom_Col])
End For
End Switch
Do $cwind.$redraw()
Sigo animando a todos los lectores de este blog a que dejéis vuestros animadores comentarios, eso permitirá que continúe añadiendo mis experiencias al mismo.

Ya sabéis que también podéis contactar conmigo a través de Skipe e incluso (si así lo deseáis) podríais colaborar con la publicación de vuestras propias experiencias, sería estupendo que este blog se convirtiera en un punto de encuentro para todos los que “nos pegamos” con el desarrollo de aplicaciones en Omnis Studio.