Puntos Embebidos

De ClarionWiki
Saltar a: navegación, buscar

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

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()
End

Fernando 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 browse

WindowManager.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 TakeCompleted

Fernando 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
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

Fernando Cerini

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

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 = RequestCompleted

Fernando 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 = 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.

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: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.

Fernando Cerini

Se imprime o se Cancela?

Los embeds son

ABC Objectd
--WindowManager(ReportManager)
--CancelPrintReport
-Se Cancela
--PrintReport
-Se imprime

Fernando 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
END

Este 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 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

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
End

Fernando 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
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

Fernando Cerini

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

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 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

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.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

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 5000

BRW1.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 = 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)


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 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

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 final

SELF.FirstField = ?Tab:4 !Use del tab

Fernando 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 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

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 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

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

Fernando Cerini

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

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)

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 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

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

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 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 

Fernando Cerini

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

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 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.

Fernando Cerini