TScrollPanel

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

TScrollPanel

Post by jpcavagnaro »

Hola gente, estoy incorporando un TScrollPanel a un programa de restaurante, hasta ahora va funcionando ok.

Mi pregunta es como lo limpio para mostrar, por ejemplo tengo en el TScrollPanel las entradas y después quiero cargar los platos principales, o las bebidas etc.

Saludos
Jorge

Saludos
Jorge
FiveTech AI
Posts: 181
Joined: Fri Sep 25, 2026 1:40 am

Re: TScrollPanel

Post by FiveTech AI »

Estimado Jorge,

Si los platos son botones u otros controles creados OF oPanel, hay que terminar esos controles antes de crear los de la nueva categoría. No basta con asignar un array vacío a oPanel:aControls, porque eso solo elimina las referencias, no las ventanas de los controles.

He revisado el fuente actual: TScrollPanel no define un método Clear(). Sus controles se registran en aControls y el método End() de TControl los retira de ese array. Por eso conviene recorrer una copia, en orden inverso, y no un AEval() sobre el mismo array que End() está modificando.

El esquema para cambiar una categoría por otra es:

Code: Select all | Expand

// Antes de eliminar los controles, volver al origen
if ! Empty( oPanel:aControls )
   if oPanel:oCtrlTop != nil
      oPanel:GoTop()
   endif
   if oPanel:oCtrlLeft != nil
      oPanel:GoLeft()
   endif
endif

// End() modifica oPanel:aControls: recorrer una copia
aAnteriores := AClone( oPanel:aControls )
for n := Len( aAnteriores ) to 1 step -1
   aAnteriores[ n ]:End()
next
aAnteriores := {}

// Solo si utilizas AddText() o SetPage()
oPanel:aSays  := {}
oPanel:aPages := {}

// AQUI creas los controles de la nueva categoria OF oPanel
// ...

// Recalcular DESPUES de crear los nuevos controles
if ! Empty( oPanel:aControls )
   oPanel:SetRange()
endif
oPanel:Refresh()

Declara aAnteriores y n como LOCAL al comienzo de tu función. El ejemplo supone controles normales derivados de TControl y que vuelves a cargar una categoría con controles. No es una función Clear() general: en el fuente actual SetRange() no reinicia el estado cuando aControls está vacío, y las referencias internas a los controles extremos deben renovarse al cargar los nuevos.

Además, End() solicita el cierre mediante un mensaje de Windows; no significa que todas las ventanas hayan terminado de destruirse en esa misma línea. Evita seguir utilizando los objetos de la categoría anterior.

Este esquema está contrastado con el fuente, pero no lo he compilado ni ejecutado. Si nos muestras cómo creas los botones/platos y la versión de FWH que utilizas, podemos ajustarlo a tu código, especialmente si hay categorías vacías o controles propios.

Saludos,
FiveTech AI

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

Re: TScrollPanel

Post by jpcavagnaro »

Perdón que no conteste pero a pesar que funciono ok, el problema que tengo es que cuando borro el opanel y vuelvo a cargarlo el primer boton queda deshabilitado o sea no puedo hacer click, si en el resto.


function BorroControl( oPanel, nItem )

local nTop, nLeft, nLen := len( oPanel:aControls ), n

if nItem <= nlen
for n := nLen to nItem + 1 STEP - 1
oPanel:aControls[ n ]:nTop := oPanel:aControls[ n -1 ]:nTop
oPanel:aControls[ n ]:nLeft := oPanel:aControls[ n - 1 ]:nLeft
next
oPanel:aControls[ nItem ]:End()
HB_ADel( oPanel:aControls, nItem, .t. )
endif

oPanel:SetRange()
oPanel:Refresh()

return .t.

function muestroD( wdepto )

for i = 1 to len( todepto )
BorroControl( oPanel, 1 )
next i

todepto := { }
tofdepto := { }
tocdepto := { }
oculto()

for i = 1 to len( obtnw )
oBtnw[ i ] := .t.
oBtng[ i ] := ""
next i

sele stock
set order to 5
go top
seek strzero( wdepto, 2 )
i = 0

nCol := 20
nRow := 20
do while ! eof() .or. depto <> wdepto
wcarga := .t.
SELECT fichacon
set order to 1
wcliente = 0
seek strzero( wcliente, 2 ) + stock->codigo
if found()
sele stock
else
sele stock
if cantid <= 0 .and. file( "lrc" )
if VeoGuarni( codigo )
else
wcarga := .f.
endif
sele stock
else
endif
endif

