Si en algo he puesto empeño al escribir la versión 6.0 de Cuaderno de Bitácora ha sido en mantener la integridad referencial. Esto es, que todos los campos de los ficheros principales que se sirven de tablas auxiliares sólo puedan contener 2 tipos de valores:
El tema es que la validación del dato introducido en el cuadro de texto se realiza en el evento bValid, que se ejecuta cuando el control pierde el foco --normalmente tras pulsar la tecla tabulador o tras pinchar con el ratón en otro control--. Esto en principio impide al usuario abandonar el cuadro de texto hasta que el contenido de éste sea correcto --blanco o dato registrado--. Pero qué sorpresa me llevé al ver, de casualidad, que si, en lugar de pulsar la tecla tabulador o pinchar en cualquier control, se hace click sobre otra pestaña del folder, el evento bValid del cuadro de texto no se ejecuta.
Consultado el problema en el foro de FWH, y tras intentar controlar sin éxito el evento bChange del Folder para reevaluar los bValid de los controles mientras se hacía click en la pestaña, no tuve otra opción que recomprobar la validez de todos los controles del formulario desde el evento onClick del botón "Aceptar" que guarda los cambios. Hacer eso es fácil. Mi objetivo entonces pasó a ser conseguirlo con una función general que valiera para cualquier formulario con Folder de todo el programa. La dificultad estaba en que si el dato incorrecto se encontraba en un cuadro de texto contenido en el folder, no bastaba con enviarle el foco; primero había que cambiar de pestaña en el folder manualmente. Así que la tarea sería:
oFld:setNumFolder()
// ...
METHOD SetNumFolder() CLASS TFolder
···LOCAL i := 0
···LOCAL j := 0
···LOCAL nDlgs := len( ::aDialogs )
···LOCAL nCtrls := 0
···LOCAL oDlg
···LOCAL oCtrl
···FOR i := 1 TO nDlgs
······oDlg := ::aDialogs[i]
······nCtrls := len( oDlg:aControls )
······FOR j := 1 TO nCtrls
·········oCtrl := oDlg:aControls[j]
·········IF oCtrl:ClassName() == "TBMPGET"
············oCtrl:Cargo := i
·········ENDIF
······NEXT
···NEXT
RETURN nil
Así, ya no queda más que evaluar la función que recomprueba el contenido de los controles antes de dar por buena la acción "Aceptar":
REDEFINE BUTTON ;
···ID IDOK ;
···OF oDlg ;
···ACTION ( if( CheckGets( aGet, oFld ), ( lOk := .t., oDlg:end() ), ) )
// ...
FUNCTION CheckGets( aGet, oFld )
···LOCAL i := 0
···LOCAL nGets := len( aGet )
···LOCAL oCtrl
···LOCAL oDlg
···LOCAL lReturn := .t.
···LOCAL nFldOpt := 0
···FOR i := 1 TO nGets
······oCtrl := aGet[i]
······IF oCtrl:ClassName() == "TBMPGET"
·········IF oCtrl:bValid != nil
············IF !eval( oCtrl:bValid )
···············IF oCtrl:oWnd:oWnd == oFld
··················oDlg := oCtrl:oWnd
··················oFld:setOption( oCtrl:Cargo )
···············ENDIF
···············oCtrl:setFocus()
···············lReturn := .f.
···············EXIT
············ENDIF
·········ENDIF
······ENDIF
···NEXT
RETURN lReturn

