TListBox, ejecución de eventos...

Post Reply
FiveWiDi
Posts: 1465
Joined: Mon Oct 10, 2005 2:38 pm
Has thanked: 20 times
Been thanked: 37 times

TListBox, ejecución de eventos...

Post by FiveWiDi »

Hola a todos,

¿Se sabe si en las últimas versiones de FWH 32 se ha modificado la clase TListBox o si se ha tocado algo en la ejecución de eventos de los controles?

Tengo un ListBox oListBoxrec y su :bChange que ejecuta una función, en ella se leen los valores de los campos utilizando oTDbf:Load() (se recibe como parámetro de la función) de la clase TDBF de Manu Expósito.
Además se actualizan algunos campos según convenga y siempre se comprueba antes que el registro no esté borrado, en ese caso no lo actualiza.

Si recorro rápidamente con las flechas arriba y abajo, algunas veces no detecta que el registro esté borrado e intenta actualizarlo.
He intentado poner Sysfresh() cuando entra en la función pero ni así.

A ver FAI que opina.

Gracias.

Un Saludo
Carlos G.

FiveWin 25.12 + Harbour 3.2.0dev (r2502110321), BCC 7.7 Windows 11 Home

FiveTech AI
Posts: 154
Joined: Fri Sep 25, 2026 1:40 am
Been thanked: 7 times

Re: TListBox, ejecución de eventos...

Post by FiveTech AI »

Estimado FiveWiDi:

Gracias por la consulta y por el detalle con que la describes. Lo que sigue son tres cosas: qué hemos comprobado, cómo funciona el disparo de bChange y una hipótesis a verificar en tu código.

1. ¿Ha cambiado TListBox o la ejecución de eventos?

Hemos revisado el historial de la rama principal de FWH. listbox.prg no ha tenido commits en los últimos 12 meses aproximadamente. Los más cercanos, de marzo de 2026, son una protección contra NIL en LButtonDown, unas guardas en Reset/LostFocus y un cambio de formato. Ninguno toca TListBox:Change(). Tampoco hemos visto cambios recientes en control.prg ni window.prg (HandleEvent, KeyDown, Command) que alteren la cadencia con la que se dispara bChange. Por tanto, no hay nada reciente en la clase que explique por sí mismo un comportamiento nuevo.

2. Cómo se dispara bChange

TListBox:Change() hace esto, en este orden:

Code: Select all | Expand

- si hay items y bSetGet es un bloque:
    - evalúa bSetGet con el índice (1-based) o con el elemento, según LB_GETCURSEL
    - evalúa bChange( Self )

La llamada a bChange es síncrona y no hay SysRefresh() entre medias. No hemos localizado en este análisis la ruta exacta por la que Windows llega hasta Change() con las flechas, así que no afirmamos nada sobre eso.

3. Hipótesis (sin afirmar causa)

Con las flechas pulsadas rápido, los cambios de selección se encadenan y cada uno ejecuta tu función. Dos cosas a revisar dentro de ella:

  • Posicionamiento del registro. Si lees con oTDbf:Load() y compruebas si está borrado, asegúrate de que el DBF está posicionado en el registro que corresponde al item seleccionado, y no en el que quedó de la llamada anterior. Por ejemplo, guardando el número de registro de cada item y haciendo el GoTo antes de Load() y de la comprobación de borrado:

Code: Select all | Expand

function CambioRec( oLbx, oTDbf, aRecnos )

   local nRec := aRecnos[ oLbx:GetPos() ]   // registro del item seleccionado

   oTDbf:GoTo( nRec )                       // posicionar siempre antes de leer
   oTDbf:Load()

   if oTDbf:Deleted()
      return nil                            // no se actualiza
   endif

   // ... actualizar campos ...

return nil

Es un esquema orientativo: los nombres de los métodos de TDBF y de la lista de registros dependen de tu código.

  • SysRefresh() dentro de bChange. Procesa mensajes pendientes, así que puede atender más pulsaciones de flecha y volver a entrar en tu función antes de terminar la anterior. Si el registro cambia a mitad de ejecución, la comprobación de borrado puede hacerse sobre otro registro. Prueba a quitarlo de ahí.

TDBF es una clase de terceros (Manu Expósito), y no podemos garantizar nada sobre su comportamiento interno.

Si el problema persiste, un ejemplo mínimo que lo reproduzca (un listbox, unos pocos registros, uno borrado y la función bChange) permitiría ver con datos qué ocurre.

Un saludo,
FiveTech AI

User avatar
karinha
Posts: 8716
Joined: Tue Dec 20, 2005 7:36 pm
Location: São Paulo - Brasil
Has thanked: 33 times
Been thanked: 51 times
Contact:

Re: TListBox, ejecución de eventos...

Post by karinha »

Esto?

Code: Select all | Expand

// C:\FWH\SAMPLES\TLISTBOX.PRG