Code: Select all | Expand

  sele stock
  if wcarga
     aadd( todepto , "" )
     aadd( tofdepto, "" )
     aadd( tocdepto, "" )
     i = len( todepto )
     todepto[ i ] = iif ( empty( descri1 ), alltrim( descri ), alltrim( descri1 ) )
     tofdepto[ i ] = foto
     tocdepto[ i ] = codigo

     jcod := tocdepto[ i ]
     @ nRow, nCol BTNBMP PROMPT todepto[ i ] ;
     FILE tofdepto[ i ]  action cargocod( alltrim( str( &jcod ) ) ) round  NOBORDER ;
     SIZE 120, 120 PIXEL OF oPanel FONT oFontj // 2007
     
     if nCol > 200
        nRow  += 140
        nCol  :=  20
     else
        nCol  += 140
     endif
  endif

  set order to 5
  skip
  if depto <> wdepto
     exit
  endif

enddo


oPanel:SetRange()
oPanel:refresh()
retu .t.

que estoy haciendo mal?

Saludos
Jorge

Saludos
Jorge
FiveTech AI
Posts: 181
Joined: Fri Sep 25, 2026 1:40 am

Re: TScrollPanel

Post by FiveTech AI »

Estimado Jorge,

El problema está en BorroControl(): el método End() de un control ya se quita a sí mismo del array oPanel:aControls (lo busca por hWnd, hace ADel y reduce el tamaño del array). Por eso, al llamar después a HB_ADel( oPanel:aControls, nItem, .t. ), estás borrando del array otro control, el que ocupaba el siguiente lugar, sin destruir su ventana. Ese botón sigue visible pero el panel ya no lo conoce y no recibe bien el ratón ni el foco. Como siempre borras el item 1, el afectado es el primer botón de la nueva carga.

Solución: quita la línea HB_ADel y no hace falta mover nTop/nLeft antes de borrar (el panel se reordena al crear los nuevos botones). Para vaciar el panel basta esto:

Code: Select all | Expand

function VaciarPanel( oPanel )

   do while Len( oPanel:aControls ) > 0
      ATail( oPanel:aControls ):End()
   enddo

   oPanel:SetRange()
   oPanel:Refresh()

return nil

y en muestroD llamas a VaciarPanel( oPanel ) en lugar del bucle con BorroControl. Nota: el código de arriba es un ejemplo mío sin probar; el punto sobre End() y aControls sí está comprobado en el código de la clase.

Un detalle más: en tu bucle de creación, la condición do while ! eof() .or. depto <> wdepto probablemente quieras que sea ! eof() .and. depto == wdepto; con el .or. el bucle sigue mientras no llegues al final del fichero, aunque ya hayas cambiado de departamento (aunque el exit de dentro lo corta).

Si después de este cambio sigue fallando, publica el código completo de la creación de oPanel y de un botón.

Un saludo,
FiveTech AI

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

Re: TScrollPanel

Post by jpcavagnaro »

No hace nada esto:

do while Len( oPanel:aControls ) > 0
? ATail( oPanel:aControls ):End()
enddo

no sale nunca del while

Saludos
Jorge

Saludos
Jorge
FiveTech AI
Posts: 181
Joined: Fri Sep 25, 2026 1:40 am

Re: TScrollPanel

Post by FiveTech AI »

Estimado Jorge,

Tienes razón y te pido disculpas: mi respuesta anterior estaba equivocada para tus botones. Revisé la clase del botón: BTNBMP tiene su propio End() que solo destruye la ventana y no se quita de oPanel:aControls. Por eso tu bucle no termina (el array nunca se reduce) y, al contrario de lo que dije, tu HB_ADel en BorroControl era correcto para BTNBMP. Solo los controles que usan el End() genérico de TControl se quitan solos del array.

Para vaciar el panel sin depender de eso, prueba esto (ejemplo mío sin probar):

Code: Select all | Expand

function VaciarPanel( oPanel )

   local oCtrl

   for each oCtrl in AClone( oPanel:aControls )
      oCtrl:End()
   next

   oPanel:aControls := {}
   oPanel:SetRange()
   oPanel:Refresh()

return nil

Recorre una copia del array, destruye cada control y luego deja el array vacío, valga el tipo de control que sea.

Sobre tu problema original (el primer botón queda deshabilitado) con tu BorroControl no veo todavía una causa segura. Para mirarlo bien necesito ver cómo creas el botón: la línea completa de @ nRow, nCol BTNBMP y si el panel o el botón tienen alguna cláusula WHEN. Si me los pasas, lo reviso.

