Page 1 of 1

Cerrar un dialogo

Posted: Fri May 29, 2015 12:03 pm
by antolin
Hola foreros

Repasando algunos asuntos que tenía pendientes, he notado que en este foro ha salido, en distintas ocasiones, el tema de cerrar un dialogo cuando pulsamos fuera con el ratón, sin que se le haya dado una solución genérica. Bueno pues quiero exponer aquí algunas soluciones que he aplicado a mis programas y que me han ido muy bien.

Como podrá imaginar todo depende del tipo de dialogo y de la cantidad de controles que tenga el dialogo; lo que sí es absolutamente necesario es que sea no modal (NOWAIT). Aun así he probado distintas soluciones, unas me han ido mejor que otras. Principalmente utilizo tres métodos, todos efectivos (al menos en mis programas):

1º Tengo un dialogo NOWAIT con un solo control. Por ejemplo un Listbox que ocupa todo el dialogo. Solución:

Code: Select all

DEFINE DIALOG oDlg ….
   REDEFINE LISTBOX oLbx …
ACTIVATE DIALOG oDlg NOWAIT …
*
oLbx:bLostFocus = { || oDlg:End() }
 
Con la única precaución de asegurarse de que el Listbox toma el foco siempre que se activa el dialogo. Por ejemplo comprobando que el nStyle del Listbox contiene la directiva WS_TABSTOP.

2º Tengo un dialogo con dos o tres controles. Por ejemplo un Listbox y dos botones. Es muy común. Mi solución:

Code: Select all

LOCAL oCtrl[3]
*
DEFINE DIALOG oDlg ….
   REDEFINE LISTBOX oCtrl[1] …
   REDEFINE BUTTON oCtrl[2] …
   REDEFINE BUTTON oCtrl[3] …