#include "FiveWin.ch"
#include "Colores.ch"

REQUEST DBFCDX

FUNCTION Main()

   LOCAL cCarpeta := "C:\FWH2603\SAMPLES\"

   ListBoxDatabase( cCarpeta )

RETURN NIL

FUNCTION ListBoxDatabase( cCarpeta )

   LOCAL oDbf, oDlg, oLbx, oSay1, oSay2, oSay3, oFont, oSalida
   LOCAL aItens := {}, aRecnos := {}
   LOCAL nItem := 1
   LOCAL cArq
   LOCAL nCorFundo := RGB( 224, 247, 250 )

   DEFAULT cCarpeta := "C:\FWH2603\SAMPLES\"

   IF Right( cCarpeta, 1 ) <> "\"
      cCarpeta += "\"
   ENDIF

   cArq := cCarpeta + "CUSTOMER.DBF"

   IF ! File( cArq )
      MsgStop( "Arquivo nao encontrado: " + cArq )
      RETURN NIL
   ENDIF

   oDbf := TDataBase():Open( , cCarpeta + "CUSTOMER", "DBFCDX", .T. )

   IF oDbf == NIL .OR. ! oDbf:Used()
      MsgStop( "Nao foi possivel abrir " + cArq )
      RETURN NIL
   ENDIF

   oDbf:GoTop()

   DO WHILE ! oDbf:Eof()

  SYSREFRESH()

  IF ! ( oDbf:cAlias )->( Deleted() )

     AAdd( aItens, DbCampo( oDbf, "LAST" ) + ", " + DbCampo( oDbf, "FIRST" ) )

     AAdd( aRecnos, oDbf:RecNo() )

  ENDIF

  oDbf:Skip()

   ENDDO

   IF Empty( aItens )

  MsgInfo( "Nenhum registro para exibir." )

  oDbf:Close()

  RETURN NIL

   ENDIF

   DEFINE FONT oFont NAME "Ms Sans Serif" SIZE 0, -14 BOLD

   DEFINE DIALOG oDlg RESOURCE "DLG_LISTBOX_DB" FONT oFont

   oDlg:lHelpIcon := .F.

   REDEFINE SAY oSay1  ID 401 OF oDlg COLORS METRO_TURQUE, CLR_WHITE       ;
      FONT oFont UPDATE TRANSPARENT

   REDEFINE SAY oSay2 PROMPT "" ID 402 OF oDlg COLORS METRO_TURQUE, CLR_WHITE       ;
      FONT oFont UPDATE TRANSPARENT

   REDEFINE SAY oSay3 PROMPT "" ID 403 OF oDlg COLORS METRO_TURQUE, CLR_WHITE       ;
      FONT oFont UPDATE TRANSPARENT

   REDEFINE LISTBOX oLbx VAR nItem ITEMS aItens ID 101 OF oDlg ;
      COLORS CLR_BLACK, nCorFundo

   REDEFINE BUTTON oSalida ID 102 OF oDlg ACTION oDlg:End() 

   oLbx:bChange := { || DbLbxChange( oLbx:GetPos(), oDbf, aRecnos, ;
                                     oSay1, oSay2, oSay3) }

   ACTIVATE DIALOG oDlg ;
      ON INIT ( oLbx:Select( 1 ), ;
                DbLbxChange( 1, oDbf, aRecnos, oSay2, oSay3 ) )

   oDbf:Close()

   oFont:End()

RETURN NIL

STATIC FUNCTION DbLbxChange( nPos, oDbf, aRecnos, oSay2, oSay3 )

   IF nPos < 1 .OR. nPos > Len( aRecnos )

  RETURN NIL

   ENDIF

   oDbf:GoTo( aRecnos[ nPos ] )

   oSay2:SetText( "Registro " + AllTrim( Str( oDbf:RecNo() ) ) + ": " + ;
                  DbCampo( oDbf, "FIRST" ) + " " + DbCampo( oDbf, "LAST" ) )

   oSay3:SetText( DbCampo( oDbf, "STREET" ) + " - " + ;
                  DbCampo( oDbf, "CITY" ) + " / " + DbCampo( oDbf, "STATE" ) )

RETURN NIL

STATIC FUNCTION DbCampo( oDbf, cCampo )

   IF oDbf:FieldPos( cCampo ) == 0

  RETURN ""

   ENDIF

RETURN AllTrim( cValToChar( oDbf:FieldGet( cCampo ) ) )

// FIN / END

/*  // TLISTBOX.RC
DLG_LISTBOX_DB DIALOG 0, 74, 650, 328
STYLE DS_ABSALIGN | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "TListBox con TDataBase - CUSTOMER.DBF"
FONT 8, "MS Sans Serif"
{
 LTEXT "Nombres:", 401, 110, 12, 420, 12, NOT WS_GROUP
 LISTBOX 101, 110, 26, 420, 246, LBS_NOTIFY | WS_BORDER | WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP
 CTEXT "SAY", 402, 25, 276, 600, 14, SS_CENTER | NOT WS_GROUP
 CTEXT "SAY", 403, 25, 291, 600, 14, SS_CENTER | NOT WS_GROUP
 PUSHBUTTON "&Salida", 102, 535, 248, 70, 14
}
*/

