Puntos Embebidos
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.
FORMULARIOS
¿Qué procedimiento llamó al Form?
Al principio del WindowManager.Init del Form (primer prioridad disponible)
GlobalErrors.GetProcedureName()
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...
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)
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.
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() End
Llamar a un form para editar una tabla con un solo registro
Típicamente una tabla de parametros, obviamente no queremos pasar por un browse
WindowManager.Init, !al principio GlobalRequest = ChangeRecord
!Luego de Open files Set(Tabla) access:Tabla.Next()
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.
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 TakeCompleted
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 end
Hay 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.Campo
Forzar grabación del form
Este código sirve para grabar el form inclusive cuando el usuario lo cancele
TakeCloseEvent PROCEDURE(),BYTE,VIRTUAL
IF loc:condicion SELF.CancelAction = Cancel:Save !immediate save (no confirmation) END
Ivan 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
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 = RequestCompleted
Inicializar el número de página
!WindowManager.OpenReport, despues del Parent Call IF ReturnValue = Level:Benign Report{PROP:NextPageNo} = xxx END
Deshabilitar la impresión
!Local Objects, Abc Objects, Previewer, Display, After ParentCall. !impresión prohibida ReturnValue = False
Por 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.
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:Preview
Gustavo 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 PageAfter
En 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.
Se imprime o se Cancela?
Los embeds son
ABC Objectd --WindowManager(ReportManager) --CancelPrintReport -Se Cancela
--PrintReport -Se imprime
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
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.
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.
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 END
Este método TakeRecord se usa también para actualizar cualquier variable calculada antes de la impresión de la banda de detalle.
Impresión de imagenes desde Campo BLOB
Este código va en el método TakeRecord, antes del PRINT
Report$?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
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
Evitar el cartel "No records to process" en un proceso
!En el metodo TAKE NO RECORDS poner un RETURN antes del parent call.
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 End
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 END
Mario 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 End
Esto 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
Reporte con múltiples detalles
Por Ejemplo:
Viajes |-->pasajeros |-->comisiones
Habrí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
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)
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 INCLUDEs
INCLUDE('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
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.app
En 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
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 5000
BRW1.View{PROP:SQLFILTER}='+ filtro sql'
Luego se refresca normalmente, o manualmente con ThisWindow.Reset (1)
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 = True
Gustavo 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.PrimeRecord
Gustavo Olmedo (Templates Clarion)
Deshabilitar el Popup
!ABC Objects -> Local Objects -> Windows Manager-->>Init Priority[9001] Brw1.Popup.KILL
Gustavo 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)
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 Call
Aux = 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
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.
Iniciar el browse en un Tab determinado
En ThisWindow.Init, al final
SELF.FirstField = ?Tab:4 !Use del tab
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()
Filtro Dinámico
Poner un control Text donde el usuario pueda escribir cualquier filtro
X# = EVALUATE(Filtro) IF ERRORCODE() MESSAGE('ERROR DE SINTAXIS EN EL FILTRO: ' & ERROR(), 'ATENCION') ELSE BRW1.SetFilter(Filtro) ThisWindow.Reset(1) END
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)
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
Detectar el cambio de registro seleccionado en un Browse
Ejemplo mostrar una foto del cliente al costado del browse
BRW1.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
Filtros complejos en un browse
Ejemplo: Mostrar sólo los clientes que tengan alguna orden
BRW1.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:OK
Poner un campo "saldo" en un browse
ThisWindow.Reset, antes del Parent Call
S#=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
Resetear/inicializar un Locator
El método a ejecutar es
BRW1::Sort1:Locator.Set
Los 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)
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 browse
ThisWindow.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
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=0
Otra 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::LocateRecord
Una 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 Call
If ?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
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 formas
ABC 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
Campos Calculados
Por ejemplo Modificar el total cuando cambia la cantidad. Primero agregar el campo cantidad en Column Specific
EditInPlace::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
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...
Validación de campos (Edit in place o Form)
Para validar campos lo ideal es usar los Global Embeds
Global 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.