bin
Open a command prompt type "mysql -u root -p" then hit
enter again when it requests a password.
You should now have access to the mysql monitor...
type the following at the prompts...
mysql>use mysql
mysql>select user, host, password from user;
# Now you should see that u have 3 entries
+-----------------------------+
| user | host | password |
+-----------------------------+
| root | localhost | |
| root | 127.0.0.1 | |
| | localhost | |
+-----------------------------+
mysql> delete from user where host="localhost" and user="";
mysql> update user set password=password("xxxx") where user="root";
mysql> flush tables;
mysql> flush privileges;
mysql> quit
-----
Copiar base de datos al cambiar de servidor
Si pasas tablas MyISAM, simplemente copias los archivos
tabla1.frm
tabla1.myd
tabla1.myi
Para las tablas inno_db
Solo esta el archivo
tabla2.frm
Ya que los datos de la misma se encuentran dentro de los archivos
ibdata1 ib_logfile0 y ib_logfile1
Si tenemos la carpeta data y estos 3 archivos:
ibdata1 ib_logfile0 e ib_logfile1
Detienes el Servicio
-> Panel de Control -> Servicios -> MySQL ->Stop
Copias tu antigua carpeta data y los archivos ibdata1 ib_logfile0 y ib_logfile1
en su respectiva carpeta.
Das click derecho al archivo ib_logfile0 propiedades y anotas cuantos megas te marca
Ahora dentro de la carpeta donde esta instalado MySQL Server esta el archivo
my.ini
Editas este archivo y debajo de la línea
default-character-set=latin1
Y la dejas asi
en la seccion [mysqld]
innodb_force_recovery=6
default-character-set=latin1
Luego vas hasta la línea donde dice
innodb_log_file_size=130M
Y colocas el tamaño en megas que te salió cuando le diste propiedades al archivo ib_logfile0
Te vas a Servicios y Start nuevamente a MySQL
Listo.
Restaurada la Base de datos.
Ahora que ya la tienes no olvides crear un MySQLDump o sea generar el archivo *.sql
para tu respaldo.
Clarion
lunes, 12 de marzo de 2018
Cadena de conección Firebird
Local database Firebird
DRIVER=Firebird/InterBase(r) driver;UID=SYSDBA;PWD=masterkey;
DBNAME=C:\database\myData.fdb;
Specifying TCP/IP port number
DRIVER=Firebird/InterBase(r) driver;UID=SYSDBA;PWD=masterkey;
DBNAME=192.168.0.1/3051:C:\database\myData.fdb;
Ejemplo:
cadenaConexion = & |
'DRIVER=Firebird/InterBase(r) driver;' & |
'UID=SYSDBA;' & |
'PWD=VIC12345;' & |
'DBNAME=C:\FirebirdClarion\CLIENTES.fdb;'
jueves, 8 de marzo de 2018
Using SQL filters in browses By Arnor Baldvinsson
Transactions {PROP:Alias} = 'TRN'
Cusip {PROP:Alias} = 'CUS'
Products {PROP:Alias} = 'PRO'
This means you can refer to columns in the Transactions table as 'TRN.' in your SQL filter. The alias must be set before the VIEW structure is opened. I normally do it in ThisWindow.Init, before the files are opened. Constructing the SQL filter can be SQL dialect dependent, so I'm not going to dig into that here, but below is code that I use to filter a browse I'm working on right now.
ResetBrowseFilter ROUTINE
DATA
SQL CSTRING(4097)
CODE
SQL = '' !! Construct the filter here
!! Use either this method:
TradeBrowse.View {PROP:SQLFilter} = SQL
!! Or this one:
TradeBrowse.SetFilter('SQL(' & SQL & ')')
TradeBrowse.ResetFromView
That's it! The PROP:SQLFilter can be used directly on any view in both ABC and Legacy applications. The documentation used to claim that PROP:SQLFilter is deprecated, but the documentation for Clarion 8 only states that it should be replaced with the more versatile SQL() function. PROP:SQLFilter was deprecated more than 10 years ago and seems now to be un-deprecated.
Código útil De ClarionWiki
Código útil
De ClarionWiki
En esta sección se incluyen rutinas de código que nos resultaron útiles en más de una ocasión.
Alguien ha posteado una pregunta sobre como Filtrar arboles, yo en lo personal he dejado de usar arboles, por que son bastante lentos y deficientes, pero en algunas aplicaciones es bastante util, de manera que comparto la experiencia y les subo una pequeña explicacion de como filtrar Arboles (generados con el template Relacion Tree), con filtros en runtime que son complejos, y que no podemos hacer con un simple SetFilter(), ya que no existe esto en la propiedad.
Saludos y espero les sirva.
Como filtrar un Tree con solamente tablas:
Dentro del Template para armar los arboles, tiene dentro de Primary y Secondary files la opcion de filtrar, agregando un filtro, pero muchas veces dicho filtro no es tan simple de armar, o necesitamos estarlo cambiando en runtime.
Aqui les presento un caso y como su solucion, esperando que les sirva...
Tengo tres tablas:
Abuelos Padres Hijos
Y en la tabla hijos, tengo un campo que define en que nivel de escuela en que va cada hijo: Pre-escolar, Primaria, Secundaria, Bachillerato, Universidad, PostGrado, Maestria y Doctorado.
En la ventana requiero que el usuario pueda filtrar por el nivel escolar, pero con las siguientes condiciones:
1.- El usuario puede seleccionar si quiere ver cualquier combinacion de estudios, es decir, que pueda ver los de primaria y universidad solamente o todos, o ninguno... 2.- Si selecciona un filtro donde resulte que un abuelo o padre no tiene hijos que cumplan la condicion, entonces no debe mostrarnos a esos abuelos y padres...
Considerando que el Tree template, muestra siempre los niveles de acuerdo al filtro que se arma, pues podria convertirse algo engorroso, estar cambiando el filtro, ya que no es una propiedad del relacion tree, tal como sucede con el browse class, sino que el tree filtra a base de If's interpuestos al llenar el queue...
Usando el template y solo poniendo los filtros de NIVELESCOLAR = LOC:MINIVEL, solo mostraria los hijos de un nivel, asi que este metodo no nos servira...
La idea seria entonces que como filtro usaremos una condicion matematica, si la condicion es Verdadera, entonces mostrara el hijo, de lo contrario no lo mostrara.
Asi que con el comando EVALUATE, evaluaremos un filtro, dentro del secondary files, opcion filter, si el filtro es bueno (igual a 1), entonces nos mostrara la info de lo contrario no...
Por ejemplo:
En el secondary files en buscamos la tabla de Hijos y le escribimos en el filtro:
0<EVALUATE(Filtro) ! Si el filtro es valido, entonces nos mostrara el resultado, de lo contrario no...
Declaramos un check box por cada cada condicion, con su respectiva variable: Preescolar, Primaria, Secundaria, etc... que sea tipo byte, con valor true = 1 y valor false = 0
! En el acepted del check box Preescolar... y cada condicion... Do Filtrar Display
! En procedure routines Filtrar Routine
Para hacer debemos entender como se llena el queue del tree, basicamente, el tree, tantas vueltas como combinaciones de nuestra estructura tengamos, es decir, suponiendo que tenemos 3 abuelos, 3 padres, con 3 hijos cada uno, lo que hace el template es:
Por cada Abuelo Barre (con Loop) la tabla Padres por completo 1 vez y selecciono los que me corresponden y por cada padre barro la tabla hijos (con Loop) por completo y selecciono los que me corresponden...
Entonces esta estructura daria 3 x 3 x 3 = 27 vueltas. (Ahora imaginen lo que tarda en entre tablas de 10000 registros...)
Bueno, siguiendo esta idea, entonces lo que necesitamos, es interceptar cada vuelta en el punto donde no queremos que se ingrese al queue y hacer un break.
Conociendo esto buscamos en nuestros embeds, embedido llamado:
RelacionTree After Next Primary File y RelationTree After Next Secondary File...
Aqui buscamos el archivo que requerimos excluir en algunos casos, que es Padres e Hijos, le escribimos en el caso de Padres:
PAD:AbueloId = ABU:AbueloId If Access:Padres.Fetch(FK AbueloId) ! Si no existen padres que sean dependientes de este abuelo
Y el caso de los hijos repetimos la condicion:
HIJ:PadreId = PAD:PadreId If Access:Hijos.Fetch(FK PadreId) ! Si no existen hijos que sean dependientes de este abuelo
El efecto sera que no nos mostrara aquellos padres y abuelos que no tengan hijos.
Si requerimos algo mas complejo podemos usar una vista, en vez de una Fetch a una tabla, pero obviamente se alentara esto cada vez mas y mas al llenar el Arbol.
Nota Si usamos un motor de SQL, una alternativa mucho mas eficiente es declarar un Vista en nuestro motor y diccionario que sirva de base para llenar el Arbol, con esto aumentaria considerablemente la velocidad.
Replace('Busacr','Reemplazar',Loc:miString)
Fernando Cerini
A. Before Global Includes:
1- Con Clarion (no sé si estarán estas funciones en versiones anteriores de Clarion)
En "Inside the Global map"
Fernando Cerini
El punto embebido donde se ubica es After Open the Report.
Se puede definir en el módulo en la parte de DATA el reporte necesario y mandarlo a imprimir.
En DATA SECTION
Luego, en el botón que imprime, poner el siguiente código:
1. Definir una cola con la siguiente estructura ( por ejemplo ):
4. Cargar la cola según corresponda después de abrir archivos, poniendo como último dato el código 0 y el texto 'TODOS LOS REGISTROS' y hacer un ADD(Cola,1) para que quede al principio.
5. Definir una variable global ( o local estática) que va a guardar el resultado elegido:
Para hacerlo con codigo:
En Global-embed 'Inside the Global Map':
En el embed
Para que no lea todo el contenido del archivo, sino que procese estrictamente lo mismo que muestra el browse, basta con agregar la antes del BRW1.SetQueueRecord el llamado a BRW1.ValidateRecord().
Primero agregar los iconos al Project
En el Codigo:
primer campo: nombre
segundo campo: entro, picture @p p, iconized, transparente
tercer campo: salio, picture @p p, iconized, transparente
En el init de la pantalla:
Luego podés hacer lo siguiente
Adrian Gallegos - Mega Sistemas S.R.L.
Otra opción: Días Hábiles del mes - Rutina que quita los días sábados y domingos del mes
CONTAR_DIAS ROUTINE
Fernando Cerini
Con SQL
La sugerencia de Carlos es muy buena. Si estás usando SQL o drivers ODBC, la otra opción es preguntarle la fecha al motor de base de datos.
La forma genérica de hacerlo es:
Posteado por Diego Sánchez al foro.
Fernando Cerini
Para mas detalles ver el documento sobre SQL Embebido (Templates Clarion)
También recomiendo leer el help "MSSQL Accelerator Calling a Stored Procedure". Ahi está explicado además el uso de valores de retorno y parámetros de salida.
Fernando Cerini
Además es muy rápido, ideal para exportaciones.
En Global - Inside Global map:
Por ejemplo para guardar el contenido de un control Text
Luego cierra la ventana, vuelve a entrar y vas a encontrar un evento timer de la ventana en los embeds.
Window Events --> Timer
Si Hora2 es del dia siguiente, la cuenta seria:
Crea una funcion que tenga un parametro (numero a verificar) y retorne el digito verificador correspondiente ejem: DigitoV5(LONG xNumero),BYTE
Crea una funcion cuyo parametro es el numero a verificar y retorne el digito verificador. Ej. DigitoV(STRING xNumero),BYTE
Gracias Fernando Cerini
Nota: El mecanismo descrito es "confiable" sólo para aplicaciones que usan TPS y en modo single-user. Para Multiusuario y SQL hay que tener cuidado con las lecturas simultáneas del último valor. También puede simplificarse el "if ... then ... else ..." como "MAS:Id = MAS:Id +1" (si es cero, será 1, no hace falta chequearlo)
Como desde los otros procedimientos no existen los use del los items de menu (o sea ?menuitem) lo que hay que hacer es ponerles un numero a cada uno. Esto se logra poniendolos de esta manera ?use, numero en la definicion del menu.
Fernando Cerini
Primero hay que activar la alerta de la tecla esc en el INIT de la ventana
Ruben Garcia (DiPS)
Mario A. Wojcik
Paso 1 En el punto embebido del FRAME (windowsManager init (priority 6100) o sea antes que se abra ningun archivo, Insertar el siguiente codigo:
Listo, el archivo de control de transacciones ya esta creado y sin dudas funcionara. Para que todo quede completo deberiamos hacer:
Para verificar que exista realmente el archivo .TCF podemos usar en cualquier punto de la aplicación lo siguiente:
Poner un string variable en pantalla y en un boton:
Loc:Tcf = SEND(CLIENTES, 'TCF') DISPLAY()
Contenido
- Como filtrar un Tree
- Reemplazar caracteres en un string
- Contar los threads abiertos
- Permitir solo una instancia del EXE en ejecución
- Cambiar los atributos de un archivo
- Crear directorios
- Modificar la posicion de un informe
- Generar un informe a partir de una cola en memoria
- Proceso BATCH que funcione sincronizado con el TIMER de la ventana.
- Drop Combo a mano, con opción a todos y recuerdo de último elegido
- En un report, poner los totales en otra página
- En un report, cambiar el color de un campo según alguna condición
- Cerrar todas las ventanas abiertas
- Abrir cualquier archivo con ShellExecute
- Abrir una pagina WEB con ShellExecute
- Convertir un LONG a un String Binario
- Convertir un string binario a LONG
- Copiar la QUEUE (solo lo que se esta viendo) de un browse a Excel
- Copiar el VIEW (cuidado: se lee todo) de un browse a Excel
- Mostrar iconos en un listbox desde una queue
- Calcular Días Hábiles
- Último día del mes y cantidad de días (Lunes, Martes, etc) entre 2 Fechas
- Fecha en Español (por ejemplo en el Frame)
- Obtener la fecha del server
- Llamar a un Stored Procedure
- Encriptación básica
- Generar un archivo de Texto a máxima velocidad
- Efecto BLINK en un campo
- Función para calcular dígito verificador en CUIT
- Validar Email
- Restar Horas
- Digito Verificador para 5 dígitos
- Digito Verificador para Cualquier Longitud
- Autoincremento Manual
- Deshabilitar menu desde cualquier procedimiento
- Formatear fechas en SQL
- Anular Tecla Escape
- Convertir Número Hexadecimal a Binario
- Transacciones, TCF, Logout, Commit, Rollback
Como filtrar un Tree
Una tecnica de Saul Perez Quezada para filtrar Arboles (generados con el template Relacion Tree), con filtros en runtime que son complejos, y que no podemos hacer con un simple SetFilter().Alguien ha posteado una pregunta sobre como Filtrar arboles, yo en lo personal he dejado de usar arboles, por que son bastante lentos y deficientes, pero en algunas aplicaciones es bastante util, de manera que comparto la experiencia y les subo una pequeña explicacion de como filtrar Arboles (generados con el template Relacion Tree), con filtros en runtime que son complejos, y que no podemos hacer con un simple SetFilter(), ya que no existe esto en la propiedad.
Saludos y espero les sirva.
Como filtrar un Tree con solamente tablas:
Dentro del Template para armar los arboles, tiene dentro de Primary y Secondary files la opcion de filtrar, agregando un filtro, pero muchas veces dicho filtro no es tan simple de armar, o necesitamos estarlo cambiando en runtime.
Aqui les presento un caso y como su solucion, esperando que les sirva...
Tengo tres tablas:
Abuelos Padres Hijos
Y en la tabla hijos, tengo un campo que define en que nivel de escuela en que va cada hijo: Pre-escolar, Primaria, Secundaria, Bachillerato, Universidad, PostGrado, Maestria y Doctorado.
En la ventana requiero que el usuario pueda filtrar por el nivel escolar, pero con las siguientes condiciones:
1.- El usuario puede seleccionar si quiere ver cualquier combinacion de estudios, es decir, que pueda ver los de primaria y universidad solamente o todos, o ninguno... 2.- Si selecciona un filtro donde resulte que un abuelo o padre no tiene hijos que cumplan la condicion, entonces no debe mostrarnos a esos abuelos y padres...
Considerando que el Tree template, muestra siempre los niveles de acuerdo al filtro que se arma, pues podria convertirse algo engorroso, estar cambiando el filtro, ya que no es una propiedad del relacion tree, tal como sucede con el browse class, sino que el tree filtra a base de If's interpuestos al llenar el queue...
Usando el template y solo poniendo los filtros de NIVELESCOLAR = LOC:MINIVEL, solo mostraria los hijos de un nivel, asi que este metodo no nos servira...
La idea seria entonces que como filtro usaremos una condicion matematica, si la condicion es Verdadera, entonces mostrara el hijo, de lo contrario no lo mostrara.
Asi que con el comando EVALUATE, evaluaremos un filtro, dentro del secondary files, opcion filter, si el filtro es bueno (igual a 1), entonces nos mostrara la info de lo contrario no...
Por ejemplo:
En el secondary files en buscamos la tabla de Hijos y le escribimos en el filtro:
0<EVALUATE(Filtro) ! Si el filtro es valido, entonces nos mostrara el resultado, de lo contrario no...
Declaramos un check box por cada cada condicion, con su respectiva variable: Preescolar, Primaria, Secundaria, etc... que sea tipo byte, con valor true = 1 y valor false = 0
! En el acepted del check box Preescolar... y cada condicion... Do Filtrar Display
! En procedure routines Filtrar Routine
Clear(Filtro) ! Variable para filtrar If Preescolar Then Filtro = 'HIJ:NIVELESCOLAR=Preescolar' .. If Filtro And Primaria Filtro = Clip(Filtro) & ' And HIJ:NIVELESCOLAR=Primaria' ElsIf Filtro And Primaria Filtro = 'HIJ:NIVELESCOLAR=Primaria' End ! Esto se repite con todas las condiciones... ... ... ! Despues de armar el filtro, refrescar el browse... DO REL4::RefreshTree ! Esta rutina la genera el template, se debe buscar en los modulos su nombre correcto. POST(EVENT:NewSelection,?RelTree) ExitHasta ahora, lo que se ha logrado es que cuando el usuario seleccione un tipo nivel escolar, nos mostrara o no los hijos, pero resulta, que ahora lo que necesito es que solo me muestre los abuelos que tienen nietos, y los padres que tienen hijos, es entonces por que no tiene caso que el arbol se llene con Mil abuelos, si solo 30 de ellos tienen nietos...
Para hacer debemos entender como se llena el queue del tree, basicamente, el tree, tantas vueltas como combinaciones de nuestra estructura tengamos, es decir, suponiendo que tenemos 3 abuelos, 3 padres, con 3 hijos cada uno, lo que hace el template es:
Por cada Abuelo Barre (con Loop) la tabla Padres por completo 1 vez y selecciono los que me corresponden y por cada padre barro la tabla hijos (con Loop) por completo y selecciono los que me corresponden...
Entonces esta estructura daria 3 x 3 x 3 = 27 vueltas. (Ahora imaginen lo que tarda en entre tablas de 10000 registros...)
Bueno, siguiendo esta idea, entonces lo que necesitamos, es interceptar cada vuelta en el punto donde no queremos que se ingrese al queue y hacer un break.
Conociendo esto buscamos en nuestros embeds, embedido llamado:
RelacionTree After Next Primary File y RelationTree After Next Secondary File...
Aqui buscamos el archivo que requerimos excluir en algunos casos, que es Padres e Hijos, le escribimos en el caso de Padres:
PAD:AbueloId = ABU:AbueloId If Access:Padres.Fetch(FK AbueloId) ! Si no existen padres que sean dependientes de este abuelo
Break ! hacer el break al Loop del armado del tree...End
Y el caso de los hijos repetimos la condicion:
HIJ:PadreId = PAD:PadreId If Access:Hijos.Fetch(FK PadreId) ! Si no existen hijos que sean dependientes de este abuelo
Break ! hacer el break al Loop del armado del tree...End
El efecto sera que no nos mostrara aquellos padres y abuelos que no tengan hijos.
Si requerimos algo mas complejo podemos usar una vista, en vez de una Fetch a una tabla, pero obviamente se alentara esto cada vez mas y mas al llenar el Arbol.
Nota Si usamos un motor de SQL, una alternativa mucho mas eficiente es declarar un Vista en nuestro motor y diccionario que sirva de base para llenar el Arbol, con esto aumentaria considerablemente la velocidad.
Reemplazar caracteres en un string
Una rutina generica para reemplazar todas las instancias de un string dentro de otroReplace PROCEDURE(string find,string replace,*cstring into)
Locate LONG,AUTO
CODE
IF UPPER(find)<>UPPER(replace)
LOOP
Locate = INSTRING(UPPER(find),UPPER(into),1,1)
IF ~Locate THEN RETURN .
into = SUB(into,1,Locate-1) & replace &SUB(into,Locate+LEN(find),LEN(into))
END
END
Para llamarlo: Replace('Busacr','Reemplazar',Loc:miString)
Fernando Cerini
Contar los threads abiertos
Este código fue posteado por Jeff SlarveChildThreadCount Procedure!,Long Ndx Long Count Long T &Window W &Window Code
Count = 0
SetTarget
T &= System{PROP:Target}
Loop Ndx = 1 to MaxThreads
SetTarget(,Ndx)
W &= System{PROP:Target}
If NOT W &= T
Count += 1
end
end
SetTarget
Return Count
Permitir solo una instancia del EXE en ejecucion
En Inside the global map:INCLUDE('CWUTIL.INC'),ONCE
En Global Program Setup: IF NOT BeginUnique('MiAPP.exe')
BEEP(BEEP:SystemExclamation)
YIELD()
CASE MESSAGE('El programa ya esta ejecutando..','Ho, ho...',ICON:Asterisk,BUTTON:OK,BUTTON:OK,0)
OF BUTTON:OK
HALT()
END
END
Fernando Cerini Cambiar los atributos de un archivo
(Del FAQ de SoftVelocity) 1. En Global EmbedsA. Before Global Includes:
LPCSTR EQUATE(CSTRING) DWORD EQUATE(ULONG)B. Global Data: Equates de los artributos
FILE_ATTRIBUTE_READONLY EQUATE(00000001h) FILE_ATTRIBUTE_HIDDEN EQUATE(00000002h) FILE_ATTRIBUTE_SYSTEM EQUATE(00000004h) FILE_ATTRIBUTE_ARCHIVE EQUATE(00000020h) FILE_ATTRIBUTE_NORMAL EQUATE(00000080h) FILE_ATTRIBUTE_TEMPORARY EQUATE(00000100h)C. Inside Global Map:
Module('Win32.lib')
SetFileAttributes(*CSTRING, ULONG), BOOL, RAW, PASCAL, NAME('SetFileAttributesA')
GetFileAttributes(*CSTRING), ULONG, RAW, PASCAL, NAME('GetFileAttributesA')
END
2. En el codigo: filename CSTRING(50)
filename = 'test.txt' y# = GetFileAttributes(filename) ! Leer atributos y# = SetFileAttributes(filename, FILE_ATTRIBUTE_READONLY)Fernando Cerini
Crear directorios
Hay 2 opciones:1- Con Clarion (no sé si estarán estas funciones en versiones anteriores de Clarion)
En "Inside the Global map"
Include('clib.clw')
En Local Data MiDir Cstring(256) Ret LongEn tu embed
MiDir = 'Dirtest' Ret = MkDir(MiDir)2- Con API En Global Embeds - inside global map:
MODULE()
DirAccess(*CSTRING,SHORT=0),SHORT,RAW,NAME('_access'),PROC
MkDir(*CSTRING),SHORT,RAW,NAME('_mkdir'),PROC
DirRename(*CSTRING, *CSTRING), SHORT, RAW, NAME('_rename'),PROC
RmDir(*CSTRING),SHORT,RAW,NAME('_rmdir'),PROC
END
Y en tu embebido podés poner lo siguiente: Var='O:\test' ! Ojo tiene que ser Cstring
IF DirAccess(Var)<>0 !Si no existe
MkDir(Var) !Crearlo
END
También se puede usar DirRename y RmDir para renombrar o borrar Fernando Cerini
Modificar la posicion de un informe
Este código permite modificar la posición inicial vertical y horizontal de un informe. Es útil cuando trabajamos con un formulario preimpreso o una hoja membretada y necesitamos desplazar la impresión sin necesidad de reconfigurar el programa.El punto embebido donde se ubica es After Open the Report.
!=====================================================
! MODIFICAR LA POSICION DEL INFORME
! El pie de página modificarlo sólo si se usa:
! Por ejemplo se pone ahí el Nro de página o algo así.
! No es necesario cambiar los atributos de las bandas
! de detalle adicionales que se usan en el informe.
!-----------------------------------------------------
DesplazamientoX = getini('Parametros',|
'DesplazamientoX',,'.\prog.ini')
DesplazamientoY = getini('Parametros',|
'DesplazamientoY',,'.\prog.ini')
if DesplazamientoX or DesplazamientoY then
SETTARGET(Report)
x# = report{prop:Xpos}
y# = report{prop:Ypos}
x# += DesplazamientoX
y# += DesplazamientoY
target{prop:Xpos} = x#
target{prop:Ypos} = y#
settarget
SETTARGET(Report,?Encabezado)
x# = ?Encabezado{prop:Xpos}
y# = ?Encabezado{prop:Ypos}
x# += DesplazamientoX
y# += DesplazamientoY
?Encabezado{prop:Xpos} = x#
?Encabezado{prop:Ypos} = y#
settarget
SETTARGET(Report,?PiePagina)
x# = ?PiePagina{prop:Xpos}
y# = ?PiePagina{prop:Ypos}
x# += DesplazamientoX
y# += DesplazamientoY
?PiePagina{prop:Xpos} = x#
?PiePagina{prop:Ypos} = y#
SETTARGET
end
Generar un informe a partir de una cola en memoria
(Sin pasar por el template)Se puede definir en el módulo en la parte de DATA el reporte necesario y mandarlo a imprimir.
En DATA SECTION
WMFQue QUEUE
PageImage STRING(64)
END
ReportRunDate LONG
ReportRunTime LONG
!!> Report (portrait)
Report REPORT,AT(1000,2000,6000,7000),THOUS,PRE(RPT),FONT('Arial',10)
HEADER,AT(1000,1000,6000,1000)
END
Detail DETAIL
END
FOOTER,AT(1000,9000,6000,1000)
END
FORM,AT(1000,1000,6000,9000)
END
END
Luego, en el botón que imprime, poner el siguiente código:
ReportRunDate = today()
ReportRunDate = today()
ReportRunTime = clock()
OPEN(Report)
LOOP x# = 1 to records(ColaError)
get(ColaError,x#)
PRINT(rpt:DetalleUno)
END
ENDPAGE(Report)
ReportPreview(WMFQue)
IF GlobalResponse = RequestCompleted
Report{PROP:FlushPreview} = True
END
CLOSE(Report)
FREE(WMFQue)
Este proceso usa el Print Preview de Clarion para visualizar el informe, por consiguiente, la apariencia es totalmente normal para el usuario Proceso BATCH que funcione sincronizado con el TIMER de la ventana.
En un proceso BATCH hecho a mano se puede operar en forma similar al report o al proccess, para eso hay que definir una pantalla que tenga el atributo TIMER (un valor de 1 es suficiente). El código fuente quedaría de la siguiente manera:open(PantaTrabaja)
Accept
case event()
of event:openwindow
Display()
! Abrir pantalla, Abrir archivos, hacer el set
! Inicial
of event:timer
?Mensaje{prop:text} = 'Procesando...'
display(?Mensaje)
loop 2 times
next(archivo)
if errorcode() then
FinArchivo = true
Break
end
! HACER AQUÍ ALGÚN PROCESO..
end
of event:closewindow
Setcursor()
Close(PantaTrabaja)
Break
End!case
case field()
of ?BotonCancelar
if event() = event:accepted then
message('Proceso cancelado por el usuario.')
post(event:closewindow)
end
End
end!accept
Drop Combo a mano, con opción a todos y recuerdo de último elegido
En muchos de mis informes, cuando pido parámetros asumo lo siguiente, por ejemplo: Si el código de cliente es igual a 0 (cero) se imprimen todos los clientes, sino, se imprime uno solo. Este código permite armar lo mismo con un DropCombo, agregando la opción TODOS LOS XXXX (código Cero).1. Definir una cola con la siguiente estructura ( por ejemplo ):
Cola QUEUE,PRE(col)
descri STRING(20)
codigo SHORT
END
2. Definir una variable local llamada por ejemplo Combo1 STRING(20)3. En la pantalla definir una drop combo a mano, en el campo FROM poner el nombre de la COLA, y en el USE poner Combo1 ( no poner ?Combo1 )
4. Cargar la cola según corresponda después de abrir archivos, poniendo como último dato el código 0 y el texto 'TODOS LOS REGISTROS' y hacer un ADD(Cola,1) para que quede al principio.
5. Definir una variable global ( o local estática) que va a guardar el resultado elegido:
Glo:codigo short6. En el evento open window, ANTES de abrir la ventana va el siguiente código
loop x# = 1 to records(Cola)
get(Cola,x#)
if col:codigo = glo:codigo then break.
end
Combo1 = col:descri
?Combo1{prop:selected} = x#
7. Al hacer esto, el combo se abre posicionado en el último elemento elegido o en TODOS LOS REGISTROS si es la primera vez. En un report, poner los totales en otra página
En algún informe, se puede solicitar como parámetro la opción de imprimir los totales correspondientes en una página nueva. Para ello, después de abrir el report:if glo:totpag then
settarget(report)
?TituloTotalCategoria{prop:pagebefore} = 1
end
En un report, cambiar el color de un campo según alguna condición
Si durante la impresión de un informe se desea cambiar algún atributo (tal como el color) de un campo puede hacer lo siguiente (antes de imprimir el detalle):if cl:impotota <> cl:totacalc
settarget(report)
?ARC:importe{PROP:FONTCOLOR} = COLOR:RED
settarget(ProgressWindow)
else
settarget(report)
?ARC:importe{PROP:FONTCOLOR} = COLOR:NONE
settarget(ProgressWindow)
end
print(rpt:Detalle) ! Imprimir la línea de detalle
Cerrar todas las ventanas abiertas
LOOP Thrd# = 2 TO 64 !1 es el Frame
POST(Event:CloseWindow,,Thrd#)
END
Fernando Cerini Abrir cualquier archivo con ShellExecute
Hay varios templates gratis que implementan Shellexecute, por ejemplo: http://www.sterlingdata.com/shellex.htmPara hacerlo con codigo:
En Global-embed 'Inside the Global Map':
Module('Win32.lib')
ShellExecute(Long,*CString,*CString,*CString,*CString,Short),UShort,PASCAL,RAW,NAME('ShellExecuteA')
END
En Local Data LOC:Handle LONG LOC:Op CSTRING (255) LOC:File CSTRING (255) LOC:Path CSTRING (255) LOC:Param CSTRING (255) LOC:Show LONG LOC:RetHandle LONGEn el embed
LOC:Handle = 0{PROP:Handle}
LOC:Op = 'Open'
LOC:File = 'C:\TEST.TXT'
LOC:Path = PATH()
LOC:Param = ' '
LOC:Show = 1
LOC:RetHandle = ShellExecute(LOC:Handle,LOC:Op,LOC:File,LOC:Param,LOC:Path,LOC:Show)
If LOC:Rethandle <> 0 Then
Message('Error','Error',Icon:Exclamation)
End
Fernando Cerini Abrir una pagina WEB con ShellExecute
(Ver declaraciones del API y variables en el ejemplo anterior)En el embed
LOC:Handle = 0{PROP:Handle}
LOC:Op = 'Open'
LOC:File = 'http://www.templatesclarion.com.ar'
LOC:Path = ' '
LOC:Param = ' '
LOC:Show = 1
LOC:RetHandle = ShellExecute(LOC:Handle,LOC:Op,LOC:File,LOC:Param,LOC:Path,LOC:Show)
Fernando Cerini Convertir un LONG a un String Binario
binario= !binario es CSTRING
LOOP F# = 1 TO 32
IF BAND(abinario, 1) !abinario es LONG
binario = '1' & binario
ELSE
binario = '0' & binario
END
abinario = abinario / 2
if abinario = 0 then break.
END
Fernando Cerini Convertir un string binario a LONG
Para volver del CSTRING al LONG seria simplementeX# = EVALUATE (binario & 'b')Fernando Cerini
Copiar la QUEUE (solo lo que se esta viendo) de un browse a Excel
Copiar= !Cstring de 4.000.000
LOOP C#= 1 TO BRW1.View{Prop:Fields}
!Primero una fila con los titulos
Copiar= Copiar & ?Browse:1{PropList:Header,C#} & '<9>'
END
Copiar= Copiar &'<13,10>'
LOOP F# = 1 TO RECORDS(BRW1.Q)
LOOP C#= 1 TO BRW1.View{Prop:Fields}
GET(BRW1.Q, F#)
Copiar= Copiar & FORMAT(WHAT(BRW1.Q, C#), ?Browse:1{PropList:Picture,C#}) & '<9>'
END
Copiar= Copiar &'<13,10>'
END
SETCLIPBOARD(Copiar)
Fernando Cerini Copiar el VIEW (cuidado: se lee todo) de un browse a Excel
Copiar= !CSTRNG de 4.000.000
LOOP C#= 1 TO BRW1.View{Prop:Fields}
Copiar= Copiar & ?Browse:1{PropList:Header,C#} & '<9>'
END
Copiar= Copiar &'<13,10>'
SET (BRW1.View)
LOOP
NEXT(BRW1.View)
IF ERRORCODE() THEN BREAK.
BRW1.SetQueueRecord
LOOP C#= 1 TO BRW1.View{Prop:Fields}
Copiar= Copiar & FORMAT(WHAT(BRW1.Q, C#), ?Browse:1{PropList:Picture,C#}) & '<9>'
END
Copiar= Copiar &'<13,10>'
END
SETCLIPBOARD(Copiar)
Fernando Cerini Para que no lea todo el contenido del archivo, sino que procese estrictamente lo mismo que muestra el browse, basta con agregar la antes del BRW1.SetQueueRecord el llamado a BRW1.ValidateRecord().
... LOOP NEXT(BRW1.View) IF ERRORCODE() THEN BREAK. IF BRW1.ValidateRecord() THEN CYCLE. BRW1.SetQueueRecrod ...Daniel Ruzo
Mostrar iconos en un listbox desde una queue
Ejemplo: marcar en una cola de memoria si un empleado tiene entrada y/o salidaPrimero agregar los iconos al Project
En el Codigo:
cola_empleados queue, pre(que)
nombre string(30)
entro long
entro_ico long
salio long
salio_ico long
end
En el list: primer campo: nombre
segundo campo: entro, picture @p p, iconized, transparente
tercer campo: salio, picture @p p, iconized, transparente
En el init de la pantalla:
?list{prop:iconlist,1} = '~no.ico'
?list{prop:iconlist,2} = '~si.ico'
Donde cargo la cola y la muestro: if condicion (si NO hay entrada)
que:entro_ico = 1 ! icono de no
else
que:entro_ico = 2 !icono de si
end
if condicion (si NO hay salida)
que:salio_ico = 1 ! icono de no
else
que:salio_ico = 2 !icono de si
end
! otras asignaciones
add(cola_empleados)
!
display(?list)
Fernando Cerini Calcular Días Hábiles
Ante todo necesitas una tabla de feriados, con al menos un campo llamado, por ej., diaferiado y una clave por dicho campo.Luego podés hacer lo siguiente
habiles# = 0 loop dia# = FechaInicial to FechaFinal if (Dia# % 7) = 0 then cycle. ! porque es domingo if (Dia# % 7) = 6 then cycle. ! porque es sabado ! ! busco si es feriado ! clear(feriado:record) feriado:diaferiado = dia# if access:feriado.fetch(Feriado:PorDia) = level:benign then cycle. !porque es feriado ! habiles# += 1 end!loop corridos# = fechafinal - fechainicialSi los cálculos que vas a realizar son muchos y continuos, sería conveniente que la tabla de feriados la cargues en una queue y realices las búsquedas sobre ella.
Adrian Gallegos - Mega Sistemas S.R.L.
Otra opción: Días Hábiles del mes - Rutina que quita los días sábados y domingos del mes
CONTAR_DIAS ROUTINE
LOC:DIAS = 0
tope# = 1
LOOP UNTIL DAY(LOC:FECHA) = tope#
CASE LOC:FECHA % 7
of 0 ! Domingo
LOC:FECHA = LOC:FECHA - 1
CYCLE
of 6 ! sabado
LOC:FECHA = LOC:FECHA - 1
CYCLE
ELSE
LOC:DIAS += 1
LOC:FECHA = LOC:FECHA - 1
END !CASE
END !LOOP
IF LOC:FECHA % 7 = 0 or LOC:FECHA % 7 = 6 THEN !si el primero es feriado
! nada
else
LOC:DIAS += 1
END !IF
Julio César Britez Último día del mes y cantidad de días (Lunes, Martes, etc) entre 2 Fechas
Incluye el truco de saber el último día del mes: en la parte " Date(4,1,2005)-1 " significa que le resto 1 al primer día del mes siguiente, lo cual es una forma de obtener el último día del mes actual...Loop Fecha# = Date(3,1,2005) TO (Date(4,1,2005)-1)
EXECUTE (Fecha# % 7) + 1
Domingo# +=1
Lunes# +=1
Martes# +=1
Miercoles# +=1
Jueves# +=1
Viernes# +=1
Sabado# +=1
END
END
Fernando Cerini Fecha en Español (por ejemplo en el Frame)
Si quieres que funcione independientemente de como este configurado windows, lo mejor es poner este embed, al final de WindowManager.InitEXECUTE (TODAY() % 7) + 1 Dia"= 'Domingo' Dia"= 'Lunes' Dia"= 'Martes' Dia"= 'Miercoles' Dia"= 'Jueves' Dia"= 'Viernes' Dia"= 'Sabado' END
EXECUTE (MONTH(TODAY()))
Mes" = 'Enero'
Mes" = 'Febrero'
Mes" = 'Marzo'
Mes" = 'Abril'
Mes" = 'Mayo'
Mes" = 'Junio'
Mes" = 'Julio'
Mes" = 'Agosto'
Mes" = 'Septiembre'
Mes" = 'Octubre'
Mes" = 'Noviembre'
Mes" = 'Diciembre'
END
AppFrame{Prop:StatusText,1} = CLIP(Dia") & ' ' & DAY(TODAY()) & ' de ' &
CLIP(Mes")
Fernando Cerini
Dia" = Choose(((TODAY() % 7) + 1),'Domingo','Lunes','Martes','Miercoles','Jueves','Viernes','Sabado')Javier A. Junca Barreto (SICyA Software - Colombia)
Obtener la fecha del server
Este código lee la fecha del servidor, usando el truco de crear un archivo en el servidor y leer la fecha y hora de los atributos:!Data
LOC:TMP STRING(254),STATIC
TMP FILE,DRIVER('Ascii'),CREATE,NAME(LOC:TMP)
RECORD RECORD
LIN STRING(1)
.
.
FILS QUEUE(File:queue),PRE(FIL)
END
CODE
LOC:TMP = PATH()&'\TMP'&RANDOM(10000,99999)&'.TMP'
CREATE(TMP)
IF NOT ERRORCODE()
DIRECTORY(FILS,LOC:TMP,0)
REMOVE(TMP)
GET(FILS,1)
IF TODAY() <> FIL:DATE OR ABS(CLOCK()-FIL:TIME) > 100
!FECHA DIFERENTE O 1 SEGUNDO DE DESFASE
SETTODAY(FIL:DATE)
SETCLOCK(FIL:TIME)
END
ELSE
REMOVE(TMP)
END
Carlos Gutierrez Con SQL
La sugerencia de Carlos es muy buena. Si estás usando SQL o drivers ODBC, la otra opción es preguntarle la fecha al motor de base de datos.
La forma genérica de hacerlo es:
temp{prop:sql}='SELECT {fn curdate() }'
Con NET TIME Posteado por Diego Sánchez al foro.
Run('NET TIME \\Server_Name /SET /Y')
Reemplazar "Server_Name" por el nombre del servidor o equipo del cual se desea obtener la hora Fue posteado originalmente por un NICOLAS VEILLEUX nveilleux@nbautomation.com, en el foro comp.lang.clarion Fernando Cerini
Llamar a un Stored Procedure
El código sería mas o menos asi:L:Query = 'CALL NombreDelStored ( & FORMAT(ParamFecha,@D12) & , & FORMAT(OtraFecha,@D12) & , ' & OtroParam1 &', ' & OtroParam2 &', ' & OtroParam3 &' )'
ResSQL{prop:sql} = L:Query
Loop Until Access:ResSql.Next()
MiVariable = R:Campo1
etc = R:Campo2
....
END
En la tabla auxiliar ResSQL obtienes el resultado del último SELECT que tenga el Stored Procedure. Para mas detalles ver el documento sobre SQL Embebido (Templates Clarion)
También recomiendo leer el help "MSSQL Accelerator Calling a Stored Procedure". Ahi está explicado además el uso de valores de retorno y parámetros de salida.
Fernando Cerini
Encriptación básica
Encriptación / desencriptación básica de un campo usando el metodo XOR.!la primera vez encripta !al volver a aplicar el algoritmo con la misma !Clave de encriptado: desencripta X# = 1 loop Y# = 1 to Len(Campo) Campo [Y#] = chr(bxor(val(Campo[Y#]), val(ClaveEncriptado[X#]))) X# += 1 if X# > len (ClaveEncriptado) then X# = 1. end display
!Campo y ClaveEncriptado son campos CStringFernando Cerini
Generar un archivo de Texto a máxima velocidad
Estas son las APIs para generar un archivo de texto sin necesidad de declararlo en el Diccionario.Además es muy rápido, ideal para exportaciones.
En Global - Inside Global map:
MODULE('Windows API')
_lcreat(*CSTRING,SIGNED),SIGNED,PASCAL,RAW
_hwrite(SIGNED,*CSTRING,LONG),LONG,PASCAL,RAW
_lclose(SIGNED),SIGNED,PASCAL
END
Por ejemplo para guardar el contenido de un control Text
IF NOT FILEDIALOG('Guardar como',FileName,'Text|*.TXT|Source|*.CLW',FILE:Save + FILE:LongName)
CYCLE
END
F# = _lcreat(FileName,0)
X# = _hwrite(F#,Texto,LEN(Texto))
X# = _lclose(F#)
Fernando Cerini Efecto BLINK en un campo
Tienes que crear un timer en la ventana, para eso ponle cada cuanto se va a ejecutar en la propiedad timer de la ventana. Son centesimas de seg, asi que si le pones 50 por ejemplo tu campo va a titilar 2 veces por segundo.Luego cierra la ventana, vuelve a entrar y vas a encontrar un evento timer de la ventana en los embeds.
Window Events --> Timer
if ?campo{prop:background} = COLOR:WHITE
?campo{prop:background} = COLOR:SILVER
else
?campo{prop:background} = COLOR:WHITE
end
Fernando Cerini Función para calcular dígito verificador en CUIT
- La siguiente función devuelve el numero de CUIT con el dígito verificador correcto. - El parámetro que recibe es el numero de CUIT a revisar incluyendo el dígito verificador. ...
CuitCliente='20-15433984-6'
IF Cuit(CuitCliente)=CuitCliente THEN
MESSAGE('Digito verificador correcto')
ELSE
MESSAGE('Digito verificador incorrecto')
END
...
Cuit PROCEDURE(cuit1)
cuit2 STRING(255)
digver LONG
lon LONG
fac LONG
car STRING(1)
CODE
cuit2=cuit1
digver=0
fac=2
lon=LEN(CLIP(cuit2))
LOOP i#=lon-1 TO 1 BY -1
car=SUB(cuit2,i#,1)
IF car<'0' OR car>'9' THEN
CYCLE
.
digver=digver+(car*fac)
fac+=1
IF fac>7 THEN
fac=2
.
.
digver=11-(digver%11)
IF digver>9 THEN
digver=0
.
cuit2=SUB(cuit2,1,lon-1) & FORMAT(digver,@n01)
RETURN(cuit2)
Este codigo esta en la documentacion del template de Impresoras Fiscales (BIGSYS TEMPLATES) del amigo Juan Carlos Rodríguez Validar Email
Puedes hacerlo con MATCH, el cual devuelve 1 o 0 si el mail no es valido. Ejemplo:X# = MATCH(UPPER(CLIP(locemail)),|
'^[-A-Z0-9._]+@{{[-A-Z0-9._]+.}+[A-Z][A-Z][A-Z]?[A-Z]?$', Match:Regular)
Fernando Cerini Restar Horas
Para sacar la diferencia entre horas es simplemente:resultado = hora2 - hora + 1El +1 es porque sino que faltaria un segundo cuando muestres el resultado (en formato @T6, por ej)
Si Hora2 es del dia siguiente, la cuenta seria:
resultado = (hora2 +(100*60*60*24)) - hora + 1Fernando Cerini
Digito Verificador para 5 digitos
Si tenes un numero de 5 digitos y deses verificar que el mismo es ingresado correctamente podes usar este codigo que genera un digito verificadorCrea una funcion que tenga un parametro (numero a verificar) y retorne el digito verificador correspondiente ejem: DigitoV5(LONG xNumero),BYTE
loc:Numero = xNumero
loc:Valor = (loc:Numero[1]*5) + |
(loc:Numero[2]*4) + |
(loc:Numero[3]*3) + |
(loc:Numero[4]*2) + |
(loc:Numero[5]*7)
IF (loc:Valor%5) + 1 = 0 OR (loc:Valor%5) + 1 = 1
loc:Digito = 0
ELSE
loc:Digito = 6 - ((loc:Valor%5) + 1)
END
RETURN loc:Digito
Ruben Garcia (DiPS) Digito Verificador para Cualquier Longitud
Si no sabes que longitud puede tener el numero a verificar podes probar verificarla con este codigoCrea una funcion cuyo parametro es el numero a verificar y retorne el digito verificador. Ej. DigitoV(STRING xNumero),BYTE
!Inicializa
loc:Numero = xNumero
loc:Valor = 0
loc:Multiplo = 1
!Barrido y calculo
LOOP loc:Posicion = LEN(CLIP(loc:Numero)) TO 1 BY -1
loc:Multiplo += 1
IF loc:Multiplo > 7
loc:Multiplo = 2
END
loc:Valor += loc:Numero[loc:Posicion] * loc:Multiplo
END
loc:Digito = loc:Valor % 11
IF loc:Digito = 10
loc:Digito = 0
END
RETURN loc:Digito
Ruben Garcia (DiPS) Autoincremento Manual
Esto se utiliza cuando tenemos una tabla en la cual queremos manejar la clave de auto incrementoCLEAR(MASTER)
SET(MAS:ClavePorID, MAS:ClavePorID)
ACCES:MASTER.Previous()
IF MAS:Id = 0 THEN
MAS:Id = 1
ELSE
MAS:Id = MAS:Id +1
END
!!!MAS:Id TENDRÍA EL VALOR DEL PROXIMO NUMERO.- Gracias Fernando Cerini
Nota: El mecanismo descrito es "confiable" sólo para aplicaciones que usan TPS y en modo single-user. Para Multiusuario y SQL hay que tener cuidado con las lecturas simultáneas del último valor. También puede simplificarse el "if ... then ... else ..." como "MAS:Id = MAS:Id +1" (si es cero, será 1, no hace falta chequearlo)
Desabilitar menu desde cualquier procedimiento
Una opcion seria usando NOTIFY. En el FrameWindow Events
Notify
DISABLE = VariableGlobal
En cualquier procedimiento que se necesite deshabilitar un menu VariableGlobal = Nro de Use del Menu NOTIFY (999, 1)Puede ser 999 o cualquier cosa ya que no estoy usando ese parametro. (bueno, en realidad podria usar ese parametro en lugar de la global...) 1 es el Tread del Frame
Como desde los otros procedimientos no existen los use del los items de menu (o sea ?menuitem) lo que hay que hacer es ponerles un numero a cada uno. Esto se logra poniendolos de esta manera ?use, numero en la definicion del menu.
Fernando Cerini
Formatear fechas en SQL
Les paso una función que utilizo para trabajar con las fechas en el SQL parecido al format de clarion. Es muy útil. CREATE FUNCTION dbo.FormatDateTime
(
@dt DATETIME,
@format VARCHAR(16)
)
RETURNS VARCHAR(64)
AS
BEGIN
DECLARE @dtVC VARCHAR(64)
SELECT @dtVC = CASE @format
WHEN 'LONGDATE' THEN
DATENAME(dw, @dt)
+ ',' + SPACE(1) + DATENAME(m, @dt)
+ SPACE(1) + CAST(DAY(@dt) AS VARCHAR(2))
+ ',' + SPACE(1) + CAST(YEAR(@dt) AS CHAR(4))
WHEN 'LONGDATEANDTIME' THEN
DATENAME(dw, @dt)
+ ',' + SPACE(1) + DATENAME(m, @dt)
+ SPACE(1) + CAST(DAY(@dt) AS VARCHAR(2))
+ ',' + SPACE(1) + CAST(YEAR(@dt) AS CHAR(4))
+ SPACE(1) + RIGHT(CONVERT(CHAR(20),
@dt - CONVERT(DATETIME, CONVERT(CHAR(8),
@dt, 112)), 22), 11)
WHEN 'SHORTDATE' THEN
LEFT(CONVERT(CHAR(19), @dt, 0), 11)
WHEN 'SHORTDATEANDTIME' THEN
REPLACE(REPLACE(CONVERT(CHAR(19), @dt, 0),
'AM', ' AM'), 'PM', ' PM')
WHEN 'UNIXTIMESTAMP' THEN
CAST(DATEDIFF(SECOND, '19700101', @dt)
AS VARCHAR(64))
WHEN 'YYYYMMDD' THEN
CONVERT(CHAR(8), @dt, 112)
WHEN 'YYYY-MM-DD' THEN
CONVERT(CHAR(10), @dt, 23)
WHEN 'YYMMDD' THEN
CONVERT(VARCHAR(8), @dt, 12)
WHEN 'YY-MM-DD' THEN
STUFF(STUFF(CONVERT(VARCHAR(8), @dt, 12),
5, 0, '-'), 3, 0, '-')
WHEN 'MMDDYY' THEN
REPLACE(CONVERT(CHAR(8), @dt, 10), '-', SPACE(0))
WHEN 'MM-DD-YY' THEN
CONVERT(CHAR(8), @dt, 10)
WHEN 'MM/DD/YY' THEN
CONVERT(CHAR(8), @dt, 1)
WHEN 'MM/DD/YYYY' THEN
CONVERT(CHAR(10), @dt, 101)
WHEN 'DDMMYY' THEN
REPLACE(CONVERT(CHAR(8), @dt, 3), '/', SPACE(0))
WHEN 'DD-MM-YY' THEN
REPLACE(CONVERT(CHAR(8), @dt, 3), '/', '-')
WHEN 'DD/MM/YY' THEN
CONVERT(CHAR(8), @dt, 3)
WHEN 'DD/MM/YYYY' THEN
CONVERT(CHAR(10), @dt, 103)
WHEN 'HH:MM:SS 24' THEN
CONVERT(CHAR(8), @dt, 8)
WHEN 'HH:MM 24' THEN
LEFT(CONVERT(VARCHAR(8), @dt, 8), 5)
WHEN 'HH:MM:SS 12' THEN
LTRIM(RIGHT(CONVERT(VARCHAR(20), @dt, 22), 11))
WHEN 'HH:MM 12' THEN
LTRIM(SUBSTRING(CONVERT(
VARCHAR(20), @dt, 22), 10, 5)
+ RIGHT(CONVERT(VARCHAR(20), @dt, 22), 3))
ELSE
'Invalid format specified'
END
RETURN @dtVC
END
GO
Ejemplos:
DECLARE @now DATETIME
SET @now = GETDATE()
PRINT dbo.FormatDateTime(@now, 'LONGDATE')
PRINT dbo.FormatDateTime(@now, 'LONGDATEANDTIME')
PRINT dbo.FormatDateTime(@now, 'SHORTDATE')
PRINT dbo.FormatDateTime(@now, 'SHORTDATEANDTIME')
PRINT dbo.FormatDateTime(@now, 'UNIXTIMESTAMP')
PRINT dbo.FormatDateTime(@now, 'YYYYMMDD')
PRINT dbo.FormatDateTime(@now, 'YYYY-MM-DD')
PRINT dbo.FormatDateTime(@now, 'YYMMDD')
PRINT dbo.FormatDateTime(@now, 'YY-MM-DD')
PRINT dbo.FormatDateTime(@now, 'MMDDYY')
PRINT dbo.FormatDateTime(@now, 'MM-DD-YY')
PRINT dbo.FormatDateTime(@now, 'MM/DD/YY')
PRINT dbo.FormatDateTime(@now, 'MM/DD/YYYY')
PRINT dbo.FormatDateTime(@now, 'DDMMYY')
PRINT dbo.FormatDateTime(@now, 'DD-MM-YY')
PRINT dbo.FormatDateTime(@now, 'DD/MM/YY')
PRINT dbo.FormatDateTime(@now, 'DD/MM/YYYY')
PRINT dbo.FormatDateTime(@now, 'HH:MM:SS 24')
PRINT dbo.FormatDateTime(@now, 'HH:MM 24')
PRINT dbo.FormatDateTime(@now, 'HH:MM:SS 12')
PRINT dbo.FormatDateTime(@now, 'HH:MM 12')
PRINT dbo.FormatDateTime(@now, 'goofy')
Posteado al foro por Omar Squiabro Anular Tecla Escape
Hay veces que se necesita que el usuario salga de una FORM o de una ventana solo cuando pulse un determinado boton o se halla completado alguna condicion. Y en estos casos puede suceder que si el usuario pulsa la tecla ESC cause algun problemaPrimero hay que activar la alerta de la tecla esc en el INIT de la ventana
ALERT(EscKey)Despues en el evento alertkey
IF KEYCODE() = EscKey SELECT(?UnDeterminadoCampo) !1= al primero RETURN Level:Notify ENDSi el codigo embebido es puesto despues del codigo generado por clarion reemplazar el RETURN por CYCLE
Ruben Garcia (DiPS)
Convertir Número Hexadecimal a Binario
A veces, y sobre todo al trabajar con estados de puertos de comunicaciones, necesitamos convertir numeros hexadecimales a binario para establecer errores o tratar envios y recepciones. Esta función hace justamente esto de forma sencilla.HexaABinario PROCEDURE(PAR:Nhexa) ! (String),String HBinario String(4),dim(16) !Equivalencias RetuBina String(100) !Variable Retorno
CODE
HBinario[01] = '0000' ! 0h
HBinario[02] = '0001' ! 1h
HBinario[03] = '0010' ! 2h
HBinario[04] = '0011' ! 3h
HBinario[05] = '0100' ! 4h
HBinario[06] = '0101' ! 5h
HBinario[07] = '0110' ! 6h
HBinario[08] = '0111' ! 7h
HBinario[09] = '1000' ! 8h
HBinario[10] = '1001' ! 9h
HBinario[11] = '1010' ! Ah 65
HBinario[12] = '1011' ! Bh 66
HBinario[13] = '1100' ! Ch 67
HBinario[14] = '1101' ! Dh 68
HBinario[15] = '1110' ! Eh 69
HBinario[16] = '1111' ! Fh 70
CLEAR(RetuBina)
PAR:NHexa = UPPER(PAR:NHexa) !Aseguro letras en mayusculas
LOOP I# = 1 to LEN(CLIP(PAR:NHexa))
IF NUMERIC(PAR:NHexa[I#]) THEN !Si es numero es <= 9
!Asigno equivalente
RetuBina = CLIP(RetuBina) & HBinario[PAR:NHexa[I#]+1]
ELSE
!Asigno equivalente tomando,por ejemplo, 65 - 54 = 11 para "A"
RetuBina = CLIP(RetuBina) & HBinario[VAL(PAR:NHexa[I#])-54]
END
END
!Retorno la cadena binaria sin ceros a la izquierda
RETURN(SUB(RetuBina, INSTRING('1',RetuBina), LEN(CLIP(RetuBina))))
Bueno, espero les sea de utilidad! Mario A. Wojcik
Transacciones, TCF, Logout, Commit, Rollback
Solucion al tema del TCF, crearlo, verlo, etc.Paso 1 En el punto embebido del FRAME (windowsManager init (priority 6100) o sea antes que se abra ningun archivo, Insertar el siguiente codigo:
GLO:Datos:TCF = 'TCF=' & CLIP(GLO:WorkDir) & '\' & 'AR.TCF' Send(CLIENTES,GLO:Datos:TCF)Nota 1: No es necesario definir en ningun lado el archivo .TCF ni su estructura porque de todo se encarga el driver. Nota 2: no enviar (SEND) los datos para el .TCF desde el diccionario, según los autores, esto no funciona. Nota 3: Solo es necesario un archivo .TCF para toda la aplicación.
Listo, el archivo de control de transacciones ya esta creado y sin dudas funcionara. Para que todo quede completo deberiamos hacer:
LOGOUT(1,CabeceraFactura,DetalleFactura) !Comienza la transaccion
DO ErrHandler !Chequear errores
ADD(CabeceraFactura) !Agregar registro al padre
DO ErrHandler !
LOOP X# = 1 TO RECORDS(DetailQue) !Procesar los registros almacenados
GET(DetailQue,X#) !traer uno de la Cola
DO ErrHandler !
Det:Record = DetailQue !Asignar al record buffer
ADD(DetalleFactura) !Hacer el Add al archivo
DO ErrHandler !
END
COMMIT !Terminar la transaccion, todo OK
ASSERT(~ERRORCODE())
ErrHandler ROUTINE !Runina de manejo de errores
IF NOT ERRORCODE() THEN EXIT. !Sale si no hay errores
Err" = ERROR() !Guarda el mensaje de error
ROLLBACK !Anula la transaccion
ASSERT(~ERRORCODE())
BEEP !Avisa al usuario
MESSAGE('Transaction Error - ' & Err")
RETURN
Lo anterior es copia de la ayuda del manual de Clarion 6.3 Para verificar que exista realmente el archivo .TCF podemos usar en cualquier punto de la aplicación lo siguiente:
Poner un string variable en pantalla y en un boton:
Loc:Tcf = SEND(CLIENTES, 'TCF') DISPLAY()
Eso deberia ser todo. Por lo menos para mi funciona. Carlos Barroso DBA Oracle Programador Clarion
Puntos Embebidos De ClarionWiki
Puntos Embebidos
De ClarionWiki
Los puntos embebidos son la pesadilla de todo principiante de Clarion. Son extremadamente útiles, pero son muy difíciles de aprender. Para colmo, la abstracción que implementa la clase ABC hace más difícil todavía conocerlos y aprovecharlos. Por eso esta sección es de vital importancia.
Contenido
- 1 FORMULARIOS
- 1.1 ¿Qué procedimiento llamó al Form?
- 1.2 Validaciones complejas
- 1.3 Cuando se Abre/inicializa el Form
- 1.4 Cambiar el Form a "Solo lectura" ante una condición
- 1.5 Truco: Llamar al form para insertar desde el menú
- 1.6 Llamar a un form para editar una tabla con un solo registro
- 1.7 Cuando se confirma un Form
- 1.8 Cuando se cierra un Form
- 1.9 Cuando se produce el borrado sin abrir el Form
- 1.10 Chequear si se modificó cualquier campo del FORM
- 1.11 Chequear si se modificó un campo del FORM
- 1.12 Forzar grabación del form
- 1.13 Filtrar un Drop Combo
- 1.14 Obtener el número autogenerado por SQL Server en tablas con identity
- 2 REPORTES Y PROCESOS
- 2.1 Ocultar un campo del detalle según condición
- 2.2 Saber si un proceso se completó
- 2.3 Inicializar el número de página
- 2.4 Deshabilitar la impresión
- 2.5 Filtros complejos en un Proceso
- 2.6 Habilitar o deshabilitar el Print Preview
- 2.7 Cantidad de Páginas generadas
- 2.8 Imprimir un Transporte
- 2.9 Acelerar las actualizaciones
- 2.10 Se imprime o se Cancela?
- 2.11 Recorrer la cola de las páginas
- 2.12 Imprimir una Queue
- 2.13 Imprimir un SQL
- 2.14 Detectar o hacer cortes de control manuales en reportes
- 2.15 Impresión de imagenes desde Campo BLOB
- 2.16 Impresión de imagenes desde Campo CSTRING
- 2.17 Evitar el cartel "No records to process" en un proceso
- 2.18 Imprimir el reporte en blanco, cuando no hay registros
- 2.19 Detectar el último registro en un process o Reporte (fin de archivo)
- 2.20 Reporte con múltiples detalles
- 2.21 Imprimir exactamente lo que estamos viendo en el browse
- 2.22 Cambiar tamaño de hoja en el reporte
- 2.23 Cambiar Impresora
- 2.24 Detectar Salto de Página de Reporte
- 2.25 Aplicar Filtro sql en el reporte
- 2.26 Imprimir mas de una Copia por Reporte
- 3 BROWSES
- 3.1 Actualizar un campo al hacer click en una columna
- 3.2 Filtrar con SQL
- 3.3 Activar un Browse invisible, o sea que está en otro TAB
- 3.4 Copiar un Registro
- 3.5 Deshabilitar el Popup
- 3.6 Ordenamiento Dinámico
- 3.7 Locator Múltiple
- 3.8 Pintar un browse según un campo
- 3.9 Refrescar un browse desde otra ventana
- 3.10 Iniciar el browse en un Tab determinado
- 3.11 Actualizar el resultado de un totaling del browse de detalle, en el Encabezado
- 3.12 Filtro Dinámico
- 3.13 Rangos múltiples y dinámicos
- 3.14 En un Browse, deshabilitar condicionalmente la actualización de registros
- 3.15 Detectar el cambio de registro seleccionado en un Browse
- 3.16 Filtros complejos en un browse
- 3.17 Poner un campo "saldo" en un browse
- 3.18 Resetear/inicializar un Locator
- 3.19 En un Browse, disparar un procedimiento después de generar un alta, modificación, etc.
- 3.20 Inserción continua refrescando el Browse
- 3.21 Posicionar un browse en un registro seleccionado
- 3.22 En Legacy, activar un filtro sobre un browse abierto.
- 3.23 Campos Calculados
- 3.24 Código antes y después de una actualización DESDE UN BROWSE.
- 3.25 Deshabilitar/esconder (cambiar a PROP:HIDE) un control si el browse está vacío - pe. un botón-.
- 3.26 Iniciar un browse con un orden "sort header"
- 4 EDIT IN PLACE
- 4.1 Edit in Place o Form, según condición
- 4.2 Desactivar Edit in Place de un campo según condición
- 4.3 Inicializar campos al insertar registro
- 4.4 Poner Disable un campo
- 4.5 Campos Calculados
- 4.6 Validaciones
- 5 GLOBALES
- 5.1 Cambiar los mensajes de error del SQL
- 5.2 Validación de campos (Edit in place o Form)
FORMULARIOS
¿Qué procedimiento llamó al Form?
Al principio del WindowManager.Init del Form (primer prioridad disponible)GlobalErrors.GetProcedureName()Fernando Cerini
Validaciones complejas
Por ejemplo validar un Email: En el accepted del campo Puedes hacerlo con MATCH, ejemplo:X# = MATCH(UPPER(CLIP(locemail)),|
'^[-A-Z0-9._]+@{{[-A-Z0-9._]+.}+[A-Z][A-Z][A-Z]?[A-Z]?$', Match:Regular)
Devuelve 1 o 0 si el mail no es válido. El MATCH puede usarse con expresiones regulares para validar casi cualquier cosa... Fernando Cerini
Cuando se Abre/inicializa el Form
ThisWindow.Init!Es interesante entrar por el botón source a este embed para entender bien todas sus prioridades !GlobalRequest (pasada por el browse) se guarda en la propiedad ThisWindow.Request !Se puede preguntar por SELF.Request para saber para qué se lo llamó al form: !InsertRecord EQUATE (1) !ChangeRecord EQUATE (2) !DeleteRecord EQUATE (3)Fernando Cerini
Cambiar el Form a "Solo lectura" ante una condición
ThisWindow.Init ! [Priority 4950] La prioridad es importante! IF FAC:facturado = 1 THEN SELF.Request = ViewRecord.Fernando Cerini
Truco: Llamar al form para insertar desde el menú
Podríamos asumir que si la GlobalRequest está en blanco es que se lo llamó para insertar, evitaríamos asi el paso por un Browse!Al principio del Init (antes de Snap-Shot global Request): !If GlobalRequest = 0 Then GlobalRequest = InsertRecord.Si la tabla tiene una clave Autonumber, se complica un poco mas, sería asi:
!Al principio del Init (antes de Snap-Shot global Request):
!Por ej prioridad 300
If GlobalRequest = 0
GlobalRequest = InsertRecord
DesdeMenu# = True
End
!Después de Open Files: !Por ej prioridad 7800 If DesdeMenu# = True Access:People.PrimeAutoinc() EndFernando Cerini
Llamar a un form para editar una tabla con un solo registro
Típicamente una tabla de parametros, obviamente no queremos pasar por un browseWindowManager.Init, !al principio GlobalRequest = ChangeRecord
!Luego de Open files Set(Tabla) access:Tabla.Next()Fernando Cerini
Cuando se confirma un Form
ThisWindow.TakeCompleted! [Priority 2800] !ANTES DE QUE SE CONFIRME LA GRABACION ! Parent Call ReturnValue = PARENT.TakeCompleted() ! [Priority 6300] !YA SE GRABÓ EL REGISTRO. !VERIFICAR SI SELF.Response=RequestCompleted A VER SI SE GRABÓ OK.Fernando Cerini
Cuando se cierra un Form
ThisWindow.Kill!VERIFICAR LOS valores de SELF.Request y SELF.Response !Para decidir que acciones tomar !Este punto ya es "demasiado tarde" para hacer algo antes de que se grabe el registro !Para eso usar TakeCompletedFernando Cerini
Cuando se produce el borrado sin abrir el Form
Si tenemos un form para actualizar una tabla, y definimos que el borrado sea Confirmando (Standard Warning) o Automático (Automatic Delete), y necesitamos ejecutar un código en el form cuando se efectúe el borrado, usamos:ThisWindow.PrimeUpdate, después del Parent Call
if self.request = DeleteRecord and self.Response = RequestCompleted !lo que quiero hacer endHay que tener en cuenta que el borrado de un registro no pasa por el TakeCompleted, excepto cuando se usa la ventana del form (Display Form).
Aporte de Gustavo Olmedo (Templates Clarion)
Chequear si se modificó cualquier campo del FORM
En el TakeCompleted ponemos el siguiente código:IF NOT SELF.Primary.Me.EqualBuffer(SELF.Saved)
! message('Cambio')
END
Aporte de Pablo Guarnieri y Gustavo Olmendo Chequear si se modificó un campo del FORM
IF CLI:Campo <> history::CLI:Record.CampoFernando Cerini
Forzar grabación del form
Este código sirve para grabar el form inclusive cuando el usuario lo canceleTakeCloseEvent PROCEDURE(),BYTE,VIRTUAL
IF loc:condicion SELF.CancelAction = Cancel:Save !immediate save (no confirmation) ENDIvan Dario Benitez Delgado (Doxa Sistemas)
Filtrar un Drop Combo
Este código sirve para filtrar un drop combo utilizando pro:sqlfilter, suponemos que en la variable loc:filtro se arma el filtro ha enviar, algo asi como: 'a.idtercero = <39>90909090<39>' FDCB14 es el nombre del objeto el cual se puede verificar en propiedades --> clases.ApplyFilter PROCEDURE,VIRTUAL
FDCB14::View:FileDropCombo{PROP:SQLFILTER} = <39>'&clip(loc:filtro)&'<39>'
IF FILEERRORCODE()
FDCB14::View:FileDropCombo{PROP:SQLFILTER} =
END
Ivan Dario Benitez Delgado (Doxa Sistemas) Obtener el número autogenerado por SQL Server en tablas con identity
Después del Parent Call, en el TakeCompleted del Form:IF Self.Response=RequestCompleted AND Self.Request = InsertRecord
Tabla{prop:Sql}= 'SELECT @@IDENTITY' !busco el número que se asignó
IF not Access:Tabla.Next()
!en el primer campo de Tabla viene cargado el número autogenerado
else
message('Error')
end
end
Tomado de una respuesta de Fernando Cerini en el foro. REPORTES Y PROCESOS
Ocultar un campo del detalle según condición
!En TakeRecord, antes del Print (Process Manager)
IF <CONDICION>
REPORT$?Control{PROP:HIDE}=True
ELSE
REPORT$?Control{PROP:HIDE}=False !si la condición no se cumple, vuelvo a hacer visible el control
END
Fernando Cerini Saber si un proceso se completó
O sea que no se salió por el botón de cancelar!En el WindowManager.Kill IF Self.Response = RequestCompletedFernando Cerini
Inicializar el número de página
!WindowManager.OpenReport, despues del Parent Call
IF ReturnValue = Level:Benign
Report{PROP:NextPageNo} = xxx
END
Fernando Cerini Deshabilitar la impresión
!Local Objects, Abc Objects, Previewer, Display, After ParentCall. !impresión prohibida ReturnValue = FalsePor ejemplo:
If ReturnValue and DemoVersion
ReturnValue = False
Message("Está utilizando una versión del demo, no puede imprimir este documento !")
End
Aporte de Olivier Cretey Otra opción, en Previewer.TakeAccepted, antes del Parent Call
IF CLIP(ACCEPTED(){PROP:TIP}) = 'Print this report' THEN RETURN LEVEL:BENIGN.
O sea pregunto por el Tool Tip del botón que se ha presionado. Ojo, si lo tienes traducido tienes que cambiar el 'Print this report' por tu Tool Tip. Fernando Cerini
Filtros complejos en un Proceso
!Windows Manager -->> Validate Records -->> [Priority 5600]
If <Condición de Filtro>
Return Record:Filtered
Else
Return Record:OK
END
Gustavo Olmedo (Templates Clarion) Habilitar o deshabilitar el Print Preview
!Windows Manager -->>Ask preview -->> [Priority 5000] Self.SkipPreview = Loc:PreviewGustavo Olmedo (Templates Clarion)
Cantidad de Páginas generadas
!Windows Manager -->>Ask preview -->> [Priority 5000] ENDPAGE(SELF.Report) Loc:Paginas = RECORDS(SELF.PreviewQueue)Gustavo Olmedo (Templates Clarion)
Imprimir un Transporte
Necesitas crear 3 details. DetailDatos DetailTransporte: aca van las variables locales con los totales DetailSalto: este detail es vacío y con la propiedad PageAfterEn el embed TakeRecord (antes de los PRINTs) habría que hacer algo asi
LOC:Fila += 1
If LOC:Fila % 28 = 0 !28 líneas por página...
PRINT(RPT:DetailSalto)
PRINT(RPT:DetailTransporte)
LOC:Fila = 1
End
LOC:Total += LIBRO:Total
PRINT (RPT:Detalle)
RETURN LEVEL:Benign
Marcelo Martínez y Fernando Cerini Acelerar las actualizaciones
Para que los procesos que agreguen registros funcionen mas rápido hay que agregar un Logout o Stream en Init y un Commit o Flush en el kill.Fernando Cerini
Se imprime o se Cancela?
Los embeds sonABC Objectd --WindowManager(ReportManager) --CancelPrintReport -Se Cancela
--PrintReport -Se imprimeFernando Cerini
Recorrer la cola de las páginas
Cada página del reporte genera una imagen .WMF, la cual se guarda en el directorio temporal. Con este ejemplo se puede recorrer la cola donde se guardan los nombres de estos archivos. Ésto es lo que usa por ejemplo el EC_Mail Template para enviar estas páginas por mail.Previewer.Open PROCEDURE
....
loop a# = 1 to RECORDS(SELF.ImageQueue)
get(SELF.ImageQueue,a#)
message(SELF.ImageQueue)
end
Fernando Cerini Imprimir una Queue
La mejor forma de hacerlo en Clarion6 es en Report Properties - General - Datasource: Queue.Yo generalmente pongo los parametros en la ventana y cargo la queue en el evento accepted del PauseButton.
Fernando Cerini
Imprimir un SQL
Lo que yo hago es poner todos los parámetros en la ventana del reporte. En el accepted del boton de pausa cargo la cola de memoria con:SQL{PROP:SQL} = !aca armo el string SQL
IF ERRORCODE() THEN STOP( FILEERROR()).
FREE(ColaMemoria)
LOOP
NEXT(SQL) !O ABC, como prefieras...
IF ERRORCODE() THEN BREAK.
ColaMemoria :=: SQL:Record
ADD(ColaMemoria)
END
El reporte lo configuro para trabajar con la cola (data source) y en el Detail del reporte pongo los campos de la cola de memoria. Lo ideal es tener una cola de memoria global con el atributo thread y usar siempre la misma para todo. Fernando Cerini
Detectar o hacer cortes de control manuales en reportes
En TakeRecord, antes del Print.!Actualizar contadores
IF AUX <> CampoCorte IF AUX <> 0 !para evitar la primera vez !Inicializar los contadores END AUX=CAMPO !SE VIENE EL CORTE ENDEste método TakeRecord se usa también para actualizar cualquier variable calculada antes de la impresión de la banda de detalle.
Fernando Cerini
Impresión de imagenes desde Campo BLOB
Este código va en el método TakeRecord, antes del PRINTReport$?Image1{PROP:NoWidth} = TRUE
Report$?Image1{PROP:NoHeight} = TRUE
Report$?Image1{PROP:ImageBlob} = STU:Photograph{PROP:Handle}
IF Report$?Image1{PROP:Height} > 1000
AspectRatio$ = Report$?Image1{PROP:Width}/Report$?Image1{PROP:Height}
Report$?Image1{PROP:Height} = 1000
Report$?Image1{PROP:Width} = 1000 * AspectRatio$
END
IF Report$?Image1{PROP:Width} > 1000
AspectRatio$ = Report$?Image1{PROP:Height}/Report$?Image1{PROP:Width}
Report$?Image1{PROP:Width} = 1000
Report$?Image1{PROP:Height} = 1000 * AspectRatio$
END
Fernando Cerini Impresión de imagenes desde Campo CSTRING
Este caso es similar al anterior, pero en el campo se encuentra el path donde está la imagen!En el método TakeRecord, antes del PRINT
Report$?Image1{PROP:Text} = PRO:Foto
Fernando Cerini Evitar el cartel "No records to process" en un proceso
!En el metodo TAKE NO RECORDS poner un RETURN antes del parent call.Fernando Cerini
Imprimir el reporte en blanco, cuando no hay registros
En WindowManager. Next, antes del Parent Call con prioridad 500.! [Priority 500] antes del Parent Call If Primera=0 !variable local byte Primera=1 SELF.Process.RecordsProcessed = 1 Return Level:Benign EndFernando Cerini
Cuando hay filtros y rangos, esta solución suele duplicar el primer registro del detalle. Para Solucionar esto en ProcessManager NextPROCEDURE(BYTE ProcessRecord) con prioridad 7500 se debe colocar el siguiente código:
IF ReturnValue = 0 AND LOC:Primera = 0 THEN !Si hay un registro LOC:Primera = 1 !Dejo al reporte seguir su curso normal ENDMario Wojcik
Detectar el último registro en un process o Reporte (fin de archivo)
ThisProcess.Next PROCEDURE(BYTE ProcessRecords=True) ! [Priority 5001] If ReturnValue = 5 !Level:Notify !Fin del proceso EndEsto por ejemplo es muy útil para cambiar etiquetas en los totales generales del reporte, en un page footer
ThisReport.Next PROCEDURE(BYTE ProcessRecords)
....
! [Priority 5001] despues del Parent Call
If ReturnValue = 5 !Level:Notify
REPORT$?LabelTotal{prop:text}= 'Total'
End
Fernando Cerini Reporte con múltiples detalles
Por Ejemplo:Viajes |-->pasajeros |-->comisionesHabría que poner solo la tabla viajes en el Table Schematic. Pasajeros y Comisiones en Other Tables
En el reporte crear 3 details, uno para cada tabla.
En ThisReport.TakeRecord, antes de los PRINTs
! [Priority 5500]
PRINT(RPT:DetailViaje)
CLEAR(PAS:RECORD)
PAS:idViaje = VIA:IdViaje
SET(PAS:ClavePorViaje, PAS:ClavePorViaje)
LOOP UNTIL ACCESS:Pasajeros.Next() OR PAS:idViaje <> VIA:IdViaje
PRINT(RPT:DetailPasajeros)
END
!Y lo mismo para las comisiones
CLEAR(COM:RECORD)
COM:idViaje = VIA:IdViaje
SET(COM:ClavePorViaje, COM:ClavePorViaje)
LOOP UNTIL ACCESS:Comisiones.Next() OR COM:idViaje <> VIA:IdViaje
PRINT(RPT:DetailComisiones)
END
Return Level:Benign !ya imprimí todo
Fernando Cerini Otra alternativa podría ser utilizando el Template ReportChildFiles (desde version 6.X ?) ya que puede ser utilizado mas de una vez en un mismo reporte, apuntando cada instancia a un archivo "hijo" diferente. Es fácil de configurar: Se debe seleccionar la tabla CHILD y la PARENT. En el reporte hay que definir el REPORT DETAIL (para el CHILD) y ponerle algún nombre en el label que luego debe ser configurado en el Template.
Jose Sturniolo
Imprimir exactamente lo que estamos viendo en el browse
En el browse pones un botón que llame al reporte y en parameters: (Brw1.view{PROP:Filter}, Brw1.view{PROP:Order})En las propiedades del reporte o proceso: Prototype: (STRING, STRING) Parameters: (FILTRO, ORDEN)
En WindowManager.Init, al final:
ThisReport.SetFilter(FILTRO) ! o ThisProcess.... ThisReport.SetOrder(ORDEN)Fernando Cerini
Cambiar tamaño de hoja en el reporte
En el embed WindowManager 'OpenReport', PRIORITY(7500)IF ~ReturnValue
SETTARGET(REPORT)
CASE PRINTER{PROPPRINT:Paper}
OF PAPER:LETTER
REPORT{PROP:height} = 8500
?Footer{PROP:Ypos} = 9000
OF PAPER:LEGAL
REPORT{PROP:height} = 11000
?Footer{PROP:Ypos} = 11500
etc.
END
SETTARGET()
END
etc. con los tamaños de papel que quieras. Esto acomoda el contenido del reporte en run-time, haciéndolo ocupar más o menos hoja según sea el largo de la misma. Chiche, ¿no?. Ajustá los valores a tus necesidades. Para tener en cuenta: el usuario tiene que elegir el tamaño del papel ANTES de generar el reporte. Si ya está en la vista previa, el reporte ya está generado, por lo tanto si cambia el largo de hoja ahí, no se lo va a tomar. Jorge Lavera
Cambiar Impresora
En Global Properties-->Embeds-->Before Global INCLUDEsINCLUDE('PRNPROP.CLW')
!para poder acceder a las propiedades de las impresoras
Luego en el Reporte en ThisReport.Open()
!para guardar la impresora por defecto de Windows
GLO:IMPRESORA = PRINTER{PROPPRINT:Device}
!Seteo la impresora que quiero usar.
!El string debe ser igual al que figura en IMPRESORAS
PRINTER{PROPPRINT:Device} = 'HP Deskjet 840'
y por último en ThisReport.Kill()
!para devolver la impresora por defecto de windows
PRINTER{PROPPRINT:Device} = CLIP(GLO:IMPRESORA)
Guillermo Ondetti, Pragma Sistemas
Detectar Salto de Página de Reporte
Este sería el codigo, siempre y cuando no tengas break groups (ahi se complica bastante) El embebido es TakeRecord en ABC, o Before Print Detail en Legacy.VarLocal += REPORT$?Detail{PROP:height}
IF VarLocal > REPORT{PROP:height}
VarLocal = REPORT$?Detail{PROP:height}
!En el siguiente PRINT Cambia la página...
END
Fernando Cerini Aplicar Filtro sql en el reporte
Se arma el filtro si se quiere en una variable por ejemlo loc:sql y se aplica en Local Objects --> Abc Objects -- > Process manager --> Apply Filter antes del parent call, asi:Process:View{Prop:SqlFilter}=Loc:SQL
Ivan Dario Benitez Imprimir mas de una Copia por Reporte
Se tiene que poner en el Window Manager --> Open (Prioridad(5000)) PRINTER{PROPPRINT:COPIES}=n !! n = Cantidad de copias
Luego es aconsejable poner en el Window Manager --> Kill PRINTER{PROPPRINT:COPIES}=1
ASC Sergio D. Caballero BROWSES
Actualizar un campo al hacer click en una columna
Por ejemplo, en People.appEn el evento Accepted del Control del browse.
!Si se hizo click en el campo
IF ?BROWSE:1{PropList:MouseDownField}= 4
ACCESS:PEOPLE.Fetch(PEO:KeyId)
PEO:Gender = 'X'
ACCESS:PEOPLE.Update()
BRW1.ResetFromFile()
END
Fernando Cerini Filtrar con SQL
A veces es muy útil aplicar un filtro SQL encima de todos los demas filtros que ya tenga el browse, por ejemplo para filtrar por usuario, por sucursal, etc. Yo normalmente le pongo el filtro SQL en el método windowmanager.Reset, prioridad 5000BRW1.View{PROP:SQLFILTER}='+ filtro sql'
Luego se refresca normalmente, o manualmente con ThisWindow.Reset (1) Fernando Cerini
Activar un Browse invisible, o sea que está en otro TAB
Esto es especialmente útil cuando necesitamos que se ejecuten totalizadores del browse aunque éste no esté visible en la pantalla. !ABC Objects -> Local Objects -> Windows Manager-> Init Priority[7500]BRW2.ActiveInvisible = TrueGustavo Olmedo (Templates Clarion)
Copiar un Registro
!ABC Objects -> Browse on Clientes -> PrimeRecord Priority[4500]
IF Loc:Copiar
SuppressClear = True
CLEAR(Loc:Copiar)
END!IF
!Control Events --> Copiar -> Accepted -> Priority[5000] Loc:Copiar = True POST(EVENT:Accepted,?UseDelBotonAgregar)
!Notas: Crear Variable Local Loc:Copiar ! SuppressClear es un Parametro de BrowseClass.PrimeRecordGustavo Olmedo (Templates Clarion)
Deshabilitar el Popup
!ABC Objects -> Local Objects -> Windows Manager-->>Init Priority[9001] Brw1.Popup.KILLGustavo Olmedo (Templates Clarion)
Ordenamiento Dinámico
En Clarion6 se puede hacer automáticamente con la opcion Sort Headers. Si se quiere hacer desde código para ordenar por cualquier campo, se puede utilizar: Brw1.SetOrder('TAB:Campo')
Brw1.ApplyOrder()
ThisWindow.Reset(1)
Por ejemplo si quisiera emular un ordenamiento con el doble click sobre una columna, habría que alertar el doble click en al browse, y el código sería mas o menos asi. Control Event -> Browse:1 ->Alert Key [4500]
! Código usado para cambiar el Orden del Browse
! cuando se hace doble click en la columna
IF KEYCODE()=MouseLeft2
?Browse:1{PropList:Header,1} ='Codigo'
?Browse:1{PropList:Header,2} ='Nombre'
Loc:Columna = ?Browse:1{PropList:MouseDownField}
CASE Loc:Columna
OF 1
?Browse:1{PropList:Header,1} ='<<<CODIGO>>>'
Brw1.SetOrder('LCA:LOCALIDAD')
OF 2
?Browse:1{PropList:Header,2} ='<<<NOMBRE>>>>'
Brw1.SetOrder('UPPER(LCA:NOMBRE)')
END!CASE
Brw1.ApplyOrder()
ThisWindow.Reset(1)
END!IF
Gustavo Olmedo (Templates Clarion) Locator Múltiple
Por ejemplo, para posicionarme en un código de cuenta contable: Lo que podrías hacer es poner una variable string sobre el browse y poner un código mas o menos asi en su accepted del entry!Asumiendo que cada código tiene cuatro dígitos !Revisar la sección String Slicing en el Help Cue:Cuenta = Loc:Cuenta[1:4] Cue:SubCuenta = Loc:Cuenta[5:8] Cue:SubSubCuenta = Loc:Cuenta[9:12] SET(Cue:PorCodigo,Cue:PorCodigo) Access:Cuentas.Next() BRW1.ResetFromFile() ThisWindow.Reset(True)Otro ejempo, en un browse de órdenes, posicionarse según el nombre del cliente
CLI:Nombre = LOC:Cliente SET(CLI:PorNombre, CLI:PorNombre) ACCESS:Clientes.Next()
ORD:CodCliente = CLI:CodCliente SET(ORD:PorCliente, ORD:PorCliente) ACCESS:Ordenes.Next()
BRW1.ResetFromFile() ThisWindow.Reset(1)
Fernando Cerini
Pintar un browse según un campo
Por ejemplo cuando vemos las facturas de un Cliente se repite el nombre, este código pinta diferente cada grupo de nombres. Primero hay que ponerle el atributo color al campo que necesites pintar, desde list box format. Luego va el siguiente código, en WindowManager.Reset antes del Parent CallAux =
LOOP F#= 1 TO RECORDS (BRW1.Q)
GET(BRW1.Q, F#)
IF Aux <> BRW1.Q.CLI:Nombre
Col# = 1- Col#
Aux = BRW1.Q.CLI:Nombre
END
If Col#
BRW1.Q.CLI:Nombre_NormalBG = COLOR:SILVER
Else
BRW1.Q.CLI:Nombre_NormalBG = COLOR:WHITE
End
PUT(BRW1.Q)
END
Fernando Cerini Refrescar un browse desde otra ventana
Lo mas fácil sería usando NOTIFY. En la Ventana 1 (la del browse a refrescar)WindowManager.Init GLO:THREAD = THREAD() !VARIABLE GLOBAL LONG
Window Events Notify BRW1.ResetfromBuffer() ! o ThisWindow.Reset(1)?En la ventana 2, cuando se necesite refrescar la 1
NOTIFY (999, GLO:THREAD)Puede ser 999 o cualquier cosa ya que en éste caso no estoy usando ese parámetro.
Fernando Cerini
Iniciar el browse en un Tab determinado
En ThisWindow.Init, al finalSELF.FirstField = ?Tab:4 !Use del tabFernando Cerini
Actualizar el resultado de un totaling del browse de detalle, en el Encabezado
En el Browse de abajo (el de detalle)ResetFromView, prioridad 4500 - La prioridad es importante GET(Encabezado, ENC:Por_Numero) ENC:Total = loc:total !ÉSTA ES LA VARIABLE DEL TOTALING PUT (Encabezado) IF ERRORCODE() THEN MESSAGE(ERROR()). BRW1.ResetFromFile()Fernando Cerini
Filtro Dinámico
Poner un control Text donde el usuario pueda escribir cualquier filtroX# = EVALUATE(Filtro)
IF ERRORCODE()
MESSAGE('ERROR DE SINTAXIS EN EL FILTRO: ' & ERROR(), 'ATENCION')
ELSE
BRW1.SetFilter(Filtro)
ThisWindow.Reset(1)
END
Fernando Cerini Rangos múltiples y dinámicos
Si tenemos una clave con varios componentes, en Actions del browse ponemos un rango por current value, elegimos el componente de la clave hasta el cual queremos hacer el rango. El siguiente componente de la clave se puede usar como locator. Por ejemplo si tenemos una clave con Tipo+Provincia+Localidad, hacemos un rango de current value por provincia y nos queda un locator por localidad.BRW1.ApplyRange PROCEDURE ! [Priority 5000] RAN:Tipo= loc:tipo RAN:Provincia=loc:provincia SELF.Order.Rangelist.AssignLeftToRight()En algun boton de aplicar rango
BRW1.ApplyRange() ThisWindow.Reset(1)Fernando Cerini
En un Browse, deshabilitar condicionalmente la actualización de registros
IF <Condición>
!Deshabilitar Actualización
BRW1.InsertControl=FALSE
BRW1.ChangeControl=FALSE
BRW1.DeleteControl=FALSE
ELSE
BRW1.InsertControl=?INSERT
BRW1.ChangeControl=?CHANGE
BRW1.DeleteControl=?DELETE
END
Fernando Cerini Detectar el cambio de registro seleccionado en un Browse
Ejemplo mostrar una foto del cliente al costado del browseBRW1.TakeNewSelection PROCEDURE
! [Priority 5001]
!EN CLI:FOTO ESTA EL PATH COMPLETO DE LA FOTO
IF CLI:FOTO <>
?Foto{PROP:TEXT}= CLI:FOTO
ELSE
?Foto{PROP:TEXT}=
END
Fernando Cerini Filtros complejos en un browse
Ejemplo: Mostrar sólo los clientes que tengan alguna ordenBRW1.ValidateRecord PROCEDURE ! [Priority 5001] !FILTROS COMPLEJOS !EJ: MOSTRAR SOLO LOS CLIENTES QUE TENGAN ALGUNA ORDEN CLEAR(ORD:RECORD) Ord:CodigoCliente=CLI:ID SET(Ord:FK_Ordenes_CLIENTES,Ord:FK_Ordenes_CLIENTES) NEXT(ORDENES) IF ERRORCODE() OR (Ord:CodigoCliente <> CLI:ID) THEN RETURN Record:Filtered. RETURN Record:OKFernando Cerini
Poner un campo "saldo" en un browse
ThisWindow.Reset, antes del Parent CallS#=0
LOOP F#= 1 TO RECORDS (Queue:Browse:1)
GET(Queue:Browse:1, F#)
S# += Queue:Browse:1.ORD:Total_Impo
Queue:Browse:1.Saldo = S#
PUT(Queue:Browse:1)
END
Fernando Cerini Resetear/inicializar un Locator
El método a ejecutar esBRW1::Sort1:Locator.SetLos nombres de las instancias de Locators por cada TAB se llaman:
BRW1::Sort0:Locator.Set BRW1::Sort1:Locator.Set BRW1::Sort2:Locator.Set !etc..Luego sería bueno volver a poner el foco en el browse para que se pueda seguir usando el locator normalmente
select (?Browse:1)Fernando Cerini
En un Browse, disparar un procedimiento después de generar un alta, modificación, etc.
El proceso a disparar se hace desde el browse, la idea es saber si del Form volvió aceptando o canceló la actualización. El punto embebido es:Local Objects
BRW1 (o el objeto de tipo browse class que sea)
ResetFromAsk (después del Parent.call)
IF VCRRequest=VCR:None
CASE Request
OF InsertRecord
IF Response = RequestCompleted
!Hacer algún proceso
END
END
END!
Inserción continua refrescando el Browse
Este otro método es similar al anterior, también sirve para lo mismo. Agrego un ejemplo para hacer inserción continua en el Form y además se vaya actualizando el browseThisWindow.Run PROCEDURE(USHORT Number,BYTE Request)
! Cuidado que existen dos ThisWindow.Run, seleccionar el correcto
! [Priority 8500]
IF Request = InsertRecord And ReturnValue = RequestCompleted
POST(EVENT:ACCEPTED, BRW1.InsertControl)
END
Fernando Cerini Posicionar un browse en un registro seleccionado
Al abrir un browse a veces necesitamos posicionar el selector en un registro determinado, tal como lo hacen los browses para selección.Por ejemplo para posicionar el browse en el registro más cercano al proporcionado por el usuario:
ThisWindow.Open PROCEDURE ! Start of "WindowManager Method Data Section" ! [Priority 5000] ! End of "WindowManager Method Data Section" CODE ! Start of "WindowManager Method Executable Code Section" ! [Priority 500] CLI:cliente= LOC:Cliente ACCESS:Clientes.fetch(CLI:Kcodigo) BRW1.selecting=1 ! Parent Call PARENT.Open() ! [Priority 6300] BRW1.selecting=0Otra idea, propuesta por Matías Flores: Ubicarlo por el valor de los campos de la clave. Por ejemplo, si en un browse de clientes querés posicionarte en un cliente que tiene un código determinado (Loc:Codigo):
Cli:Codigo = Loc:Codigo SET(Cli:PorCodigo,Cli:PorCodigo) Access:Clientes.Next() BRW1.ResetFromFile() ThisWindow.Reset(True)un ejemplo parecido al anterior, pero en legacy, sería:
CLI:Codigo = LOC:Codigo BRW1::LocateMode = LocateOnValue DO BRW1::LocateRecordUna opción más es: En el WindowManager.init, antes de Initialize browse y después de OpenFiles, colocar el siguiente código:
BRWx.StartAtCurrent = True TAB:Campo = Valor_Buscado get(Tabla, TAB:Key_que_ordena_por_Campo)
En Legacy, activar un filtro sobre un browse abierto.
Esto se pone en el punto embebido "Before opening VIEW". Acordarse de BINDEAR todas las variables que se necesiten.!---ANTES DE ABRIR EL VIEW---
if SoloPendientes then
BRW2::View:Browse{Prop:Filter} = |
'MER:numertra = BRW2::Sort1:Reset:TRA:numertra AND MER:saldunid <> 0'
end
Campos Calculados
Por ejemplo cuando quieras presentar el nombre completo sin los espacios de un campo a otro. Pones en el browse una variable local. En el SETQUEUERECORD (4500)Loc:ApellidoNombre = clip(CON:Apellido) & ' ' & CON:Nombre!(Tip de Alex B.)
Código antes y después de una actualización DESDE UN BROWSE.
- Antes de la llamada al Form de actualización ! ABC Objects -> Browse on Archvivo ->Ask Priority[4500]IF Request = InsertRecord
BEEP
MESSAGE('ATENCION: SE VA A DAR ALTA ','ALTA',ICON:EXCLAMATION)
ELSIF Request = ChangeRecord
BEEP
MESSAGE('ATENCION: SE VA A ACTUALIZAR ','CAMBIO',ICON:EXCLAMATION)
ELSIF Request = DeleteRecord
BEEP
MESSAGE('ATENCION: SE VA A DAR DE BAJA ','BAJA',ICON:EXCLAMATION)
END
- Después de la actualización del registro !ABC Objects -> Browse on Provincia ->Ask Priority[5500] IF ReturnValue =1
BEEP
MESSAGE('ATENCIÓN: SE ACTUALIZÓ EL REGISTRO','ACTUALIZÓ OK',ICON:EXCLAMATION)
ELSIF ReturnValue =2
BEEP
MESSAGE('SE CANCELÓ LA ACTUALIZACIÓN DEL REGISTRO','ATENCIÓN',ICON:HAND)
END
Gustavo Olmedo (Templates Clarion) Deshabilitar/esconder (cambiar a PROP:HIDE) un control si el browse está vacío - pe. un botón-.
En el Browse.UpdateWindow(), última prioridad:?TuBoton{PROP:Disable}=Choose(Self.Records()<>0,0,1)
Iniciar un browse con un orden "sort header"
Si queremos que al abrir un browse el mismo se encuentre ordenado como si hubiéramos hecho click en el sortheader, ponemos luego del generated code del evento openwindow lo siguiente:BRW1::SortHeader.SetSortFromString('TAB:Campo')
donde TAB:Campo es uno de los campos de la tabla que estoy mostrando en el browse. EDIT IN PLACE
Edit in Place o Form, según condición
En BrowseClass, Ask, antes del Parent CallIf ?browse:1{proplist:mousedownfield} = 5
Self.AskProcedure = 0
else
Self.AskProcedure = 1
end
Puede usarse cualquier condición, en este caso si el usuario hace doble click en la 5a columna se usa edit in place, sino se usa el formulario Fernando Cerini
Desactivar Edit in Place de un campo según condición
En las propiedades del EIP del campo admitir la edición del mismo.En el EIP Field Manager del campo, TakeEvent, antes del Parent Call
if Queue:Browse:1.TAB:Campo_de_mi_Tabla > 0
Return EditAction:Cancel
thiswindow.reset(true)
end
En este ejemplo sólo permito que pueda editarse el campo si el valor actual del mismo es <= 0 Inicializar campos al insertar registro
O sea lo que hace el Prime Fields en las formasABC Objects BROWSE ON TABLA using ?LIST PrimeRecords Después del Parent Call TPS:TuCampo= valor inicial
Poner Disable un campo
Local Objects -->>
Abc Objects -->>
EIP Field Manag...for Field NombreCampo
-->>Init [Priority 5001]
If SELF.FEQ then SELF.FEQ{Prop:Disable} = true.
También se pueden cambiar otras propiedades como por ejemplo UPPER (mayúsculas) o READ ONLY (sólo lectura) If SELF.FEQ
SELF.FEQ{prop:readonly} = TRUE
SELF.FEQ{prop:upr} = TRUE
END
Fernando Cerini Campos Calculados
Por ejemplo Modificar el total cuando cambia la cantidad. Primero agregar el campo cantidad en Column SpecificEditInPlace::DET:Cantidad.TakeEvent PROCEDURE(UNSIGNED Event) .... !Despues del Parent Call
CASE ReturnValue
OF EditAction:None OROF EditAction:Cancel
ELSE
Update(Self.Feq)
BRW1.Q.DET:Total = BRW1.Q.DET:Cantidad * BRW1.Q.DET:Importe
!Etc... siempre haciendo referencia a los campos de la cola BRW1.Q.
END
Fernando Cerini Validaciones
Por ejemplo una validación básica de un número requerido!Local Objects -->> Abc Objects -->> EIP Field Manager for Field Cli:NroDocumento
-->>Take Event [Priority 5001]
! Validación del nro. de Documento
CASE ReturnValue
OF EditAction:None OROF EditAction:Cancel
ELSE
UPDATE(Self.Feq)
IF NOT BRW1.Q.Cli:NroDocumento
BEEP(BEEP:SystemExclamation) ; YIELD()
MESSAGE('No se informó el número de Documento', |
'Atención!', ICON:Exclamation)
ReturnValue = EditAction:NONE
Return ReturnValue
END
END
Validación con un lookup a una tabla !Local Objects -->> Abc Objects -->> EIP Field Manager for Field Cli:Provincia
-->>Take Event [Priority 5001]
!Valida Provincia
CASE ReturnValue
OF EditAction:None OROF EditAction:Cancel
ELSE
Update(Self.Feq)
Pro:Codigo= BRW1.Q.Cli:Provincia
IF Access:Provincia.Fetch(Pro:PorCodigo)
GlobalRequest = SelectRecord
SelectProvincia
IF NOT Pro:Codigo
ReturnValue = EditAction:NONE
Return ReturnValue
ELSE
BRW1.Q.Cli:Provincia = Pro:Codigo
BRW1.Q.Pro:NOMBRE = Pro:NOMBRE
END
END
END
Gustavo Olmedo (Templates Clarion) GLOBALES
Cambiar los mensajes de error del SQL
Con cambiar los TRN no alcanza para ese tipo de errores.Lo que se puede hacer es determinar el mensaje que viene desde el SQLServer para mostrar un mensaje mas amigable.
El embebido es Global Embeds:
Global Objets
Abc Objects
Error Manager
TakeNotify
!Antes del Parent Call
IF INSTRING('COLUMN REFERENCE', SELF.SubsString(),1,1)
SELF.Msg('El registro no puede borrarse porque tiene datos de detalle
que dependen de él. |Presione [OK] para una descripción detallada del
error',SELF.GetErrorBufferTitle(),ICON:Exclamation,Button:OK,BUTTON:OK,0)
END
Hay que armar un IF de estos por cada tipo de error... Fernando Cerini
Validación de campos (Edit in place o Form)
Para validar campos lo ideal es usar los Global EmbedsGlobal Embeds
Field Level Validation
Tabla
Campo
IF <No cumple Condición>
Message('error...')
RETURN Level:Notify
END
Esto te va a funcionar "para siempre" ya sea que uses Edit in place, un Form o código embebido para actualizar la tabla. Fernando Cerini
Suscribirse a:
Comentarios (Atom)