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 Expand view RUN
- 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 Expand view RUN
- 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 ambos casos, 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 Expand view RUN
- 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 Expand view RUN
- 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 Expand view RUN
- 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.