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() }
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(),) } } )
- 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 …
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
- 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 …
Espero os sea de utilidad.
Saludos.