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.

Tabla de contenidos

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)

Herramientas personales