Eventos VFP
Eventos VFP
Eventos VFP
com/
En la siguiente tabla se muestra la secuencia de activación general de los eventos de Visual FoxPro. Se supone que la propiedad AutoOpenTables
del entorno de datos está establecida en el valor verdadero (.T.). Otros eventos pueden tener lugar en base a interacciones de usuario y a respuesta
del sistema.
Objeto Eventos
Entorno de datos BeforeOpenTables
Conjunto de formularios Load
Formulario Load
Cursor o cursores del entorno de Init
datos
Entorno de datos Init
Objetos 1 Init
Formulario Init
Conjunto de formularios Init
Conjunto de formularios Activate
Formulario Activate
Object1 2 When
Formulario GotFocus
Object1 GotFocus
Object1 Message
Object1 Valid 3
Object1 LostFocus
Object2 3 When
Object2 GotFocus
Object2 Message
Object2 Valid 4
Object2 LostFocus
Formulario QueryUnload
Formulario Destroy
Objeto 5 Destroy
Formulario Unload
Conjunto de formularios Unload
Entorno de datos AfterCloseTables
Entorno de datos Destroy
Cursor o cursores del entorno de Destroy
datos
1 Para cada objeto, desde el objeto más interno hasta el contenedor más externo
2 Primer objeto según el orden de tabulación
3 Siguiente objeto que va a recibir el enfoque
4 Cuando el objeto pierde el enfoque
5 Para cada objeto, desde el contenedor más externo hasta el objeto más interno
Eventos y métodos.
Primero, antes de meternos en los eventos que se ejecutan durante varias operaciones en Visual
FoxPro, permítanme clasificar los eventos y métodos que se encuentran disponibles en el producto.
Prefiero referirme a estas localizaciones de código como métodos de evento y métodos. La
diferencia entre un método de evento y un simple método está en cómo se activa. Un método
simple solo se dispara si una línea de código lo ha llamado, puede tener un comportamiento
predeterminado dentro de VFP o puede simplemente estar donde se pueda escribir el código y
luego llamar a ese código cuando desee.
Un método de evento es automáticamente llamado si un evento ocurre, por ejemplo el método de
evento KeyPress de un control será automáticamente llamado en cualquier momento que el
usuario presione una tecla mientras el foco lo tenga este control. Es importante diferenciar entre el
evento (presionar la tecla) y el método de evento (KeyPress) que se activa como resultado del
evento que ocurre. Llamar a un método de evento en un código, no causa que el evento asociado
ocurra. Simplemente corre el código de ese método de evento.
Para utilizar las ventajas del modelo de eventos en Visual FoxPro es necesario entender claramente
los métodos de eventos que son activados y el punto en el que son activados. En el proceso de
recibir el foco la secuencia de métodos de eventos es 1 – When y luego 2 – GotFocus
El método de evento when puede ser pensado como el lugar para determinar si se le permitirá o no
al foco llegar al control. Devolviendo un valor .F. desde el método de evento When evita que el
foco llegue al control. Una vez que el evento devuelve un valor .T. el GotFocus es disparado (si el
When devuelve .F. no se ejecuta el GotFocus.) El GotFocus puede ser clasificado como un método
de evento en el cual se prepara al control para recibir el foco, ya que solo se dispara si el control en
efecto recibirá el foco.
El método SetFocus tiene un comportamiento predeterminado en el inicio del proceso de recibir el
foco por el control. El método SetFocus de un control es similar a la variable de foxpro2.x _curobj
Existen dos controles que actúan ligeramente diferente con relación al método de evento When.
Son el ListBox y Combobox. Con estos dos controles, el evento When se dispara de igual forma que
para otros controles, cuando llega el foco y antes de que el objeto en realidad lo tome. El When
puede devolver .F. y evita que el foco llegue al control.
Sin embargo, con listas y combos, el When también se dispara a cada momento cuando el item
seleccionado cambia. Debido a este comportamiento es importante ser cuidadosos con el código
que se coloca en el evento When de listas y combos. Debe estar seguro que el When se disparará
tantas veces para estos controles, como el usuario navegue por sus listas.
El proceso de pérdida del foco es similar. Existen dos métodos de evento involucrados, Valid y
LostFocus. El Valid se dispara primero y puede ser utilizado para evitar que el control pierda el foco.
Devolver .F. en el Valid ocasionará que el control retendrá el foco. Devolver .T. del valid permitirá al
foco abandonar el control y con seguridad se disparará el LostFocus. Igual que el When y GotFocus,
el Valid puede ser usado para decidir si se permitirá al foco abandonar el control y el LostFocus
puede ser utilizado para controlar las reacciones del control cuando pierda el foco.
Si, el diseño de los controles en VFP es tal que ellos actualizan su ControlSource con su Value
inmediatamente antes de que sea llamado el evento Valid. Si el evento Valid no es llamado
entonces el ControlSource no será actualizado.
En la sección previa hemos discutido el evento Valid y encontramos que se activa en respuesta a la
intención del control de perder el foco.
En el primer caso, el InteractiveChange, el control no pierde el foco, no se dispara el Valid y el
ControlSource retiene su valor original, la llamada a refrescar (Refresh) del formulario causa que el
control también sea refrescado, durante su refresque re_ lee su ControlSource (el que todavía tiene
su control original)
En el segundo caso, la barra de herramientas, el problema se encuentra en el hecho de que las
barras de herramientas y menús nunca reciben el foco. Como el toolbar nunca recibe el foco el
control nunca pierde el foco y el Valid nunca será disparado y el ControlSource no será actualizado.
Antes de mirar las soluciones, vamos a correr un pequeño test para ver los efectos de este
comportamiento. En esta prueba cree un formulario con un cuadro de texto (textbox) que tenga un
campo de una tabla como ControlSource. Luego vaya a cada uno de los siguientes eventos y entre:
DEBUGOUT “<Event Name> ControlSource: “ + EVALUATE(THIS.ControlSource) ;
+ “Value: “ + THIS.Value
Reemplace <Event Name> con el nombre del método de evento en particular en el que está
colocando el código. Coloque este código en los métodos de evento: GotFocus, InteractiveChange,
KeyPress, Valid, y LostFocus.
Luego, abra el depurador y corra el formulario. Teclee el nuevo valor dentro del textbox y luego
presione Tab para salir del textbox. En la ventana del Depurador verá unos resultados similares a la
tabla que se muestra a continuación:
Método de
ControlSource Value Comentarios
Evento
El ControlSource y Value son iguales en el textbox que tiene el
GotFocus Jones Jones
control
Cuando escribe una letra, el primer método de evento que se
activa es el InteractiveChange. Durante el InteractiveChange la
InteractiveChange Jones S
propiedad Value fue alterada pero el ControlSource retiene su
valor original.
Después del InteractiveChange, el método de evento KeyPress
KeyPress Jones S es llamado. El ControlSource se mantiene inalterado con
relación al Value
InteractiveChange Jones Sm Este proceso continúa mientras continúe escribiendo en el
control textbox.
KeyPress Jones Sm
InteractiveChange Jones Smi
KeyPress Jones Smi
InteractiveChange Jones Smit
KeyPress Jones Smit
InteractiveChange Jones Smith
KeyPress Jones Smith
Esto es cuando se presiona el Tabulador (KeyPress) que se usa
KeyPress Jones Smith
para salir del control
Ahora, el método de evento Valid es activado, el ControlSource
Valid Smith Smith
es actualizado con el nuevo valor
Como el LostFocus se activa después del Valid, el ControlSource
LostFocus Smith Smith
ya tiene el nuevo valor.
Observe que en la tabla hay un período de tiempo en el que el valor del ControlSource no es igual
al Value del Control. Si se hubiera refrescado el control en ese tiempo el valor del control (Value)
hubiera sido revertido con el valor actual del origen de datos (ControlSource).
Si no presiona el tabulador después de introducir el dato y hace clic en la barra de herramientas o
selecciona una opción del menú. El textbox va a retener aun el foco. Esto significa que el valid no se
ha disparado y el ControlSource no será actualizado. El código ejecutado por el botón del toolbar (o
menú de opción) utilizado GetFldState(-1) para chequear los campos modificados, no verá que el
campo actual ha sido modificado. Esto es debido a que el campo no ha cambiado aún.
La solución es forzar el control actual a actualizar el ControlSource en cualquier código de la barra
de herramientas u opción de menú que utilice GetFldState() o TableUpdate(). Esto lo puede hacer
simplemente llamando el SetFocus de ese control. Llamando al SetFocus del control provoca que el
VFP ejecute primero el Valid y LostFocus antes de ejecutar el método SetFocus. Así el control va a
actualizar su ControlSource.
Debe tener mucha precaución obrando de esta manera. Existe siempre la posibilidad de que el
formulario actual no tenga control activo o que el control activo no tenga método SetFocus. Este
código de ejemplo muestra cómo puede verificar estas situaciones.
IF TYPE(“_screen.ActiveForm.ActiveControl.Name”) = “C”
IF PEMSTATUS(_screen.ActiveForm.ActiveControl,”SetFocus”,5)
_screen.ActiveForm.ActiveControl.SetFocus()
ENDIF
ENDIF
* El resto de su código va aquí.
El código de arriba verifica el tipo de la propiedad Name del control activo en el formulario activo.
Si la propiedad Name es de caracteres entonces podemos asumir con seguridad que el control
activo es un objeto. El segundo IF verifica si el ActiveControl tiene un método SetFocus. Si lo tiene,
lo llama. Llamar al SetFocus provoca el proceso de pérdida del foco llamando los métodos de
evento Valid y LostFocus y actualizando el ControlSource.
¿Por qué es esto tan poco intuitivo? ¿Por qué no trabaja esto de la forma en que yo pienso que
trabaja? ¿Por qué es tan raro? Bueno, su rareza es algo relativo, para alguna gente este
comportamiento pudiera ser raro, para otros es visto como la mejor manera de hacer que las cosas
funcionen. Para una persona es comida, lo que para otra es veneno.
Lo importante en este artículo es la prueba que se ha realizado. Los resultados son solo descriptivos
del modelo de evento para un conjunto particular de procesos en VFP. Ser capaz de crear una lista
de eventos y el estado de las cosas durante estos eventos es la utilidad real de este artículo.
Puede tratar de memorizar cada matiz de Visual FoxPro si desea. No creo que nunca llegue a
conocerlos a todos y tampoco creo que sea un buen uso del tiempo memorizar las peculiaridades
de cada herramienta. Lo que si es un buen uso del tiempo es dejar un proyecto de desarrollo de la
aplicación por unos minutos y construir una pequeña prueba para el estudio de que es lo que pasa
en una situación determinada. Después regresar al proyecto y programar en correspondencia con
lo que ha aprendido durante la prueba.
Lo que nunca te contó tu madre sobre instanciar y destruir formularios
Sesión original: What Your Mother Never Told You About Form Instantiation and
Destruction (DevEssentials 2004)
Autor: Drew Speedie
Traducido por: Ana María Bisbé York
Resumen
Esta sesión intenta ayudar a entender mejor la secuencia normal de eventos en VFP cuando los
formularios se instancian y se destruyen ... existe mucho más los eventos Init y Destroy. Dotado de
este conocimiento, puede depurar problemas e implementar buenas técnicas como las que vamos
a demostrar aquí.
Todos los ejemplos se pueden probar en VFP 8 o VFP 9, porque no se utiliza código específico para
VFP 9. La mayoría de los ejemplos se aplican a todas las versiones de VFP; pero algunos de los
ejemplos utilizan las funciones BINDEVENT()s y las características de la clase DataEnvironment que
fueron agregadas en VFP 8.
La mayoría de los ejemplos se pueden ejecutar desde la interfaz DEMO.APP, aunque algunos deben
comenzar con CLEAR ALL/CLOSE ALL, y deben ejecutarse desde la ventana de comandos.
DEMO.APP es el único archivo incluido con la presentación. Una vez que selecciona los botones Run
(Ejecutar) o Source (Fuente) desde la interfaz, los archivos con código fuente para ese ejemplo se
copian al disco, en la carpeta donde se encuentra DEMO.APP. A partir de ahí, puede ejecutar los
ejemplos desde la ventana de comandos. La mayoría pueden ser ejecutados directamente desde la
interfaz DEMO.APP.
Instanciación
Como se demuestra en LISAG_QRDU.SCX, aquí está la lista de eventos que ocurren durante la
instanciación:
1. Evento Form.DataEnvironment.OpenTables (Tablas/vistas en el Entorno de datos
DataEnvironment no están en uso USE() o abiertas)
2. Evento Form.DataEnvironment.BeforeOpenTables (Tablas/vistas en el Entorno de datos
DataEnvironment no están en uso USE() o abiertas)
3. Evento Form.Load (Tablas/vistas en el Entorno de datos DataEnvironment están en uso USE()
o abiertas)
4. Evento Init de cualquier objeto cursor en DataEnvironment
5. Evento Form.DataEnvironment.Init
6. Evento Init de cada miembro del formulario que es instanciado
7. Evento Form.Init
8. Evento Form.Show
9. Evento Form.Activate
10. Evento When del primer control del formulario en el orden de tabulación (tab order)
11. Evento Form.GotFocus
12. Evento GotFocus del primer control del formulario en el orden de tabulación
Destrucción
Como se demuestra en LISAG_QRDU.SCX, aquí está la lista de eventos que ocurren durante la
destrucción cuando el formulario es cerrado por el usuario haciendo Clic en la "X" en la esquina
superior derecha de la barra de título del formulario ... o ... por el usuario, seleccionando la opción
Cerrar desde el ControlBox en la esquina superior izquierda de la barra de título del formulario:
1. Evento Form.QueryUnload
2. Evento Form.Destroy
3. Evento Destroy para cada uno de los miembros del formulario.
4. Evento Form.Unload (Tablas/vistas en el Entorno de datos DataEnvironment están en uso
USE() o abiertas)
5. Evento Form.DataEnvironment.CloseTables (Tablas/vistas en el Entorno de datos
DataEnvironment no están en uso USE() o abiertas)
6. Evento Form.DataEnvironment.Destroy
7. Evento Destroy para cada cursor en el DataEnvironment
Como se demuestra en LISAG_QRDU.SCX, aquí está la lista de eventos que ocurren durante la
destrucción cuando el formulario es cerrado por una llamada a THISFORM.Release(), por ejemplo,
al hacer Clic en un botón Aceptar.
1. Evento Form.Release
2. Evento Form.Destroy
3. Evento Destroy para cada uno de los miembros del formulario.
4. Evento Form.Unload (Tablas/vistas en el Entorno de datos DataEnvironment están en uso
USE() o abiertas)
5. Evento Form.DataEnvironment.CloseTables (Tablas/vistas en el Entorno de datos
DataEnvironment no están en uso USE() o abiertas)
6. Evento Form.DataEnvironment.Destroy
7. Evento Destroy para cada cursor en el DataEnvironment
El Init de los miembros del DataEnvironment: Cursor, CursorAdapter, y Relation se disparan antes
que el Init del DataEnvironment, siguiendo el comportamiento nativo de VFP donde el Init de los
miembros ocurre antes que el Init del contenedor padre.
El evento Destroy del DataEnvironment ocurre antes que el Destroy de sus miembros.
DataEnvironment
Conclusión
Por ejemplo, algo que necesitamos a menudo es colocar SET TALK OFF antes de que se disparen los
eventos del DataEnvironment, esto se debe hacer en DataEnvironment::Init y posiblemente
también en el Init de su clase base Cursor. Para el nivel instanciado la configuración de las
propiedades no es fiable hasta que no se dispare el Form.Load.
¿Dónde ubicar los comandos SET que están limitados a la Sesión privada de datos?
Varios comandos SET de VFP están limitados a la Sesión privada de datos. Puede revisar la lista en
el tópico SET DATASSESION del archivo Ayuda de VFP. Muchos de esos comandos SET afectan los
datos y por tanto necesitan ser ejecutados para cada sesión privada de datos. Además, SET TALK
está limitado a la sesión privada de datos y debe ser establecido lo antes posible, en OFF que es el
valor no-predeterminado para VFP, en el momento en que la sesión privada de datos (formulario)
es instanciado, para eliminar las salidas TALK al _Screen o al formulario.
Sin embargo, teniendo en cuenta las inconsistencias planteadas anteriormente en relación con el
objeto DataEnvironment, ¿Cuál es el mejor lugar para colocar los comandos SET en una sesión
privada de datos, de forma tal que el formulario se instancia correctamente?
Los archivos LISAG_PDS_SETS*.PRGs y sus correspondientes .SCX demuestran la solución.
No utiliza Dataenvironment
Pienso que es la mejor elección, porque es más consistente, y es más fácil de mantener (vea la
siguiente sección de este documento). En ese caso, lo más lógico es colocar los comandos SET de la
sesión privada de datos en el Load del formulario de la más alta jerarquía que establezca la
propiedad DataSession a 2 -Private Data Session. Comience el Load de cada instancia de formulario
con llamada hacia atrás (callback), para asegurarse que los comandos SET quedarán configurados
desde el inicio:
IF NOT DODEFAULT()
RETURN .F.
ENDIF
DataEnvironment nativo en un formulario basado en .SCX
Agregue un método de usuario para colocar la configuración lógica de los comandos SET para su
sesión privada de datos junto a cualquier otra cosa que necesite que ocurra al inicio del todo de la
ejecución del formulario.
En el nivel instanciado, llame al método de usuario desde los métodos del DataEnvironment
OpenTables o, mi preferido, BeforeOpenTables:
THISFORM.CustomMethod()
Sin embargo, cuando ejecute FORM LISAG_SetFocus, verá que el orden de los eventos no es LISAG,
sino LIAGIS como se muestra en la figura 7. Cuando el Init realiza el SetFocus, VFP tiene que activar
inmediatamente el formulario y darle el foco, para que el botón OK pueda tener el foco en ese
punto. Después de esa línea de código, el Form.Init ejecuta cualquier código restante después de
esa línea y continúa con el evento Show.
No muchos formularios dan el SetFocus al primer control de esta forma ... podría simplemente
establecer el comando OK primero en el orden de tabulación. Lo que es más común es establecer el
foco condicionalmente a un control en particular, basado en alguna acción del formulario, basado a
se vez, en un parámetro que reside en el Init. Simplemente he omitido esta condición en el ejemplo
LISAG_SetFocus.SCX para demostrar lo que ocurre cuando se encuentra la condición.
Pero, ¿Cuál es el daño? Eso depende de qué ha codificado en los otros eventos de instanciación del
formulario que normalmente se ejecutan en orden nativo una vez que el Init haya terminado
completamente.
Por ejemplo, puede tener código en lo eventos Show y Activate que se disparan solamente si se
está ejecutando durante la instanciación del formulario (es posible hacerlo por programa - Show()
de un formulario en cualquier momento y el Activate se ejecuta cada vez que el formulario se
convierte en formulario activo, por ejemplo cuando el usuario hace clic en un formulario no modal
abierto en el escritorio VFP) con este fin, agregue una propiedad de usuario (una bandera) con
valor predeterminado a .T. , y lo hace igual a .F. en el Activate o GotFocus. El código a través del
proceso de instanciación pudiera ejecutar condicionalmente sólo si el formulario está siendo
instanciado:
IF THIS.lInstantiating
* realizar estas acciones solo si THISFORM se está instanciando
ENDIF
¿Cuál es la solución?
Entonces, ¿qué se puede hacer en esos casos donde se necesita establecer condicionalmente el
foco a un control particular al instanciar un formulario? El ejemplo LISAG_SetFocus1.SCX demuestra
una técnica. Además de demostrar la idea de una propiedad lInstantiating, utilizada como bandera,
añade un método de usuario InitalSetFocus llamado desde Form.Activate sólo durante la
instanciación. InitalSetFocus proporciona un lugar específico para que el desarrollador ponga su
código para establecer el foco condicionalmente a un miembro en particular de un formulario ... sin
romper la secuencia nativa de los eventos al instanciar.
Bueno, ¿y qué?
Bueno, pues frecuentemente colocamos el código de limpieza en el Form.Destroy, y puede tener
código abstracto en el evento Destroy en la clase base o en otras clases Form que están en la
jerarquía del instanciado actualmente. Lo mismo se puede cumplir para Form.Unload. ¡El código de
limpieza del Cleanup no se ejecuta cuando el Form.Load o el Form.Init devuelven .F. y el código de
limpieza del Cleanup no se ejecuta si el Form.Load devuelve .F.!
Utiliza este comportamiento para determinar cómo debe ser invocado Form.Destroy.
LISAG_QRDU_AbortLoadInit1.SCX
Los eventos Destroy e Init de LISAG_QRDU_AbortLoadInit1.SCX contienen una instrucción IF .F., tal
que si la cambia por IF .T., provoca que el método devuelva .F. La técnica demostrada en
LISAG_QRDU_AbortLoadInit1.SCX es:
1. Al devolver .F. desde Init/Load debido a un failed callback, NO incluya una llamada manual al
Form.Destroy, asumiendo que cada clase padre Form que devuelva .F. desde su código
abstracto lo hará.
2. Al devolver .F. desde Init/Load debido a código en ese nivel, llama THIS.Destroy() antes del
Return .F., asegurándose de que cualquier código de limpieza en el Destroy se va a ejecutar
adecuadamente.
3. El evento Form.Destroy contiene código para llamar a SYS(1271,THISFORM) para determinar
si el Destroy ocurre normalmente (no existe error en SYS(1271)) o debido a llamarlo
explícitamente desde el Form.Load (SYS(1271) genera un error). Al llamar manualmente
desde Form.Load, es llamado el Form.UnLoad.
LISAG_QRDU_AbortLoadInit2.SCX
Los eventos Destroy e Init de LISAG_QRDU_AbortLoadInit1.SCX contienen una instrucción IF .F., tal
que si la cambia por IF .T., provoca que el método devuelva .F. La técnica demostrada en
LISAG_QRDU_AbortLoadInit2.SCX es:
1. Al devolver .F. desde Init/Load debido a un failed callback, NO incluya una llamada manual al
Form.Destroy, asumiendo que cada clase padre form que devuelva .F. desde su código
abstracto lo hará. (igual que LISAG_QRDU_AbortLoadInit1.SCX)
2. Al devolver .F. desde Init/Load debido a código en ese nivel, llama THIS.Destroy() antes del
Return .F., asegurándose de que cualquier código de limpieza en el Destroy se va a ejecutar
adecuadamente. (igual que LISAG_QRDU_AbortLoadInit1.SCX)
3. El Form.Destroy contiene código para verificar PROGRAM(PROGRAM(-1)-1) para determinar
si el Destroy se ha llamado manualmente desde el Form.Load y, en tal caso, llama al
Form.Unload.
Para que cada objeto se libere / destruya adecuadamente, todas las referencias externas a sus
miembros deben ser liberadas. Esto significa que para cerrar/destruir un formulario, cualquier
objeto externo que mantenga referencia a uno o más miembros debe liberarse explícitamente esa
referencia o establecerse igual a .NULL.
Esta técnica requiere, por supuesto, utilizar VFP 8.0, versión en la que fue agregada al lenguaje la
poderosa función BINDEVENT().
LOCAL loActiveForm
loActiveForm = X6SAF()
IF ISNULL(m.loActiveForm)
* aquí no es el formulario activo
ELSE
* m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activo
ENDIF
A partir de ahora, THISFORM, puede "hablar" al formulario llamado via la referencia de objeto
THISFORM.oCallingForm object. THISFORM.Destroy establece en .NULL.
THISFORM.oCallingForm object:
¡Abstraerlo!
El comportamiento que ve en Load, Destroy, y ORCleanup puede abstraerse simplemente en su
formulario base clase. Todos los formularios que heredarán esta característica cada instancia lo
utilicen o no.
Esto toma un poco más de trabajo, puede fácilmente abstraerse de tal forma que esté disponible
para todos los formularios _ScreenActiveForm1.SCX contiene el código necesario:
1. Load contiene un código que verifica la pila de ejecución del programa para la llamada del
control en el formulario llamado, si se encuentra uno, se guarda una referencia en
THISFORM.oCallingFormControl para utilizarlo mientras exista THISFORM.
2. Como se ha explicado previamente en este documento, en cualquier momento puede
guardar una referencia de objeto para un miembro de objeto, debe proporcionar para la
limpieza de la referencia de objeto. El Load de _ScreenActiveForm1.SCX hace que
BINDEVENT() enlaza el Destroy del formulario llamado con THISFORM.ORCleanup. Si
_ScreenActiveForm1.SCX es modal, esta acción no se requiere actualmente, porque
THISFORM se puede cerrar antes de que el formulario puede ser llamado.
3. El Destroy de _ScreenActiveForm1.SCX llama a su ORCleanup de usuario.
4. El ORCleanup de_ScreenActiveForm1.SCX libera explícitamente las referencias de objeto
oCallingForm y oCallingFormControl. Cuando THISFORM es no modal, se dispara el Destroy
del formulario llamado THISFORM.ORCleanup gracias al BINDEVENT() en THISFORM.Load.
Observe que la referencia de objeto del formulario llamado, si este control es realmente llamado
por el formulario llamado, se guarda sólo en THISFORM.oCallingFormControl. Por ejemplo, si se
ejecuta un formulario cuando se llama un segundo formulario desde otro lado como una opción de
menú, el control activo en el formulario activo no llama al segundo formulario.
THISFORM.oCallingFormControl no se guarda debido a que un método del control activo en el
formulario activo no está en la pila de ejecución del programa. Entonces, como se ha explicado en
el comentario del código al final del método Load de _ScreenActiveForm1.SCX, puede decirlo
fácilmente si THISFORM.oCallingForm es llamado por THISFORM, o fue simplemente el formulario
llamado cuando es llamado THISFORM.
Estas características se demuestran en el ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX, como
muestra la Figura 12:
1. El Init de _ScreenActiveForm1.SCX determina sus posiciones Top y Left relativos al control
llamado, si existe. Las referencias de objeto a un formulario llamado y su control llamado
podría pasar al Init, en lugar de utilizar la referencia de objeto THIS.oCallingFormControl
guardada en Load. Sin embargo, esto requiere que el desarrollador que codifica la llamada al
formulario recordando que pase siempre los parámetros necesarios a
_ScreenActiveForm1.SCX, y en el orden correcto.
2. El AfterRowColChange de grdCustomers en _ScreenActiveForm1.SCX actualiza el Caption del
botón del formulario llamado. Esto es solamente por propósitos de diversión / demo, para
mostrar cual fácil es "hablar" al control del formulario llamado.
3. El ORCleanup de _ScreenActiveForm1.SCX contiene código para cambiar el Caption del
botón del formulario llamado.
Puede además hacer DO FORM _ScreenActiveForm1 con nada, y el código escrito antes encuentra
que no hay mucho que hacer.
¡Abstraerlo!
El comportamiento que ve en el Load, Destroy, y ORCleanup se puede abstraer fácilmente en su
clase base formulario. Todos los formularios heredan esta característica aunque la utilice o no la
instancia indicada.
FormORCleanupCaller2.SCX
El ejemplo FormORCleanupCaller2.SCX es el mismo que FormORCleanupCaller1.SCX, excepto que
cuando el formulario invocador es cerrado, todos los formulario que llama se cierran
automáticamente. Encontrará el código para esto en el evento Destroy.
Un par de meses atrás escribí sobre buffer; pero deliberadamente dejé fuera de la discusión dos
funciones vitales mientras trabajaba con buffer- nombradas TableUpdate() y TableRevert(). Estas
son la base mediante la cual usted, el desarrollador, controla la transferencia de los datos entre el
buffer de Visual FoxPro y el origen de datos originales. La función TableUpdate() toma los datos
pendientes desde el buffer y los actualiza en la tabla original, mientras TableRevert() refresca los
buffers para releer el dato desde el origen de datos. La realización exitosa de otras funciones da
como resultado un buffer 'limpio' lo que significa que, para Visual FoxPro, los buffer y el origen de
datos son sincronizados.
TableRevert() devuelve el número de filas que fueron revertidos y no pueden realmente "fallar" -
sin que fuera un problema físico, como perdiendo una conexión de red. En una fila de tabla en
buffer, o cuando específicamente el alcance como .F., el valor devuelto, por tanto siempre 1.
Si está actualizando un solo registro, esto es muy sencillo; pero si está actualizando múltiples
registros en una tabla, y un registro no puede ser actualizado, esto significa que cualquier
actualización posterior no puede ser verificada. Pero, después de solucionar el conflicto para el
registro que ha fallado, no hay garantía que re-intentar la actualización que no va a fallar al registro
más cercano. Esto puede ser un problema!
Al utilizar buffer de tablas, al llamar TableUpdate() con un parámetro de alcance de "2" Visual
FoxPro intenta actualizar todos los registros que tienen cambios pendientes. Sin embargo, si un
registro no puede ser actualizado, en lugar de parar, la función registra el número de registro que
ha fallado en una matriz (cuyo nombre puede ser especificado como el cuarto parámetro) y
continúa tratando de actualizar otros registros cambiados. La función devuelve .F.
si cualquier registro falla la actualización; pero al menos tratará de actualizar todos los registros
disponibles. La matriz de salida contiene una lista de los números de registros para aquellos
registros fallan la actualización.
De forma predeterminada, Visual FoxPro debe rechazar un cambio cuando un conflicto entre el
buffer de datos y se detecta el dato original (vea debajo un debate completo del conflicto y la
resolución). Al especificar un valor lógico .T. como el segundo parámetro puede forzar una
actualización a que sea aceptada incluso en situaciones cuando debería fallar. Naturalmente esto
es algo que deseará hacer de forma predeterminada; pero existen, como veremos luego,
situaciones donde este comportamiento no es solamente deseado, sino esencial.
Especificar la tabla a ser actualizada o revertida
Ambas funciones TableUpdate() y TableRevert() operan sobre una tabla al mismo tiempo. El
comportamiento predeterminado es que, a menos que específicamente, se determine lo contrario;
actuará en la tabla en el área de trabajo seleccionada. Si no hay una tabla abierta en el área de
trabajo, hay un error (No se encuentra el alias - Error 13). Ambos, sin embargo, pueden actuar
sobre una tabla abierta disponible en la sesión de datos actual y puede aceptar cualquiera de los
nombres de ALIAS (el tercer parámetro para TableUpdate(), segundo para TableRevert()) o un
número de área de trabajo.
No recomendamos el uso del número del área de trabajo en esto, o cualquiera, situación donde está
especificando una tabla diferente que la seleccionada. Tal y como hemos podido ver esta
funcionalidad está incluida, sólo por compatibilidad hacia atrás y no tiene lugar en el entorno VFP.
Existen dos razones para evitar el uso del área de trabajo. Primeramente, hace su código
dependiente de tablas específicas estén abiertas en áreas específicas de trabajo - lo que es aun
mayor limitación si piensa cambiar! En segundo lugar, no tiene control real sobre las tablas abiertas
en VFP, los cursores o vistas cuando utiliza de cualquier forma el Entorno de datos del formulario.
Entonces, liberar el número del área de trabajo en lugar del alias, es una estrategia muy arriesgada
e innecesaria.
El único momento que recomendamos el uso del número de área de trabajo es cuando guardamos
el área de trabajo actual guardando el valor devuelto de la función SELECT(0). Utilizar el número del
área de trabajo en este caso asegura que el área de trabajo actual está vacía, o que las tablas que
contienen se cierren durante cualquier operación que esté haciendo, puede aun devolverlo sin error.
Conclusión
Existe mucha funcionalidad y flexibilidad oculta dentro de TableUpdate() y TableRevert(). Al utilizar
buffer, necesita tener cuidado de, exactamente cuáles de varias combinaciones de sus parámetros,
puede pasarles para asegurar que está utilizando la combinación correcta que necesita.
Mientras TableRevert() es bastante simple, TableUpdate() es más compleja y por eso, en la Tabla 2
que muestro debajo se brinda un resumen de algunas combinaciones "prácticas" de parámetros
para TableUpdate().
Tabla 2. Opciones de TableUpdate()
Parámetros
Al utilizar buffer, Visual FoxPro hace una copia de todos los datos como e recuperan de la tabla
física, cuando se pide una actualización, se compara esa copia con el estado actual del dato. Si no
hay cambios, la actualización es permitida, en otro caso, la actualización se genera un error de
conflicto (#1585 para las vistas, #1595 para tablas).
Aquí hay un problema al utilizar CurVal() para verificar el estado de la vista. Visual FoxPro
realmente mantiene un nivel adicional de buffer para una vista, la cual, a menos que sea refrescada
inmediatamente antes de verificar el valor del campo, puede causar CurVal() que devuelve la
respuesta incorrecta.
La segunda es menos obvia y ocurre cuando un usuario hace un cambio; pero entonces los cambios
van a sus valores originales. El resultado es que no cambia cuando en realidad hace; pero cuando
tratan de "guardar" el registro Visual FoxPro va a ver aun esto como un conflicto debido a que los
valores OldVal() y CurVal(), en realidad son diferentes. Para evitar este tipo de error puede
simplemente confiar en GetFldState(); pero debe comparar expresamente los valores en el buffer
actual con estos en OldVal().
Por tanto, a menos que tenga una razón para sobreescribir cambios pre-validados, recomendamos
fuertemente que permita que Visual FoxPro detecte los conflictos y justamente atrápelo para
aquellos errores que han surgido.
De acuerdo, entonces, habiendo detectado un conflicto de actualización, ¿qué puedo hacer sobre
esto?
He aquí cuatro estrategias básicas para actualizar conflictos. Puede escoger una, o combine más de
una en una aplicación en dependencia de la situación real:
[1] El usuario actual siempre gana - Esta estrategia es apropiada solamente en aquellas situaciones
en las que se asume que son correctos los datos del usuario que está actualmente intentando
guardar. Típicamente esto debería ser implementado en la base del ID del usuario, el que es en
realidad está haciendo el guardado y debería implementar una regla de negocio que cierta
información de una gente es más útil que otra.
Un ejemplo podría ser tener una aplicación donde un operador hable al usuario podría tener
derechos de sobreescritura para contactar información para el cliente (en la base que la persona
realmente habla al cliente es más probablemente capaz de obtener los detalles correctos). El
conflicto puede surgir en este caso cuando un administrador está actualizando un detalle de cliente
desde un dato en archivo o última orden, mientras un operador tiene detalles nuevos,
directamente desde el cliente. La implementación de esta estrategia en Visual FoxPro es muy
sencilla. Simplemente establezca el parámetro "FORCE", (el segundo) en la función TableUpdate() a
".T." y reintente la actualización.
[2] El usuario actual siempre pierde - Esto es exactamente lo contrario a la anterior. Al usuario
actual se le permite solamente guardar los cambios siempre que no hay otro usuario que haya
hecho cambios. Por el contrario, será implementado normalmente en base al ID del usuario y
podría reflejar la probabilidad que este usuario en particular es propenso a trabajar con
información "histórica" en lugar de información "actual". La implementación en Visual FoxPro es
también muy sencilla. Los cambios del usuario actual se revierten, se recarga la información original
y el usuario tiene que hacer cualquier cambio que necesite, una vez más. Esta es, probablemente la
estrategia que es adoptada más frecuentemente - pero usualmente en base global.
[3] El usuario actual gana a veces - Esta estrategia es la más compleja de las cuatro a implementar;
pero es en la actualidad, bastante frecuente. El principio básico es que cuando ocurre un conflicto
de actualización, usted determina si alguno de los campos, que el usuario actual ha cambiado, van
a afectar los cambios hechos por otro usuario. Si no, el registro del usuario actual es actualizado
automáticamente (utilizando el valor de CURVAL()), entonces esto provoca que es negado el
conflicto y la actualización se reintenta. Sin embargo, debido a no puede cambiar los valores
devueltos por OldVal(), necesita forzar la segunda actualización.
Incidentalmente, esta estrategia también está dirigida al problema de cómo controlar el conflicto
de actualización "falso positivo". Esto ocurre cuando existen discrepancias entre los valores del
disco y aquellos en el buffer de usuario, pero los actuales cambios del usuario, no crean en realidad
un conflicto con cualquier otro cambio que se haya hecho. Claramente, esta situación no es en
realidad un conflicto; pero necesita ser controlada.
[4] El usuario actual decide - Este es el caso de "Coge todo". El conflicto no falla bajo ninguna regla
de negocio reconocida, así que la única solución es preguntarle al usuario cuya acción de guardar
desencadena el conflicto que es lo que desea hacer. La idea básica es que usted muestre al usuario
que ha desencadenado el conflicto con una lista de valores - aquellos que acaba de entrar) y el
valor que hay ahora en la tabla ( es decir con el cual alguien ha cambiado el valor original). El
usuario puede entonces decidir si hay que forzar o revertir sus propios cambios. Las herramientas
básicas para la implementación de esta estrategia han sido discutidos en secciones anteriores.
Todo lo requerido es determinar qué cambios traen conflicto y los presentan al usuario como vía
para que el usuario pueda decidir en un campo a campo qué hacer, en base al conflicto. En la
práctica, esta estrategia en general se combina con la estrategia [3] mostrada antes, así al usuario
sólo se le presenta una lista de campos donde hay un conflicto en los campos que han modificado
por ellos mismos.
En el próximo artículo de esta serie, veré el diseño e implementación de una clase que controle un
conflicto que puede ser arrastrada a un formulario.