Recomendaciones
Recomendaciones y Trucos que nos pueden evitar dolores de cabeza
Versión Demo (básica)
Poner este embed global en un par de tablas importantes
Global Embeds Abc Objects File Managers File Manager for YourFile Insert - Before Parent Call IF RECORDS(SELF.FILE)> 10 MESSAGE('Demo Version') RETURN LEVEL:FATAL END
Obtener un total general (al final) en un Reporte
Lo mas facil es armar un break group sobre una variable que no cambie nunca, (puede ser cualquier variable global, por ejemplo) Luego pones los totales en un group footer de este break y listo. Como la variable nunca cambia (puede ser una global por ejemplo) solo se imprime el group footer al final del reporte. Tambien podria usarse el Group Header de ese break como "caratula" del reporte
Usar DLLs no creadas en Clarion
Para Usar DLLs no creadas en Clarion, hay que seguir los siguientes pasos:
- Crear los prototipos equivalentes en Clarion para esto hay que averiguar cuales son los prototipos originales (en c, por ej.) y ver la equivalencia de tipos entre Clarion, C y o C++ - Usar el Libmaker para la creacion de una Clarion Library (.LIB File) para la DLL - Incluir el .LIB File al proyecto
Para mas detalles, ver .\docs\FAQsTips&Tricks.pdf el tema "How to Use Windows DLLs NOT Created in Clarion".
Modificar código en múltiples embeds, o varias tablas del DCT
Se puede exportar la aplicación o el DCT a Texto (app.TXA) modificar el texto con un buen editor (yo recomiendo el textpad) y luego importar de nuevo.
Obviamente, hacer un backup primero porque esto es una opración "peligrosa"
En la sección de Downloads el documento de migración de TPS a SQL se basa en esta técnica para modificar el DCT.
Deshabilitar el PopUp en un control TEXT
Simplemente asignar la Key MouseRight al control TEXT
Desplegar una opción de menú desde un botón
Pónle un hotkey al menu, por ejemplo:
Menu Text: &Browse
Luego en el botón
PRESSKEY(AltB)
Obtener los diferentes elementos del path
Global Embeds - Inside Global Map
Include('clib.clw')
En el embed:
X# = FnSplit(LOC:Path,LOC:Drive,LOC:Dir,LOC:File,LOC:Ext) DISPLAY
Las variables locales son todas CSTRING(256) Por ejemplo para saber solo el nombre del archivo se usaria LOC:File & LOC:Ext
Conversión de Archivos
1) Tenés que tener un diccionario aparte, en principio vacio. Yo lo llamo conversiones.dct.
2) Abrís ambos DCT. Te parás en la definición que querés convertir y apretás "copiar". Te parás en el conversiones.dct y apretás "pegar". A este archivo pegado lo renombrás con la fecha (por ejemplo, si mi archivo a convertir se llama CLIENTES, en conversiones lo pego como CLIENTES_ 050328).
3) Hacés todos los cambios que necesites en la definición de tu archivos CLIENTES. Acá ya podés recompilar todo tu sistema.
4) Cerrá tu diccionario, dejá abierto Conversiones.dct. Seleccioná "convert" el archivo, te pide el path al archivo y primero te hace un "browse" sobre el mismo, de ahí seleccionás el menú "File", "Convert File" y te abre una ventanita.
5) Acá, en target dictionary elegís tu DCT, el que tiene la definición ahora modificada (Acordate que estás en el conversiones.dct).
6) En target structure, buscás en tu DCT el nombre del archivo convertido.
7) En Generated source, tipeá algo que tenga sentido: yo voy numerando mis programas conversores, así que voy poniendo algo como CONV0001, CONV0002, etc. Fijate bien el path donde lo metés.
8) Si no salió solo, salí del browser, cerrá todos los DCT.
9) Andá por "File", "Change directory" y cambiate al directorio donde grabaste el fuente del conversor. Yo uso un subdirectorio "pasa".
10) Andá a "File", "Open", elegí "Clarion source", y abrí el programa conversor.
11) Andá a "Project", "Set" y seleccioná el archivo de proyecto que tiene el mismo nombre que el fuente del conversor.
12) Editale lo que quieras al programa conversor. Acá por ejemplo a veces cambio el directorio de salida, o ajusto la ventana de display, le pongo mensajes o hago cualquier asignación que sea necesaria.
13) Compilá, correlo y listo. Te queda un ejecutable autónomo que podés correr en el cliente llamándolo a mano, desde un instalador o desde tu propia aplicación.
14) Ventaja adicional: Si tu cliente guarda backups de los archivos, y por la circunstancia que sea tiene que reprocesar información de un archivo viejo, se pueden correr todos los programas de conversión que corresponden a ese archivo, en secuencia, para llevar su formato al actual.
15) Otra ventaja: este método sirve para cualquier base ISAM, para SQL hay que hacer algunos pasos más pero básicamente se puede hacer lo mismo.
Finalmente, te digo que esto lleva muchísimo más tiempo explicarlo y aprenderlo que hacerlo. Cuando te digo que todo esto lleva 1 minuto, es en serio, es lo que lleva cuando ya sabés cómo hacerlo de memoria.
Una acotación, con este método te van quedando en el conversiones.dct todas las definiciones históricas de tus archivos, algo que puede llegar a ser muy últil, mientras que en tu dct de "trabajo" sólo está la definición "actual" y no hay nada que haga ruido.
Saludos, Jorge A. Lavera
Encriptación de TPS
Sobre el Owner Name y su seguridad o capacidad de encriptación depende mas que nada del planeamiento que a la herramienta en sí misma
Caso 1 : Owner Name : " Tu palabra clave "
Este caso es el mas vulnerable de todos no se demora mas de una hora en buscarlo y encontrarlo a simple vista , con la tecnología adecuada son solo minutos.
Caso 2 : Owner Name : " ©}êô?Ñé? " ( Carcateres ALT + xxxx)
Este caso ya comienza a complicar el nivel de búsqueda dentro del exe porque no se encuentra relación y lleva a la confusión con los otros caracteres dentro del exe y todos los hexadecimales, pero es vulnerable.
Caso 3 : Owner Name : " VGLO:xxxxxxxxxxxxxxx"
Aca el valor del Owler Name está dentro de una variable global, esa variable global puede traer la informacion desde otro lado , archivo , otra variable,etc. Este método es el mas complicado de todos para aquel que quiera romper el TPS.
Para mi el mejor caso de seguridad en TPS's es el 3, por el nivel de dificultad que presenta y ante un intento de vulnerabilidad por fuerza bruta siempre devuelve un valor 0. Es muy importante tener bien documentado lo realizado utilizando el caso 3 porque de perder la información, el diseño, etc. el mismo desarrollador puede quedar preso y victima de su propia seguridad
Estos son lo niveles que se pueden manejar desde el IDE de desarrollo de Clarion, pero tambien se le pueden agregar elementos externos.
Por ejemplo utilizando el Caso 3 + algún compactador de exes ( ASPACK ) realmente se hace casi imposible encontrar las cabeceras de definición de las tablas , etc.etc. .
Remarco de lo CASI , porque se puede vulnerar, pero ahi ya comienzan a medir otros parámetros como ser el costo/beneficio de invertirle horas y horas o días y días a ese intento de poder vulnerar el archivo , etc, etc, etc. el cual también está relacionado al hardware y su capacidad de procesamiento , etc.
Espero que les sirva.
Ing. Fabian Coria
Consultor Seguridad Informatica, Capital Soft
Diferir la carga de un browse ABC
Los browse ABC se cargan automáticamente al abrir la ventana. Pero muchas veces se desea controlar el momento de la carga porque se utilizan filtros que deben ser ingresados por el usuario (por ejemplo, un rango de fechas). Es un tema particularmente importante cuando se trata de browses “file loaded”, o cuando se trabaja sobre MySQL (al utilizar este motor todos los browse son “file loaded”).
Para diferir la carga de un browse ABC hay dos posiblidades.
A. Mediante BrowseClass.ActiveInvisible
Esta técnica consiste en “esconder” el ListBox mediante HIDE y apagar la propiedad BrowseClass.ActiveInvisible. Cuando se quiere cargar el browse se quita la propiedad HIDE al ListBox.
Los pasos a seguir son los siguientes:
1. En el Window Formatter encender la propiedad HIDE (o utilizar el comando HIDE en el código del procedimiento) para el control correspondiente al ListBox del browse.
2. En WindowManager.Init, con prioridad 8150, agregar el siguiente código:
BRW1.ActiveInvisible = False !BRW1 es el nombre del objeto Browse
3. Cuando se quiera cargar y mostrar el browse, agregar el siguiente código:
?Browse:1{PROP:Hide} = False !?Browse:1 es el control del ListBox ThisWindow.Reset(True)
B. Derivando la BrowseClass
Esta técnica tiene la ventaja de que no es necesario que el ListBox esté oculto. Consiste en agregar una nueva propiedad a la clase BrowseClass que controle cuándo se desea cargar el browse. Según el estado de esta propiedad se omite o se ejecuta el método BrowseClass.ResetQueue.
Los pasos a seguir son los siguientes:
1. En BrowseBoxBehavior, en la solapa Classes, encender la opción “Derive?”
2. En New Class Properties agregar una propiedad de tipo BYTE. Para seguir con la nomenclatura en inglés del resto de la clase, la llamaremos “WaitToFill”
Property Name: WaitToFill Property Type: BYTE
3. En WindowManager.Init, con prioridad 8150, agregar el siguiente código:
BRW1.WaitToFill = True !BRW1 es el nombre del objeto Browse
4. En BrowseClass.ResetQueue, con prioridad 2500 (antes del ParentCall), agregar el siguiente código:
IF SELF.WaitToFill RETURN END
5. Cuando se quiera cargar y mostrar el browse, agregar el siguiente código:
BRW1.WaitToFill = False !BRW1 es el nombre del objeto browse ThisWindow.Reset(True)
Si bien esto mismo se podría lograr con una variable, con una nueva propiedad se puede administrar más de un browse en el mismo procedimiento, aunque tengan diferente comportamiento.
Otras posibilidades
Como solución elemental esta técnica es suficiente. Una solución más completa sería derivar directamente la clase BrowseClass y utilizar la clase derivada en lugar de la clase original. El método ResetQueue de la nueva clase haría su propia omisión en base al valor de la propiedad WaitToFill. También se podría agregar un método para encender y apagar la propiedad WaitToFill.
La solución final consistiría en escribir un template que se encargue de implementar la técnica.
Daniel Ruzo (amazingGUI)
Identificación ÚNICA
En ocasiones el campo de identificación de nuestros archivos no son suficiente con que éste sea autonumerado de forma secuencial. Por ejemplo tener un pedido en un archivo y los productos del pedido en otro archivo, podría ocurrir que ambos archivos sean reemplazados por backups de diferentes fechas (ej. pedidos mas viejo que item) asi cuando abrimos un nuevo pedido éste ya poseerá productos. Otro ejemplo es que tengamos varias bases distribuidas en diferentes máquinas (no en red) y éstas se vuelcan sobre un solo grupo de archivos via internet (o cualquier otro medio) si son autonumeradas existirán mas de una ID con el mismo número. Para solucionar esto pruebe lo siguiente:
En el Campo del archivo que sirve de Identificación Ej. ClienteNumero colocar en INITIAL VALUE colocar la función GeneraID()
Crear la función GeneraID
loc:IDU STRING(20) CODE !Identificación Única loc:IDU = FORMAT(TODAY(),@d11) & FORMAT(CLOCK(),@t5) & FORMAT(RANDOM(0,999),@n03) RETURN loc:IDU
un saludo DIPS
Parámetros en procedimientos llamados en threads nuevos
Si quiero mandar parámetros a un procedimiento llamado con START (es decir, en un thread nuevo), puedo mandar solo hasta 3 parámetros y todos deben estar definidos como string en el prototipo del procedimiento. Internamente puedo convertir esos parámetros al tipo que necesito en variables locales al procedimiento.
Meses, días, botones en Español
En el main, en el embed "Initialize the procedure" colocar:
LOCALE('CLAMONTH','Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Agosto,Septiembre,Octubre,Noviembre,Diciembre') LOCALE('CLAMON','Ene,Feb,Mar,Abr,May,Jun,Jul,Ago,Sep,Oct,Nov,Dic') LOCALE('CLACOLSEQ','AÁÄÅÆaàáâäåæBbCÇcçDdEÉeèéêëFfGgHhIÍiìíîïJjKkLlMmNnÑñOÓÖoòóôöPpQqRrSsßTtUÚÜuùúûüVvWwXxYyÿZz') LOCALE('CLABUTTON','Aceptar,&Sí,&No,&Abortar,&Reintentar,&Ignorar,Cancelar,&Ayuda') LOCALE('CLACASE','ÄÁÅÆÇÉÍÑÓÖÚÜ,äáåæçéíñóöúü') LOCALE('CLAAMPM','a.m.,p.m.')
Consultas SQL que en el Motor corren rapido pero en Clarion NO
Un truco fácil pero funcional (sólo válido para SQL Server).
Suponiendo que la instrucción original sea:
Select P.Codigo, P.Descripcion, K.Entradas, K.Salidas, K.Valor_Entradas, K.Valor_Salidas From Productos P Inner Join Kardex K On P.Id_Producto = K.Id_Producto Where P.Codigo = 'AAA001' And K.Fecha = '01-10-2006' Order By K.Fecha, P.Codigo
Como CLARION genera un cursor puede que en ocaciones dicho query sea muy pesado y el cursor tarde mucho en resolverlo, para ello simplemente agrega lo siguiente:
Select P.Codigo, P.Descripcion, K.Entradas, K.Salidas, K.Valor_Entradas, K.Valor_Salidas Into #TempTable From Productos P Inner Join Kardex K On P.Id_Producto = K.Id_Producto Where P.Codigo = 'AAA001' And K.Fecha = '01-10-2006' Order By K.Fecha, P.Codigo Select * From #TempTable
De esta manera todo se realiza en el servidor y solo va a devolver los datos ya procesados en el #TempTable
Este truco se lo agradezco a mi amigo Juan Manuel Medina.
Javier A. Junca Barreto. (SICyA Software Ltda.)
Exportar fechas a Excel
Para exportar campos fecha a Excel hay que mandarlos con format(campo, @d01b), de lo contrario Excel interpreta aleatoriamente meses como días, días como meses, etc.
De todas maneras, que el mecanismo comentado arriba funcione depende de la configuración de Excel. Otra opción es usar la función “date” de Excel, y enviar por separado año, mes y día. Por ejemplo:
MSExcel1.ExecFunction('A1', '=date(' & year(FechaClarion) & ',' & month(FechaClarion) & ',' & day(FechaClarion) & ')')