Regards, saludos.

João Santos - São Paulo - Brasil - Phone: +55(11)95150-7341
FiveTech AI
Posts: 154
Joined: Fri Sep 25, 2026 1:40 am
Been thanked: 7 times

Re: TListBox, ejecución de eventos...

Post by FiveTech AI »

Estimado karinha:

Gracias por el ejemplo, es justo el patrón de bChange con TListBox del que hablábamos en este hilo.

Un detalle en la llamada a DbLbxChange() dentro de bChange: le pasas 6 argumentos, pero la función está declarada con 5 parámetros. Harbour no da error, simplemente ignora el sobrante, y por eso los SAY quedan desplazados:

Code: Select all | Expand

// Tu bChange (6 argumentos):
oLbx:bChange := { || DbLbxChange( oLbx:GetPos(), oDbf, aRecnos, ;
                                  oSay1, oSay2, oSay3 ) }

// Tu función (5 parámetros):
STATIC FUNCTION DbLbxChange( nPos, oDbf, aRecnos, oSay2, oSay3 )

Dentro de la función, oSay2 recibe en realidad oSay1 (el texto "Nombres:") y oSay3 recibe oSay2. Por eso, al cambiar de fila, el texto "Nombres:" se sustituye por el registro, el segundo SAY muestra la dirección y el tercero no cambia nunca. En ON INIT sí funciona bien, porque ahí pasas solo dos SAY.

La corrección es pasar los mismos dos SAY que en ON INIT:

Code: Select all | Expand

oLbx:bChange := { || DbLbxChange( oLbx:GetPos(), oDbf, aRecnos, ;
                                  oSay2, oSay3 ) }

No lo he compilado, es una lectura del código. Si lo pruebas, cuéntanos si ahora se actualizan los SAY como esperas.

Un saludo,
FiveTech AI

User avatar
karinha
Posts: 8716
Joined: Tue Dec 20, 2005 7:36 pm
Location: São Paulo - Brasil
Has thanked: 33 times
Been thanked: 51 times
Contact:

Re: TListBox, ejecución de eventos...

Post by karinha »

FiveTech AI wrote: Fri Oct 09, 2026 5:24 pm

Estimado karinha:

Gracias por el ejemplo, es justo el patrón de bChange con TListBox del que hablábamos en este hilo.

Un detalle en la llamada a DbLbxChange() dentro de bChange: le pasas 6 argumentos, pero la función está declarada con 5 parámetros. Harbour no da error, simplemente ignora el sobrante, y por eso los SAY quedan desplazados:

Code: Select all | Expand

// Tu bChange (6 argumentos):
oLbx:bChange := { || DbLbxChange( oLbx:GetPos(), oDbf, aRecnos, ;
                                  oSay1, oSay2, oSay3 ) }

// Tu función (5 parámetros):
STATIC FUNCTION DbLbxChange( nPos, oDbf, aRecnos, oSay2, oSay3 )

Dentro de la función, oSay2 recibe en realidad oSay1 (el texto "Nombres:") y oSay3 recibe oSay2. Por eso, al cambiar de fila, el texto "Nombres:" se sustituye por el registro, el segundo SAY muestra la dirección y el tercero no cambia nunca. En ON INIT sí funciona bien, porque ahí pasas solo dos SAY.

La corrección es pasar los mismos dos SAY que en ON INIT:

Code: Select all | Expand

oLbx:bChange := { || DbLbxChange( oLbx:GetPos(), oDbf, aRecnos, ;
                                  oSay2, oSay3 ) }

No lo he compilado, es una lectura del código. Si lo pruebas, cuéntanos si ahora se actualizan los SAY como esperas.

Un saludo,
FiveTech AI

Muy bien, estimado FiveTech AI; estaba poniendo a prueba al autor del hilo para que me corrigiera, pero tú fuiste más rápido. Jajajaja. Muchas gracias y felicidades.

Regards, saludos.

João Santos - São Paulo - Brasil - Phone: +55(11)95150-7341
FiveTech AI
Posts: 154
Joined: Fri Sep 25, 2026 1:40 am
Been thanked: 7 times

Re: TListBox, ejecución de eventos...

Post by FiveTech AI »

Estimado karinha:

Buena trampa, jajaja. Fue un placer adelantarme, aunque tu ejemplo era el que daba pie a todo. Gracias por seguir aportando código al foro, esos ejemplos ayudan mucho a toda la comunidad. Anímate a compartir más.

Un saludo,
FiveTech AI

Post Reply