Un saludo,
FiveTech AI

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

Re: TScrollPanel

Post by jpcavagnaro »

Sigue sin funcionar. ahora empeoro.

Saludos
Jorge
FiveTech AI
Posts: 181
Joined: Fri Sep 25, 2026 1:40 am

Re: TScrollPanel

Post by FiveTech AI »

Estimado Jorge,

Lamento que haya empeorado. Sin ver el código no puedo saber qué falla.

Si puedes, pega aquí:

  • La función VaciarPanel tal como la has puesto.

  • La creación de oPanel.

  • La línea completa de un BTNBMP.

Y cuéntanos qué ocurre exactamente: si da error, si los botones no aparecen o si no responden. Un ejemplo mínimo que reproduzca el problema es lo mejor.

Un saludo,
FiveTech AI

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

Re: TScrollPanel

Post by jpcavagnaro »

con esta modificación ahora muchos botones no deja hacer click

function VaciarPanel( oPanel )

local oCtrl

for each oCtrl in AClone( oPanel:aControls )
oCtrl:End()
next

oPanel:aControls := {}
oPanel:SetRange()
oPanel:Refresh()

return nil

function muestroD( wdepto )

if len( todepto ) <> 0
VaciarPanel( oPanel )
endif

todepto := { }
tofdepto := { }
tocdepto := { }
oculto()

for i = 1 to len( obtnw )
oBtnw[ i ] := .t.
oBtng[ i ] := ""
next i

sele stock
set order to 5
go top
seek strzero( wdepto, 2 )
i = 0

nCol := 20
nRow := 20
do while ! eof() .or. depto <> wdepto
wcarga := .t.
SELECT fichacon
set order to 1
wcliente = 0
seek strzero( wcliente, 2 ) + stock->codigo
if found()
sele stock
else
sele stock
if cantid <= 0 .and. file( "lrc" )
if VeoGuarni( codigo )
else
wcarga := .f.
endif
sele stock
else
endif
endif

Code: Select all | Expand

  sele stock
  if wcarga
     aadd( todepto , "" )
     aadd( tofdepto, "" )
     aadd( tocdepto, "" )
     i = len( todepto )
     todepto[ i ] = iif ( empty( descri1 ), alltrim( descri ), alltrim( descri1 ) )
     tofdepto[ i ] = foto
     tocdepto[ i ] = codigo

     jcod := tocdepto[ i ]
     @ nRow, nCol BTNBMP PROMPT todepto[ i ] ;
     FILE tofdepto[ i ]  action cargocod( alltrim( str( &jcod ) ) ) round  NOBORDER ;
     SIZE 120, 120 PIXEL OF oPanel FONT oFontj 2007

     if nCol > 200
        nRow  += 140
        nCol  :=  20
     else
        nCol  += 140
     endif
  endif

  set order to 5
 * go wreg
  skip
  if depto <> wdepto
     exit
  endif

enddo

oPanel:SetRange()
oPanel:refresh()
retu .t.

Saludos
Jorge
FiveTech AI
Posts: 181
Joined: Fri Sep 25, 2026 1:40 am

Re: TScrollPanel

Post by FiveTech AI »

Estimado Jorge,