A ver quién es el listo que mete ahora un valor incorrecto ;)
comentarios (3) |
¡¡¡ Enhorabuena Jaime !!!
Dos cabezas piensan mejor que una... :-)
Saludos
Jaime,
Este sistema es correcto menos en (por ejemplo) la siguiente situación
...
...
...
REDEFINE GET oGET1 VAR cVAR OF oDlg ;
VALID IIF( cvar == "1", MSgInfo("A"),MsgInfo("B")), (MsgInfo("C"),.T.);
UPDATE
Como puedes observar siempre (en esta simple prueba) aparecerá un msginfo cada vez que se evalúe el valid. En el caso de aplicar la función que indicas puede que el usuario quede desconcertado al aparecer ese msginfo que ya le apareció con anterioridad.
Otro caso donde pueda ser un inconveniente puede ser el siguiente:
REDEFINE GET oGET1 VAR cVAR1 OF oDlg ;
VALID IIF( cVar1 == "A", (cVar2 := "Nuevo valor", oVar2:Refresh(), .T.), .T.) ;
UPDATE
REDEFINE GET oGET2 VAR cVar2 OF oDlg UPDATE
Este caso es más común. Imagínate que dependiendo del valor de un campo rellenamos el valor de un segundo campo. Además, permitimos que ese segundo campo sea modificable por el usuario. En el ejemplo que muestro si cVar1 es igual a "A" entonces rellenamos automáticamente cVar2 con "Nuevo Valor". Entonces, según el mismo ejemplo, tambien permitimos cambiar el valor de cVar2. El usuario lo cambia a "Otro Valor". Entonces queda que cVar1 := "A" y cVar2 := "Otro Valor". Pero al aplicar tu función ¿que ocurrirá? Ocurrirá que cVar1 := "A" y cVar2 := "Nuevo Valor" con lo que estamos introduciendo valores erróneos en la aplicación.
Por lo tanto este tipo de comprobaciones 'tan automáticas' yo las desaconsejo para evitar efectos no deseados (como los descrito más arriba).
Por cierto, una versión más compacta y completa de CheckGets es la siguiente:
FUNCTION ChkGetValids(oDlg)
Local nFor, oCtl
Local nFor2, nFor3, oCtl2, oCtl3
For nFor := 1 to len(oDlg:aControls)
oCtl := oDlg:aControls[nFor]
If oCtl:Classname() = "TGET" .OR. ;
oCtl:Classname() = "TCOMBOBOX"
IF oCtl:ClassName() == "TCOMBOBOX"
/*
If oCtl:bValid != Nil ;
.and. ( oCtl:bWhen == NIL .or. Eval(oCtl:bWhen) )
msginfo("si!!!!")
ENDIF
*/
ENDIF
If oCtl:bValid != Nil ;
.and. ( oCtl:bWhen == NIL .or. Eval(oCtl:bWhen) )
If !Eval(oCtl:bValid)
oCtl:SetFocus()
Return .f.
Endif
Endif
ELSE
IF oCtl:ClassName() = "TFOLDER"
FOR nFor2 := 1 TO LEN(oCtl:aDialogs)
oCtl2 := oCtl:aDialogs[nFor2]
FOR nFor3 := 1 TO LEN(oCtl2:aControls)
oCtl3 := oCtl2:aControls[nFor3]
If oCtl3:Classname() = "TGET" .OR. ;
oCtl3:Classname() = "TCOMBOBOX"
If oCtl3:bValid != Nil ;
.and. ( oCtl3:bWhen == NIL .or. Eval(oCtl3:bWhen) )
If !Eval(oCtl3:bValid)
oCtl:aDialogs[nFor2]:SetFocus()
oCtl3:SetFocus()
Return .f.
Endif
ENDIF
Endif
NEXT
NEXT
ENDIF
ENDIF
Next
Return .t.
Se puede mejorar (utilizando aEval y aSend y otras cosas), pero como ya no la uso, no lo hice.
Saludos,
José Luis Capel
José Luis,
Gracias por el consejo. De todas formas, como te dije el otro día, por ahora voy a dejarlo como está, y más adelante, con calma (andamos pillados de tiempo), lo miraré detenidamente y veré si realmente la solución que he usado me puede causar problemas. Y me leeré tu comentario con más tiempo... que a primeras tampoco lo pillo mucho :)