ACTIVATE DIALOG oDlg NOWAIT …
*
AEVAL( oCtrl, { |oCt|  oCt:bLostFocus := { || IF(GetActiveWindow() # oDlg:hWnd,oDlg:End(),) } } )
 
Precauciones:
- Que al menos uno de los controles tome el foco al activarse el dialogo.
- Definir todos los controles un en solo array, en este caso oCtrl[3] para manejarlos con AEVAL(), ahorra tener que definir un bLostFocus para cada control, aunque tampoco pasa nada pues sólo son tres controles.

También podría dirigir todos los bLostFocus hacia una función que controle el cierre del dialogo:
AEVAL( oCtrl, { |oCt| oCt:bLostFocus := { |Self| FuncEnd( Self,…) } } )

¿Ha notado que, en _, he definido los bLostFocus después de activar el dialogo? Es importante, pero no necesario, aunque aconsejo que lo haga así, especialmente cuando tiene que definir un ON INIT para el dialogo.

Por otra parte, he de señalar que muchas veces el bLostFocus interfiere con las acciones a las que llaman los distintos controles. Por ejemplo desplegar un MessageBox al pulsar uno de los botones, en cuyo caso se activaría el bLostFocus y se cerraría el dialogo inoportunamente. No es problema, en la función que llama al MessageBox guarde el contenido del bLostFocus en una variable, ponga el bLostFocus a NIL y recupérelo después de la acción, al final de la función.

Code: Select all

FUNCTION MiFunc(…)
   LOCAL bLost := oCtrl: bLostFocus
   *
   bLostFocus := NIL
   …
   MSGALERT( … )
   …
   bLostFocus := bLost
RETURN …

O

FUNCTION MiFunc(…)
   LOCAL bLost := oCtrl[1]: bLostFocus
   *
   AEVAL(oDlg:aControls,{ |o| o:bLostFocus := NIL })
   …
   MSGALERT( … )
   …
   AEVAL(oDlg:aControls,{ |o| o:bLostFocus := bLost })
RETURN …

 
3º Tengo un dialogo NOWAIT complejo, con controles de todo tipo, y el dialogo tiene definidas clausulas ON INIT, VALID, etc y quiero que se cierre cuando pulso fuera con el ratón, sin renunciar al comportamiento normal del dialogo, todos sus controles, sus cláusulas y codeblocks.

Esta solución implica modificar ligeramente la clase TWINDOW, o en su defecto la TDIALOG, pero merece la pena, pues el programa hace exactamente lo que queremos que haga. En cuanto pulsas fuera del dialogo este ejecuta su VALID y se cierra (si el VALID devuelvió .T. claro)

1) Hay que añadir un data, el que recoje el codeblock que deberá ejecutarse cuando pulse fuera del dialogo. Yo lo he llamado ‘bNcLost’, pero podeis darle el nombre que más os guste

- Hay que modificar el METHOD NcActivate() de TWINDOWS de la siguiente manera (o podeis crearlo en TDIALOG):

Code: Select all

METHOD NcActivate( lOnOff )     CLASS TDIalog
   LOCAL hFWnd := GetFocus()
   *
   IF !lOnOff .AND. ::hWnd # hFWnd
      IF ::bNcLost # NIL
         Eval( ::bNcLost, Self, hFWnd)
      ELSEIF ::bLostFocus # NIL
         Eval( ::bLostFocus, Self, hFWnd )
      ENDIF
   ENDIF
RETURN NIL
 
Si analizáis el METHOD comprobará que varía muy poquito respecto del original, aunque no lo parezca.

- ahora, en vuestro programa, definid vuestro dialogo de la siguiente manera:

Code: Select all

DEFINE DIALOG oDlg ….
   REDEFINE LISTBOX oLbx …
   REDEFINE SAY oSay1 …
   REDEFINE SAY oSay2 …
   …
   REDEFINE BUTTON oBot1 …
   REDEFINE BUTTON oBot2 …
   …
   oDlg: bPainted  := { |hdc| …}
   oDlg: bRClicked := { ||  … }
  …

oDlG:bNcLost   := { |oDiag| PostMessage(oDiag:hWnd,WM_CLOSE) } 
o
oDlG:bNcLost   := { |oDiag| FuncClose( oDiag,…) } 

  …
ACTIVATE DIALOG oDlg NOWAIT … ON INIT … VALID …
 
En este caso no es necesario definir bNcLost después de activar el dialogo.

Espero os sea de utilidad.

Saludos.

Re: Cerrar un dialogo

Posted: Fri May 29, 2015 12:27 pm
by hmpaquito
Magnifico post Antolín.

Antonio Linares puso un post para cerrar un dialog. Utilizaba la tecnica de capturar el mouse para el dialogo. Cualquier clicking fuera de las dimensiones del dialogo provocaba el cierre de este. No lo llegue a probar pero la idea me parecio muy buena: el proceso era limpio y rapido.

De tus metodos, el que parece que me gusta mas es el ultimo. Creo que fwh deberia incorporar de serie un sistema que hiciera esto, especialmente pensando en los sistemas tactiles. Quiza se podria aprovechar tu sistema: me parece limpio y claro.

Re: Cerrar un dialogo

Posted: Fri May 29, 2015 3:26 pm
by antolin
La verdad es que, ahora, mirando mi post, me he dado cuenta de un pequeño detalle. Parece como si bNcLost hiciera lo mismo que bLostFocus, sin embargo he de decir, en honor a la verdad, que yo no he modificado el METHDO de TWINDOWS, pues tengo un TDIALOG modificado y en esa clase he añadido el METHOD NcActivate (sin la parte de bLostFocus). En teoría es lo mismo puesto que TDIALOG hereda de TWINDOWS, pero lo que si es cierto es que con bLostFocus de TWINDOS no ocurre el efecto que logro con el bNcLost de mi TDIALOG y no se porqué.

Un saludo

Re: Cerrar un dialogo

Posted: Fri May 29, 2015 4:58 pm
by horacio
Colegas, aquí pongo un ejemplo donde nunca se llega a abrir el dialogo, ya que no hace nunca foco.

Code: Select all

#include 'fivewin.ch'

//----------------------------------------------------------------------------//
Function Main()

    Local oGet
    Local oDlg
    Local cVar := Space( 60 )
    
    Define Dialog oDlg From 100, 100 To 400, 500 Pixel
    
        @ 20, 10 Get oGet Var cVar Of oDlg Size 100, 12 Picture '@!' Action AbreCaja( oGet, oDlg ) Bitmap FWDArrow() Pixel
        
    Activate Dialog oDlg
    Return 0

//----------------------------------------------------------------------------//
Function AbreCaja( oGet, oDialogo )

    Local oDlg
    Local aPoint := { oGet : nTop() - 80, oGet : nLeft() }
    
    ClientToScreen( oDialogo : hWnd, aPoint )

    Define Dialog oDlg Of oDialogo From aPoint[ 1 ] + 110, aPoint[ 2 ] To aPoint[ 1 ] + 180, aPoint[ 2 ] + 180 Pixel Style nOr( WS_POPUP, WS_BORDER ) Color 0, CLR_WHITE

        oDlg : bLostFocus := { || oDlg : End() }
        
    Activate Dialog oDlg On Init oDlg : SetFocus() NoWait
    Return 0    
 
Alguna solución para este problema ? Muchísimas gracias

Saludos

Re: Cerrar un dialogo

Posted: Fri May 29, 2015 5:09 pm
by hmpaquito
horacio,

Si el problema es que no hay ningun control definido, yo definiria un get asi:

@ 3000,3000 GET cValor // Workaround

Esta solucion tambien es valida cuando hay que validar el ultimo get, pero no se valida porque no pierde el foco porque no hay control después.

Saludos

Re: Cerrar un dialogo

Posted: Fri May 29, 2015 5:16 pm
by horacio
Antolín, aunque le ponga un control tampoco llega a hacer foco. Me he estado peleando con esto hace tiempo. Lo que noté es que no ocurre en una window con un dialog NoWait, con esa configuración si funciona.

Saludos

Re: Cerrar un dialogo

Posted: Fri May 29, 2015 5:24 pm
by horacio
Un ejemplo con un control en el dialogo que quiero abrir

Code: Select all

#include 'fivewin.ch'

//----------------------------------------------------------------------------//
Function Main()

    Local oGet
    Local oDlg
    Local cVar := Space( 60 )
    
    Define Dialog oDlg From 100, 100 To 400, 500 Pixel
    
        @ 20, 10 Get oGet Var cVar Of oDlg Size 100, 12 Picture '@!' Action AbreCaja( oGet, oDlg ) Bitmap FWDArrow() Pixel
        
    Activate Dialog oDlg
    Return 0

//----------------------------------------------------------------------------//
Function AbreCaja( oGet, oDialogo )

    Local oDlg
    Local aDatos := { 'UNO', 'DOS', 'TRES' }
    Local aPoint := { oGet : nTop() - 80, oGet : nLeft() }
    
    ClientToScreen( oDialogo : hWnd, aPoint )

    Define Dialog oDlg Of oDialogo From aPoint[ 1 ] + 110, aPoint[ 2 ] To aPoint[ 1 ] + 180, aPoint[ 2 ] + 180 Pixel Style nOr( WS_POPUP, WS_BORDER ) Color 0, CLR_WHITE

        
        @ 0, 0 xBrowse oBrw DataSource aDatos Columns 1 Size 92, 35 Pixel NoBorder
        oBrw : CreateFromCode()     
        
        oDlg : bLostFocus := { || oDlg : End() }
        
    Activate Dialog oDlg On Init oBrw : SetFocus() NoWait
    Return 0    
 
Saludos

Re: Cerrar un dialogo

Posted: Mon Jun 01, 2015 8:30 am
by antolin
Horacio, si no hay controles, no pueden tomar o perder el foco... La única solución que a mi me funciona es la tercera vía que he explicado en mi post: 'bNcLost', significa modificar TDIALOG, pero merece la pena, funciona muy bien.

Re: Cerrar un dialogo

Posted: Mon Jun 01, 2015 8:38 am
by antolin
En cuanto al xBrowse, Horacio, no sé, hace muchos años que no lo utilizo. Asegúrate de que tiene definido el WS_TABSTOP en su nStyle y que el dialogo permite que el xBrowse tome el foco. También asegúrate de que no le has definido el bLostFocus por otro lado. Pero primero, prueba a poner bLostFocus fuera de la estructura DEFINE DIALOG...ACTIVATE, es decir después del ACTIVATE, a veces me ha pasado. Si no, prueba con la segunda fórmula:

oBrw:bLostFocus := { || IF(GetActiveWindow() # oDlg:hWnd,oDlg:End(),) }

Espero te sirva. Un saludo

Re: Cerrar un dialogo

Posted: Mon Jun 01, 2015 8:53 am
by Antonio Linares
Horacio,

Yo lo hago asi:

Code: Select all

function PopupBrowse( aValue, oGet, bInit )

   local oDlg, oBrw
   local aPoint := { oGet:nTop + oGet:nHeight, oGet:nLeft } 

   if oGet:Cargo == nil
      aPoint = ClientToScreen( oGet:oWnd:hWnd, aPoint )
    
      DEFINE DIALOG oDlg OF oGet:oWnd STYLE WS_POPUP ;
         SIZE 500, 180      
      
      ACTIVATE DIALOG oDlg NOWAIT ;
         ON INIT oDlg:SetPos( aPoint[ 1 ], aPoint[ 2 ] )
    
      @ 0, 0 XBROWSE oBrw ARRAY aValue AUTOSORT ;
         SIZE oDlg:nWidth, oDlg:nHeight OF oDlg PIXEL
         
      oBrw:CreateFromCode()     
            
      Eval( bInit, oBrw )                   

      oBrw:PostMsg( WM_SETFOCUS )
      oBrw:bKeyDown = { | nKey | If( nKey == VK_RETURN, oDlg:End(),) }      
      oBrw:bChange = { || oGet:VarPut( oBrw:aCols[ 1 ]:Value ), oGet:Refresh() }        
      oBrw:bLButtonUp = { | nRow, nCol | If( nRow > oBrw:nHeaderHeight,;
                          ( Eval( oBrw:bChange ), oDlg:End(),;
                            oGet:oWnd:GoNextCtrl( oGet:hWnd ),;
                            oGet:Cargo := nil ),) }
                             
      oBrw:Seek( oGet:GetText() )                                                             

      oGet:Cargo = oDlg     
      oGet:bKeyDown = { | nKey | If( nKey == VK_DOWN, oBrw:GoDown(),),;
                                 If( nKey == VK_UP, oBrw:GoUp(),),;
                                 If( nKey == VK_RETURN, oDlg:End(),), 0 }
                                 
      oGet:oWnd:bLClicked = { || oDlg:End(), oGet:Cargo := nil }                                                                    
      oGet:oWnd:bMouseWheel = { || oDlg:SetFocus() }
   else
      oGet:Cargo:End()
      oGet:Cargo = nil      
   endif        
            
return nil               

Re: Cerrar un dialogo

Posted: Mon Jun 01, 2015 9:12 am
by antolin
Exacto, Horacio, se trata de que tu xBrowse tome el foco para poder decirle que cierre el dialogo cuando lo pierda. Antonio lo solventa muy sabiamente con oBrw:PostMsg( WM_SETFOCUS ). Lo que no veo tan claro es:

Code: Select all

      oGet:oWnd:bLClicked = { || oDlg:End(), oGet:Cargo := nil }                                                                    
      oGet:oWnd:bMouseWheel = { || oDlg:SetFocus() }
 
¿Que pasa si pincho fuera del oGet:oWnd?, y por otro lado, porque hay que decirle al xBrowse que retome el foco a cada MouseWheel. No sería más efectivo que no pierda el foco con la rueda del ratón, porqué habría de perderlo? Todo desde el mayor de los respetos por el maestro claro.

Re: Cerrar un dialogo

Posted: Mon Jun 01, 2015 9:14 am
by antolin
Perdón, he confundido oGet:oWnd:bMouseWheel con oBrw:bMouseWheel. Mejor será que me calle a ese rescpecto... Perdón de nuevo