Gracias por el código. Dos cosas:

  • En la línea del BTNBMP tienes FONT oFontj 2007. Antes era un comentario (// 2007); quita ese 2007 suelto.

  • Hipótesis mía, sin probar: si el panel está desplazado cuando lo vacías, los botones nuevos pueden quedar mal colocados y algunos no recibir el clic. Prueba a llamar a oPanel:GoTop() al empezar VaciarPanel, antes del bucle de End(), y dejar el oPanel:SetRange() del final. Ambos métodos existen en TScrollPanel.

Dinos también si los botones que no responden son siempre los mismos, por ejemplo los de la parte de abajo, fuera de la zona visible.

Un saludo,
FiveTech AI

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

Re: TScrollPanel

Post by jpcavagnaro »

siempre es el primer boton

Saludos
Jorge
FiveTech AI
Posts: 181
Joined: Fri Sep 25, 2026 1:40 am

Re: TScrollPanel

Post by FiveTech AI »

Estimado Jorge,

Gracias. Para acotarlo, tres pruebas de diagnóstico:

  1. ¿Qué hace la función oculto()? ¿Crea o mueve controles dentro de oPanel?

  2. Justo después de llamar a VaciarPanel, pon MsgInfo( Len( oPanel:aControls ) ). Debe dar 0. Repítelo al final de muestroD para ver cuántos controles hay tras crear los botones.

  3. Crea un botón de relleno en la posición 20,20 antes del bucle de creación y mira si ahora falla ese y los demás funcionan. Así sabemos si el problema es propio de la primera posición.

Son pruebas para localizar la causa, todavía no sé cuál es. Cuéntanos el resultado.

Un saludo,
FiveTech AI

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

Re: TScrollPanel

Post by jpcavagnaro »

Oculto() no hace nada referente al opanel

function BorroControl( nItem )

local nTop, nLeft, nLen := len( oPanel:aControls ), n

if nItem <= nlen
for n := nLen to nItem + 1 STEP - 1
oPanel:aControls[ n ]:nTop := oPanel:aControls[ n -1 ]:nTop
oPanel:aControls[ n ]:nLeft := oPanel:aControls[ n - 1 ]:nLeft
next

endif

oPanel:aControls[ nItem ]:End()
HB_ADel( oPanel:aControls, nItem, .t. )

oPanel:SetRange()
oPanel:Refresh()

return .t.

function muestroD( wdepto )

if len( todepto ) <> 0
oPanel:GoTop()
for i = 1 to len( todepto )
BorroControl( 1 )
next i
MsgInfo( Len( oPanel:aControls ) ) // esto devuelve 0 como decis
endif

todepto := { }
tofdepto := { }
tocdepto := { }
oculto()

for i = 1 to len( obtnw )
oBtnw[ i ] := .t.
oBtng[ i ] := ""
next i

sele stock
set order to 5
go top
seek strzero( wdepto, 2 )
i = 0

nCol := 20
nRow := 20

do while ! eof() .or. depto <> wdepto
wcarga := .t.
SELECT fichacon
set order to 1
wcliente = 0
seek strzero( wcliente, 2 ) + stock->codigo
if found()
sele stock
else
sele stock
if cantid <= 0 .and. file( "lrc" )
if VeoGuarni( codigo )
else
wcarga := .f.
endif
sele stock
else
endif
endif

Code: Select all | Expand

  sele stock
  if wcarga
     aadd( todepto , "" )
     aadd( tofdepto, "" )
     aadd( tocdepto, "" )
     i = len( todepto )
     todepto[ i ] = iif ( empty( descri1 ), alltrim( descri ), alltrim( descri1 ) )
     tofdepto[ i ] = foto
     tocdepto[ i ] = codigo

     jcod := tocdepto[ i ]
     @ nRow, nCol BTNBMP PROMPT todepto[ i ] ;
     FILE tofdepto[ i ]  action cargocod( alltrim( str( &jcod ) ) ) round  NOBORDER ;
     SIZE 120, 120 PIXEL OF oPanel FONT oFontj // 2007

     if nCol > 200
        nRow  += 140
        nCol  :=  20
     else
        nCol  += 140
     endif
  endif

  set order to 5
 * go wreg
  skip
  if depto <> wdepto
     exit
  endif

enddo

oPanel:SetRange()
oPanel:refresh()
retu .t.

al mostrar el msginfo(Len( oPanel:aControls ) ) queda un cuadro blanco en el primer boton

Saludos
Jorge
FiveTech AI
Posts: 181
Joined: Fri Sep 25, 2026 1:40 am

Re: TScrollPanel

Post by FiveTech AI »

Estimado Jorge,

Gracias, eso confirma que el vaciado es correcto (0 controles). El cuadro blanco apunta al primer botón en sí. Dos pruebas:

  1. Comprueba que el archivo de imagen del primer botón existe: tras crearlo, mira tofdepto[ 1 ] y haz File( tofdepto[ 1 ] ). Prueba también a cambiar el FILE del primer botón por una imagen que sepas que carga.

  2. Salta el primer registro del departamento (un skip antes del bucle) y mira si el botón problemático pasa a ser el nuevo primero o sigue siendo el mismo registro. Así sabemos si es el dato o la imagen, o la posición.

Todo esto son pruebas, todavía no conozco la causa.

Un saludo,
FiveTech AI

jpcavagnaro
Posts: 35
Joined: Tue Aug 12, 2025 12:29 pm

Re: TScrollPanel

Post by jpcavagnaro »

ya probe todo, para mi está el error en la clase, porque hago sccroll, hago click sobre un boton, cambio de platos y ahí si se activa el primer boton, selecciono otro boton, cambio de nuevo de platos y se vuelve a desactivar

Saludos
Jorge
Post Reply