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.
FORMULARIOS
Que procedmiento llamó al Form?
Al principio del WindowManager.Init del Form (primer prioridad disponible)
GlobalErrors.GetProcedureName()
Fernando Cerini (Evolution)
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 valido. El MATCH puede usarse con expresiones regulares para validar casi cualquier cosa...
Fernando Cerini (Evolution)
Cuando se Abre/inicializa el Form
ThisWindow.Init
!Es interesante entrar por el boton 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 que se lo llamo al form: !InsertRecord EQUATE (1) !ChangeRecord EQUATE (2) !DeleteRecord EQUATE (3)
Fernando Cerini (Evolution)
Cambiar el Form a "Solo lectura" ante una condicion
ThisWindow.Init ! [Priority 4950] La prioridad es importante! IF FAC:facturado = 1 THEN SELF.Request = ViewRecord.
Fernando Cerini (Evolution)
Truco: Llamar al form para insertar desde el menu
Podriamos asumir que si la GlobalRequest esta en blanco es que se lo llamo para insertar, evitariamos 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, seria asi:
!Al principio del Init (antes de Snap-Shot global Request):
!Por ej prioridad 300
If GlobalRequest = 0
GlobalRequest = InsertRecord
DesdeMenu# = True
End
!Despues de Open Files: !Por ej prioridad 7800 If DesdeMenu# = True Access:People.PrimeAutoinc() End
Fernando Cerini (Evolution)
Llamar a un form para editar una tabla con un solo registro
Tipicamente 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()
Fernando Cerini (Evolution)
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 GRABO EL REGISTRO. !VERIFICAR SI SELF.Response=RequestCompleted A VER SI SE GRABO OK.
Fernando Cerini (Evolution)
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
Fernando Cerini (Evolution)
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
Chequear si se modificó un campo en el FORM
En el TakeCompleted ponemos el siguiente codigo:
IF NOT SELF.Primary.Me.EqualBuffer(SELF.Saved)
! message('Cambio')
END
Aporte de Pablo Guarnieri y Gustavo Olmendo
Forzar grabacion del form
Este codigo 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
REPORTES Y PROCESOS
Inicializar el numero de pagina
!WindowManager.OpenReport, despues del Parent Call
IF ReturnValue = Level:Benign
Report{PROP:NextPageNo} = xxx
END
Fernando Cerini (Evolution)
Deshabilitar la impresion
!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 opcion, 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 boton que se ha presionado. Ojo, si lo tienes traducido tienes que cambiar el 'Print this report' por tu Tool Tip.
Fernando Cerini (Evolution)
Filtros complejos en un Proceso
!Windows Manager -->> Validate Records -->> [Priority 5600]
If <Condicion de Filtro>
Return Record:Filtered
Else
Return Record:OK
END
Gustavo Olmedo (Evolution)
Habilitar o deshabilitar el Print Preview
!Windows Manager -->>Ask preview -->> [Priority 5000] Self.SkipPreview = Loc:Preview
Gustavo Olmedo (Evolution)
Cantidad de Paginas generadas
!Windows Manager -->>Ask preview -->> [Priority 5000] ENDPAGE(SELF.Report) Loc:Paginas = RECORDS(SELF.PreviewQueue)
Gustavo Olmedo (Evolution)
Imprimir un Transporte
Necesitas crear 3 details. DetailDatos DetailTransporte: aca van las variables locales con los totales DetailSalto: este detail es vacio y con la propiedad PageAfter
En el embed TakeRecord (antes de los PRINTs) habria que hacer algo asi
LOC:Fila += 1
If LOC:Fila % 28 = 0 !28 lineas por pagina...
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 (Evolution)
Acelerar las actualizaciones
Para que los procesos que agreguen registros funcionen mas rapido hay que agregar un Logout o Stream en Init y un Commit o Flush en el kill El Evolution Tools tiene una extension que hace eso mismo. (Evolution Downloads)
Se imprime o se Cancela?
Los embeds son
ABC Objectd --WindowManager(ReportManager) --CancelPrintReport -Se Cancela
--PrintReport -Se imprime
Fernando Cerini (Evolution)
Recorrer la cola de las paginas
Cada pagina 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. Esto es lo que usa por ejemplo el Evolution Mail Template para enviar estas paginas por mail.
Previewer.Open PROCEDURE
....
loop a# = 1 to RECORDS(SELF.ImageQueue)
get(SELF.ImageQueue,a#)
message(SELF.ImageQueue)
end
Fernando Cerini (Evolution)
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 (Evolution)
Imprimir un SQL
Lo que yo hago es poner todos los parametros 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 (Evolution)
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 metodo TakeRecord se usa tambien para actualizar cualquier variable calculada antes de la impresion de la banda de detalle.
Fernando Cerini (Evolution)
Impresion de imagenes desde Campo BLOB
Este codigo va en el metodo 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
Fernando Cerini (Evolution)
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 (Evolution)
Imprimir el reporte en blanco, cuando no hay registros
En WindowManager. Next, antes del Parent Call
if Primera=0 !variable local byte
Primera=1
SELF.Process.RecordsProcessed = 1
Return Level:Benign
End
Fernando Cerini (Evolution)
Detectar el ultimo 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 util 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 (Evolution)
Reporte con multiples detalles
Por Ejemplo:
Viajes |-->pasajeros |-->comisiones
Habria 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 imprimi todo
Fernando Cerini (Evolution)
Imprimir exactamente lo que estamos viendo en el browse
En el browse pones un boton 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 (Evolution)
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 ultimo en
ThisReport.Kill()
!para devolver la impresora por defecto de windows
PRINTER{PROPPRINT:Device} = CLIP(GLO:IMPRESORA)
Guillermo Ondetti, Pragma Sistemas
Detectar Salto de Pagina de Reporte
Este seria 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 pagina...
END
Fernando Cerini (Evolution)
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
Filtrar con SQL
A veces es muy util 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 metodo windowmanager.Reset, prioridad 5000
BRW1.View{PROP:SQLFILTER}='+ filtro sql'
Luego se refresca normalmente, o manualmente con ThisWindow.Reset (1)
Fernando Cerini (Evolution)
Activar un Browse invisible, o sea que esta en otro TAB
Esto es espacalmente util cuando necesitamos que el se ejecuten totalizadores del browse aunque este no este visible en la pantalla. !ABC Objects -> Local Objects -> Windows Manager-> Init Priority[7500]
BRW2.ActiveInvisible = True
Gustavo Olmedo (Evolution)
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 (Evolution)
Deshabilitar el Popup
!ABC Objects -> Local Objects -> Windows Manager-->>Init Priority[9001] Brw1.Popup.KILL
Gustavo Olmedo (Evolution)
Ordenamiento Dinamico
En Clarion6 se puede hacer automaticamente con la opcion Sort Headers. Si se quiere hacer desde codigo 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, habria que alertar el doble click en al browse, y el codigo seria 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 (Evolution)
Locator Multiple
Por ejemplo, para posicionarme en un codigo de cuenta contable: Lo que podrias hacer es poner una variable string sobre el browse y poner un codigo mas o menos asi en su accepted del entry
!Asumiendo que cada codigo tiene cuatro digitos !Revisar la seccion 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 ordenes, posicionarse segun 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 (Evolution)
Pintar un browse segun un campo
Por ejemplo cuando vemos las facturas de un Cliente se repite el nombre, este codigo 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 codigo, 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
Fernando Cerini (Evolution)
Refrescar un browse desde otra ventana
Lo mas facil seria 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 este caso no estoy usando ese parametro.
Fernando Cerini (Evolution)
Iniciar el browse en un Tab determinado
En ThisWindow.Init, al final
SELF.FirstField = ?Tab:4 !Use del tab
Fernando Cerini (Evolution)
Actualizar el resultado de un totalling 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 !ESTA ES LA VARIABLE DEL TOTALLING PUT (Encabezado) IF ERRORCODE() THEN MESSAGE(ERROR()). BRW1.ResetFromFile()
Fernando Cerini (Evolution)
Filtro Dinamico
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
Fernando Cerini (Evolution)
Rangos multiples y dinamicos
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 (Evolution)
En un Browse, deshabilitar condicionalmente la actualizacion de registros
IF <Condicion>
!Desabilitar Actualizacion
BRW1.InsertControl=FALSE
BRW1.ChangeControl=FALSE
BRW1.DeleteControl=FALSE
ELSE
BRW1.InsertControl=?INSERT
BRW1.ChangeControl=?CHANGE
BRW1.DeleteControl=?DELETE
END
Fernando Cerini (Evolution)
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
Fernando Cerini (Evolution)
Filtros complejos en un browse
Ejemplo: Mostrar solo 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
Fernando Cerini (Evolution)
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
Fernando Cerini (Evolution)
Resetear/inicializar un Locator
El metodo 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 seria bueno volver a poner el foco en el browse para que se pueda seguir usando el locator normalmente
select (?Browse:1)
Fernando Cerini (Evolution)
En un Browse, disparar un procedimiento después de generar un alta, modicació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!
Insercion continua refrescando el Browse
Este otro metodo es similar al anterior, tambien sirve para lo mismo. Agrego un ejemplo para hacer insercion continua en el Form y ademas 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
Fernando Cerini (Evolution)
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
En un BROWSE cuando quieras presentar el nombre completo sin los espacios de un campo a otro.
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 actualizacion del registro !ABC Objects -> Browse on Provincia ->Ask Priority[5500]
IF ReturnValue =1
BEEP
MESSAGE('ATENCION: SE ACTUALIZO EL REGISTRO','ACTUALIZO OK',ICON:EXCLAMATION)
ELSIF ReturnValue =2
BEEP
MESSAGE('SE CANCELO LA ACTUALIZACION DEL REGISTRO','ATENCION',ICON:HAND)
END
Gustavo Olmedo (Evolution)
Deshabilitar/esconder (cambiar a PROP:HIDE) un control si el browse está vacío - pe. un boton-.
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 hubieramos 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, segun condicion
En BrowseClass, Ask, antes del Parent Call
If ?browse:1{proplist:mousedownfield} = 5
Self.AskProcedure = 0
else
Self.AskProcedure = 1
end
Puede usarse cualquier condicion, 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 (Evolution)
Inicializar campos al insertar registro
O sea lo que hace el Prime Fields en las formas
ABC Objects BROWSE ON TABLA using ?LIST PrimeRecords Despues 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.
Tambien se pueden cambiar otras propiedades como por ejemplo UPPER (mayusculas) o READ ONLY (solo lectura)
If SELF.FEQ
SELF.FEQ{prop:readonly} = TRUE
SELF.FEQ{prop:upr} = TRUE
END
Fernando Cerini (Evolution)
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
Fernando Cerini (Evolution)
Validaciones
Por ejemplo una validacion basica de un numero requerido
!Local Objects -->> Abc Objects -->> EIP Field Manager for Field Cli:NroDocumento
-->>Take Event [Priority 5001]
! Validacion 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 informo el numero de Documento', |
'Atencion!', ICON:Exclamation)
ReturnValue = EditAction:NONE
Return ReturnValue
END
END
Validacion 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 (Evolution)
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 el. |Presione [OK] para una descripcion 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 (Evolution)
Validacion 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 Condicion>
Message('error...')
RETURN Level:Notify
END
Esto te va a funcionar "para siempre" ya sea que uses Edit in place, un Form o codigo embebido para actualizar la tabla.
Fernando Cerini (Evolution)

