SlideShare une entreprise Scribd logo
1  sur  22
Télécharger pour lire hors ligne
Secuencia normal de eventos en VFP cuando se Crean y Destruyen Formularios
Tomado de “What Your Mother Never Told You About Form Instantiation and Destruction presentada por el autor en la
Conferencia DevEssentials Kansas, 2004)
Leído y Editado a mi modo: jun/2013
Al final de este documento está disponible la traducción original
Secuencia de eventos LISAG y QRDU
LISAG (Load-Init-Show-Activate-GotFocus) y QRDU (QueryUnload- Release- Destroy- Unload)
NOTA:NOTA:NOTA:NOTA: He tomado “He tomado “He tomado “He tomado “Form Instantiation” como creación deForm Instantiation” como creación deForm Instantiation” como creación deForm Instantiation” como creación dellll Form.Form.Form.Form. Ya queYa queYa queYa que eseseses en ese momento seen ese momento seen ese momento seen ese momento se dadadadannnn valores específicosvalores específicosvalores específicosvalores específicos
a las propiedades del Forma las propiedades del Forma las propiedades del Forma las propiedades del Form y por lo tanto se crea un Form particulary por lo tanto se crea un Form particulary por lo tanto se crea un Form particulary por lo tanto se crea un Form particular....
Resumen
Esta sesión intenta ayudar a entender mejor la secuencia normal de eventos en VFP cuando se crean y destruyen formularios.
Para implementar exitosamente los escenarios de creación y destrucción de un formulario, lo primero que debe entender es la
secuencia nativa de los eventos.
Creación de un Formulario
Aquí está la lista de eventos que ocurren durante la creació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 creado
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 de un Formulario
Formulario cerrado por el usuario:
Esta es 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
Formulario cerrado por una llamada:
Aquí está la lista de eventos que ocurren 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
Creación / Destrucción de miembros contenedores
En la Creación:
Los contenedores se crean "de dentro hacia afuera" de igual forma que hace el propio formulario (debido a que este, también es
un contenedor). El Init de los miembros contenidos de disparan antes que el Init del contenedor padre.
En la Destrucción:
Ocurre lo contrario, los contenedores se destruyen "de afuera hacia dentro". El Destroy del contenedor se dispara antes que el
Destroy de sus miembros contenidos.
Formulario con valores establecido para DEClass, DEClassLibrary
Cuando un formulario establece valores para DEClass, DEClassLibrary (disponibles a partir de VFP 8.0), las cosas son ligeramente
diferentes. Debido a que el objeto DataEnvironment es un objeto completamente separado, se crea completamente antes del
Form.Load.
El Init de los miembros del DataEnvironment (Es decir, el Init del 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 (Es decir, del Cursor, CursorAdapter, y
Relation).
Aspectos de interés del Form:
1. Dependiendo de cómo se cierra el formulario, se ejecuta el método Release o el evento QueryUnload; pero no ambos:
El evento Form.Release no es un buen lugar para colocar código, no importa lo que sea, que se deba ejecuta cada vez que se
destruya el formulario. Utilice en su lugar los eventos Destroy o Unload.
2. Debido a que muchos comandos como SET TALK están limitados a la sesión privada de datos (ver DATASESSION TO en la ayuda
de VFP para ver la lista de estos comandos SET), decidir en que lugar va a asignar valores a estos comandos SET depende de cómo
abre sus datos.
Si no utiliza nunca DataEnvironment: Los comandos SET para la sesión privada de datos se colocan en el Load de la
clase form.
Si utiliza DataEnvironment nativo en un formulario basado en .SCX: Tendrá que establecer estos comandos SET en
DataEnvironment.OpenTables/BeforeOpenTables, antes de que las tablas/cursores sean abiertos.
Si implementa un DataEnvironment de usuario especificado en las propiedades DEClass/DEClassLibrary (VFP 8.0 y
superior): Podrá establecer los comandos SET para la sesión privada de datos en el método Init de la clase
DataEnvironment; pero sea consciente, que el Init de los miembros Cursor, CursorAdapter, y los objetos Relation se
disparan antes que DataEnvironment.Init.
3. Al destruir un formulario no se disparan los eventos Form.Deactivate ni Form.LostFocus
DataEnvironment
1. El DataEnvironment abre sus datos implícitamente entre el DataEnvironment.BeforeOpenTables y el Form.Load(),
inmediatamente antes del Form.Load.
2. El DataEnvironment NO abre los datos en el evento OpenTable -- OpenTables es de uso opcional para abrir
programáticamente los datos si se utiliza AutoOpenTables = .F.
3. El evento BeforeOpenTables no se dispara antes (Before) que el evento OpenTables, BeforeOpenTables está mal
nombrado y se debió llamar algo como AfterOpenTablesBeforeImplicitOpenTables.
(DespuesAbrirTablasAntesImplicitamenteAbrirTablas)
4. El DataEnvironment.Cursor.Init se dispara DESPUÉS que el cursor ya fue abierto y DESPUÉS del Form.Load
5. El DataEnvironment.Init se dispara DESPUÉS que el DataEnvironment halla cumplido todas las misiones encomendadas.
DESPUÉS que los cursores han sido puestos en uso, y DESPUÉS del Form.Load. Excepto cuando el DataEnvironment es una
creación de usuario especificada en las propiedades DEClass/DEClassLibrary, en ese caso se crea completamente antes de
Form.Load.
6. Consecuente con el comportamiento de OpenTables y cuando las tablas se abren explícitamente por el
DataEnvironment, el mismo cierra las tablas ANTES del método CloseTables.
7. Mientras la secuencia de creación / destrucción para el formulario y sus miembros es consistente, la secuencia de
creación / destrucción para el DataEnvironment depende de su implementación:
Tipos de Implementación DataEnvironment:
1.- DataEnvironment nativo para un formulario basado en .SCX,
2.- Implementación de la propiedad DEClass DataEnvironment de usuario para
formulario basado en .SCX o .VCX.
Llamar al método THISFORM.Metodos o consultar THISFORM.Propiedades desde el DataEnvironment
Los ejemplos muestran un comportamiento inconsistente relacionado con llamar a métodos del formulario (THISFORM) desde el
DataEnvironment.
DataEnvironment nativo en un formulario basado en .SCX:
Cuando un formulario basado en .SCX utiliza DataEnvironment nativo de VFP, el DataEnvironment se crea primero que el
formulario como tal. Sin embargo
1. Las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment que se disparan antes del
Form.Load son ¡COMPLETAMENTE IGNORADAS!. Por ejemplo, las llamadas a THISFORM.Metodos desde los eventos de
DataEnvironment OpenTables y BeforeOpenTables no hacen nada, no se invoca ningún método y no se genera un error. Supongo
que VFP no ha iniciado todavía la creación del formulario y por tanto, no reconoce THISFORM como un objeto; pero yo debería
esperar que se generara un error, como hace SYS(1271,THISFORM).
2. Las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment que se disparan antes del
Form.Load SÍ disparan esos métodos EN CASO que hayan sido definidos en la clase Form a partir de la cual hereda el formulario
actual. Por supuesto, todos los métodos nativos de VFP heredan de la clase base Form de VFP. Entonces, el código que se ejecuta es
el heredado de la clase Form, NO cualquier código ubicado en los métodos nativos o de usuarios del formulario creado. Una vez
que se dispara el Form.Load, la llamada a estos mismos métodos disparan los métodos para el nivel creado.
3. De igual forma, si en el DataEnvironment que se dispara antes del Form.Load, consulta el valor de una propiedad (nativa o de
usuario) que se establece explícitamente en la ficha Propiedades a nivel del formulario creado, los valores son los que sean
predeterminados por VFP para esa propiedad (.F. para todas las propiedades de usuario), como si hubiera establecido las
propiedades como predeterminadas en la ficha propiedades. Sin embargo; para las propiedades establecidas en la ventana
propiedades de cualquier clase padre del formulario creado, el valor es evaluado adecuadamente, tal y como esperamos. Sin
embargo, puede establecer una propiedad en cualquier método del DataEnvironment.
4. En los métodos de DataEnvironment, IntelliSense no muestra NINGUN PEM (Propiedades, Eventos, Métodos) de usuario, ya sea
de la creación actual o de alguna clase padre de la creación actual del formulario.
Conclusión
El DataEnvironment para métodos de usuario de formulario, ya que estos métodos están basados en formularios .SCX. Y debe
recordar que el código que coloque en estos métodos en el nivel creación del .SCX será completamente ignorado, cuando esos
métodos son llamados desde eventos del DataEnvironment como OpenTables y BeforeOpenTables que se ejecutan antes que
Form.Load. Para este nivel de creación establecer propiedades es poco confiable hasta que no se ejecute Form.Load.
DataEnvironment de usuario especificado en las propiedades DEClass/DEClassLibrary para un formulario basado en .SCX
Cuando las propiedades DEClass y DEClassLibrary de un formulario basado en .SCX tienen asignado un valor que especifique un
objeto DataEnvironment de usuario, las cosas son algo diferentes:
1. En tiempo de diseño, cuando se da valor a las propiedades DEClass y DEClassLibrary asignando una clase DataEnvironment con
código en su evento Init, o en el Init de alguno de sus miembros cursor/relation, y ese Init llama a un método de usuario de ese
formulario, sólo el hecho de asignar valor a las propiedades DEClass y DEClassLibrary genera un error "Objeto no contenido en el
formulario" ("Object is not contained in a Form"), para cada llamada al método de usuario del formulario (THISFORM).
2. En tiempo de ejecución, se genera el mismo error "Objeto no contenido en el formulario.
3. En tiempo de ejecución, aquellas mismas llamadas a métodos de usuario de THISFORM que generaban un error cuando eran
llamados desde el DataEnvironment.Init o desde el Init de uno de sus miembros, NO HACEN NADA si son llamados desde los
eventos DataEnvironment.OpenTables o BeforeOpenTables ... los métodos de usuario agregados al .SCX en la creación actual son
completamente ignorados.
4. En tiempo de ejecución, cuando ocurren eventos como OpenTables and BeforeOpenTables del DataEnvironment, que se
disparan después del DataEnvironment.Init y el Init de sus miembros llaman a métodos de THISFORM, sólo se ejecuta el código
para aquellos métodos que es heredado de la clase padre ... no el código colocado en los métodos del nivel actual creado.
Conclusión
La conclusión es que el código colocado en métodos de la instancia actual del formulario nunca se ejecuta cuando el método es
llamado desde eventos del DataEnvironment. Para confiar en los comportamientos abstractos del DataEnvironment, debe crear
todo el código en la clase DataEnvironment (jerárquicamente).
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 confiable 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. 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 creado, 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 cree
correctamente?
Eso depende de si utiliza o no DataEnvironment y en ese caso, si utiliza el DataEnvironment nativo de VFP o una clase personalizada
DataEnvironment.
No utilizar Dataenvironment
Pienso que es la mejor elección, porque es más consistente, y es más fácil de mantener. 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 creación 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
En el formulario de la más alta jerarquía establezca la propiedad DataSession a 2- Private Data Session. 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 creado, llame al método de usuario desde los métodos del DataEnvironment OpenTables o, yo preferido,
BeforeOpenTables:
THISFORM.CustomMethod()
Recuerde, cualquier código que coloque en el método de usuario en el nivel creado no se ejecutará, como fue explicado en la
sección anterior ... solamente se ejecuta el código colocado en la(s) clase(s) padre de la instancia actual.
DataEnvironment de usuario con DEClass/DEClassLibrary
Coloque la configuración lógica de los comandos SET para su sesión privada de datos en el Init de la clase DataEnvironment. Sea
consciente de que pudiera tener algunos comandos SET como SET TALK OFF en el Init de sus clases base Cursor o Relation, ya que
estos se ejecutan antes que el Init de DataEnvironment.
¿Cómo establecer los comandos SET?
Una vez que haya determinado dónde necesita colocar su código para los comandos SET de su sesión privada de datos, la cuestión
es cómo debe escribir ese código. Puede escribir un código estricto con los comandos SET deseados; pero el mejor enfoque pudiera
ser tener la sesión privada de datos que lea los valores desde la sesión predeterminada de datos ... frecuentemente se utiliza la
misma configuración de comandos SET, estos se configuran globalmente para la sesión predeterminada de datos cuando inicia su
aplicación.
Los ejemplos LISAG_PDS_SETs_Abstract*.PRGs y sus correspondientes .SCX demuestran una de estas técnicas. Cada
LISAG_PDS_SETs_Abstract*.PRG instancia un demo (objeto aplicación) "application object", que contiene un método de usuario
GetSETCommandSetting(). Como el objeto application es instanciado en la sesión predeterminada de datos #1, al llamar a su
método GetSETCommandSetting() DEVUELVE la configuración especificada como lo establece la sesión predeterminada de datos.
Como la sesión privada de datos instancia, pueden establecer sus comandos SET para que coincidan con aquellos establecidos en la
sesión predeterminada de datos llamado al objeto application global. Aquí está el código esencial del método SetSETCommands de
la clase frmLISAG_PDS_SETs_Abstract en LISAG_PDS_SETs_Abstract.VCX:
LOCAL laSETs[3], luSetting, lcString
laSets[1] = "DELETED"
laSets[2] = "MULTILOCKS"
laSets[3] = "TALK"
FOR EACH lcSet IN laSETs
luSetting = goApplication.GetSetCommandSetting(m.lcSet)
lcString = "SET " + m.lcSet + SPACE(1) + TRANSFORM(m.luSetting)
&lcString
ENDFOR
Utilice DataEnvironment sólo en tiempo de diseño:
Antes de que decida abandonar del todo el uso del DataEnvironment, existe una idea que debe considerar: Mientras esté
diseñando formularios basados en .SCX, utilice DataEnvironment solo para cuestiones de diseño:
• Arrastrar y soltar un(os) cursor(es) al formulario para crear instantáneamente controles Grid.
• Arrastrar y soltar los campos al formulario para agregar controles cuyos ControlSource ya estarán definidos y con un
ancho aproximado (Width) (si el mapeo de campos (field mapping) tiene establecido que incluya el título del campo, se
puede obtener ya la etiqueta correspondiente al Caption existente).
• Establecer el ControlSource de cualquier control desde la ventana propiedades, seleccionando uno los cursores actuales
desde el cuadro desplegable.
• Tener acceso al DataEnvironment y sus miembros (cursores) en los generadores de usuarios.
Recuerde establecer las propiedades AutoOpenTables y AutoCloseTables a .F. como muestra la Figura 5, entonces VFP ignora todo
lo que esté en el DataEnvironment en tiempo de ejecución. Sin embargo, para formularios con sesión privada de datos, cuando el
formulario es cerrado/destruido, VFP cierra todos los cursores abiertos mientras estuvo activo el formulario.
En tiempo de ejecución abra las vistas y tablas manualmente, utilizando una de las técnicas descritas en el método Load de
DesignTimeDE.SCX, aprovechando sus ventajas sobre el comportamiento del DataEnvironment en tiempo de ejecución:
• Si/cuando hay un problema al abrir una tabla/vista, se puede enviar un mensaje de usuario y devolver (RETURN) .F.,
interrumpir la creación de esa tabla/vista mientras continúa intacto el resto de la aplicación. Por el contrario, cuando
DataEnvironment encuentra un problema como un archivo no encontrado, cabecera de tabla dañada, índice dañado, etc.
El DataEnvironment falla, se interrumpe su ejecución, así como todo el resto de la aplicación.
• Se puede establecer el comando SET PATH antes de llamar los datos, de esta forma se puede intercambiar entre
diferentes conjuntos de datos o simplemente ajustar la ruta (PATH) antes de llamar los datos
• Puede utilizar herramientas como Stonefield Database Toolkit para reparar problemas con tablas, índices, memos, etc.
Utilizando esta técnica, puede ignorar las inconsistencias por utilizar DataEnvironment, que se han descrito anteriormente en este
documento, ya que no hace nada en tiempo de ejecución.
Incluso en un diseño n-Capas, si sus objetos de Negocio proporcionan un cursor de datos puede agregar tablas/vistas al
DataEnvironment (establecer propiedad Alias), allí donde están disponibles para conveniencia en tiempo de diseño y son ignoradas
en tiempo de ejecución, cuando el objeto negocio proporciona el dato real.
Esta técnica trabaja igualmente bien para formularios basados en .VCX ... no existe objeto DataEnvironment nativo, con lo cual
tiene que cargar los datos y código desde el método Load y por tanto ignorar el DataEnvironment.
Si se establecen las propiedades DEClass/DEClassLibrary, los datos indicados están disponibles en tiempo de ejecución pero el
DataEnvironment no está disponible en tiempo de diseño.
Mucho cuidado al romper la secuencia nativa de eventos de creación
Existen vías para romper la secuencia nativa de eventos de creación. Algunas veces las consecuencias son menos graves, en otras
son catastróficas y pueden causar todo tipo de comportamiento indeseado.
El ejemplo LISAG_SetFocus demuestra una de las posibilidades. Desafortunadamente esto es muy común y muy fácil de hacer.
La última línea de código en el evento Form.Init, es esta línea aparentemente inocente que asegura que al crear, el botón OK tiene
el foco:
THIS.cmdOK.SetFocus()
Sin embargo 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 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 creació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
creació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 creación pudiera ejecutar condicionalmente sólo si el
formulario está siendo creado:
IF THIS.lInstantiating
* realizar estas acciones solo si THISFORM se está instanciando
ENDIF
Ahora, si existe Member.SetFocus(), en el Form.Init la bandera se establece prematuramente en .F., antes de que el Init finalice y
antes de que se ejecute el Show. Cualquier código en Form.Show que se debe ejecutar solamente durante la creación será ignorado
porque la bandera ya está en .F. en un escenario normal (no una demo) es un error muy difícil de depurar porque solo ocurre si la
condición encuentra el Member.SetFocus() explícito.
El ejemplo demuestra como establecer ese tipo de propiedad de usuario
¿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 crear
un formulario? El ejemplo LISAG_SetFocus1 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 creació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.
Cuando al llamar Form.Load() o Form.Init() devuelven .F. no se disparan los eventos Form.Destroy ni Form.Unload
Devolviendo .F. desde el Load o el Init de un formulario no se crea, es evidente; pero lo que no se dijo:
1. Cuando el Load devuelve .F., no se disparan ni Form.Destroy ni Form.Unload.
2. Cuando el Init devuelve .F., se dispara Form.Unload; pero no lo hace Form.Destroy. Debido a que los miembros
contenidos en el formulario se crean antes que el Form.Init, el Destroy de esos miembros se dispara ya que están fuera de
límites. (Since form members instantiate before the Form.Init, the Destroy of form members does fires as they go out of
scope).
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 creado 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.!
Ejecutar Form.Destroy consistentemente
Los formularios LISAG_QRDU_AbortLoadInit1.SCX and LISAG_QRDU_AbortLoadInit2.SCX demuestran técnicas similares que puede
utilizar para garantizar que los Form.Destroy/Unload (códigos de limpieza) se ejecuten adecuadamente.
Peculiaridad del SYS(1271)
Primeramente vamos a mirar la función SYS(1271) que podemos poner a trabajar para nuestro beneficio. El ejemplo The
LISAG_SYS1271.SCX demuestra que no puede hacer esta llamada:
SYS(1271,THISFORM)
Hasta que se haya completado Form.Load, no se puede llamar desde un método de DataEnvionment, el Form.Load ni desde otro
método llamado desde el Form.Load. De hacerlo, recibirá el mensaje, poco intuitivo, "Insuficiente memoria para completar esta
operación" ("Not enough memory to complete this operation"). Puede verlo al hacer DO FORM LISAG_SYS1271 ... seleccionar
<Ignore> para continuar con la instalación de LISAG_SYS1271.SCX.
Por un lado, es inconveniente tener que esperar hasta el Form.Load para consultar SYS(1271,THISFORM) si desea realmente
verificarlo antes. Pero LISAG_QRDU_AbortLoadInit1.SCX 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.
Referencia de objetos en la destrucción de formularios. (Object Reference Cleanup On Form Destruction)
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.
Si algunas de las referencias de objetos no se liberan explícitamente, el contenedor no se libera. Si el contenedor es un formulario o
un contenedor dentro de un formulario, el formulario no se libera. Esta es la causa del error "Referencia de objeto dañada"
("Dangling object reference").
ORCleanup1.PRG crea 2 instancias de ORCleanup1.SCX para demostrar el problema:
1. DO ORCleanup1
2. En la instancia 2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una
referencia de objeto a un miembro de la instancia1 del formulario.
3. Intente cerrar la instancia 1: Haga clic en OK, haga clic en "X" a la derecha de la barra de título, o seleccione Close desde
el menú en la caja de control de la barra de título. La instancia 1 del formulario se niega a cerrarse, debido a referencias
de objeto dañadas, de hecho, si selecciona Cerrar desde el menú en la caja de control de la barra de título, la opción
Cerrar no aparece y la "X" a la derecha de la barra de título aparece deshabilitada.
4. Cierre la instancia 2 del formulario. En cuanto se cierra, se cierra también la instancia1 ... su código Destroy ya se había
disparado, solo estaba esperando a que se liberaran las referencias de objetos externos a sus miembros.
Lo que no te dijo tu madre
Para formularios, la limpieza de referencia de objetos nunca se debe hacer después de Form.Destroy. Esto es fácil de hacer.
Sin embargo, cuando los miembros del formulario necesitan limpiar las referencias de objetos, el código colocado en su Destroy
puede ser inútil. Recuerde: el formulario destruye "de afuera hacia adentro", por tanto el Destroy de los miembros se dispara
después del Destroy del formulario como tal. Cuando se daña la referencia de objetos existente, se dispara el Form.Destroy; pero la
destrucción frena aquí, y no se dispara ningún otro evento, incluyendo el Destroy de sus miembros, hasta que se libera la referencia
de objeto dañada.
Esto es un gran problema cuando diseña formularios de tal forma que un formulario no modal mantenga referencias de objetos a
un miembro de otro formulario no modal u otros objetos externos. Dos casos comunes de daño de referencia de objetos:
1. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario intenta cerrar Form2;
pero se niega a cerrar hasta que los miembros de Form1 liberan sus referencias de objeto (este es el escenario que se
muestra en ORCleanup1.PRG/.SCX).
2. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario cierra Form1; pero
Form1 no se cierra del todo, queda como un objeto artificial como una sesión de datos "desconocida" ("Unknown") visible
en la ventana Sesión de datos.
Cuando objetos externos contienen referencias a miembros de THISFORM
ORCleanup1a.SCX demuestra una vía para solucionar el comportamiento necesario. Repita los pasos para ORCleanup1 y observe la
diferencia:
1. DO ORCleanup1a.
2. En la instancia2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una
referencia de objeto a un miembro de la instancia1 del formulario.
3. Cierre la instancia 1del formulario. Se cierra normalmente, como se espera, aún cuando aparentemente no se ha hecho
la limpieza de referencia de objetos para liberar las referencias de la instancia 2 del formulario tiene a los objetos de la
instancia 1 del formulario.
Puede ver la técnica que he utilizado en ORCleanup1a.SCX para examinar el código en el evento Clic de cada casilla de verificación
del grupo inferior. He utilizado la función BINDEVENT() para asegurar que cuando se guarda una referencia de objeto, el Destroy de
los objetos guardados del formulario llama automáticamente al código de limpieza de los objetos haciendo su almacenaje.
Entonces, cuando es cerrado el formulario cuyas referencias a objetos miembros han sido guardadas, su Destroy llama al código de
limpieza de la referencia de objetos de los objetos externos manteniendo la referencia de objetos. He aquí lo que ocurre:
1. DO ORCleanup1a
2. Haga Clic en la primera casilla de verificación del grupo inferior de la instancia 2 del formulario. El código del evento Clic
guarda una referencia de objeto al miembro txtDemo1 en la instancia 1 del formulario a la propiedad de usuario
oFormMember de la instancia 2 del formulario. El código Clic también ejecuta BINDEVENT() para asegurarse de que
cuando se cierra la instancia 1 del formulario se ejecuta el método ORCleanup de la instancia 2 del formulario.
3. Haga Clic en el botón de comandos OK de la instancia1 del formulario. Cuando se dispara el Destroy, ejecuta el método
Cleanup de la segunda instancia del formulario, gracias al BINDEVENT(). Entre otras cosas, el método ORCleanup de la
instancia 2 del formulario establece su propiedad oFormMember a .NULL., liberando la referencia de objeto guardada el
txtDemo miembro de la instancia 1 del formulario.
En un conjunto real de clases jerárquicas, pudiera probablemente agregar el método ORCleanup a cada una de sus clases bases, de
tal forma que el Checkbox.Click haría BINDEVENT() a su propio método ORCleanup en lugar de THISFORM.ORCleanup, haciendo
más granular el control del proceso.
Esta técnica requiere, por supuesto, utilizar VFP 8.0, versión en la que fue agregada al lenguaje la poderosa función BINDEVENT().
Cuando los miembros de THISFORM contienen referencias a objetos externos
Como se ha descrito antes, toda la limpieza a las referencias de objeto "garbage collection" debe hacerse antes de Form.Destroy,
que ocurre antes que se dispare el evento Destroy de cualquier miembro.
La solución es codificar el Form.Destroy con mensajes a sus miembros para establecer explícitamente todas las referencias de
objeto guardadas a .NULL. Mi sugerencia es separar esta tarea a un método de usuario ORCleanup agregado a sus clases base.
Primero, agregue un método de usuario ORCleanup agregado a sus clases base Form. Agregue código al Destroy de su clase base
formulario para llamar a THISFORM.ORCleanup().
En cada clase formulario o instancia, cada vez que escriba código para guardar una referencia de objeto a un miembro de
formulario, asegúrese de colocar el código de liberación correspondiente en el ORCleanup del formulario contenido. Cada vez que
se dispara el Form.Destroy, es realizada la limpieza a todas las referencias de objeto.
Sin embargo, puede que quiera abstraerse en lo delante de este comportamiento, ya que cuando está diseñando la clase
contenedora (pageframes, optiongroups, grids, containers, etc.), no puede colocar código en el Destroy del formulario contenido
porque no sabe qué instancia del formulario o clase va a contener finalmente el contenedor que está preparando. Para solucionar
este problema, agregue un método de usuario ORCleanup para cada una de sus clases base que pueden ser miembros de un
formulario (Textbox, Spinner, Custom, etc.). Programe su clase base form Form.ORCleanup para interactuar con cada uno de sus
miembros, llamando su método ORCleanup.
Si diseña el método ORCleanup de objetos contenedores (grids, pageframes, pages, optiongroups, containers, etc.) para interactuar
con cada uno de sus miembros de la misma forma que el ORCleanup a nivel de formulario solo hace un lazo entre su arreglo de
controles y llama al ORCleanup de cada uno de sus miembros directos.
Dañar la referencia de objetos cuando se utilizan colecciones
Ver la serie de ejemplos ORCleanup2.SCX
_Screen.ActiveForm y qué se puede hacer con esto
Durante el curso de creación del formulario hay un punto en el cual el formulario se convierte en _Screen.ActiveForm. Sabiendo
cuando esto ocurre nos permite poder hacer cosas muy buenas con la referencia de objeto _Screen.ActiveForm.
Cuando THISFORM se convierte en _Screen.ActiveForm
Como se demuestra en el ejemplo _ScreenActiveForm.SCX, el formulario que se está instanciando se convierte en
_Screen.ActiveForm inmediatamente después de ser mostrados (Show())
Lo que nunca te dijo tu madre
Desafortunadamente, _Screen.ActiveForm no es siempre _Screen.ActiveForm. En muchas ocasiones donde el formulario activo
contiene uno o más controles ActiveX, _Screen.ActiveForm puede ser una referencia de objeto a un control ActiveX, NO al
formulario que lo contiene.
La biblioteca en tiempo de ejecución X6SAF.PRG para el framework Visual MaxFrame Profesional está incluida en esta sesión y
controla este caso. Para tener una referencia de objeto confiable _Screen.ActiveForm, sustituya el código por el siguiente:
LOCAL loActiveForm
loActiveForm = _Screen.ActiveForm
IF TYPE("loActiveForm.BaseClass") = "C"
* aquí no es el formulario activo
ELSE
* m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activo
ENDIF
lo que es el equivalente más fiable:
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
Guardar una referencia al formulario y objeto llamados
Al combinar los conocimientos sobre la secuencia de eventos de creación de formulario LISAG y el conocimiento de cuando
THISFORM se convierte en _Screen.ActiveForm, hay algunas cosas interesantes que puede hacer con esta información.
El ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX demuestra una buena técnica para guardar fácilmente una referencia de objeto al
formulario y objeto (si existe) llamados.
Guardar una referencia al formulario llamado
Cuando se ejecuta Form.Load, THISFORM no se ha instanciado todavía, y no es el formulario activo _Screen.ActiveForm. Más
importante, cualquier formulario (si existe) que se ejecuta cuando THISFORM se llama se refleja aun en _Screen.ActiveForm. Así, es
como un pestañeo a obtener una referencia de objeto "libre" al formulario que se está ejecutando cuando se llama THISFORM, y lo
guarda en una propiedad de usuario disponible durante la vida del THISFORM:
LOCAL loActiveForm
loActiveForm = X6SAF()
IF VARTYPE(m.loActiveForm) = "O"
THIS.oCallingForm = m.loActiveForm
ELSE
THIS.oCallingForm = .NULL.
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:
Algunas cosas interesantes sobre estas técnicas:
1. Puede consultar información sobre el formulario llamado antes del THISFORM.Init, donde se reciben parámetros,
aceptando otra configuración que necesita optimizar lo que ocurre antes de que puedan ser verificados los parámetros.
2. No hay necesidad de llamar al formulario para pasar una referencia de objeto a sí mismo al formulario llamado.
3. Cuando THISFORM se instancia desde cualquier sitio, como un menú, no hay sitio para el formulario activo para pasar
una referencia de objeto a si mismo a THISFORM.Init
4. Debido a que la referencia de objeto para el formulario llamado se guarda en una propiedad de usuario de THISFORM,
está disponible durante la vida de THISFORM, luego de _Screen.ActiveForm se actualiza nunca después de la referencia
del formulario llamado.
Estas características se muestran en el ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX como se muestra en la figura 11:
1. El evento Clic del cmdCallForm en _SAF1.SCX un comando DO FORM ... no se pasan parámetros.
2. El evento Load del _ScreenActiveForm1.SCX contiene código para verificar el formulario llamado, y entonces, guarda
una referencia de objeto a su THISFORM.oCallingForm.
3. El evento Init del grdCustomers en _ScreenActiveForm1.SCX contiene código para verificar el formulario llamado. En tal
caso este formulario es consultado por su actual Orders.CustomerID, en cualquier caso, como se muestra en la figura 11.
En ese caso grdCustomers establece el puntero a su registro inicial al CustomerID indicado. Observe que esta aplicación
tiene lugar antes que THISFORM.Init, donde un parámetro CustomerID pudiera ser recibido y aplicado.
Puede DO FORM _ScreenActiveForm1.SCX con nada, y el código descrito arriba simplemente encuentra que no hay mucho que
puedas hacer.
¡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.
Guardar una referencia a un control del formulario llamado
Al guardar una referencia de objeto de un formulario existente es fácil. Pero, ¿qué tal si guardamos una referencia de objeto al
control actual en el formulario; pero sólo si este control está en la pila de ejecución del programa, indicando que es responsable por
llamar THISFORM (como el botón cmdCallForm en _SAF1.SCX)?
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.
Mantener referencias de objetos a los formularios llamados
El ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX descrito en la sección previa pudiera hacerle pensar sobre una técnica más
poderosa.
En ese caso, seguramente disfrutará de esta.
Me han preguntado por un código para situaciones donde el formulario no modal necesite no sólo llamar a uno o más formularios
no modales adicionales; pero además para cada uno de los formularios llamados para mantener las referencias de objeto a cada
otro así que puede actualizar cada otro de tiempo en tiempo.
Con más frecuencia me han preguntado algunos desarrolladores que han tratado de implementar una situación, como para
depurarlo.
No es trivial, mantener todas las referencias de objeto en ambas direcciones, ni para asegurarse que esa limpieza de referencia de
objeto (garbage collection) fue hecha para prevenir una posibilidad de dañar la referencia de objeto.
Gracias a la función BINDEVENT() agregada en VFP 8.0, este escenario es ahora muy fácil de implementar. Mejor aún, el código
necesario es fácil de conceptuar. La serie de ejemplos FormORCleanupCaller*.SCX contienen el código. Todos los formularios en la
serie FormORCleanupCaller*.SCX están basados en el ejemplo de la clase base formulario frmBaseForm en la biblioteca
FormORCleanup.VCX.
La técnica es manipular todo sin pasar ningún parámetro, utilizando una técnica similar demostrada en el ejemplo
_SAF1.SCX/_ScreenActiveForm1.SCX.
Aquí vemos los conceptos en frmBaseForm:
1. La llamada desde Load llama al método de usuario StoreCalledForm.
2. La llamada desde StoreCalledForm verifica si existe un formulario llamado. En ese caso:<ol type="a">
3. La referencia de objeto a un formulario llamado desde cualquier objeto llamado es guardada en
THISFORM.oCallingForm y THISFORM.oCallingFormControl.
4. Un BINDEVENT() es utilizado para asegurar que cuando se cierra el formulario llamado, es
llamadoTHISFORM.ORCleanupCallingForm, donde las referencias de objeto se guardan en THISFORM.oCallingForm y son
liberados THISFORM.oCallingFormControl, permitiendo que el formulario llamado cierre adecuadamente.
5. Si el formulario llamado tiene un método de usuario StoreCalledForm (será si hereda desde frmBaseForm), es llamado
su método StoreCalledForm y se pasa una referencia de objeto a THISFORM.
• La llamada desde StoreCalledForm:<ol type="a">
• Guarda una referencia de objeto al formulario llamado en una propiedad de arreglo (Tuve problemas de hacer una propiedad
colección para trabajar adecuadamente).
• Utilice un BINDEVENT() para asegurar que cuando se cierra el formulario llamado (el que está actualmente bajo creación), el
método ORCleanupCalledForm del formulario llamado es llamado, donde es liberada la referencia de objeto al formulario llamado.
El resultado en cadena es que ambas llamadas y llamados formularios mantengan la referencia de objetos entre ellos. Cada
formulario llamado puede tener una referencia de objeto a sólo un formulario invocador; pero cada formulario invocador puede
mantener la referencia a un número ilimitado de formularios invocados. Más importante, es manipulada la necesaria limpieza de
referencia de objeto.
Cada instancia de formulario hereda todo el comportamiento necesario, y el formulario o sus miembros puede simplemente
THISFORM., THISFORM.oCallingFormObject, y THISFORM.aCalledForms[] objetos en cualquier momento (por supuesto, después de
verificar para ver si son referencias válidas de objeto)
Este comportamiento se demuestra de esta forma, como se observa en las figuras 14 y 15:
1. DO FormORCleanupCaller1.SCX
2. Haga Clic en cualquiera de los botones DO FORM. Si hace Clic en alguno de ellos más de una vez, se cargan nuevas instancias de
FormORCleanupCalled*.SCX sobre las ya existentes.
3. Haga Clic en el botón <?> de cualquier formulario para ver en cada formulario sobre el otro invocador/invocado.
4. Haga Clic en el botón <OK> de cualquier formulario para que vea que no hay problemas de referencias de objetos dañadas.
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.
Nota: El autor ha dado su autorización, y los ejemplos se pueden descargar de: DrewSpeedieDemo.zip (167 KB).
Hola invitado 15 Jun, 2013 - 01:02
Menú principal
• Inicio
• Temas
• Secciones
• Descargas
• Enlaces
• Indice
• Conferencias
• MasFoxPro
• Enviar noticia
• Búsquedas
• Usuarios
• Preferencias
• Top 10
• P+F [FAQ]
• RSS
Anuncios
Lo que nunca te contó tu madre sobre instanciar y destruir
formularios
(7606 palabras totales en este texto)
(22009 lecturas)
Lo que nunca te contó tu madre sobre instanciar y destruir formularios
Autor: Drew Speedie (www.visionds.com)
Traducido por: Ana María Bisbé York (amby@telefonica.net)
Para: PortalFox (www.portalfox.com)
(Sesión What Your Mother Never Told You About Form Instantiation and Destruction presentada por el autor en la Conferencia
DevEssentials Kansas, 2004)
Nota aclaratoria de la traductora
El artículo publicado inicialmente tenía un error en su contenido, al explicar los eventos que se suceden al destruir formularios.
Consultado el autor, el artículo fue corregido el 13 de Abril de 2005. (ver noticia Más sobre "Lo que tu madre no te contó sobre
instanciar y destruir Formularios"). Asimismo el autor autorizó la descarga de los ejemplos. Los ejemplos citados se pueden descargar
de: DrewSpeedieDemo.zip (167 KB).
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.
Las bases LISAG (Load-Init-Show-Activate-GotFocus) y QRDU (QueryUnload- Release- Destroy- Unload)
Para implementar exitosamente los escenarios de instanciación y destrucción de un formulario, lo primero que debe entender es la
secuencia nativa de los eventos.
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)
Anuncios Google
► VFP Foxpro
► Foxpro
► Fox pro
Página 1 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c...
15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
© 2012 PortalFox
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
Instanciación / Destrucción de miembros contenedores
Como demostrará posteriormente el ejemplo LISAG_QRDU2.SCX, los contenedores se instancian "de dentro hacia afuera" de igual forma
que hace el propio formulario (debido a que también es un contenedor) ... El Init de los miembros contenidos de disparan antes que el
Init del contenedor padre. En la destrucción ocurre lo contrario ... el Destroy del contenedor se dispara antes que el Destroy de sus
miembros contenidos.
Cuando el formulario tiene establecido valores para DEClass, DEClassLibrary
Como muestra el ejemplo LISAG_QRDU_DE.SCX, cuando un formulario establece valores para DEClass, DEClassLibrary (disponibles a
partir de VFP 8.0), las cosas son ligeramente diferentes. Debido a que el objeto DataEnvironment es un objeto completamente separado,
se instancia completamente antes del Form.Load.
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.
Lo que nunca te contó tu madre
Los ejemplos LISAG_QRDU*.SCX demuestran algunos aspectos de interés, muchos de los cuales pudiera no encontrar intuitivos:
Form
1. En dependencia de cómo se cierra el formulario, se ejecuta el método Release o el evento QueryUnload; pero no ambos ...
Form.Release no es un buen lugar para colocar código, no importa lo que sea, que se deba ejecuta cada vez que se destruya el
formulario. Utilice en su lugar los eventos Destroy o Unload.
2. Debido a que muchos comandos como SET TALK están limitados a la sesión privada de datos (ver DATASESSION TO en la ayuda
de VFP para ver la lista de estos comandos SET), decidir en que lugar va a asignar valores a estos comandos SET depende de
cómo abre sus datos. Si no utiliza nunca DataEnvironment es fácil: los comandos SET para la sesión privada de datos se colocan
en el Load de la clase form. Si utiliza DataEnvironment nativo en un formulario basado en .SCX, tendrá que establecer estos
comandos SET en DataEnvironment.OpenTables/BeforeOpenTables, antes de que las tablas/cursores sean abiertos. Si
implementa un DataEnvironment de usuario especificado en las propiedades DEClass/DEClassLibrary (VFP 8.0 y superior), podrá
establecer los comandos SET para la sesión privada de datos en el método Init de la clase DataEnvironment; pero sea consciente,
que el Init de los miembros Cursor, CursorAdapter, y los objetos Relation se disparan antes que DataEnvironment.Init. Vea
además un par de secciones a continuación en este documento.
3. Al destruir un formulario no se disparan los eventos Form.Deactivate ni Form.LostFocus
DataEnvironment
1. El DataEnvironment abre sus datos implícitamente entre el DataEnvironment.BeforeOpenTables y el Form.Load() ...
inmediatamente antes del Form.Load.
2. El DataEnvironment NO abre los datos en el evento OpenTable -- OpenTables es de uso opcional para abrir programaticamente
los datos si se utiliza AutoOpenTables = .F.
3. El evento BeforeOpenTables no se dispara antes (Before) que el evento OpenTables, BeforeOpenTables está mal nombrado y se
debió llamar algo como AfterOpenTablesBeforeImplicitOpenTables. (DespuesAbrirTablasAntesImplicitamenteAbrirTablas)
4. El DataEnvironment.Cursor.Init se dispara DESPUÉS que el cursor ya fue abierto y DESPUÉS del Form.Load
5. El DataEnvironment.Init se dispara DESPUÉS que el DataEnvironment halla cumplido todas las misiones encomendadas.
DESPUÉS que los cursores han sido puestos en uso, y DESPUÉS del Form.Load. Excepto cuando el DataEnvironment es una
instancia de usuario especificada en las propiedades DEClass/DEClassLibrary, en ese caso se instancia completamente antes de
Form.Load.
6. Consecuente con el comportamiento de OpenTables y cuando las tablas se abren explícitamente por el DataEnvironment, el
mismo cierra las tablas ANTES del método CloseTables.
7. Mientras la secuencia de instanciación / destrucción para el formulario y sus miembros es consistente, la secuencia de
instanciación / destrucción para el DataEnvironment depende de su implementación: DataEnvironment nativo para un formulario
basado en .SCX, DEClass DataEnvironment de usuario para un formulario basado en .SCX o .VCX.
Llamar THISFORM.Metodos o consultar THISFORM.Propiedades desde el DataEnvironment
Los ejemplos LISAG_DE*.SCX demuestran comportamiento inconsistente relacionado con llamar a métodos del formulario (THISFORM)
desde el DataEnvironment.
Lo que nunca te dijo tu madre
DataEnvironment nativo de VFP para un formulario (.SCX)
Cuando un formulario basado en .SCX utiliza DataEnvironment nativo de VFP, el DataEnvironment se instancia primero que el formulario
como tal. Ver los ejemplos LISAG_QRDU*.SCX, los que demuestran el su comportamiento nativo. Sin embargo
1. LISAG_DE.SCX demuestra que las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment
que se disparan antes del Form.Load son ¡COMPLETAMENTE IGNORADAS!. Por ejemplo, las llamadas a THISFORM.Metodos desde
los eventos de DataEnvironment OpenTables y BeforeOpenTables no hacen nada, no se invoca ningún método y no se genera un
error. Supongo que VFP no ha iniciado todavía la instanciación del formulario y por tanto, no reconoce THISFORM como un
objeto; pero yo debería esperar que se generara un error, como hace SYS(1271,THISFORM).
Página 2 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c...
15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
2. LISAG_DE2.SCX demuestra que las llamadas a métodos de formulario (nativos o de usuario), desde métodos del
DataEnvironment que se disparan antes del Form.Load SÍ disparan esos métodos EN CASO que hayan sido definidos en la clase
Form a partir de la cual hereda el formulario actual. Por supuesto, todos los métodos nativos de VFP heredan de la clase base
Form de VFP. Entonces, el código que se ejecuta es el heredado de la clase Form, NO cualquier código ubicado en los métodos
nativos o de usuarios del formulario instanciado. Una vez que se dispara el Form.Load, la llamada a estos mismos métodos
disparan los métodos para el nivel instanciado.
3. De igual forma, si en el DataEnvironment que se dispara antes del Form.Load, consulta el valor de una propiedad (nativa o de
usuario) que se establece explícitamente en la ficha Propiedades a nivel del formulario instanciado, los valores son los que sean
predeterminados por VFP para esa propiedad (.F. para todas las propiedades de usuario), como si hubiera establecido las
propiedades como predeterminadas en la ficha propiedades. Sin embargo; para las propiedades establecidas en la ventana
propiedades de cualquier clase padre del formulario instanciado, el valor es evaluado adecuadamente, tal y como esperamos. Sin
embargo, puede establecer una propiedad en cualquier método del DataEnvironment.
4. En los métodos de DataEnvironment, IntelliSense no muestra NINGUN PEM (Propiedades, Eventos, Métodos) de usuario, ya sea
de la instancia actual o de alguna clase padre de la instancia actual del formulario.
Conclusión
La conclusión es que se puede abstraer del comportamiento de DataEnvironment para métodos de usuario de formulario, ya que estos
métodos están basados en formularios .SCX. Y debe recordar que el código que coloque en estos métodos en el nivel instanciado
del .SCX será completamente ignorado, cuando esos métodos son llamados desde eventos del DataEnvironment como OpenTables y
BeforeOpenTables que se ejecutan antes que Form.Load. Para el nivel instanciado establecer propiedades es poco fiable hasta que no se
ejecute Form.Load.
DataEnvironment con DEClass/DEClassLibrary de usuario para un formulario basado en .SCX
Cuando las propiedades DEClass y DEClassLibrary de un formulario basado en .SCX tienen asignado un valor que especifique un objeto
DataEnvironment de usuario, las cosas son algo diferentes
1. En tiempo de diseño, cuando da valor a las propiedades DEClass y DEClassLibrary asignando una clase DataEnvironment con
código en su evento Init, o en el Init de alguno de sus miembros cursor/relation, y ese Init llama a un método de usuario de ese
formulario, sólo el hecho de asignar valor a las propiedades DEClass y DEClassLibrary genera un error "Objeto no contenido en el
formulario" ("Object is not contained in a Form"), para cada llamada al método de usuario del formulario (THISFORM).
2. En tiempo de ejecución, como demuestra LISAG_DEClass.SCX se genera el mismo error "Objeto no contenido en el formulario".
Puede seleccionar dos veces <Ignore> para que continúe LISAG_DEClass.SCX
3. En tiempo de ejecución, como demuestra LISAG_DEClass.SCX, aquellas mismas llamadas a métodos de usuario de THISFORM
que generaban un error cuando eran llamados desde el DataEnvironment.Init o desde el Init de uno de sus miembros, NO HACEN
NADA si son llamados desde los eventos DataEnvironment.OpenTables o BeforeOpenTables ... los métodos de usuario agregados
al .SCX en la instancia actual son completamente ignorados.
4. En tiempo de ejecución, como demuestra LISAG_DEClass2.SCX, cuando ocurren eventos como OpenTables and
BeforeOpenTables del DataEnvironment, que se disparan después del DataEnvironment.Init y el Init de sus miembros llaman a
métodos de THISFORM, sólo se ejecuta el código para aquellos métodos que es heredado de la clase padre ... no el código
colocado en los métodos del nivel actual instanciado.
Conclusión
La conclusión es que el código colocado en métodos de la instancia actual del formulario nunca se ejecuta cuando el método es llamado
desde eventos del DataEnvironment. Para confiar en los comportamientos abstractos del DataEnvironment, debe crear todo el código en
la clase DataEnvironment (jerárquicamente).
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.
Lo que nunca te dijo tu madre
Eso depende de si utiliza o no DataEnvironment y en ese caso, si utiliza el DataEnvironment nativo de VFP o una clase personalizada
DataEnvironment.
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
En el formulario de la más alta jerarquía establezca la propiedad DataSession a 2- Private Data Session.
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()
Página 3 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c...
15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
Recuerde, cualquier código que coloque en el método de usuario en el nivel instanciado no se ejecutará, como fue explicado en la
sección anterior ... solamente se ejecuta el código colocado en la(s) clase(s) padre de la instancia actual.
DataEnvironment de usuario con DEClass/DEClassLibrary
Coloque la configuración lógica de los comandos SET para su sesión privada de datos en el Init de la clase DataEnvironment. Sea
consciente de que pudiera tener algunos comandos SET como SET TALK OFF en el Init de sus clases base Cursor o Relation, ya que
estos se ejecutan antes que el Init de DataEnvironment.
¿Cómo establecer los comandos SET?
Una vez que haya determinado dónde necesita colocar su código para los comandos SET de su sesión privada de datos, la cuestión es
cómo debe escribir ese código. Puede escribir un código estricto con los comandos SET deseados (como se demuestra en
LISAG_PDS_SETs.SCX); pero el mejor enfoque pudiera ser tener la sesión privada de datos que lea los valores desde la sesión
predeterminada de datos ... frecuentemente se utiliza la misma configuración de comandos SET, estos se configuran globalmente para la
sesión predeterminada de datos cuando inicia su aplicación (como se demuestra en LISAG_PDS_SETs_Abstract*.SCXs)
Los ejemplos LISAG_PDS_SETs_Abstract*.PRGs y sus correspondientes .SCX demuestran una de estas técnicas. Cada
LISAG_PDS_SETs_Abstract*.PRG instancia un demo (objeto aplicación) "application object", que contiene un método de usuario
GetSETCommandSetting(). Como el objeto application es instanciado en la sesión predeterminada de datos #1, al llamar a su método
GetSETCommandSetting() DEVUELVE la configuración especificada como lo establece la sesión predeterminada de datos. Como la sesión
privada de datos instancia, pueden establecer sus comandos SET para que coincidan con aquellos establecidos en la sesión
predeterminada de datos llamado al objeto application global. Aquí está el código esencial del método SetSETCommands de la clase
frmLISAG_PDS_SETs_Abstract en LISAG_PDS_SETs_Abstract.VCX:
LOCAL laSETs[3], luSetting, lcString
laSets[1] = "DELETED"
laSets[2] = "MULTILOCKS"
laSets[3] = "TALK"
FOR EACH lcSet IN laSETs
luSetting = goApplication.GetSetCommandSetting(m.lcSet)
lcString = "SET " + m.lcSet + SPACE(1) + TRANSFORM(m.luSetting)
&lcString
ENDFOR
Utilice DataEnvironment sólo en tiempo de diseño:
Antes de que decida abandonar del todo el uso del DataEnvironment, existe una idea que debe considerar: Mientras esté diseñando
formularios basados en .SCX, utilice DataEnvironment solo para cuestiones de diseño:
• Arrastrar y soltar un(os) cursor(es) al formulario para crear instantáneamente controles Grid.
• Arrastrar y soltar los campos al formulario para agregar controles cuyos ControlSource ya estarán definidos y con un ancho
aproximado (Width) (si el mapeo de campos (field mapping) tiene establecido que incluya el título del campo, se puede obtener
ya la etiqueta correspondiente al Caption existente).
• Establecer el ControlSource de cualquier control desde la ventana propiedades, seleccionando uno los cursores actuales desde el
cuadro desplegable.
• Tener acceso al DataEnvironment y sus miembros (cursores) en los generadores de usuarios.
Recuerde establecer las propiedades AutoOpenTables y AutoCloseTables a .F. como muestra la Figura 5, entonces VFP ignora todo lo que
esté en el DataEnvironment en tiempo de ejecución. Sin embargo, para formularios con sesión privada de datos, cuando el formulario es
cerrado/destruido, VFP cierra todos los cursores abiertos mientras estuvo activo el formulario.
En tiempo de ejecución abra las vistas y tablas manualmente, utilizando una de las técnicas descritas en el método Load de
DesignTimeDE.SCX, aprovechando sus ventajas sobre el comportamiento del DataEnvironment en tiempo de ejecución:
• Si/cuando hay un problema al abrir una tabla/vista, se puede enviar un mensaje de usuario y devolver (RETURN) .F., interrumpir
la instanciación de esa tabla/vista mientras continúa intacto el resto de la aplicación. Por el contrario, cuando DataEnvironment
encuentra un problema como un archivo no encontrado, cabecera de tabla dañada, índice dañado, etc. El DataEnvironment falla,
se interrumpe su ejecución, así como todo el resto de la aplicación.
• Se puede establecer el comando SET PATH antes de llamar los datos, de esta forma se puede intercambiar entre diferentes
conjuntos de datos o simplemente ajustar la ruta (PATH) antes de llamar los datos
• Puede utilizar herramientas como Stonefield Database Toolkit para reparar problemas con tablas, índices, memos, etc.
Utilizando esta técnica, puede ignorar las inconsistencias por utilizar DataEnvironment, que se han documentado anteriormente en este
documento, ya que no hace nada en tiempo de ejecución.
Incluso en un diseño n-Capas, si sus objetos de Negocio proporcionan un cursor de datos puede agregar tablas/vistas al
DataEnvironment (establecer propiedad Alias), allí donde están disponibles para conveniencia en tiempo de diseño y son ignoradas en
tiempo de ejecución, cuando el objeto negocio proporciona el dato real.
Esta técnica trabaja igualmente bien para formularios basados en .VCX ... no existe objeto DataEnvironment nativo, con lo cual tiene
que cargar los datos e código desde el método Load y por tanto ignorar el DataEnvironment.
Si se establecen las propiedades DEClass/DEClassLibrary, los datos indicados están disponibles en tiempo de ejecución pero el
DataEnvironment no está disponible en tiempo de diseño.
Mucho cuidado al romper la secuencia nativa de eventos de instanciación
Existen vías para romper la secuencia nativa de eventos de instanciación. Algunas veces las consecuencias son menos graves, en otras
son catastróficas y pueden causar todo tipo de comportamiento indeseado.
Lo que tu madre nunca te dijo
El ejemplo LISAG_SetFocus.SCX demuestra una de las posibilidades. Desafortunadamente esto es muy común y muy fácil de hacer.
La última línea de código en el evento Form.Init, es esta línea aparentemente inocente que asegura que al instanciar, el botón OK tiene
el foco:
THIS.cmdOK.SetFocus()
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
Página 4 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c...
15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
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
Ahora, si existe Member.SetFocus(), en el Form.Init la bandera se establece prematuramente en .F., antes de que el Init finalice y antes
de que se ejecute el Show. Cualquier código en Form.Show que se debe ejecutar solamente durante la instanciación será ignorado
porque la bandera ya está en .F. en un escenario normal (no una demo) es un error muy difícil de depurar porque solo ocurre si la
condición encuentra el Member.SetFocus() explícito.
El ejemplo demuestra como establecer ese tipo de propiedad de usuario
¿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.
Cuando al llamar Form.Load() o Form.Init() devuelven .F. no se disparan los eventos Form.Destroy ni Form.Unload
Devolviendo .F. desde el Load o el Init de un formulario no se instancia, es evidente; pero
Lo que no te dijo tu madre
1. Como demuestra LISAG_QRDU_AbortLoad.SCX, cuando el Load devuelve .F., no se disparan ni Form.Destroy ni Form.Unload.
2. Como demuestra LISAG_QRDU_AbortInit.SCX, cuando el Init devuelve .F., se dispara Form.Unload; pero no lo hace
Form.Destroy. Debido a que los miembros contenidos en el formulario se instancian antes que el Form.Init, el Destroy de esos
miembros se dispara ya que están fuera de límites. (Since form members instantiate before the Form.Init, the Destroy of form
members does fires as they go out of scope).
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.!
Ejecutar Form.Destroy consistentemente
Los formularios LISAG_QRDU_AbortLoadInit1.SCX and LISAG_QRDU_AbortLoadInit2.SCX demuestran técnicas similares que puede
utilizar para garantizar que los Form.Destroy/Unload (códigos de limpieza) se ejecuten adecuadamente.
Peculiaridad del SYS(1271)
Primeramente vamos a mirar la función SYS(1271) que podemos poner a trabajar para nuestro beneficio. El ejemplo The
LISAG_SYS1271.SCX demuestra que no puede hacer esta llamada:
SYS(1271,THISFORM)
Hasta que se haya completado Form.Load, no se puede llamar desde un método de DataEnvionment, el Form.Load ni desde otro método
llamado desde el Form.Load. De hacerlo, recibirá el mensaje, poco intuitivo, "Insuficiente memoria para completar esta operación" ("
Not enough memory to complete this operation"). Puede verlo al hacer DO FORM LISAG_SYS1271 ... seleccionar <Ignore> para
continuar con la instalación de LISAG_SYS1271.SCX.
Por un lado, es inconveniente tener que esperar hasta el Form.Load para consultar SYS(1271,THISFORM) si desea realmente verificarlo
antes. Pero LISAG_QRDU_AbortLoadInit1.SCX
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
Página 5 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c...
15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
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.
Referencia de objetos en la destrucción de formularios. (Object Reference Cleanup On Form Destruction)
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.
Si algunas de las referencias de objetos no se liberan explícitamente, el contenedor no se libera. Si el contenedor es un formulario o un
contenedor dentro de un formulario, el formulario no se libera. Esta es la causa del error "Referencia de objeto dañada" ("Dangling
object reference").
ORCleanup1.PRG crea 2 instancias de ORCleanup1.SCX para demostrar el problema:
1. DO ORCleanup1
2. En la instancia 2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia de
objeto a un miembro de la instancia1 del formulario.
3. Intente cerrar la instancia 1: Haga clic en OK, haga clic en "X" a la derecha de la barra de título, o seleccione Close desde el
menú en la caja de control de la barra de título. La instancia 1 del formulario se niega a cerrarse, debido a referencias de objeto
dañadas, de hecho, si selecciona Cerrar desde el menú en la caja de control de la barra de título, la opción Cerrar no aparece y la
"X" a la derecha de la barra de título aparece deshabilitada.
4. Cierre la instancia 2 del formulario. En cuanto se cierra, se cierra también la instancia1 ... su código Destroy ya se había
disparado, solo estaba esperando a que se liberaran las referencias de objetos externos a sus miembros.
Lo que no te dijo tu madre
Para formularios, la limpieza de referencia de objetos nunca se debe hacer después de Form.Destroy. Esto es fácil de hacer.
Sin embargo, cuando los miembros del formulario necesitan limpiar las referencias de objetos, el código colocado en su Destroy puede
ser inútil. Recuerde: el formulario destruye "de afuera hacia adentro", por tanto el Destroy de los miembros se dispara después del
Destroy del formulario como tal. Cuando se daña la referencia de objetos existente, se dispara el Form.Destroy; pero la destrucción
frena aquí, y no se dispara ningún otro evento, incluyendo el Destroy de sus miembros, hasta que se libera la referencia de objeto
dañada.
Esto es un gran problema cuando diseña formularios de tal forma que un formulario no modal mantenga referencias de objetos a un
miembro de otro formulario no modal u otros objetos externos. Dos casos comunes de daño de referencia de objetos:
1. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario intenta cerrar Form2; pero se
niega a cerrar hasta que los miembros de Form1 liberan sus referencias de objeto (este es el escenario que se muestra en
ORCleanup1.PRG/.SCX).
2. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario cierra Form1; pero Form1 no se
cierra del todo, queda como un objeto artificial como una sesión de datos "desconocida" ("Unknown") visible en la ventana Sesión
de datos.
Cuando objetos externos contienen referencias a miembros de THISFORM
ORCleanup1a.SCX demuestra una vía para solucionar el comportamiento necesario. Repita los pasos para ORCleanup1 y observe la
diferencia:
1. DO ORCleanup1a.
2. En la instancia2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia de
objeto a un miembro de la instancia1 del formulario.
3. Cierre la instancia 1del formulario. Se cierra normalmente, como se espera, aún cuando aparentemente no se ha hecho la
limpieza de referencia de objetos para liberar las referencias de la instancia 2 del formulario tiene a los objetos de la instancia 1
del formulario.
Puede ver la técnica que he utilizado en ORCleanup1a.SCX para examinar el código en el evento Clic de cada casilla de verificación del
grupo inferior. He utilizado la función BINDEVENT() para asegurar que cuando se guarda una referencia de objeto, el Destroy de los
objetos guardados del formulario llama automáticamente al código de limpieza de los objetos haciendo su almacenaje. Entonces, cuando
es cerrado el formulario cuyas referencias a objetos miembros han sido guardadas, su Destroy llama al código de limpieza de la
referencia de objetos de los objetos externos manteniendo la referencia de objetos. He aquí lo que ocurre:
1. DO ORCleanup1a
2. Haga Clic en la primera casilla de verificación del grupo inferior de la instancia 2 del formulario. El código del evento Clic guarda
una referencia de objeto al miembro txtDemo1 en la instancia 1 del formulario a la propiedad de usuario oFormMember de la
instancia 2 del formulario. El código Clic también ejecuta BINDEVENT() para asegurarse de que cuando se cierra la instancia 1 del
formulario se ejecuta el método ORCleanup de la instancia 2 del formulario.
3. Haga Clic en el botón de comandos OK de la instancia1 del formulario. Cuando se dispara el Destroy, ejecuta el método Cleanup
de la segunda instancia del formulario, gracias al BINDEVENT(). Entre otras cosas, el método ORCleanup de la instancia 2 del
formulario establece su propiedad oFormMember a .NULL., liberando la referencia de objeto guardada el txtDemo miembro de la
instancia 1 del formulario.
En un conjunto real de clases jerárquicas, pudiera probablemente agregar el método ORCleanup a cada una de sus clases bases, de tal
forma que el Checkbox.Click haría BINDEVENT() a su propio método ORCleanup en lugar de THISFORM.ORCleanup, haciendo más
granular el control del proceso.
Esta técnica requiere, por supuesto, utilizar VFP 8.0, versión en la que fue agregada al lenguaje la poderosa función BINDEVENT().
Cuando los miembros de THISFORM contienen referencias a objetos externos
Como se ha descrito antes, toda la limpieza a las referencias de objeto "garbage collection" debe hacerse antes de Form.Destroy, que
ocurre antes que se dispare el evento Destroy de cualquier miembro.
Página 6 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c...
15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
La solución es codificar el Form.Destroy con mensajes a sus miembros para establecer explícitamente todas las referencias de objeto
guardadas a .NULL. Mi sugerencia es separar esta tarea a un método de usuario ORCleanup agregado a sus clases base.
Primero, agregue un método de usuario ORCleanup agregado a sus clases base Form. Agregue código al Destroy de su clase base
formulario para llamar a THISFORM.ORCleanup().
En cada clase formulario o instancia, cada vez que escriba código para guardar una referencia de objeto a un miembro de formulario,
asegúrese de colocar el código de liberación correspondiente en el ORCleanup del formulario contenido. Cada vez que se dispara el
Form.Destroy, es realizada la limpieza a todas las referencias de objeto.
Sin embargo, puede que quiera abstraerse en lo delante de este comportamiento, ya que cuando está diseñando la clase contenedora
(pageframes, optiongroups, grids, containers, etc.), no puede colocar código en el Destroy del formulario contenido porque no sabe qué
instancia del formulario o clase va a contener finalmente el contenedor que está preparando. Para solucionar este problema, agregue un
método de usuario ORCleanup para cada una de sus clases base que pueden ser miembros de un formulario (Textbox, Spinner, Custom,
etc.). Programe su clase base form Form.ORCleanup para interactuar con cada uno de sus miembros, llamando su método ORCleanup.
Si diseña el método ORCleanup de objetos contenedores (grids, pageframes, pages, optiongroups, containers, etc.) para interactuar con
cada uno de sus miembros de la misma forma que el ORCleanup a nivel de formulario solo hace un lazo entre su arreglo de controles y
llama al ORCleanup de cada uno de sus miembros directos.
Dañar la referencia de objetos cuando se utilizan colecciones
Ver la serie de ejemplos ORCleanup2.SCX
_Screen.ActiveForm y qué se puede hacer con esto
Durante el curso de instanciación del formulario hay un punto en el cual el formulario se convierte en _Screen.ActiveForm. Sabiendo
cuando esto ocurre nos permite poder hacer cosas muy buenas con la referencia de objeto _Screen.ActiveForm.
Cuando THISFORM se convierte en _Screen.ActiveForm
Como se demuestra en el ejemplo _ScreenActiveForm.SCX, el formulario que se está instanciando se convierte en _Screen.ActiveForm
inmediatamente después de ser mostrados (Show())
Lo que nunca te dijo tu madre
Desafortunadamente, _Screen.ActiveForm no es siempre _Screen.ActiveForm. En muchas ocasiones donde el formulario activo contiene
uno o más controles ActiveX, _Screen.ActiveForm puede ser una referencia de objeto a un control ActiveX, NO al formulario que lo
contiene.
La biblioteca en tiempo de ejecución X6SAF.PRG para el framework Visual MaxFrame Profesional está incluida en esta sesión y controla
este caso. Para tener una referencia de objeto confiable _Screen.ActiveForm, sustituya el código por el siguiente:
LOCAL loActiveForm
loActiveForm = _Screen.ActiveForm
IF TYPE("loActiveForm.BaseClass") = "C"
* aquí no es el formulario activo
ELSE
* m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activo
ENDIF
lo que es el equivalente más fiable:
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
Guardar una referencia al formulario y objeto llamados
Al combinar los conocimientos sobre la secuencia de eventos de instanciación de formulario LISAG y el conocimiento de cuando
THISFORM se convierte en _Screen.ActiveForm, hay algunas cosas interesantes que puede hacer con esta información.
El ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX demuestra una buena técnica para guardar fácilmente una referencia de objeto al
formulario y objeto (si existe) llamados.
Guardar una referencia al formulario llamado
Cuando se ejecuta Form.Load, THISFORM no se ha instanciado todavía, y no es el formulario activo _Screen.ActiveForm. Más
importante, cualquier formulario (si existe) que se ejecuta cuando THISFORM se llama se refleja aun en _Screen.ActiveForm. Así, es
como un pestañeo a obtener una referencia de objeto "libre" al formulario que se está ejecutando cuando se llama THISFORM, y lo
guarda en una propiedad de usuario disponible durante la vida del THISFORM:
LOCAL loActiveForm
loActiveForm = X6SAF()
IF VARTYPE(m.loActiveForm) = "O"
THIS.oCallingForm = m.loActiveForm
ELSE
THIS.oCallingForm = .NULL.
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:
Algunas cosas interesantes sobre estas técnicas:
1. Puede consultar información sobre el formulario llamado antes del THISFORM.Init, donde se reciben parámetros, aceptando otra
configuración que necesita optimizar lo que ocurre antes de que puedan ser verificados los parámetros.
2. No hay necesidad de llamar al formulario para pasar una referencia de objeto a sí mismo al formulario llamado.
3. Cuando THISFORM se instancia desde cualquier sitio, como un menú, no hay sitio para el formulario activo para pasar una
referencia de objeto a si mismo a THISFORM.Init.
Página 7 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c...
15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
Secuencia de eventos vfp
Secuencia de eventos vfp

Contenu connexe

Similaire à Secuencia de eventos vfp

Procedimiento de instalación de aplicativos con Windows Vista, 7 y 8.
Procedimiento de instalación de aplicativos con Windows Vista,  7 y 8.Procedimiento de instalación de aplicativos con Windows Vista,  7 y 8.
Procedimiento de instalación de aplicativos con Windows Vista, 7 y 8.Alejandro Germán Rodriguez
 
Seguridad en access
Seguridad en accessSeguridad en access
Seguridad en accessssuser3a82fb
 
Continuación 3 de la creación de reportes con jasper report
Continuación 3 de la creación de reportes con jasper reportContinuación 3 de la creación de reportes con jasper report
Continuación 3 de la creación de reportes con jasper reportjbersosa
 
Spring OSGI , dm server - Leonardo Torres Altez
Spring OSGI , dm server - Leonardo Torres AltezSpring OSGI , dm server - Leonardo Torres Altez
Spring OSGI , dm server - Leonardo Torres Alteza19987225
 
Desarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datosDesarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datosGabriel Hernadez Meza
 
Desarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datosDesarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datosEduardo_Staiti11
 
As3 unidad 14 crear una aplicación air con flash y action script
As3 unidad 14 crear una aplicación air con flash y action scriptAs3 unidad 14 crear una aplicación air con flash y action script
As3 unidad 14 crear una aplicación air con flash y action scriptFrancisco Javier Arce Anguiano
 
Programando bajo el paradigma orientado a objetos, parte uno
Programando bajo el paradigma orientado a objetos, parte unoProgramando bajo el paradigma orientado a objetos, parte uno
Programando bajo el paradigma orientado a objetos, parte unoIng-D-SW-TorresKhano--ME
 
A toda máquina con herencia visual
A toda máquina con herencia visualA toda máquina con herencia visual
A toda máquina con herencia visualIgnacio Monllor
 
Todo Javascript para canibales
Todo Javascript para canibalesTodo Javascript para canibales
Todo Javascript para canibalesbriant pati
 
Javascript de Canibales
Javascript de CanibalesJavascript de Canibales
Javascript de Canibalesbriant pati
 
Java Script de canibaless
Java Script de canibalessJava Script de canibaless
Java Script de canibalessbriant pati
 
Proyecto sencillo con gui
Proyecto sencillo con guiProyecto sencillo con gui
Proyecto sencillo con guijbersosa
 

Similaire à Secuencia de eventos vfp (20)

Procedimiento de instalación de aplicativos con Windows Vista, 7 y 8.
Procedimiento de instalación de aplicativos con Windows Vista,  7 y 8.Procedimiento de instalación de aplicativos con Windows Vista,  7 y 8.
Procedimiento de instalación de aplicativos con Windows Vista, 7 y 8.
 
Microservicios con Quarkus
Microservicios con QuarkusMicroservicios con Quarkus
Microservicios con Quarkus
 
Seguridad en access
Seguridad en accessSeguridad en access
Seguridad en access
 
Continuación 3 de la creación de reportes con jasper report
Continuación 3 de la creación de reportes con jasper reportContinuación 3 de la creación de reportes con jasper report
Continuación 3 de la creación de reportes con jasper report
 
Spring OSGI , dm server - Leonardo Torres Altez
Spring OSGI , dm server - Leonardo Torres AltezSpring OSGI , dm server - Leonardo Torres Altez
Spring OSGI , dm server - Leonardo Torres Altez
 
Eventos
EventosEventos
Eventos
 
Ejercicio simple java
Ejercicio simple javaEjercicio simple java
Ejercicio simple java
 
Desarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datosDesarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datos
 
Desarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datosDesarrolla aplicaciones de escritorio con bases de datos
Desarrolla aplicaciones de escritorio con bases de datos
 
As3 unidad 14 crear una aplicación air con flash y action script
As3 unidad 14 crear una aplicación air con flash y action scriptAs3 unidad 14 crear una aplicación air con flash y action script
As3 unidad 14 crear una aplicación air con flash y action script
 
Programando bajo el paradigma orientado a objetos, parte uno
Programando bajo el paradigma orientado a objetos, parte unoProgramando bajo el paradigma orientado a objetos, parte uno
Programando bajo el paradigma orientado a objetos, parte uno
 
A toda máquina con herencia visual
A toda máquina con herencia visualA toda máquina con herencia visual
A toda máquina con herencia visual
 
Eclipse1»
Eclipse1»Eclipse1»
Eclipse1»
 
Todo Javascript para canibales
Todo Javascript para canibalesTodo Javascript para canibales
Todo Javascript para canibales
 
Javascript de Canibales
Javascript de CanibalesJavascript de Canibales
Javascript de Canibales
 
Java Script de canibaless
Java Script de canibalessJava Script de canibaless
Java Script de canibaless
 
Proyecto sencillo con gui
Proyecto sencillo con guiProyecto sencillo con gui
Proyecto sencillo con gui
 
Ceubas10
Ceubas10Ceubas10
Ceubas10
 
Eventos
EventosEventos
Eventos
 
Unidad 2
Unidad 2Unidad 2
Unidad 2
 

Plus de hmosquera

Calculo diferencial e integral taylor-wade-limusa
Calculo diferencial e integral   taylor-wade-limusaCalculo diferencial e integral   taylor-wade-limusa
Calculo diferencial e integral taylor-wade-limusahmosquera
 
Normativa general de instalaciones de gas, electricas y de telefonos
Normativa general de instalaciones de gas, electricas y de telefonosNormativa general de instalaciones de gas, electricas y de telefonos
Normativa general de instalaciones de gas, electricas y de telefonoshmosquera
 
Acad cómo desactivar el centro de comunicación en autocad
Acad cómo desactivar el centro de comunicación en autocadAcad cómo desactivar el centro de comunicación en autocad
Acad cómo desactivar el centro de comunicación en autocadhmosquera
 
Curso de visual fox pro - Desprotejido para Imprimirlo
Curso de visual fox pro - Desprotejido para ImprimirloCurso de visual fox pro - Desprotejido para Imprimirlo
Curso de visual fox pro - Desprotejido para Imprimirlohmosquera
 
Comandos de configuracion vfp
Comandos de configuracion vfpComandos de configuracion vfp
Comandos de configuracion vfphmosquera
 
Paradigma orientado a objetos
Paradigma orientado a objetosParadigma orientado a objetos
Paradigma orientado a objetoshmosquera
 
Visual fox pro sql server y asp programación multiusuario
Visual fox pro sql server y asp   programación multiusuarioVisual fox pro sql server y asp   programación multiusuario
Visual fox pro sql server y asp programación multiusuariohmosquera
 
Curso de bases de datos y postgre sql
Curso de bases de datos y postgre sqlCurso de bases de datos y postgre sql
Curso de bases de datos y postgre sqlhmosquera
 
Visual fox pro 9.0 y sqlserver 2005
Visual fox pro 9.0 y sqlserver 2005Visual fox pro 9.0 y sqlserver 2005
Visual fox pro 9.0 y sqlserver 2005hmosquera
 
Manual del programador fox pro
Manual del programador fox proManual del programador fox pro
Manual del programador fox prohmosquera
 
Aplicaciones genexus
Aplicaciones genexusAplicaciones genexus
Aplicaciones genexushmosquera
 
ratios financieros y matematicas de la mercadotecnia
ratios financieros y matematicas de la mercadotecniaratios financieros y matematicas de la mercadotecnia
ratios financieros y matematicas de la mercadotecniahmosquera
 
Libro matematicas financieras para toma de decisiones empresariales
Libro matematicas financieras para toma de decisiones empresarialesLibro matematicas financieras para toma de decisiones empresariales
Libro matematicas financieras para toma de decisiones empresarialeshmosquera
 
Libro matematicas financieras en excel
Libro matematicas financieras en excelLibro matematicas financieras en excel
Libro matematicas financieras en excelhmosquera
 
Instalaciones domiciliarias
Instalaciones domiciliariasInstalaciones domiciliarias
Instalaciones domiciliariashmosquera
 
Introducción al auto cad
Introducción al auto cadIntroducción al auto cad
Introducción al auto cadhmosquera
 
Algebra de baldor
Algebra de baldorAlgebra de baldor
Algebra de baldorhmosquera
 
Algebra de baldor respuestas
Algebra de baldor respuestasAlgebra de baldor respuestas
Algebra de baldor respuestashmosquera
 
Algebra arrayan
Algebra arrayanAlgebra arrayan
Algebra arrayanhmosquera
 
Antonio millán puelles cap XII
Antonio millán puelles   cap XIIAntonio millán puelles   cap XII
Antonio millán puelles cap XIIhmosquera
 

Plus de hmosquera (20)

Calculo diferencial e integral taylor-wade-limusa
Calculo diferencial e integral   taylor-wade-limusaCalculo diferencial e integral   taylor-wade-limusa
Calculo diferencial e integral taylor-wade-limusa
 
Normativa general de instalaciones de gas, electricas y de telefonos
Normativa general de instalaciones de gas, electricas y de telefonosNormativa general de instalaciones de gas, electricas y de telefonos
Normativa general de instalaciones de gas, electricas y de telefonos
 
Acad cómo desactivar el centro de comunicación en autocad
Acad cómo desactivar el centro de comunicación en autocadAcad cómo desactivar el centro de comunicación en autocad
Acad cómo desactivar el centro de comunicación en autocad
 
Curso de visual fox pro - Desprotejido para Imprimirlo
Curso de visual fox pro - Desprotejido para ImprimirloCurso de visual fox pro - Desprotejido para Imprimirlo
Curso de visual fox pro - Desprotejido para Imprimirlo
 
Comandos de configuracion vfp
Comandos de configuracion vfpComandos de configuracion vfp
Comandos de configuracion vfp
 
Paradigma orientado a objetos
Paradigma orientado a objetosParadigma orientado a objetos
Paradigma orientado a objetos
 
Visual fox pro sql server y asp programación multiusuario
Visual fox pro sql server y asp   programación multiusuarioVisual fox pro sql server y asp   programación multiusuario
Visual fox pro sql server y asp programación multiusuario
 
Curso de bases de datos y postgre sql
Curso de bases de datos y postgre sqlCurso de bases de datos y postgre sql
Curso de bases de datos y postgre sql
 
Visual fox pro 9.0 y sqlserver 2005
Visual fox pro 9.0 y sqlserver 2005Visual fox pro 9.0 y sqlserver 2005
Visual fox pro 9.0 y sqlserver 2005
 
Manual del programador fox pro
Manual del programador fox proManual del programador fox pro
Manual del programador fox pro
 
Aplicaciones genexus
Aplicaciones genexusAplicaciones genexus
Aplicaciones genexus
 
ratios financieros y matematicas de la mercadotecnia
ratios financieros y matematicas de la mercadotecniaratios financieros y matematicas de la mercadotecnia
ratios financieros y matematicas de la mercadotecnia
 
Libro matematicas financieras para toma de decisiones empresariales
Libro matematicas financieras para toma de decisiones empresarialesLibro matematicas financieras para toma de decisiones empresariales
Libro matematicas financieras para toma de decisiones empresariales
 
Libro matematicas financieras en excel
Libro matematicas financieras en excelLibro matematicas financieras en excel
Libro matematicas financieras en excel
 
Instalaciones domiciliarias
Instalaciones domiciliariasInstalaciones domiciliarias
Instalaciones domiciliarias
 
Introducción al auto cad
Introducción al auto cadIntroducción al auto cad
Introducción al auto cad
 
Algebra de baldor
Algebra de baldorAlgebra de baldor
Algebra de baldor
 
Algebra de baldor respuestas
Algebra de baldor respuestasAlgebra de baldor respuestas
Algebra de baldor respuestas
 
Algebra arrayan
Algebra arrayanAlgebra arrayan
Algebra arrayan
 
Antonio millán puelles cap XII
Antonio millán puelles   cap XIIAntonio millán puelles   cap XII
Antonio millán puelles cap XII
 

Secuencia de eventos vfp

  • 1. Secuencia normal de eventos en VFP cuando se Crean y Destruyen Formularios Tomado de “What Your Mother Never Told You About Form Instantiation and Destruction presentada por el autor en la Conferencia DevEssentials Kansas, 2004) Leído y Editado a mi modo: jun/2013 Al final de este documento está disponible la traducción original Secuencia de eventos LISAG y QRDU LISAG (Load-Init-Show-Activate-GotFocus) y QRDU (QueryUnload- Release- Destroy- Unload) NOTA:NOTA:NOTA:NOTA: He tomado “He tomado “He tomado “He tomado “Form Instantiation” como creación deForm Instantiation” como creación deForm Instantiation” como creación deForm Instantiation” como creación dellll Form.Form.Form.Form. Ya queYa queYa queYa que eseseses en ese momento seen ese momento seen ese momento seen ese momento se dadadadannnn valores específicosvalores específicosvalores específicosvalores específicos a las propiedades del Forma las propiedades del Forma las propiedades del Forma las propiedades del Form y por lo tanto se crea un Form particulary por lo tanto se crea un Form particulary por lo tanto se crea un Form particulary por lo tanto se crea un Form particular.... Resumen Esta sesión intenta ayudar a entender mejor la secuencia normal de eventos en VFP cuando se crean y destruyen formularios. Para implementar exitosamente los escenarios de creación y destrucción de un formulario, lo primero que debe entender es la secuencia nativa de los eventos. Creación de un Formulario Aquí está la lista de eventos que ocurren durante la creació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 creado 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 de un Formulario Formulario cerrado por el usuario: Esta es 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. 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 Formulario cerrado por una llamada: Aquí está la lista de eventos que ocurren 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 Creación / Destrucción de miembros contenedores En la Creación: Los contenedores se crean "de dentro hacia afuera" de igual forma que hace el propio formulario (debido a que este, también es un contenedor). El Init de los miembros contenidos de disparan antes que el Init del contenedor padre. En la Destrucción: Ocurre lo contrario, los contenedores se destruyen "de afuera hacia dentro". El Destroy del contenedor se dispara antes que el Destroy de sus miembros contenidos. Formulario con valores establecido para DEClass, DEClassLibrary Cuando un formulario establece valores para DEClass, DEClassLibrary (disponibles a partir de VFP 8.0), las cosas son ligeramente diferentes. Debido a que el objeto DataEnvironment es un objeto completamente separado, se crea completamente antes del Form.Load. El Init de los miembros del DataEnvironment (Es decir, el Init del 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 (Es decir, del Cursor, CursorAdapter, y Relation).
  • 3. Aspectos de interés del Form: 1. Dependiendo de cómo se cierra el formulario, se ejecuta el método Release o el evento QueryUnload; pero no ambos: El evento Form.Release no es un buen lugar para colocar código, no importa lo que sea, que se deba ejecuta cada vez que se destruya el formulario. Utilice en su lugar los eventos Destroy o Unload. 2. Debido a que muchos comandos como SET TALK están limitados a la sesión privada de datos (ver DATASESSION TO en la ayuda de VFP para ver la lista de estos comandos SET), decidir en que lugar va a asignar valores a estos comandos SET depende de cómo abre sus datos. Si no utiliza nunca DataEnvironment: Los comandos SET para la sesión privada de datos se colocan en el Load de la clase form. Si utiliza DataEnvironment nativo en un formulario basado en .SCX: Tendrá que establecer estos comandos SET en DataEnvironment.OpenTables/BeforeOpenTables, antes de que las tablas/cursores sean abiertos. Si implementa un DataEnvironment de usuario especificado en las propiedades DEClass/DEClassLibrary (VFP 8.0 y superior): Podrá establecer los comandos SET para la sesión privada de datos en el método Init de la clase DataEnvironment; pero sea consciente, que el Init de los miembros Cursor, CursorAdapter, y los objetos Relation se disparan antes que DataEnvironment.Init. 3. Al destruir un formulario no se disparan los eventos Form.Deactivate ni Form.LostFocus DataEnvironment 1. El DataEnvironment abre sus datos implícitamente entre el DataEnvironment.BeforeOpenTables y el Form.Load(), inmediatamente antes del Form.Load. 2. El DataEnvironment NO abre los datos en el evento OpenTable -- OpenTables es de uso opcional para abrir programáticamente los datos si se utiliza AutoOpenTables = .F. 3. El evento BeforeOpenTables no se dispara antes (Before) que el evento OpenTables, BeforeOpenTables está mal nombrado y se debió llamar algo como AfterOpenTablesBeforeImplicitOpenTables. (DespuesAbrirTablasAntesImplicitamenteAbrirTablas) 4. El DataEnvironment.Cursor.Init se dispara DESPUÉS que el cursor ya fue abierto y DESPUÉS del Form.Load 5. El DataEnvironment.Init se dispara DESPUÉS que el DataEnvironment halla cumplido todas las misiones encomendadas. DESPUÉS que los cursores han sido puestos en uso, y DESPUÉS del Form.Load. Excepto cuando el DataEnvironment es una creación de usuario especificada en las propiedades DEClass/DEClassLibrary, en ese caso se crea completamente antes de Form.Load. 6. Consecuente con el comportamiento de OpenTables y cuando las tablas se abren explícitamente por el DataEnvironment, el mismo cierra las tablas ANTES del método CloseTables. 7. Mientras la secuencia de creación / destrucción para el formulario y sus miembros es consistente, la secuencia de creación / destrucción para el DataEnvironment depende de su implementación: Tipos de Implementación DataEnvironment: 1.- DataEnvironment nativo para un formulario basado en .SCX, 2.- Implementación de la propiedad DEClass DataEnvironment de usuario para formulario basado en .SCX o .VCX. Llamar al método THISFORM.Metodos o consultar THISFORM.Propiedades desde el DataEnvironment Los ejemplos muestran un comportamiento inconsistente relacionado con llamar a métodos del formulario (THISFORM) desde el DataEnvironment. DataEnvironment nativo en un formulario basado en .SCX: Cuando un formulario basado en .SCX utiliza DataEnvironment nativo de VFP, el DataEnvironment se crea primero que el formulario como tal. Sin embargo
  • 4. 1. Las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment que se disparan antes del Form.Load son ¡COMPLETAMENTE IGNORADAS!. Por ejemplo, las llamadas a THISFORM.Metodos desde los eventos de DataEnvironment OpenTables y BeforeOpenTables no hacen nada, no se invoca ningún método y no se genera un error. Supongo que VFP no ha iniciado todavía la creación del formulario y por tanto, no reconoce THISFORM como un objeto; pero yo debería esperar que se generara un error, como hace SYS(1271,THISFORM). 2. Las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment que se disparan antes del Form.Load SÍ disparan esos métodos EN CASO que hayan sido definidos en la clase Form a partir de la cual hereda el formulario actual. Por supuesto, todos los métodos nativos de VFP heredan de la clase base Form de VFP. Entonces, el código que se ejecuta es el heredado de la clase Form, NO cualquier código ubicado en los métodos nativos o de usuarios del formulario creado. Una vez que se dispara el Form.Load, la llamada a estos mismos métodos disparan los métodos para el nivel creado. 3. De igual forma, si en el DataEnvironment que se dispara antes del Form.Load, consulta el valor de una propiedad (nativa o de usuario) que se establece explícitamente en la ficha Propiedades a nivel del formulario creado, los valores son los que sean predeterminados por VFP para esa propiedad (.F. para todas las propiedades de usuario), como si hubiera establecido las propiedades como predeterminadas en la ficha propiedades. Sin embargo; para las propiedades establecidas en la ventana propiedades de cualquier clase padre del formulario creado, el valor es evaluado adecuadamente, tal y como esperamos. Sin embargo, puede establecer una propiedad en cualquier método del DataEnvironment. 4. En los métodos de DataEnvironment, IntelliSense no muestra NINGUN PEM (Propiedades, Eventos, Métodos) de usuario, ya sea de la creación actual o de alguna clase padre de la creación actual del formulario. Conclusión El DataEnvironment para métodos de usuario de formulario, ya que estos métodos están basados en formularios .SCX. Y debe recordar que el código que coloque en estos métodos en el nivel creación del .SCX será completamente ignorado, cuando esos métodos son llamados desde eventos del DataEnvironment como OpenTables y BeforeOpenTables que se ejecutan antes que Form.Load. Para este nivel de creación establecer propiedades es poco confiable hasta que no se ejecute Form.Load. DataEnvironment de usuario especificado en las propiedades DEClass/DEClassLibrary para un formulario basado en .SCX Cuando las propiedades DEClass y DEClassLibrary de un formulario basado en .SCX tienen asignado un valor que especifique un objeto DataEnvironment de usuario, las cosas son algo diferentes: 1. En tiempo de diseño, cuando se da valor a las propiedades DEClass y DEClassLibrary asignando una clase DataEnvironment con código en su evento Init, o en el Init de alguno de sus miembros cursor/relation, y ese Init llama a un método de usuario de ese formulario, sólo el hecho de asignar valor a las propiedades DEClass y DEClassLibrary genera un error "Objeto no contenido en el formulario" ("Object is not contained in a Form"), para cada llamada al método de usuario del formulario (THISFORM). 2. En tiempo de ejecución, se genera el mismo error "Objeto no contenido en el formulario. 3. En tiempo de ejecución, aquellas mismas llamadas a métodos de usuario de THISFORM que generaban un error cuando eran llamados desde el DataEnvironment.Init o desde el Init de uno de sus miembros, NO HACEN NADA si son llamados desde los eventos DataEnvironment.OpenTables o BeforeOpenTables ... los métodos de usuario agregados al .SCX en la creación actual son completamente ignorados. 4. En tiempo de ejecución, cuando ocurren eventos como OpenTables and BeforeOpenTables del DataEnvironment, que se disparan después del DataEnvironment.Init y el Init de sus miembros llaman a métodos de THISFORM, sólo se ejecuta el código para aquellos métodos que es heredado de la clase padre ... no el código colocado en los métodos del nivel actual creado. Conclusión La conclusión es que el código colocado en métodos de la instancia actual del formulario nunca se ejecuta cuando el método es llamado desde eventos del DataEnvironment. Para confiar en los comportamientos abstractos del DataEnvironment, debe crear todo el código en la clase DataEnvironment (jerárquicamente). 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 confiable hasta que no se dispare el Form.Load.
  • 5. ¿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. 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 creado, 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 cree correctamente? Eso depende de si utiliza o no DataEnvironment y en ese caso, si utiliza el DataEnvironment nativo de VFP o una clase personalizada DataEnvironment. No utilizar Dataenvironment Pienso que es la mejor elección, porque es más consistente, y es más fácil de mantener. 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 creación 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 En el formulario de la más alta jerarquía establezca la propiedad DataSession a 2- Private Data Session. 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 creado, llame al método de usuario desde los métodos del DataEnvironment OpenTables o, yo preferido, BeforeOpenTables: THISFORM.CustomMethod() Recuerde, cualquier código que coloque en el método de usuario en el nivel creado no se ejecutará, como fue explicado en la sección anterior ... solamente se ejecuta el código colocado en la(s) clase(s) padre de la instancia actual. DataEnvironment de usuario con DEClass/DEClassLibrary Coloque la configuración lógica de los comandos SET para su sesión privada de datos en el Init de la clase DataEnvironment. Sea consciente de que pudiera tener algunos comandos SET como SET TALK OFF en el Init de sus clases base Cursor o Relation, ya que estos se ejecutan antes que el Init de DataEnvironment. ¿Cómo establecer los comandos SET? Una vez que haya determinado dónde necesita colocar su código para los comandos SET de su sesión privada de datos, la cuestión es cómo debe escribir ese código. Puede escribir un código estricto con los comandos SET deseados; pero el mejor enfoque pudiera ser tener la sesión privada de datos que lea los valores desde la sesión predeterminada de datos ... frecuentemente se utiliza la misma configuración de comandos SET, estos se configuran globalmente para la sesión predeterminada de datos cuando inicia su aplicación. Los ejemplos LISAG_PDS_SETs_Abstract*.PRGs y sus correspondientes .SCX demuestran una de estas técnicas. Cada LISAG_PDS_SETs_Abstract*.PRG instancia un demo (objeto aplicación) "application object", que contiene un método de usuario GetSETCommandSetting(). Como el objeto application es instanciado en la sesión predeterminada de datos #1, al llamar a su método GetSETCommandSetting() DEVUELVE la configuración especificada como lo establece la sesión predeterminada de datos. Como la sesión privada de datos instancia, pueden establecer sus comandos SET para que coincidan con aquellos establecidos en la sesión predeterminada de datos llamado al objeto application global. Aquí está el código esencial del método SetSETCommands de la clase frmLISAG_PDS_SETs_Abstract en LISAG_PDS_SETs_Abstract.VCX:
  • 6. LOCAL laSETs[3], luSetting, lcString laSets[1] = "DELETED" laSets[2] = "MULTILOCKS" laSets[3] = "TALK" FOR EACH lcSet IN laSETs luSetting = goApplication.GetSetCommandSetting(m.lcSet) lcString = "SET " + m.lcSet + SPACE(1) + TRANSFORM(m.luSetting) &lcString ENDFOR Utilice DataEnvironment sólo en tiempo de diseño: Antes de que decida abandonar del todo el uso del DataEnvironment, existe una idea que debe considerar: Mientras esté diseñando formularios basados en .SCX, utilice DataEnvironment solo para cuestiones de diseño: • Arrastrar y soltar un(os) cursor(es) al formulario para crear instantáneamente controles Grid. • Arrastrar y soltar los campos al formulario para agregar controles cuyos ControlSource ya estarán definidos y con un ancho aproximado (Width) (si el mapeo de campos (field mapping) tiene establecido que incluya el título del campo, se puede obtener ya la etiqueta correspondiente al Caption existente). • Establecer el ControlSource de cualquier control desde la ventana propiedades, seleccionando uno los cursores actuales desde el cuadro desplegable. • Tener acceso al DataEnvironment y sus miembros (cursores) en los generadores de usuarios. Recuerde establecer las propiedades AutoOpenTables y AutoCloseTables a .F. como muestra la Figura 5, entonces VFP ignora todo lo que esté en el DataEnvironment en tiempo de ejecución. Sin embargo, para formularios con sesión privada de datos, cuando el formulario es cerrado/destruido, VFP cierra todos los cursores abiertos mientras estuvo activo el formulario. En tiempo de ejecución abra las vistas y tablas manualmente, utilizando una de las técnicas descritas en el método Load de DesignTimeDE.SCX, aprovechando sus ventajas sobre el comportamiento del DataEnvironment en tiempo de ejecución: • Si/cuando hay un problema al abrir una tabla/vista, se puede enviar un mensaje de usuario y devolver (RETURN) .F., interrumpir la creación de esa tabla/vista mientras continúa intacto el resto de la aplicación. Por el contrario, cuando DataEnvironment encuentra un problema como un archivo no encontrado, cabecera de tabla dañada, índice dañado, etc. El DataEnvironment falla, se interrumpe su ejecución, así como todo el resto de la aplicación. • Se puede establecer el comando SET PATH antes de llamar los datos, de esta forma se puede intercambiar entre diferentes conjuntos de datos o simplemente ajustar la ruta (PATH) antes de llamar los datos • Puede utilizar herramientas como Stonefield Database Toolkit para reparar problemas con tablas, índices, memos, etc. Utilizando esta técnica, puede ignorar las inconsistencias por utilizar DataEnvironment, que se han descrito anteriormente en este documento, ya que no hace nada en tiempo de ejecución. Incluso en un diseño n-Capas, si sus objetos de Negocio proporcionan un cursor de datos puede agregar tablas/vistas al DataEnvironment (establecer propiedad Alias), allí donde están disponibles para conveniencia en tiempo de diseño y son ignoradas en tiempo de ejecución, cuando el objeto negocio proporciona el dato real. Esta técnica trabaja igualmente bien para formularios basados en .VCX ... no existe objeto DataEnvironment nativo, con lo cual tiene que cargar los datos y código desde el método Load y por tanto ignorar el DataEnvironment. Si se establecen las propiedades DEClass/DEClassLibrary, los datos indicados están disponibles en tiempo de ejecución pero el DataEnvironment no está disponible en tiempo de diseño. Mucho cuidado al romper la secuencia nativa de eventos de creación Existen vías para romper la secuencia nativa de eventos de creación. Algunas veces las consecuencias son menos graves, en otras son catastróficas y pueden causar todo tipo de comportamiento indeseado. El ejemplo LISAG_SetFocus demuestra una de las posibilidades. Desafortunadamente esto es muy común y muy fácil de hacer. La última línea de código en el evento Form.Init, es esta línea aparentemente inocente que asegura que al crear, el botón OK tiene el foco: THIS.cmdOK.SetFocus()
  • 7. Sin embargo 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 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 creació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 creació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 creación pudiera ejecutar condicionalmente sólo si el formulario está siendo creado: IF THIS.lInstantiating * realizar estas acciones solo si THISFORM se está instanciando ENDIF Ahora, si existe Member.SetFocus(), en el Form.Init la bandera se establece prematuramente en .F., antes de que el Init finalice y antes de que se ejecute el Show. Cualquier código en Form.Show que se debe ejecutar solamente durante la creación será ignorado porque la bandera ya está en .F. en un escenario normal (no una demo) es un error muy difícil de depurar porque solo ocurre si la condición encuentra el Member.SetFocus() explícito. El ejemplo demuestra como establecer ese tipo de propiedad de usuario ¿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 crear un formulario? El ejemplo LISAG_SetFocus1 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 creació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. Cuando al llamar Form.Load() o Form.Init() devuelven .F. no se disparan los eventos Form.Destroy ni Form.Unload Devolviendo .F. desde el Load o el Init de un formulario no se crea, es evidente; pero lo que no se dijo: 1. Cuando el Load devuelve .F., no se disparan ni Form.Destroy ni Form.Unload. 2. Cuando el Init devuelve .F., se dispara Form.Unload; pero no lo hace Form.Destroy. Debido a que los miembros contenidos en el formulario se crean antes que el Form.Init, el Destroy de esos miembros se dispara ya que están fuera de límites. (Since form members instantiate before the Form.Init, the Destroy of form members does fires as they go out of scope). 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 creado 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.! Ejecutar Form.Destroy consistentemente Los formularios LISAG_QRDU_AbortLoadInit1.SCX and LISAG_QRDU_AbortLoadInit2.SCX demuestran técnicas similares que puede utilizar para garantizar que los Form.Destroy/Unload (códigos de limpieza) se ejecuten adecuadamente.
  • 8. Peculiaridad del SYS(1271) Primeramente vamos a mirar la función SYS(1271) que podemos poner a trabajar para nuestro beneficio. El ejemplo The LISAG_SYS1271.SCX demuestra que no puede hacer esta llamada: SYS(1271,THISFORM) Hasta que se haya completado Form.Load, no se puede llamar desde un método de DataEnvionment, el Form.Load ni desde otro método llamado desde el Form.Load. De hacerlo, recibirá el mensaje, poco intuitivo, "Insuficiente memoria para completar esta operación" ("Not enough memory to complete this operation"). Puede verlo al hacer DO FORM LISAG_SYS1271 ... seleccionar <Ignore> para continuar con la instalación de LISAG_SYS1271.SCX. Por un lado, es inconveniente tener que esperar hasta el Form.Load para consultar SYS(1271,THISFORM) si desea realmente verificarlo antes. Pero LISAG_QRDU_AbortLoadInit1.SCX 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. Referencia de objetos en la destrucción de formularios. (Object Reference Cleanup On Form Destruction) 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. Si algunas de las referencias de objetos no se liberan explícitamente, el contenedor no se libera. Si el contenedor es un formulario o un contenedor dentro de un formulario, el formulario no se libera. Esta es la causa del error "Referencia de objeto dañada" ("Dangling object reference"). ORCleanup1.PRG crea 2 instancias de ORCleanup1.SCX para demostrar el problema: 1. DO ORCleanup1 2. En la instancia 2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia de objeto a un miembro de la instancia1 del formulario.
  • 9. 3. Intente cerrar la instancia 1: Haga clic en OK, haga clic en "X" a la derecha de la barra de título, o seleccione Close desde el menú en la caja de control de la barra de título. La instancia 1 del formulario se niega a cerrarse, debido a referencias de objeto dañadas, de hecho, si selecciona Cerrar desde el menú en la caja de control de la barra de título, la opción Cerrar no aparece y la "X" a la derecha de la barra de título aparece deshabilitada. 4. Cierre la instancia 2 del formulario. En cuanto se cierra, se cierra también la instancia1 ... su código Destroy ya se había disparado, solo estaba esperando a que se liberaran las referencias de objetos externos a sus miembros. Lo que no te dijo tu madre Para formularios, la limpieza de referencia de objetos nunca se debe hacer después de Form.Destroy. Esto es fácil de hacer. Sin embargo, cuando los miembros del formulario necesitan limpiar las referencias de objetos, el código colocado en su Destroy puede ser inútil. Recuerde: el formulario destruye "de afuera hacia adentro", por tanto el Destroy de los miembros se dispara después del Destroy del formulario como tal. Cuando se daña la referencia de objetos existente, se dispara el Form.Destroy; pero la destrucción frena aquí, y no se dispara ningún otro evento, incluyendo el Destroy de sus miembros, hasta que se libera la referencia de objeto dañada. Esto es un gran problema cuando diseña formularios de tal forma que un formulario no modal mantenga referencias de objetos a un miembro de otro formulario no modal u otros objetos externos. Dos casos comunes de daño de referencia de objetos: 1. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario intenta cerrar Form2; pero se niega a cerrar hasta que los miembros de Form1 liberan sus referencias de objeto (este es el escenario que se muestra en ORCleanup1.PRG/.SCX). 2. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario cierra Form1; pero Form1 no se cierra del todo, queda como un objeto artificial como una sesión de datos "desconocida" ("Unknown") visible en la ventana Sesión de datos. Cuando objetos externos contienen referencias a miembros de THISFORM ORCleanup1a.SCX demuestra una vía para solucionar el comportamiento necesario. Repita los pasos para ORCleanup1 y observe la diferencia: 1. DO ORCleanup1a. 2. En la instancia2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia de objeto a un miembro de la instancia1 del formulario. 3. Cierre la instancia 1del formulario. Se cierra normalmente, como se espera, aún cuando aparentemente no se ha hecho la limpieza de referencia de objetos para liberar las referencias de la instancia 2 del formulario tiene a los objetos de la instancia 1 del formulario. Puede ver la técnica que he utilizado en ORCleanup1a.SCX para examinar el código en el evento Clic de cada casilla de verificación del grupo inferior. He utilizado la función BINDEVENT() para asegurar que cuando se guarda una referencia de objeto, el Destroy de los objetos guardados del formulario llama automáticamente al código de limpieza de los objetos haciendo su almacenaje. Entonces, cuando es cerrado el formulario cuyas referencias a objetos miembros han sido guardadas, su Destroy llama al código de limpieza de la referencia de objetos de los objetos externos manteniendo la referencia de objetos. He aquí lo que ocurre: 1. DO ORCleanup1a 2. Haga Clic en la primera casilla de verificación del grupo inferior de la instancia 2 del formulario. El código del evento Clic guarda una referencia de objeto al miembro txtDemo1 en la instancia 1 del formulario a la propiedad de usuario oFormMember de la instancia 2 del formulario. El código Clic también ejecuta BINDEVENT() para asegurarse de que cuando se cierra la instancia 1 del formulario se ejecuta el método ORCleanup de la instancia 2 del formulario. 3. Haga Clic en el botón de comandos OK de la instancia1 del formulario. Cuando se dispara el Destroy, ejecuta el método Cleanup de la segunda instancia del formulario, gracias al BINDEVENT(). Entre otras cosas, el método ORCleanup de la instancia 2 del formulario establece su propiedad oFormMember a .NULL., liberando la referencia de objeto guardada el txtDemo miembro de la instancia 1 del formulario. En un conjunto real de clases jerárquicas, pudiera probablemente agregar el método ORCleanup a cada una de sus clases bases, de tal forma que el Checkbox.Click haría BINDEVENT() a su propio método ORCleanup en lugar de THISFORM.ORCleanup, haciendo más granular el control del proceso. Esta técnica requiere, por supuesto, utilizar VFP 8.0, versión en la que fue agregada al lenguaje la poderosa función BINDEVENT().
  • 10. Cuando los miembros de THISFORM contienen referencias a objetos externos Como se ha descrito antes, toda la limpieza a las referencias de objeto "garbage collection" debe hacerse antes de Form.Destroy, que ocurre antes que se dispare el evento Destroy de cualquier miembro. La solución es codificar el Form.Destroy con mensajes a sus miembros para establecer explícitamente todas las referencias de objeto guardadas a .NULL. Mi sugerencia es separar esta tarea a un método de usuario ORCleanup agregado a sus clases base. Primero, agregue un método de usuario ORCleanup agregado a sus clases base Form. Agregue código al Destroy de su clase base formulario para llamar a THISFORM.ORCleanup(). En cada clase formulario o instancia, cada vez que escriba código para guardar una referencia de objeto a un miembro de formulario, asegúrese de colocar el código de liberación correspondiente en el ORCleanup del formulario contenido. Cada vez que se dispara el Form.Destroy, es realizada la limpieza a todas las referencias de objeto. Sin embargo, puede que quiera abstraerse en lo delante de este comportamiento, ya que cuando está diseñando la clase contenedora (pageframes, optiongroups, grids, containers, etc.), no puede colocar código en el Destroy del formulario contenido porque no sabe qué instancia del formulario o clase va a contener finalmente el contenedor que está preparando. Para solucionar este problema, agregue un método de usuario ORCleanup para cada una de sus clases base que pueden ser miembros de un formulario (Textbox, Spinner, Custom, etc.). Programe su clase base form Form.ORCleanup para interactuar con cada uno de sus miembros, llamando su método ORCleanup. Si diseña el método ORCleanup de objetos contenedores (grids, pageframes, pages, optiongroups, containers, etc.) para interactuar con cada uno de sus miembros de la misma forma que el ORCleanup a nivel de formulario solo hace un lazo entre su arreglo de controles y llama al ORCleanup de cada uno de sus miembros directos. Dañar la referencia de objetos cuando se utilizan colecciones Ver la serie de ejemplos ORCleanup2.SCX _Screen.ActiveForm y qué se puede hacer con esto Durante el curso de creación del formulario hay un punto en el cual el formulario se convierte en _Screen.ActiveForm. Sabiendo cuando esto ocurre nos permite poder hacer cosas muy buenas con la referencia de objeto _Screen.ActiveForm. Cuando THISFORM se convierte en _Screen.ActiveForm Como se demuestra en el ejemplo _ScreenActiveForm.SCX, el formulario que se está instanciando se convierte en _Screen.ActiveForm inmediatamente después de ser mostrados (Show()) Lo que nunca te dijo tu madre Desafortunadamente, _Screen.ActiveForm no es siempre _Screen.ActiveForm. En muchas ocasiones donde el formulario activo contiene uno o más controles ActiveX, _Screen.ActiveForm puede ser una referencia de objeto a un control ActiveX, NO al formulario que lo contiene. La biblioteca en tiempo de ejecución X6SAF.PRG para el framework Visual MaxFrame Profesional está incluida en esta sesión y controla este caso. Para tener una referencia de objeto confiable _Screen.ActiveForm, sustituya el código por el siguiente: LOCAL loActiveForm loActiveForm = _Screen.ActiveForm IF TYPE("loActiveForm.BaseClass") = "C" * aquí no es el formulario activo ELSE * m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activo ENDIF lo que es el equivalente más fiable: LOCAL loActiveForm
  • 11. 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 Guardar una referencia al formulario y objeto llamados Al combinar los conocimientos sobre la secuencia de eventos de creación de formulario LISAG y el conocimiento de cuando THISFORM se convierte en _Screen.ActiveForm, hay algunas cosas interesantes que puede hacer con esta información. El ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX demuestra una buena técnica para guardar fácilmente una referencia de objeto al formulario y objeto (si existe) llamados. Guardar una referencia al formulario llamado Cuando se ejecuta Form.Load, THISFORM no se ha instanciado todavía, y no es el formulario activo _Screen.ActiveForm. Más importante, cualquier formulario (si existe) que se ejecuta cuando THISFORM se llama se refleja aun en _Screen.ActiveForm. Así, es como un pestañeo a obtener una referencia de objeto "libre" al formulario que se está ejecutando cuando se llama THISFORM, y lo guarda en una propiedad de usuario disponible durante la vida del THISFORM: LOCAL loActiveForm loActiveForm = X6SAF() IF VARTYPE(m.loActiveForm) = "O" THIS.oCallingForm = m.loActiveForm ELSE THIS.oCallingForm = .NULL. 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: Algunas cosas interesantes sobre estas técnicas: 1. Puede consultar información sobre el formulario llamado antes del THISFORM.Init, donde se reciben parámetros, aceptando otra configuración que necesita optimizar lo que ocurre antes de que puedan ser verificados los parámetros. 2. No hay necesidad de llamar al formulario para pasar una referencia de objeto a sí mismo al formulario llamado. 3. Cuando THISFORM se instancia desde cualquier sitio, como un menú, no hay sitio para el formulario activo para pasar una referencia de objeto a si mismo a THISFORM.Init 4. Debido a que la referencia de objeto para el formulario llamado se guarda en una propiedad de usuario de THISFORM, está disponible durante la vida de THISFORM, luego de _Screen.ActiveForm se actualiza nunca después de la referencia del formulario llamado. Estas características se muestran en el ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX como se muestra en la figura 11: 1. El evento Clic del cmdCallForm en _SAF1.SCX un comando DO FORM ... no se pasan parámetros. 2. El evento Load del _ScreenActiveForm1.SCX contiene código para verificar el formulario llamado, y entonces, guarda una referencia de objeto a su THISFORM.oCallingForm. 3. El evento Init del grdCustomers en _ScreenActiveForm1.SCX contiene código para verificar el formulario llamado. En tal caso este formulario es consultado por su actual Orders.CustomerID, en cualquier caso, como se muestra en la figura 11. En ese caso grdCustomers establece el puntero a su registro inicial al CustomerID indicado. Observe que esta aplicación tiene lugar antes que THISFORM.Init, donde un parámetro CustomerID pudiera ser recibido y aplicado. Puede DO FORM _ScreenActiveForm1.SCX con nada, y el código descrito arriba simplemente encuentra que no hay mucho que puedas hacer. ¡Abstraerlo!
  • 12. 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. Guardar una referencia a un control del formulario llamado Al guardar una referencia de objeto de un formulario existente es fácil. Pero, ¿qué tal si guardamos una referencia de objeto al control actual en el formulario; pero sólo si este control está en la pila de ejecución del programa, indicando que es responsable por llamar THISFORM (como el botón cmdCallForm en _SAF1.SCX)? 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. Mantener referencias de objetos a los formularios llamados El ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX descrito en la sección previa pudiera hacerle pensar sobre una técnica más poderosa. En ese caso, seguramente disfrutará de esta. Me han preguntado por un código para situaciones donde el formulario no modal necesite no sólo llamar a uno o más formularios no modales adicionales; pero además para cada uno de los formularios llamados para mantener las referencias de objeto a cada otro así que puede actualizar cada otro de tiempo en tiempo. Con más frecuencia me han preguntado algunos desarrolladores que han tratado de implementar una situación, como para depurarlo. No es trivial, mantener todas las referencias de objeto en ambas direcciones, ni para asegurarse que esa limpieza de referencia de objeto (garbage collection) fue hecha para prevenir una posibilidad de dañar la referencia de objeto.
  • 13. Gracias a la función BINDEVENT() agregada en VFP 8.0, este escenario es ahora muy fácil de implementar. Mejor aún, el código necesario es fácil de conceptuar. La serie de ejemplos FormORCleanupCaller*.SCX contienen el código. Todos los formularios en la serie FormORCleanupCaller*.SCX están basados en el ejemplo de la clase base formulario frmBaseForm en la biblioteca FormORCleanup.VCX. La técnica es manipular todo sin pasar ningún parámetro, utilizando una técnica similar demostrada en el ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX. Aquí vemos los conceptos en frmBaseForm: 1. La llamada desde Load llama al método de usuario StoreCalledForm. 2. La llamada desde StoreCalledForm verifica si existe un formulario llamado. En ese caso:<ol type="a"> 3. La referencia de objeto a un formulario llamado desde cualquier objeto llamado es guardada en THISFORM.oCallingForm y THISFORM.oCallingFormControl. 4. Un BINDEVENT() es utilizado para asegurar que cuando se cierra el formulario llamado, es llamadoTHISFORM.ORCleanupCallingForm, donde las referencias de objeto se guardan en THISFORM.oCallingForm y son liberados THISFORM.oCallingFormControl, permitiendo que el formulario llamado cierre adecuadamente. 5. Si el formulario llamado tiene un método de usuario StoreCalledForm (será si hereda desde frmBaseForm), es llamado su método StoreCalledForm y se pasa una referencia de objeto a THISFORM. • La llamada desde StoreCalledForm:<ol type="a"> • Guarda una referencia de objeto al formulario llamado en una propiedad de arreglo (Tuve problemas de hacer una propiedad colección para trabajar adecuadamente). • Utilice un BINDEVENT() para asegurar que cuando se cierra el formulario llamado (el que está actualmente bajo creación), el método ORCleanupCalledForm del formulario llamado es llamado, donde es liberada la referencia de objeto al formulario llamado. El resultado en cadena es que ambas llamadas y llamados formularios mantengan la referencia de objetos entre ellos. Cada formulario llamado puede tener una referencia de objeto a sólo un formulario invocador; pero cada formulario invocador puede mantener la referencia a un número ilimitado de formularios invocados. Más importante, es manipulada la necesaria limpieza de referencia de objeto. Cada instancia de formulario hereda todo el comportamiento necesario, y el formulario o sus miembros puede simplemente THISFORM., THISFORM.oCallingFormObject, y THISFORM.aCalledForms[] objetos en cualquier momento (por supuesto, después de verificar para ver si son referencias válidas de objeto) Este comportamiento se demuestra de esta forma, como se observa en las figuras 14 y 15: 1. DO FormORCleanupCaller1.SCX 2. Haga Clic en cualquiera de los botones DO FORM. Si hace Clic en alguno de ellos más de una vez, se cargan nuevas instancias de FormORCleanupCalled*.SCX sobre las ya existentes. 3. Haga Clic en el botón <?> de cualquier formulario para ver en cada formulario sobre el otro invocador/invocado. 4. Haga Clic en el botón <OK> de cualquier formulario para que vea que no hay problemas de referencias de objetos dañadas. 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. Nota: El autor ha dado su autorización, y los ejemplos se pueden descargar de: DrewSpeedieDemo.zip (167 KB).
  • 14. Hola invitado 15 Jun, 2013 - 01:02 Menú principal • Inicio • Temas • Secciones • Descargas • Enlaces • Indice • Conferencias • MasFoxPro • Enviar noticia • Búsquedas • Usuarios • Preferencias • Top 10 • P+F [FAQ] • RSS Anuncios Lo que nunca te contó tu madre sobre instanciar y destruir formularios (7606 palabras totales en este texto) (22009 lecturas) Lo que nunca te contó tu madre sobre instanciar y destruir formularios Autor: Drew Speedie (www.visionds.com) Traducido por: Ana María Bisbé York (amby@telefonica.net) Para: PortalFox (www.portalfox.com) (Sesión What Your Mother Never Told You About Form Instantiation and Destruction presentada por el autor en la Conferencia DevEssentials Kansas, 2004) Nota aclaratoria de la traductora El artículo publicado inicialmente tenía un error en su contenido, al explicar los eventos que se suceden al destruir formularios. Consultado el autor, el artículo fue corregido el 13 de Abril de 2005. (ver noticia Más sobre "Lo que tu madre no te contó sobre instanciar y destruir Formularios"). Asimismo el autor autorizó la descarga de los ejemplos. Los ejemplos citados se pueden descargar de: DrewSpeedieDemo.zip (167 KB). 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. Las bases LISAG (Load-Init-Show-Activate-GotFocus) y QRDU (QueryUnload- Release- Destroy- Unload) Para implementar exitosamente los escenarios de instanciación y destrucción de un formulario, lo primero que debe entender es la secuencia nativa de los eventos. 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) Anuncios Google ► VFP Foxpro ► Foxpro ► Fox pro Página 1 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c... 15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
  • 15. © 2012 PortalFox 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 Instanciación / Destrucción de miembros contenedores Como demostrará posteriormente el ejemplo LISAG_QRDU2.SCX, los contenedores se instancian "de dentro hacia afuera" de igual forma que hace el propio formulario (debido a que también es un contenedor) ... El Init de los miembros contenidos de disparan antes que el Init del contenedor padre. En la destrucción ocurre lo contrario ... el Destroy del contenedor se dispara antes que el Destroy de sus miembros contenidos. Cuando el formulario tiene establecido valores para DEClass, DEClassLibrary Como muestra el ejemplo LISAG_QRDU_DE.SCX, cuando un formulario establece valores para DEClass, DEClassLibrary (disponibles a partir de VFP 8.0), las cosas son ligeramente diferentes. Debido a que el objeto DataEnvironment es un objeto completamente separado, se instancia completamente antes del Form.Load. 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. Lo que nunca te contó tu madre Los ejemplos LISAG_QRDU*.SCX demuestran algunos aspectos de interés, muchos de los cuales pudiera no encontrar intuitivos: Form 1. En dependencia de cómo se cierra el formulario, se ejecuta el método Release o el evento QueryUnload; pero no ambos ... Form.Release no es un buen lugar para colocar código, no importa lo que sea, que se deba ejecuta cada vez que se destruya el formulario. Utilice en su lugar los eventos Destroy o Unload. 2. Debido a que muchos comandos como SET TALK están limitados a la sesión privada de datos (ver DATASESSION TO en la ayuda de VFP para ver la lista de estos comandos SET), decidir en que lugar va a asignar valores a estos comandos SET depende de cómo abre sus datos. Si no utiliza nunca DataEnvironment es fácil: los comandos SET para la sesión privada de datos se colocan en el Load de la clase form. Si utiliza DataEnvironment nativo en un formulario basado en .SCX, tendrá que establecer estos comandos SET en DataEnvironment.OpenTables/BeforeOpenTables, antes de que las tablas/cursores sean abiertos. Si implementa un DataEnvironment de usuario especificado en las propiedades DEClass/DEClassLibrary (VFP 8.0 y superior), podrá establecer los comandos SET para la sesión privada de datos en el método Init de la clase DataEnvironment; pero sea consciente, que el Init de los miembros Cursor, CursorAdapter, y los objetos Relation se disparan antes que DataEnvironment.Init. Vea además un par de secciones a continuación en este documento. 3. Al destruir un formulario no se disparan los eventos Form.Deactivate ni Form.LostFocus DataEnvironment 1. El DataEnvironment abre sus datos implícitamente entre el DataEnvironment.BeforeOpenTables y el Form.Load() ... inmediatamente antes del Form.Load. 2. El DataEnvironment NO abre los datos en el evento OpenTable -- OpenTables es de uso opcional para abrir programaticamente los datos si se utiliza AutoOpenTables = .F. 3. El evento BeforeOpenTables no se dispara antes (Before) que el evento OpenTables, BeforeOpenTables está mal nombrado y se debió llamar algo como AfterOpenTablesBeforeImplicitOpenTables. (DespuesAbrirTablasAntesImplicitamenteAbrirTablas) 4. El DataEnvironment.Cursor.Init se dispara DESPUÉS que el cursor ya fue abierto y DESPUÉS del Form.Load 5. El DataEnvironment.Init se dispara DESPUÉS que el DataEnvironment halla cumplido todas las misiones encomendadas. DESPUÉS que los cursores han sido puestos en uso, y DESPUÉS del Form.Load. Excepto cuando el DataEnvironment es una instancia de usuario especificada en las propiedades DEClass/DEClassLibrary, en ese caso se instancia completamente antes de Form.Load. 6. Consecuente con el comportamiento de OpenTables y cuando las tablas se abren explícitamente por el DataEnvironment, el mismo cierra las tablas ANTES del método CloseTables. 7. Mientras la secuencia de instanciación / destrucción para el formulario y sus miembros es consistente, la secuencia de instanciación / destrucción para el DataEnvironment depende de su implementación: DataEnvironment nativo para un formulario basado en .SCX, DEClass DataEnvironment de usuario para un formulario basado en .SCX o .VCX. Llamar THISFORM.Metodos o consultar THISFORM.Propiedades desde el DataEnvironment Los ejemplos LISAG_DE*.SCX demuestran comportamiento inconsistente relacionado con llamar a métodos del formulario (THISFORM) desde el DataEnvironment. Lo que nunca te dijo tu madre DataEnvironment nativo de VFP para un formulario (.SCX) Cuando un formulario basado en .SCX utiliza DataEnvironment nativo de VFP, el DataEnvironment se instancia primero que el formulario como tal. Ver los ejemplos LISAG_QRDU*.SCX, los que demuestran el su comportamiento nativo. Sin embargo 1. LISAG_DE.SCX demuestra que las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment que se disparan antes del Form.Load son ¡COMPLETAMENTE IGNORADAS!. Por ejemplo, las llamadas a THISFORM.Metodos desde los eventos de DataEnvironment OpenTables y BeforeOpenTables no hacen nada, no se invoca ningún método y no se genera un error. Supongo que VFP no ha iniciado todavía la instanciación del formulario y por tanto, no reconoce THISFORM como un objeto; pero yo debería esperar que se generara un error, como hace SYS(1271,THISFORM). Página 2 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c... 15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
  • 16. 2. LISAG_DE2.SCX demuestra que las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment que se disparan antes del Form.Load SÍ disparan esos métodos EN CASO que hayan sido definidos en la clase Form a partir de la cual hereda el formulario actual. Por supuesto, todos los métodos nativos de VFP heredan de la clase base Form de VFP. Entonces, el código que se ejecuta es el heredado de la clase Form, NO cualquier código ubicado en los métodos nativos o de usuarios del formulario instanciado. Una vez que se dispara el Form.Load, la llamada a estos mismos métodos disparan los métodos para el nivel instanciado. 3. De igual forma, si en el DataEnvironment que se dispara antes del Form.Load, consulta el valor de una propiedad (nativa o de usuario) que se establece explícitamente en la ficha Propiedades a nivel del formulario instanciado, los valores son los que sean predeterminados por VFP para esa propiedad (.F. para todas las propiedades de usuario), como si hubiera establecido las propiedades como predeterminadas en la ficha propiedades. Sin embargo; para las propiedades establecidas en la ventana propiedades de cualquier clase padre del formulario instanciado, el valor es evaluado adecuadamente, tal y como esperamos. Sin embargo, puede establecer una propiedad en cualquier método del DataEnvironment. 4. En los métodos de DataEnvironment, IntelliSense no muestra NINGUN PEM (Propiedades, Eventos, Métodos) de usuario, ya sea de la instancia actual o de alguna clase padre de la instancia actual del formulario. Conclusión La conclusión es que se puede abstraer del comportamiento de DataEnvironment para métodos de usuario de formulario, ya que estos métodos están basados en formularios .SCX. Y debe recordar que el código que coloque en estos métodos en el nivel instanciado del .SCX será completamente ignorado, cuando esos métodos son llamados desde eventos del DataEnvironment como OpenTables y BeforeOpenTables que se ejecutan antes que Form.Load. Para el nivel instanciado establecer propiedades es poco fiable hasta que no se ejecute Form.Load. DataEnvironment con DEClass/DEClassLibrary de usuario para un formulario basado en .SCX Cuando las propiedades DEClass y DEClassLibrary de un formulario basado en .SCX tienen asignado un valor que especifique un objeto DataEnvironment de usuario, las cosas son algo diferentes 1. En tiempo de diseño, cuando da valor a las propiedades DEClass y DEClassLibrary asignando una clase DataEnvironment con código en su evento Init, o en el Init de alguno de sus miembros cursor/relation, y ese Init llama a un método de usuario de ese formulario, sólo el hecho de asignar valor a las propiedades DEClass y DEClassLibrary genera un error "Objeto no contenido en el formulario" ("Object is not contained in a Form"), para cada llamada al método de usuario del formulario (THISFORM). 2. En tiempo de ejecución, como demuestra LISAG_DEClass.SCX se genera el mismo error "Objeto no contenido en el formulario". Puede seleccionar dos veces <Ignore> para que continúe LISAG_DEClass.SCX 3. En tiempo de ejecución, como demuestra LISAG_DEClass.SCX, aquellas mismas llamadas a métodos de usuario de THISFORM que generaban un error cuando eran llamados desde el DataEnvironment.Init o desde el Init de uno de sus miembros, NO HACEN NADA si son llamados desde los eventos DataEnvironment.OpenTables o BeforeOpenTables ... los métodos de usuario agregados al .SCX en la instancia actual son completamente ignorados. 4. En tiempo de ejecución, como demuestra LISAG_DEClass2.SCX, cuando ocurren eventos como OpenTables and BeforeOpenTables del DataEnvironment, que se disparan después del DataEnvironment.Init y el Init de sus miembros llaman a métodos de THISFORM, sólo se ejecuta el código para aquellos métodos que es heredado de la clase padre ... no el código colocado en los métodos del nivel actual instanciado. Conclusión La conclusión es que el código colocado en métodos de la instancia actual del formulario nunca se ejecuta cuando el método es llamado desde eventos del DataEnvironment. Para confiar en los comportamientos abstractos del DataEnvironment, debe crear todo el código en la clase DataEnvironment (jerárquicamente). 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. Lo que nunca te dijo tu madre Eso depende de si utiliza o no DataEnvironment y en ese caso, si utiliza el DataEnvironment nativo de VFP o una clase personalizada DataEnvironment. 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 En el formulario de la más alta jerarquía establezca la propiedad DataSession a 2- Private Data Session. 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() Página 3 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c... 15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
  • 17. Recuerde, cualquier código que coloque en el método de usuario en el nivel instanciado no se ejecutará, como fue explicado en la sección anterior ... solamente se ejecuta el código colocado en la(s) clase(s) padre de la instancia actual. DataEnvironment de usuario con DEClass/DEClassLibrary Coloque la configuración lógica de los comandos SET para su sesión privada de datos en el Init de la clase DataEnvironment. Sea consciente de que pudiera tener algunos comandos SET como SET TALK OFF en el Init de sus clases base Cursor o Relation, ya que estos se ejecutan antes que el Init de DataEnvironment. ¿Cómo establecer los comandos SET? Una vez que haya determinado dónde necesita colocar su código para los comandos SET de su sesión privada de datos, la cuestión es cómo debe escribir ese código. Puede escribir un código estricto con los comandos SET deseados (como se demuestra en LISAG_PDS_SETs.SCX); pero el mejor enfoque pudiera ser tener la sesión privada de datos que lea los valores desde la sesión predeterminada de datos ... frecuentemente se utiliza la misma configuración de comandos SET, estos se configuran globalmente para la sesión predeterminada de datos cuando inicia su aplicación (como se demuestra en LISAG_PDS_SETs_Abstract*.SCXs) Los ejemplos LISAG_PDS_SETs_Abstract*.PRGs y sus correspondientes .SCX demuestran una de estas técnicas. Cada LISAG_PDS_SETs_Abstract*.PRG instancia un demo (objeto aplicación) "application object", que contiene un método de usuario GetSETCommandSetting(). Como el objeto application es instanciado en la sesión predeterminada de datos #1, al llamar a su método GetSETCommandSetting() DEVUELVE la configuración especificada como lo establece la sesión predeterminada de datos. Como la sesión privada de datos instancia, pueden establecer sus comandos SET para que coincidan con aquellos establecidos en la sesión predeterminada de datos llamado al objeto application global. Aquí está el código esencial del método SetSETCommands de la clase frmLISAG_PDS_SETs_Abstract en LISAG_PDS_SETs_Abstract.VCX: LOCAL laSETs[3], luSetting, lcString laSets[1] = "DELETED" laSets[2] = "MULTILOCKS" laSets[3] = "TALK" FOR EACH lcSet IN laSETs luSetting = goApplication.GetSetCommandSetting(m.lcSet) lcString = "SET " + m.lcSet + SPACE(1) + TRANSFORM(m.luSetting) &lcString ENDFOR Utilice DataEnvironment sólo en tiempo de diseño: Antes de que decida abandonar del todo el uso del DataEnvironment, existe una idea que debe considerar: Mientras esté diseñando formularios basados en .SCX, utilice DataEnvironment solo para cuestiones de diseño: • Arrastrar y soltar un(os) cursor(es) al formulario para crear instantáneamente controles Grid. • Arrastrar y soltar los campos al formulario para agregar controles cuyos ControlSource ya estarán definidos y con un ancho aproximado (Width) (si el mapeo de campos (field mapping) tiene establecido que incluya el título del campo, se puede obtener ya la etiqueta correspondiente al Caption existente). • Establecer el ControlSource de cualquier control desde la ventana propiedades, seleccionando uno los cursores actuales desde el cuadro desplegable. • Tener acceso al DataEnvironment y sus miembros (cursores) en los generadores de usuarios. Recuerde establecer las propiedades AutoOpenTables y AutoCloseTables a .F. como muestra la Figura 5, entonces VFP ignora todo lo que esté en el DataEnvironment en tiempo de ejecución. Sin embargo, para formularios con sesión privada de datos, cuando el formulario es cerrado/destruido, VFP cierra todos los cursores abiertos mientras estuvo activo el formulario. En tiempo de ejecución abra las vistas y tablas manualmente, utilizando una de las técnicas descritas en el método Load de DesignTimeDE.SCX, aprovechando sus ventajas sobre el comportamiento del DataEnvironment en tiempo de ejecución: • Si/cuando hay un problema al abrir una tabla/vista, se puede enviar un mensaje de usuario y devolver (RETURN) .F., interrumpir la instanciación de esa tabla/vista mientras continúa intacto el resto de la aplicación. Por el contrario, cuando DataEnvironment encuentra un problema como un archivo no encontrado, cabecera de tabla dañada, índice dañado, etc. El DataEnvironment falla, se interrumpe su ejecución, así como todo el resto de la aplicación. • Se puede establecer el comando SET PATH antes de llamar los datos, de esta forma se puede intercambiar entre diferentes conjuntos de datos o simplemente ajustar la ruta (PATH) antes de llamar los datos • Puede utilizar herramientas como Stonefield Database Toolkit para reparar problemas con tablas, índices, memos, etc. Utilizando esta técnica, puede ignorar las inconsistencias por utilizar DataEnvironment, que se han documentado anteriormente en este documento, ya que no hace nada en tiempo de ejecución. Incluso en un diseño n-Capas, si sus objetos de Negocio proporcionan un cursor de datos puede agregar tablas/vistas al DataEnvironment (establecer propiedad Alias), allí donde están disponibles para conveniencia en tiempo de diseño y son ignoradas en tiempo de ejecución, cuando el objeto negocio proporciona el dato real. Esta técnica trabaja igualmente bien para formularios basados en .VCX ... no existe objeto DataEnvironment nativo, con lo cual tiene que cargar los datos e código desde el método Load y por tanto ignorar el DataEnvironment. Si se establecen las propiedades DEClass/DEClassLibrary, los datos indicados están disponibles en tiempo de ejecución pero el DataEnvironment no está disponible en tiempo de diseño. Mucho cuidado al romper la secuencia nativa de eventos de instanciación Existen vías para romper la secuencia nativa de eventos de instanciación. Algunas veces las consecuencias son menos graves, en otras son catastróficas y pueden causar todo tipo de comportamiento indeseado. Lo que tu madre nunca te dijo El ejemplo LISAG_SetFocus.SCX demuestra una de las posibilidades. Desafortunadamente esto es muy común y muy fácil de hacer. La última línea de código en el evento Form.Init, es esta línea aparentemente inocente que asegura que al instanciar, el botón OK tiene el foco: THIS.cmdOK.SetFocus() 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 Página 4 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c... 15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
  • 18. 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 Ahora, si existe Member.SetFocus(), en el Form.Init la bandera se establece prematuramente en .F., antes de que el Init finalice y antes de que se ejecute el Show. Cualquier código en Form.Show que se debe ejecutar solamente durante la instanciación será ignorado porque la bandera ya está en .F. en un escenario normal (no una demo) es un error muy difícil de depurar porque solo ocurre si la condición encuentra el Member.SetFocus() explícito. El ejemplo demuestra como establecer ese tipo de propiedad de usuario ¿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. Cuando al llamar Form.Load() o Form.Init() devuelven .F. no se disparan los eventos Form.Destroy ni Form.Unload Devolviendo .F. desde el Load o el Init de un formulario no se instancia, es evidente; pero Lo que no te dijo tu madre 1. Como demuestra LISAG_QRDU_AbortLoad.SCX, cuando el Load devuelve .F., no se disparan ni Form.Destroy ni Form.Unload. 2. Como demuestra LISAG_QRDU_AbortInit.SCX, cuando el Init devuelve .F., se dispara Form.Unload; pero no lo hace Form.Destroy. Debido a que los miembros contenidos en el formulario se instancian antes que el Form.Init, el Destroy de esos miembros se dispara ya que están fuera de límites. (Since form members instantiate before the Form.Init, the Destroy of form members does fires as they go out of scope). 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.! Ejecutar Form.Destroy consistentemente Los formularios LISAG_QRDU_AbortLoadInit1.SCX and LISAG_QRDU_AbortLoadInit2.SCX demuestran técnicas similares que puede utilizar para garantizar que los Form.Destroy/Unload (códigos de limpieza) se ejecuten adecuadamente. Peculiaridad del SYS(1271) Primeramente vamos a mirar la función SYS(1271) que podemos poner a trabajar para nuestro beneficio. El ejemplo The LISAG_SYS1271.SCX demuestra que no puede hacer esta llamada: SYS(1271,THISFORM) Hasta que se haya completado Form.Load, no se puede llamar desde un método de DataEnvionment, el Form.Load ni desde otro método llamado desde el Form.Load. De hacerlo, recibirá el mensaje, poco intuitivo, "Insuficiente memoria para completar esta operación" (" Not enough memory to complete this operation"). Puede verlo al hacer DO FORM LISAG_SYS1271 ... seleccionar <Ignore> para continuar con la instalación de LISAG_SYS1271.SCX. Por un lado, es inconveniente tener que esperar hasta el Form.Load para consultar SYS(1271,THISFORM) si desea realmente verificarlo antes. Pero LISAG_QRDU_AbortLoadInit1.SCX 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 Página 5 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c... 15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
  • 19. 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. Referencia de objetos en la destrucción de formularios. (Object Reference Cleanup On Form Destruction) 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. Si algunas de las referencias de objetos no se liberan explícitamente, el contenedor no se libera. Si el contenedor es un formulario o un contenedor dentro de un formulario, el formulario no se libera. Esta es la causa del error "Referencia de objeto dañada" ("Dangling object reference"). ORCleanup1.PRG crea 2 instancias de ORCleanup1.SCX para demostrar el problema: 1. DO ORCleanup1 2. En la instancia 2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia de objeto a un miembro de la instancia1 del formulario. 3. Intente cerrar la instancia 1: Haga clic en OK, haga clic en "X" a la derecha de la barra de título, o seleccione Close desde el menú en la caja de control de la barra de título. La instancia 1 del formulario se niega a cerrarse, debido a referencias de objeto dañadas, de hecho, si selecciona Cerrar desde el menú en la caja de control de la barra de título, la opción Cerrar no aparece y la "X" a la derecha de la barra de título aparece deshabilitada. 4. Cierre la instancia 2 del formulario. En cuanto se cierra, se cierra también la instancia1 ... su código Destroy ya se había disparado, solo estaba esperando a que se liberaran las referencias de objetos externos a sus miembros. Lo que no te dijo tu madre Para formularios, la limpieza de referencia de objetos nunca se debe hacer después de Form.Destroy. Esto es fácil de hacer. Sin embargo, cuando los miembros del formulario necesitan limpiar las referencias de objetos, el código colocado en su Destroy puede ser inútil. Recuerde: el formulario destruye "de afuera hacia adentro", por tanto el Destroy de los miembros se dispara después del Destroy del formulario como tal. Cuando se daña la referencia de objetos existente, se dispara el Form.Destroy; pero la destrucción frena aquí, y no se dispara ningún otro evento, incluyendo el Destroy de sus miembros, hasta que se libera la referencia de objeto dañada. Esto es un gran problema cuando diseña formularios de tal forma que un formulario no modal mantenga referencias de objetos a un miembro de otro formulario no modal u otros objetos externos. Dos casos comunes de daño de referencia de objetos: 1. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario intenta cerrar Form2; pero se niega a cerrar hasta que los miembros de Form1 liberan sus referencias de objeto (este es el escenario que se muestra en ORCleanup1.PRG/.SCX). 2. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario cierra Form1; pero Form1 no se cierra del todo, queda como un objeto artificial como una sesión de datos "desconocida" ("Unknown") visible en la ventana Sesión de datos. Cuando objetos externos contienen referencias a miembros de THISFORM ORCleanup1a.SCX demuestra una vía para solucionar el comportamiento necesario. Repita los pasos para ORCleanup1 y observe la diferencia: 1. DO ORCleanup1a. 2. En la instancia2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia de objeto a un miembro de la instancia1 del formulario. 3. Cierre la instancia 1del formulario. Se cierra normalmente, como se espera, aún cuando aparentemente no se ha hecho la limpieza de referencia de objetos para liberar las referencias de la instancia 2 del formulario tiene a los objetos de la instancia 1 del formulario. Puede ver la técnica que he utilizado en ORCleanup1a.SCX para examinar el código en el evento Clic de cada casilla de verificación del grupo inferior. He utilizado la función BINDEVENT() para asegurar que cuando se guarda una referencia de objeto, el Destroy de los objetos guardados del formulario llama automáticamente al código de limpieza de los objetos haciendo su almacenaje. Entonces, cuando es cerrado el formulario cuyas referencias a objetos miembros han sido guardadas, su Destroy llama al código de limpieza de la referencia de objetos de los objetos externos manteniendo la referencia de objetos. He aquí lo que ocurre: 1. DO ORCleanup1a 2. Haga Clic en la primera casilla de verificación del grupo inferior de la instancia 2 del formulario. El código del evento Clic guarda una referencia de objeto al miembro txtDemo1 en la instancia 1 del formulario a la propiedad de usuario oFormMember de la instancia 2 del formulario. El código Clic también ejecuta BINDEVENT() para asegurarse de que cuando se cierra la instancia 1 del formulario se ejecuta el método ORCleanup de la instancia 2 del formulario. 3. Haga Clic en el botón de comandos OK de la instancia1 del formulario. Cuando se dispara el Destroy, ejecuta el método Cleanup de la segunda instancia del formulario, gracias al BINDEVENT(). Entre otras cosas, el método ORCleanup de la instancia 2 del formulario establece su propiedad oFormMember a .NULL., liberando la referencia de objeto guardada el txtDemo miembro de la instancia 1 del formulario. En un conjunto real de clases jerárquicas, pudiera probablemente agregar el método ORCleanup a cada una de sus clases bases, de tal forma que el Checkbox.Click haría BINDEVENT() a su propio método ORCleanup en lugar de THISFORM.ORCleanup, haciendo más granular el control del proceso. Esta técnica requiere, por supuesto, utilizar VFP 8.0, versión en la que fue agregada al lenguaje la poderosa función BINDEVENT(). Cuando los miembros de THISFORM contienen referencias a objetos externos Como se ha descrito antes, toda la limpieza a las referencias de objeto "garbage collection" debe hacerse antes de Form.Destroy, que ocurre antes que se dispare el evento Destroy de cualquier miembro. Página 6 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c... 15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1
  • 20. La solución es codificar el Form.Destroy con mensajes a sus miembros para establecer explícitamente todas las referencias de objeto guardadas a .NULL. Mi sugerencia es separar esta tarea a un método de usuario ORCleanup agregado a sus clases base. Primero, agregue un método de usuario ORCleanup agregado a sus clases base Form. Agregue código al Destroy de su clase base formulario para llamar a THISFORM.ORCleanup(). En cada clase formulario o instancia, cada vez que escriba código para guardar una referencia de objeto a un miembro de formulario, asegúrese de colocar el código de liberación correspondiente en el ORCleanup del formulario contenido. Cada vez que se dispara el Form.Destroy, es realizada la limpieza a todas las referencias de objeto. Sin embargo, puede que quiera abstraerse en lo delante de este comportamiento, ya que cuando está diseñando la clase contenedora (pageframes, optiongroups, grids, containers, etc.), no puede colocar código en el Destroy del formulario contenido porque no sabe qué instancia del formulario o clase va a contener finalmente el contenedor que está preparando. Para solucionar este problema, agregue un método de usuario ORCleanup para cada una de sus clases base que pueden ser miembros de un formulario (Textbox, Spinner, Custom, etc.). Programe su clase base form Form.ORCleanup para interactuar con cada uno de sus miembros, llamando su método ORCleanup. Si diseña el método ORCleanup de objetos contenedores (grids, pageframes, pages, optiongroups, containers, etc.) para interactuar con cada uno de sus miembros de la misma forma que el ORCleanup a nivel de formulario solo hace un lazo entre su arreglo de controles y llama al ORCleanup de cada uno de sus miembros directos. Dañar la referencia de objetos cuando se utilizan colecciones Ver la serie de ejemplos ORCleanup2.SCX _Screen.ActiveForm y qué se puede hacer con esto Durante el curso de instanciación del formulario hay un punto en el cual el formulario se convierte en _Screen.ActiveForm. Sabiendo cuando esto ocurre nos permite poder hacer cosas muy buenas con la referencia de objeto _Screen.ActiveForm. Cuando THISFORM se convierte en _Screen.ActiveForm Como se demuestra en el ejemplo _ScreenActiveForm.SCX, el formulario que se está instanciando se convierte en _Screen.ActiveForm inmediatamente después de ser mostrados (Show()) Lo que nunca te dijo tu madre Desafortunadamente, _Screen.ActiveForm no es siempre _Screen.ActiveForm. En muchas ocasiones donde el formulario activo contiene uno o más controles ActiveX, _Screen.ActiveForm puede ser una referencia de objeto a un control ActiveX, NO al formulario que lo contiene. La biblioteca en tiempo de ejecución X6SAF.PRG para el framework Visual MaxFrame Profesional está incluida en esta sesión y controla este caso. Para tener una referencia de objeto confiable _Screen.ActiveForm, sustituya el código por el siguiente: LOCAL loActiveForm loActiveForm = _Screen.ActiveForm IF TYPE("loActiveForm.BaseClass") = "C" * aquí no es el formulario activo ELSE * m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activo ENDIF lo que es el equivalente más fiable: 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 Guardar una referencia al formulario y objeto llamados Al combinar los conocimientos sobre la secuencia de eventos de instanciación de formulario LISAG y el conocimiento de cuando THISFORM se convierte en _Screen.ActiveForm, hay algunas cosas interesantes que puede hacer con esta información. El ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX demuestra una buena técnica para guardar fácilmente una referencia de objeto al formulario y objeto (si existe) llamados. Guardar una referencia al formulario llamado Cuando se ejecuta Form.Load, THISFORM no se ha instanciado todavía, y no es el formulario activo _Screen.ActiveForm. Más importante, cualquier formulario (si existe) que se ejecuta cuando THISFORM se llama se refleja aun en _Screen.ActiveForm. Así, es como un pestañeo a obtener una referencia de objeto "libre" al formulario que se está ejecutando cuando se llama THISFORM, y lo guarda en una propiedad de usuario disponible durante la vida del THISFORM: LOCAL loActiveForm loActiveForm = X6SAF() IF VARTYPE(m.loActiveForm) = "O" THIS.oCallingForm = m.loActiveForm ELSE THIS.oCallingForm = .NULL. 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: Algunas cosas interesantes sobre estas técnicas: 1. Puede consultar información sobre el formulario llamado antes del THISFORM.Init, donde se reciben parámetros, aceptando otra configuración que necesita optimizar lo que ocurre antes de que puedan ser verificados los parámetros. 2. No hay necesidad de llamar al formulario para pasar una referencia de objeto a sí mismo al formulario llamado. 3. Cuando THISFORM se instancia desde cualquier sitio, como un menú, no hay sitio para el formulario activo para pasar una referencia de objeto a si mismo a THISFORM.Init. Página 7 de 9Lo que nunca te contó tu madre sobre instanciar y destruir formularios :: PortalFox :: Nada corre c... 15-06-2013http://www.portalfox.com/index.php?name=Sections&req=viewarticle&artid=42&page=1