Secuencia de eventos vfp

3,069 views
2,908 views

Published on

Secuencia de Eventos VFP - Como se cargan los eventos en visual FoxPro

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,069
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
109
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Secuencia de eventos vfp

  1. 1. Secuencia normal de eventos en VFP cuando se Crean y Destruyen FormulariosTomado de “What Your Mother Never Told You About Form Instantiation and Destruction presentada por el autor en laConferencia DevEssentials Kansas, 2004)Leído y Editado a mi modo: jun/2013Al final de este documento está disponible la traducción originalSecuencia de eventos LISAG y QRDULISAG (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íficosa 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....ResumenEsta 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 lasecuencia nativa de los eventos.Creación de un FormularioAquí 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() oabiertas)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 DataEnvironment5. Evento Form.DataEnvironment.Init6. Evento Init de cada miembro del formulario que es creado7. Evento Form.Init8. Evento Form.Show9. Evento Form.Activate10. Evento When del primer control del formulario en el orden de tabulación (tab order)11. Evento Form.GotFocus12. Evento GotFocus del primer control del formulario en el orden de tabulaciónDestrucción de un FormularioFormulario 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 desdeel ControlBox en la esquina superior izquierda de la barra de título del formulario:1. Evento Form.QueryUnload
  2. 2. 2. Evento Form.Destroy3. 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() oabiertas)6. Evento Form.DataEnvironment.Destroy7. Evento Destroy para cada cursor en el DataEnvironmentFormulario 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, alhacer Clic en un botón Aceptar1. Evento Form.Release2. Evento Form.Destroy3. 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() oabiertas)6. Evento Form.DataEnvironment.Destroy7. Evento Destroy para cada cursor en el DataEnvironmentCreación / Destrucción de miembros contenedoresEn 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 esun 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 elDestroy de sus miembros contenidos.Formulario con valores establecido para DEClass, DEClassLibraryCuando un formulario establece valores para DEClass, DEClassLibrary (disponibles a partir de VFP 8.0), las cosas son ligeramentediferentes. Debido a que el objeto DataEnvironment es un objeto completamente separado, se crea completamente antes delForm.Load.El Init de los miembros del DataEnvironment (Es decir, el Init del Cursor, CursorAdapter, y Relation): Se disparan antes que el Initdel DataEnvironment, siguiendo el comportamiento nativo de VFP donde el Init de los miembros ocurre antes que el Init delcontenedor padre.El evento Destroy del DataEnvironment: Ocurre antes que el Destroy de sus miembros (Es decir, del Cursor, CursorAdapter, yRelation).
  3. 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 sedestruya 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 ayudade VFP para ver la lista de estos comandos SET), decidir en que lugar va a asignar valores a estos comandos SET depende de cómoabre sus datos.Si no utiliza nunca DataEnvironment: Los comandos SET para la sesión privada de datos se colocan en el Load de laclase form.Si utiliza DataEnvironment nativo en un formulario basado en .SCX: Tendrá que establecer estos comandos SET enDataEnvironment.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 ysuperior): Podrá establecer los comandos SET para la sesión privada de datos en el método Init de la claseDataEnvironment; pero sea consciente, que el Init de los miembros Cursor, CursorAdapter, y los objetos Relation sedisparan antes que DataEnvironment.Init.3. Al destruir un formulario no se disparan los eventos Form.Deactivate ni Form.LostFocusDataEnvironment1. 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 abrirprogramáticamente los datos si se utiliza AutoOpenTables = .F.3. El evento BeforeOpenTables no se dispara antes (Before) que el evento OpenTables, BeforeOpenTables está malnombrado 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.Load5. 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 unacreación de usuario especificada en las propiedades DEClass/DEClassLibrary, en ese caso se crea completamente antes deForm.Load.6. Consecuente con el comportamiento de OpenTables y cuando las tablas se abren explícitamente por elDataEnvironment, 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 decreació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 paraformulario basado en .SCX o .VCX.Llamar al método THISFORM.Metodos o consultar THISFORM.Propiedades desde el DataEnvironmentLos ejemplos muestran un comportamiento inconsistente relacionado con llamar a métodos del formulario (THISFORM) desde elDataEnvironment.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 elformulario como tal. Sin embargo
  4. 4. 1. Las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironment que se disparan antes delForm.Load son ¡COMPLETAMENTE IGNORADAS!. Por ejemplo, las llamadas a THISFORM.Metodos desde los eventos deDataEnvironment OpenTables y BeforeOpenTables no hacen nada, no se invoca ningún método y no se genera un error. Supongoque VFP no ha iniciado todavía la creación del formulario y por tanto, no reconoce THISFORM como un objeto; pero yo deberíaesperar 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 delForm.Load SÍ disparan esos métodos EN CASO que hayan sido definidos en la clase Form a partir de la cual hereda el formularioactual. Por supuesto, todos los métodos nativos de VFP heredan de la clase base Form de VFP. Entonces, el código que se ejecuta esel heredado de la clase Form, NO cualquier código ubicado en los métodos nativos o de usuarios del formulario creado. Una vezque 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 deusuario) que se establece explícitamente en la ficha Propiedades a nivel del formulario creado, los valores son los que seanpredeterminados por VFP para esa propiedad (.F. para todas las propiedades de usuario), como si hubiera establecido laspropiedades como predeterminadas en la ficha propiedades. Sin embargo; para las propiedades establecidas en la ventanapropiedades de cualquier clase padre del formulario creado, el valor es evaluado adecuadamente, tal y como esperamos. Sinembargo, 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 seade la creación actual o de alguna clase padre de la creación actual del formulario.ConclusiónEl DataEnvironment para métodos de usuario de formulario, ya que estos métodos están basados en formularios .SCX. Y deberecordar que el código que coloque en estos métodos en el nivel creación del .SCX será completamente ignorado, cuando esosmétodos son llamados desde eventos del DataEnvironment como OpenTables y BeforeOpenTables que se ejecutan antes queForm.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 .SCXCuando las propiedades DEClass y DEClassLibrary de un formulario basado en .SCX tienen asignado un valor que especifique unobjeto 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 concó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 eseformulario, sólo el hecho de asignar valor a las propiedades DEClass y DEClassLibrary genera un error "Objeto no contenido en elformulario" ("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 eranllamados desde el DataEnvironment.Init o desde el Init de uno de sus miembros, NO HACEN NADA si son llamados desde loseventos DataEnvironment.OpenTables o BeforeOpenTables ... los métodos de usuario agregados al .SCX en la creación actual soncompletamente ignorados.4. En tiempo de ejecución, cuando ocurren eventos como OpenTables and BeforeOpenTables del DataEnvironment, que sedisparan después del DataEnvironment.Init y el Init de sus miembros llaman a métodos de THISFORM, sólo se ejecuta el códigopara aquellos métodos que es heredado de la clase padre ... no el código colocado en los métodos del nivel actual creado.ConclusiónLa conclusión es que el código colocado en métodos de la instancia actual del formulario nunca se ejecuta cuando el método esllamado desde eventos del DataEnvironment. Para confiar en los comportamientos abstractos del DataEnvironment, debe creartodo 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 laconfiguración de las propiedades no es confiable hasta que no se dispare el Form.Load.
  5. 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 portanto necesitan ser ejecutados para cada sesión privada de datos. Además, SET TALK está limitado a la sesión privada de datos ydebe ser establecido lo antes posible, en OFF que es el valor no predeterminado para VFP, en el momento en que la sesión privadade 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 esel mejor lugar para colocar los comandos SET en una sesión privada de datos, de forma tal que el formulario se creecorrectamente?Eso depende de si utiliza o no DataEnvironment y en ese caso, si utiliza el DataEnvironment nativo de VFP o una clase personalizadaDataEnvironment.No utilizar DataenvironmentPienso 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 loscomandos SET de la sesión privada de datos en el Load del formulario de la más alta jerarquía que establezca la propiedadDataSession a 2 -Private Data Session. Comience el Load de cada creación de formulario con llamada hacia atrás (callback), paraasegurarse que los comandos SET quedarán configurados desde el inicio:IF NOT DODEFAULT()RETURN .F.ENDIFDataEnvironment nativo en un formulario basado en .SCXEn el formulario de la más alta jerarquía establezca la propiedad DataSession a 2- Private Data Session. Agregue un método deusuario para colocar la configuración lógica de los comandos SET para su sesión privada de datos junto a cualquier otra cosa quenecesite 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 lasección anterior ... solamente se ejecuta el código colocado en la(s) clase(s) padre de la instancia actual.DataEnvironment de usuario con DEClass/DEClassLibraryColoque la configuración lógica de los comandos SET para su sesión privada de datos en el Init de la clase DataEnvironment. Seaconsciente de que pudiera tener algunos comandos SET como SET TALK OFF en el Init de sus clases base Cursor o Relation, ya queestos 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ónes cómo debe escribir ese código. Puede escribir un código estricto con los comandos SET deseados; pero el mejor enfoque pudieraser tener la sesión privada de datos que lea los valores desde la sesión predeterminada de datos ... frecuentemente se utiliza lamisma configuración de comandos SET, estos se configuran globalmente para la sesión predeterminada de datos cuando inicia suaplicación.Los ejemplos LISAG_PDS_SETs_Abstract*.PRGs y sus correspondientes .SCX demuestran una de estas técnicas. CadaLISAG_PDS_SETs_Abstract*.PRG instancia un demo (objeto aplicación) "application object", que contiene un método de usuarioGetSETCommandSetting(). Como el objeto application es instanciado en la sesión predeterminada de datos #1, al llamar a sumé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 lasesión predeterminada de datos llamado al objeto application global. Aquí está el código esencial del método SetSETCommands dela clase frmLISAG_PDS_SETs_Abstract en LISAG_PDS_SETs_Abstract.VCX:
  6. 6. LOCAL laSETs[3], luSetting, lcStringlaSets[1] = "DELETED"laSets[2] = "MULTILOCKS"laSets[3] = "TALK"FOR EACH lcSet IN laSETsluSetting = goApplication.GetSetCommandSetting(m.lcSet)lcString = "SET " + m.lcSet + SPACE(1) + TRANSFORM(m.luSetting)&lcStringENDFORUtilice 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 unancho aproximado (Width) (si el mapeo de campos (field mapping) tiene establecido que incluya el título del campo, sepuede obtener ya la etiqueta correspondiente al Caption existente).• Establecer el ControlSource de cualquier control desde la ventana propiedades, seleccionando uno los cursores actualesdesde 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 todolo que esté en el DataEnvironment en tiempo de ejecución. Sin embargo, para formularios con sesión privada de datos, cuando elformulario 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 deDesignTimeDE.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, cuandoDataEnvironment 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 entrediferentes 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 estedocumento, 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 alDataEnvironment (establecer propiedad Alias), allí donde están disponibles para conveniencia en tiempo de diseño y son ignoradasen 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 cualtiene 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 elDataEnvironment no está disponible en tiempo de diseño.Mucho cuidado al romper la secuencia nativa de eventos de creaciónExisten vías para romper la secuencia nativa de eventos de creación. Algunas veces las consecuencias son menos graves, en otrasson 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 tieneel foco:THIS.cmdOK.SetFocus()
  7. 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 elSetFocus, VFP tiene que activar inmediatamente el formulario y darle el foco, para que el botón OK pueda tener el foco en esepunto. 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 eventoShow.No muchos formularios dan el SetFocus al primer control de esta forma ... podría simplemente establecer el comando OK primeroen el orden de tabulación. Lo que es más común es establecer el foco condicionalmente a un control en particular, basado enalguna acción del formulario, basado a se vez, en un parámetro que reside en el Init. Simplemente he omitido esta condición en elejemplo 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 seejecutan 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 lacreación del formulario (es posible hacerlo por programa - Show() de un formulario en cualquier momento y el Activate se ejecutacada vez que el formulario se convierte en formulario activo, por ejemplo cuando el usuario hace clic en un formulario no modalabierto en el escritorio VFP) con este fin, agregue una propiedad de usuario (una bandera) con valor predeterminado a .T. , y lohace 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 elformulario está siendo creado:IF THIS.lInstantiating* realizar estas acciones solo si THISFORM se está instanciandoENDIFAhora, si existe Member.SetFocus(), en el Form.Init la bandera se establece prematuramente en .F., antes de que el Init finalice yantes de que se ejecute el Show. Cualquier código en Form.Show que se debe ejecutar solamente durante la creación será ignoradoporque 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 lacondició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 crearun 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 condicionalmentea 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.UnloadDevolviendo .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 miembroscontenidos en el formulario se crean antes que el Form.Init, el Destroy de esos miembros se dispara ya que están fuera delímites. (Since form members instantiate before the Form.Init, the Destroy of form members does fires as they go out ofscope).Bueno, ¿y qué?Bueno, pues frecuentemente colocamos el código de limpieza en el Form.Destroy, y puede tener código abstracto en el eventoDestroy en la clase base o en otras clases Form que están en la jerarquía del creado actualmente. Lo mismo se puede cumplir paraForm.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 delimpieza del Cleanup no se ejecuta si el Form.Load devuelve .F.!Ejecutar Form.Destroy consistentementeLos formularios LISAG_QRDU_AbortLoadInit1.SCX and LISAG_QRDU_AbortLoadInit2.SCX demuestran técnicas similares que puedeutilizar para garantizar que los Form.Destroy/Unload (códigos de limpieza) se ejecuten adecuadamente.
  8. 8. Peculiaridad del SYS(1271)Primeramente vamos a mirar la función SYS(1271) que podemos poner a trabajar para nuestro beneficio. El ejemplo TheLISAG_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 otrométodo llamado desde el Form.Load. De hacerlo, recibirá el mensaje, poco intuitivo, "Insuficiente memoria para completar estaoperació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 realmenteverificarlo antes. Pero LISAG_QRDU_AbortLoadInit1.SCX utiliza este comportamiento para determinar cómo debe ser invocadoForm.Destroy.LISAG_QRDU_AbortLoadInit1.SCXLos 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, asumiendoque 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 deque 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 ocurrenormalmente (no existe error en SYS(1271)) o debido a llamarlo explícitamente desde el Form.Load (SYS(1271) genera unerror). Al llamar manualmente desde Form.Load, es llamado el Form.UnLoad.LISAG_QRDU_AbortLoadInit2.SCXLos 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, asumiendoque cada clase padre form que devuelva .F. desde su código abstracto lo hará. (igual queLISAG_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 deque cualquier código de limpieza en el Destroy se va a ejecutar adecuadamente. (igual queLISAG_QRDU_AbortLoadInit1.SCX)3. El Form.Destroy contiene código para verificar PROGRAM(PROGRAM(-1)-1) para determinar si el Destroy se ha llamadomanualmente 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. Estosignifica que para cerrar/destruir un formulario, cualquier objeto externo que mantenga referencia a uno o más miembros debeliberarse 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 oun 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 ORCleanup12. En la instancia 2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar unareferencia de objeto a un miembro de la instancia1 del formulario.
  9. 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 desdeel menú en la caja de control de la barra de título. La instancia 1 del formulario se niega a cerrarse, debido a referenciasde objeto dañadas, de hecho, si selecciona Cerrar desde el menú en la caja de control de la barra de título, la opciónCerrar 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íadisparado, solo estaba esperando a que se liberaran las referencias de objetos externos a sus miembros.Lo que no te dijo tu madrePara 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 Destroypuede ser inútil. Recuerde: el formulario destruye "de afuera hacia adentro", por tanto el Destroy de los miembros se disparadespués del Destroy del formulario como tal. Cuando se daña la referencia de objetos existente, se dispara el Form.Destroy; pero ladestrucción frena aquí, y no se dispara ningún otro evento, incluyendo el Destroy de sus miembros, hasta que se libera la referenciade objeto dañada.Esto es un gran problema cuando diseña formularios de tal forma que un formulario no modal mantenga referencias de objetos aun 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 semuestra en ORCleanup1.PRG/.SCX).2. Uno o más miembros de Form1 contienen referencias de objeto a miembros de Form2. El usuario cierra Form1; peroForm1 no se cierra del todo, queda como un objeto artificial como una sesión de datos "desconocida" ("Unknown") visibleen la ventana Sesión de datos.Cuando objetos externos contienen referencias a miembros de THISFORMORCleanup1a.SCX demuestra una vía para solucionar el comportamiento necesario. Repita los pasos para ORCleanup1 y observe ladiferencia:1. DO ORCleanup1a.2. En la instancia2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar unareferencia 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 hechola limpieza de referencia de objetos para liberar las referencias de la instancia 2 del formulario tiene a los objetos de lainstancia 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óndel grupo inferior. He utilizado la función BINDEVENT() para asegurar que cuando se guarda una referencia de objeto, el Destroy delos 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 delimpieza de la referencia de objetos de los objetos externos manteniendo la referencia de objetos. He aquí lo que ocurre:1. DO ORCleanup1a2. Haga Clic en la primera casilla de verificación del grupo inferior de la instancia 2 del formulario. El código del evento Clicguarda una referencia de objeto al miembro txtDemo1 en la instancia 1 del formulario a la propiedad de usuariooFormMember de la instancia 2 del formulario. El código Clic también ejecuta BINDEVENT() para asegurarse de quecuando 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étodoCleanup de la segunda instancia del formulario, gracias al BINDEVENT(). Entre otras cosas, el método ORCleanup de lainstancia 2 del formulario establece su propiedad oFormMember a .NULL., liberando la referencia de objeto guardada eltxtDemo 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, detal forma que el Checkbox.Click haría BINDEVENT() a su propio método ORCleanup en lugar de THISFORM.ORCleanup, haciendomá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. 10. Cuando los miembros de THISFORM contienen referencias a objetos externosComo 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 deobjeto 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 baseformulario 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 deformulario, asegúrese de colocar el código de liberación correspondiente en el ORCleanup del formulario contenido. Cada vez quese 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 clasecontenedora (pageframes, optiongroups, grids, containers, etc.), no puede colocar código en el Destroy del formulario contenidoporque no sabe qué instancia del formulario o clase va a contener finalmente el contenedor que está preparando. Para solucionareste problema, agregue un método de usuario ORCleanup para cada una de sus clases base que pueden ser miembros de unformulario (Textbox, Spinner, Custom, etc.). Programe su clase base form Form.ORCleanup para interactuar con cada uno de susmiembros, llamando su método ORCleanup.Si diseña el método ORCleanup de objetos contenedores (grids, pageframes, pages, optiongroups, containers, etc.) para interactuarcon cada uno de sus miembros de la misma forma que el ORCleanup a nivel de formulario solo hace un lazo entre su arreglo decontroles y llama al ORCleanup de cada uno de sus miembros directos.Dañar la referencia de objetos cuando se utilizan coleccionesVer la serie de ejemplos ORCleanup2.SCX_Screen.ActiveForm y qué se puede hacer con estoDurante el curso de creación del formulario hay un punto en el cual el formulario se convierte en _Screen.ActiveForm. Sabiendocuando esto ocurre nos permite poder hacer cosas muy buenas con la referencia de objeto _Screen.ActiveForm.Cuando THISFORM se convierte en _Screen.ActiveFormComo 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 madreDesafortunadamente, _Screen.ActiveForm no es siempre _Screen.ActiveForm. En muchas ocasiones donde el formulario activocontiene uno o más controles ActiveX, _Screen.ActiveForm puede ser una referencia de objeto a un control ActiveX, NO alformulario que lo contiene.La biblioteca en tiempo de ejecución X6SAF.PRG para el framework Visual MaxFrame Profesional está incluida en esta sesión ycontrola este caso. Para tener una referencia de objeto confiable _Screen.ActiveForm, sustituya el código por el siguiente:LOCAL loActiveFormloActiveForm = _Screen.ActiveFormIF TYPE("loActiveForm.BaseClass") = "C"* aquí no es el formulario activoELSE* m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activoENDIFlo que es el equivalente más fiable:LOCAL loActiveForm
  11. 11. loActiveForm = X6SAF()IF ISNULL(m.loActiveForm)* aquí no es el formulario activoELSE* m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activoENDIFGuardar una referencia al formulario y objeto llamadosAl combinar los conocimientos sobre la secuencia de eventos de creación de formulario LISAG y el conocimiento de cuandoTHISFORM 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 alformulario y objeto (si existe) llamados.Guardar una referencia al formulario llamadoCuando se ejecuta Form.Load, THISFORM no se ha instanciado todavía, y no es el formulario activo _Screen.ActiveForm. Másimportante, cualquier formulario (si existe) que se ejecuta cuando THISFORM se llama se refleja aun en _Screen.ActiveForm. Así, escomo un pestañeo a obtener una referencia de objeto "libre" al formulario que se está ejecutando cuando se llama THISFORM, y loguarda en una propiedad de usuario disponible durante la vida del THISFORM:LOCAL loActiveFormloActiveForm = X6SAF()IF VARTYPE(m.loActiveForm) = "O"THIS.oCallingForm = m.loActiveFormELSETHIS.oCallingForm = .NULL.ENDIFA 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 pasaruna referencia de objeto a si mismo a THISFORM.Init4. 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 referenciadel 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, guardauna 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 talcaso 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óntiene 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 quepuedas hacer.¡Abstraerlo!
  12. 12. El comportamiento que ve en Load, Destroy, y ORCleanup puede abstraerse simplemente en su formulario base clase. Todos losformularios que heredarán esta característica cada instancia lo utilicen o no.Guardar una referencia a un control del formulario llamadoAl guardar una referencia de objeto de un formulario existente es fácil. Pero, ¿qué tal si guardamos una referencia de objeto alcontrol actual en el formulario; pero sólo si este control está en la pila de ejecución del programa, indicando que es responsable porllamar 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 seencuentra 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 unmiembro de objeto, debe proporcionar para la limpieza de la referencia de objeto. El Load de _ScreenActiveForm1.SCX hace queBINDEVENT() enlaza el Destroy del formulario llamado con THISFORM.ORCleanup. Si _ScreenActiveForm1.SCX es modal, esta acciónno 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() enTHISFORM.Load Observe que la referencia de objeto del formulario llamado, si este control es realmente llamado por el formulariollamado, se guarda sólo en THISFORM.oCallingFormControl. Por ejemplo, si se ejecuta un formulario cuando se llama un segundoformulario 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 pilade 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 elformulario 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 deobjeto a un formulario llamado y su control llamado podría pasar al Init, en lugar de utilizar la referencia de objetoTHIS.oCallingFormControl guardada en Load. Sin embargo, esto requiere que el desarrollador que codifica la llamada al formulariorecordando 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. Estoes 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 losformularios heredan esta característica aunque la utilice o no la instancia indicada.Mantener referencias de objetos a los formularios llamadosEl ejemplo _SAF1.SCX/_ScreenActiveForm1.SCX descrito en la sección previa pudiera hacerle pensar sobre una técnica máspoderosa.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 formulariosno modales adicionales; pero además para cada uno de los formularios llamados para mantener las referencias de objeto a cadaotro 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 paradepurarlo.No es trivial, mantener todas las referencias de objeto en ambas direcciones, ni para asegurarse que esa limpieza de referencia deobjeto (garbage collection) fue hecha para prevenir una posibilidad de dañar la referencia de objeto.
  13. 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ódigonecesario es fácil de conceptuar. La serie de ejemplos FormORCleanupCaller*.SCX contienen el código. Todos los formularios en laserie FormORCleanupCaller*.SCX están basados en el ejemplo de la clase base formulario frmBaseForm en la bibliotecaFormORCleanup.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 enTHISFORM.oCallingForm y THISFORM.oCallingFormControl.4. Un BINDEVENT() es utilizado para asegurar que cuando se cierra el formulario llamado, esllamadoTHISFORM.ORCleanupCallingForm, donde las referencias de objeto se guardan en THISFORM.oCallingForm y sonliberados 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 llamadosu 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 propiedadcolección para trabajar adecuadamente).• Utilice un BINDEVENT() para asegurar que cuando se cierra el formulario llamado (el que está actualmente bajo creación), elmé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. Cadaformulario llamado puede tener una referencia de objeto a sólo un formulario invocador; pero cada formulario invocador puedemantener la referencia a un número ilimitado de formularios invocados. Más importante, es manipulada la necesaria limpieza dereferencia de objeto.Cada instancia de formulario hereda todo el comportamiento necesario, y el formulario o sus miembros puede simplementeTHISFORM., THISFORM.oCallingFormObject, y THISFORM.aCalledForms[] objetos en cualquier momento (por supuesto, después deverificar 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.SCX2. 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 deFormORCleanupCalled*.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.SCXEl ejemplo FormORCleanupCaller2.SCX es el mismo que FormORCleanupCaller1.SCX, excepto que cuando el formulario invocadores 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. 14. Hola invitado 15 Jun, 2013 - 01:02Menú principal• Inicio• Temas• Secciones• Descargas• Enlaces• Indice• Conferencias• MasFoxPro• Enviar noticia• Búsquedas• Usuarios• Preferencias• Top 10• P+F [FAQ]• RSSAnunciosLo que nunca te contó tu madre sobre instanciar y destruirformularios(7606 palabras totales en este texto)(22009 lecturas)Lo que nunca te contó tu madre sobre instanciar y destruir formulariosAutor: 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 ConferenciaDevEssentials Kansas, 2004)Nota aclaratoria de la traductoraEl 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ó sobreinstanciar y destruir Formularios"). Asimismo el autor autorizó la descarga de los ejemplos. Los ejemplos citados se pueden descargarde: DrewSpeedieDemo.zip (167 KB).ResumenEsta sesión intenta ayudar a entender mejor la secuencia normal de eventos en VFP cuando los formularios se instancian y sedestruyen ... existe mucho más los eventos Init y Destroy. Dotado de este conocimiento, puede depurar problemas e implementarbuenas 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 seaplican a todas las versiones de VFP; pero algunos de los ejemplos utilizan las funciones BINDEVENT()s y las características de la claseDataEnvironment 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/CLOSEALL, y deben ejecutarse desde la ventana de comandos. DEMO.APP es el único archivo incluido con la presentación. Una vez queselecciona los botones Run (Ejecutar) o Source (Fuente) desde la interfaz, los archivos con código fuente para ese ejemplo se copian aldisco, en la carpeta donde se encuentra DEMO.APP. A partir de ahí, puede ejecutar los ejemplos desde la ventana de comandos. Lamayorí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 lasecuencia nativa de los eventos.InstanciaciónComo 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() oabiertas)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 DataEnvironment5. Evento Form.DataEnvironment.Init6. Evento Init de cada miembro del formulario que es instanciado7. Evento Form.Init8. Evento Form.Show9. Evento Form.Activate10. Evento When del primer control del formulario en el orden de tabulación (tab order)11. Evento Form.GotFocus12. Evento GotFocus del primer control del formulario en el orden de tabulaciónDestrucciónComo se demuestra en LISAG_QRDU.SCX, aquí está la lista de eventos que ocurren durante la destrucción cuando el formulario escerrado 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.QueryUnload2. Evento Form.Destroy3. 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 proPá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. 15. © 2012 PortalFox5. Evento Form.DataEnvironment.CloseTables (Tablas/vistas en el Entorno de datos DataEnvironment no están en uso USE() oabiertas)6. Evento Form.DataEnvironment.Destroy7. Evento Destroy para cada cursor en el DataEnvironmentComo se demuestra en LISAG_QRDU.SCX, aquí está la lista de eventos que ocurren durante la destrucción cuando el formulario escerrado por una llamada a THISFORM.Release(), por ejemplo, al hacer Clic en un botón Aceptar1. Evento Form.Release2. Evento Form.Destroy3. 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() oabiertas)6. Evento Form.DataEnvironment.Destroy7. Evento Destroy para cada cursor en el DataEnvironmentInstanciación / Destrucción de miembros contenedoresComo demostrará posteriormente el ejemplo LISAG_QRDU2.SCX, los contenedores se instancian "de dentro hacia afuera" de igual formaque hace el propio formulario (debido a que también es un contenedor) ... El Init de los miembros contenidos de disparan antes que elInit del contenedor padre. En la destrucción ocurre lo contrario ... el Destroy del contenedor se dispara antes que el Destroy de susmiembros contenidos.Cuando el formulario tiene establecido valores para DEClass, DEClassLibraryComo muestra el ejemplo LISAG_QRDU_DE.SCX, cuando un formulario establece valores para DEClass, DEClassLibrary (disponibles apartir 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 madreLos ejemplos LISAG_QRDU*.SCX demuestran algunos aspectos de interés, muchos de los cuales pudiera no encontrar intuitivos:Form1. 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 elformulario. 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 ayudade VFP para ver la lista de estos comandos SET), decidir en que lugar va a asignar valores a estos comandos SET depende decómo abre sus datos. Si no utiliza nunca DataEnvironment es fácil: los comandos SET para la sesión privada de datos se colocanen el Load de la clase form. Si utiliza DataEnvironment nativo en un formulario basado en .SCX, tendrá que establecer estoscomandos SET en DataEnvironment.OpenTables/BeforeOpenTables, antes de que las tablas/cursores sean abiertos. Siimplementa 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. Veaademá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.LostFocusDataEnvironment1. 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 programaticamentelos datos si se utiliza AutoOpenTables = .F.3. El evento BeforeOpenTables no se dispara antes (Before) que el evento OpenTables, BeforeOpenTables está mal nombrado y sedebió 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.Load5. 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 unainstancia de usuario especificada en las propiedades DEClass/DEClassLibrary, en ese caso se instancia completamente antes deForm.Load.6. Consecuente con el comportamiento de OpenTables y cuando las tablas se abren explícitamente por el DataEnvironment, elmismo 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 deinstanciación / destrucción para el DataEnvironment depende de su implementación: DataEnvironment nativo para un formulariobasado en .SCX, DEClass DataEnvironment de usuario para un formulario basado en .SCX o .VCX.Llamar THISFORM.Metodos o consultar THISFORM.Propiedades desde el DataEnvironmentLos 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 madreDataEnvironment 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 formulariocomo tal. Ver los ejemplos LISAG_QRDU*.SCX, los que demuestran el su comportamiento nativo. Sin embargo1. LISAG_DE.SCX demuestra que las llamadas a métodos de formulario (nativos o de usuario), desde métodos del DataEnvironmentque se disparan antes del Form.Load son ¡COMPLETAMENTE IGNORADAS!. Por ejemplo, las llamadas a THISFORM.Metodos desdelos eventos de DataEnvironment OpenTables y BeforeOpenTables no hacen nada, no se invoca ningún método y no se genera unerror. Supongo que VFP no ha iniciado todavía la instanciación del formulario y por tanto, no reconoce THISFORM como unobjeto; 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. 16. 2. LISAG_DE2.SCX demuestra que las llamadas a métodos de formulario (nativos o de usuario), desde métodos delDataEnvironment que se disparan antes del Form.Load SÍ disparan esos métodos EN CASO que hayan sido definidos en la claseForm a partir de la cual hereda el formulario actual. Por supuesto, todos los métodos nativos de VFP heredan de la clase baseForm de VFP. Entonces, el código que se ejecuta es el heredado de la clase Form, NO cualquier código ubicado en los métodosnativos o de usuarios del formulario instanciado. Una vez que se dispara el Form.Load, la llamada a estos mismos métodosdisparan 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 deusuario) que se establece explícitamente en la ficha Propiedades a nivel del formulario instanciado, los valores son los que seanpredeterminados por VFP para esa propiedad (.F. para todas las propiedades de usuario), como si hubiera establecido laspropiedades como predeterminadas en la ficha propiedades. Sin embargo; para las propiedades establecidas en la ventanapropiedades de cualquier clase padre del formulario instanciado, el valor es evaluado adecuadamente, tal y como esperamos. Sinembargo, 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 seade la instancia actual o de alguna clase padre de la instancia actual del formulario.ConclusiónLa conclusión es que se puede abstraer del comportamiento de DataEnvironment para métodos de usuario de formulario, ya que estosmétodos están basados en formularios .SCX. Y debe recordar que el código que coloque en estos métodos en el nivel instanciadodel .SCX será completamente ignorado, cuando esos métodos son llamados desde eventos del DataEnvironment como OpenTables yBeforeOpenTables que se ejecutan antes que Form.Load. Para el nivel instanciado establecer propiedades es poco fiable hasta que no seejecute Form.Load.DataEnvironment con DEClass/DEClassLibrary de usuario para un formulario basado en .SCXCuando las propiedades DEClass y DEClassLibrary de un formulario basado en .SCX tienen asignado un valor que especifique un objetoDataEnvironment de usuario, las cosas son algo diferentes1. En tiempo de diseño, cuando da valor a las propiedades DEClass y DEClassLibrary asignando una clase DataEnvironment concó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 eseformulario, sólo el hecho de asignar valor a las propiedades DEClass y DEClassLibrary genera un error "Objeto no contenido en elformulario" ("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.SCX3. En tiempo de ejecución, como demuestra LISAG_DEClass.SCX, aquellas mismas llamadas a métodos de usuario de THISFORMque generaban un error cuando eran llamados desde el DataEnvironment.Init o desde el Init de uno de sus miembros, NO HACENNADA si son llamados desde los eventos DataEnvironment.OpenTables o BeforeOpenTables ... los métodos de usuario agregadosal .SCX en la instancia actual son completamente ignorados.4. En tiempo de ejecución, como demuestra LISAG_DEClass2.SCX, cuando ocurren eventos como OpenTables andBeforeOpenTables del DataEnvironment, que se disparan después del DataEnvironment.Init y el Init de sus miembros llaman amétodos de THISFORM, sólo se ejecuta el código para aquellos métodos que es heredado de la clase padre ... no el códigocolocado en los métodos del nivel actual instanciado.ConclusiónLa 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 llamadodesde eventos del DataEnvironment. Para confiar en los comportamientos abstractos del DataEnvironment, debe crear todo el código enla 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, estose debe hacer en DataEnvironment::Init y posiblemente también en el Init de su clase base Cursor. Para el nivel instanciado laconfiguració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 delarchivo Ayuda de VFP. Muchos de esos comandos SET afectan los datos y por tanto necesitan ser ejecutados para cada sesión privadade 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 TALKal _Screen o al formulario.Sin embargo, teniendo en cuenta las inconsistencias planteadas anteriormente en relación con el objeto DataEnvironment, ¿Cuál es elmejor 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 madreEso depende de si utiliza o no DataEnvironment y en ese caso, si utiliza el DataEnvironment nativo de VFP o una clase personalizadaDataEnvironment.No utiliza DataenvironmentPienso 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íaque establezca la propiedad DataSession a 2 -Private Data Session. Comience el Load de cada instancia de formulario con llamada haciaatrás (callback), para asegurarse que los comandos SET quedarán configurados desde el inicio:IF NOT DODEFAULT()RETURN .F.ENDIFDataEnvironment nativo en un formulario basado en .SCXEn 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 acualquier 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. 17. Recuerde, cualquier código que coloque en el método de usuario en el nivel instanciado no se ejecutará, como fue explicado en lasección anterior ... solamente se ejecuta el código colocado en la(s) clase(s) padre de la instancia actual.DataEnvironment de usuario con DEClass/DEClassLibraryColoque la configuración lógica de los comandos SET para su sesión privada de datos en el Init de la clase DataEnvironment. Seaconsciente de que pudiera tener algunos comandos SET como SET TALK OFF en el Init de sus clases base Cursor o Relation, ya queestos 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 escómo debe escribir ese código. Puede escribir un código estricto con los comandos SET deseados (como se demuestra enLISAG_PDS_SETs.SCX); pero el mejor enfoque pudiera ser tener la sesión privada de datos que lea los valores desde la sesiónpredeterminada de datos ... frecuentemente se utiliza la misma configuración de comandos SET, estos se configuran globalmente para lasesió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. CadaLISAG_PDS_SETs_Abstract*.PRG instancia un demo (objeto aplicación) "application object", que contiene un método de usuarioGetSETCommandSetting(). Como el objeto application es instanciado en la sesión predeterminada de datos #1, al llamar a su métodoGetSETCommandSetting() DEVUELVE la configuración especificada como lo establece la sesión predeterminada de datos. Como la sesiónprivada de datos instancia, pueden establecer sus comandos SET para que coincidan con aquellos establecidos en la sesiónpredeterminada de datos llamado al objeto application global. Aquí está el código esencial del método SetSETCommands de la clasefrmLISAG_PDS_SETs_Abstract en LISAG_PDS_SETs_Abstract.VCX:LOCAL laSETs[3], luSetting, lcStringlaSets[1] = "DELETED"laSets[2] = "MULTILOCKS"laSets[3] = "TALK"FOR EACH lcSet IN laSETsluSetting = goApplication.GetSetCommandSetting(m.lcSet)lcString = "SET " + m.lcSet + SPACE(1) + TRANSFORM(m.luSetting)&lcStringENDFORUtilice 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ñandoformularios 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 anchoaproximado (Width) (si el mapeo de campos (field mapping) tiene establecido que incluya el título del campo, se puede obtenerya la etiqueta correspondiente al Caption existente).• Establecer el ControlSource de cualquier control desde la ventana propiedades, seleccionando uno los cursores actuales desde elcuadro 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 queesté en el DataEnvironment en tiempo de ejecución. Sin embargo, para formularios con sesión privada de datos, cuando el formulario escerrado/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 deDesignTimeDE.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., interrumpirla instanciación de esa tabla/vista mientras continúa intacto el resto de la aplicación. Por el contrario, cuando DataEnvironmentencuentra 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 diferentesconjuntos 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 estedocumento, 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 alDataEnvironment (establecer propiedad Alias), allí donde están disponibles para conveniencia en tiempo de diseño y son ignoradas entiempo 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 tieneque 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 elDataEnvironment no está disponible en tiempo de diseño.Mucho cuidado al romper la secuencia nativa de eventos de instanciaciónExisten vías para romper la secuencia nativa de eventos de instanciación. Algunas veces las consecuencias son menos graves, en otrasson catastróficas y pueden causar todo tipo de comportamiento indeseado.Lo que tu madre nunca te dijoEl 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 tieneel 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 lafigura 7. Cuando el Init realiza el SetFocus, VFP tiene que activar inmediatamente el formulario y darle el foco, para que el botón OKPá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. 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 ycontinú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 elorden de tabulación. Lo que es más común es establecer el foco condicionalmente a un control en particular, basado en alguna accióndel formulario, basado a se vez, en un parámetro que reside en el Init. Simplemente he omitido esta condición en el ejemploLISAG_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 seejecutan 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óndel formulario (es posible hacerlo por programa - Show() de un formulario en cualquier momento y el Activate se ejecuta cada vez queel formulario se convierte en formulario activo, por ejemplo cuando el usuario hace clic en un formulario no modal abierto en el escritorioVFP) con este fin, agregue una propiedad de usuario (una bandera) con valor predeterminado a .T. , y lo hace igual a .F. en el Activate oGotFocus. 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á instanciandoENDIFAhora, si existe Member.SetFocus(), en el Form.Init la bandera se establece prematuramente en .F., antes de que el Init finalice y antesde que se ejecute el Show. Cualquier código en Form.Show que se debe ejecutar solamente durante la instanciación será ignoradoporque 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 lacondició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 instanciarun 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 unmiembro 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.UnloadDevolviendo .F. desde el Load o el Init de un formulario no se instancia, es evidente; peroLo que no te dijo tu madre1. 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 haceForm.Destroy. Debido a que los miembros contenidos en el formulario se instancian antes que el Form.Init, el Destroy de esosmiembros se dispara ya que están fuera de límites. (Since form members instantiate before the Form.Init, the Destroy of formmembers 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 Destroyen la clase base o en otras clases Form que están en la jerarquía del instanciado actualmente. Lo mismo se puede cumplir paraForm.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 limpiezadel Cleanup no se ejecuta si el Form.Load devuelve .F.!Ejecutar Form.Destroy consistentementeLos formularios LISAG_QRDU_AbortLoadInit1.SCX and LISAG_QRDU_AbortLoadInit2.SCX demuestran técnicas similares que puedeutilizar 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 TheLISAG_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étodollamado 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> paracontinuar 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 verificarloantes. Pero LISAG_QRDU_AbortLoadInit1.SCXUtiliza este comportamiento para determinar cómo debe ser invocado Form.Destroy.LISAG_QRDU_AbortLoadInit1.SCXLos eventos Destroy e Init de LISAG_QRDU_AbortLoadInit1.SCX contienen una instrucción IF .F., tal que si la cambia por IF .T., provocaque 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 cadaclase 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 quecualquier 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 llamarmanualmente desde Form.Load, es llamado el Form.UnLoad.LISAG_QRDU_AbortLoadInit2.SCXPá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. 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., provocaque 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 cadaclase 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 quecualquier 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 llamadomanualmente 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. Estosignifica que para cerrar/destruir un formulario, cualquier objeto externo que mantenga referencia a uno o más miembros debe liberarseexplí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 uncontenedor dentro de un formulario, el formulario no se libera. Esta es la causa del error "Referencia de objeto dañada" ("Danglingobject reference").ORCleanup1.PRG crea 2 instancias de ORCleanup1.SCX para demostrar el problema:1. DO ORCleanup12. En la instancia 2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia deobjeto 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 elmenú en la caja de control de la barra de título. La instancia 1 del formulario se niega a cerrarse, debido a referencias de objetodañ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íadisparado, solo estaba esperando a que se liberaran las referencias de objetos externos a sus miembros.Lo que no te dijo tu madrePara 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 puedeser inútil. Recuerde: el formulario destruye "de afuera hacia adentro", por tanto el Destroy de los miembros se dispara después delDestroy del formulario como tal. Cuando se daña la referencia de objetos existente, se dispara el Form.Destroy; pero la destrucciónfrena aquí, y no se dispara ningún otro evento, incluyendo el Destroy de sus miembros, hasta que se libera la referencia de objetodañada.Esto es un gran problema cuando diseña formularios de tal forma que un formulario no modal mantenga referencias de objetos a unmiembro 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 seniega a cerrar hasta que los miembros de Form1 liberan sus referencias de objeto (este es el escenario que se muestra enORCleanup1.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 secierra del todo, queda como un objeto artificial como una sesión de datos "desconocida" ("Unknown") visible en la ventana Sesiónde datos.Cuando objetos externos contienen referencias a miembros de THISFORMORCleanup1a.SCX demuestra una vía para solucionar el comportamiento necesario. Repita los pasos para ORCleanup1 y observe ladiferencia:1. DO ORCleanup1a.2. En la instancia2 del formulario, seleccione cualquier casilla de verificación del grupo inferior para guardar una referencia deobjeto 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 lalimpieza de referencia de objetos para liberar las referencias de la instancia 2 del formulario tiene a los objetos de la instancia 1del 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 delgrupo inferior. He utilizado la función BINDEVENT() para asegurar que cuando se guarda una referencia de objeto, el Destroy de losobjetos guardados del formulario llama automáticamente al código de limpieza de los objetos haciendo su almacenaje. Entonces, cuandoes cerrado el formulario cuyas referencias a objetos miembros han sido guardadas, su Destroy llama al código de limpieza de lareferencia de objetos de los objetos externos manteniendo la referencia de objetos. He aquí lo que ocurre:1. DO ORCleanup1a2. Haga Clic en la primera casilla de verificación del grupo inferior de la instancia 2 del formulario. El código del evento Clic guardauna referencia de objeto al miembro txtDemo1 en la instancia 1 del formulario a la propiedad de usuario oFormMember de lainstancia 2 del formulario. El código Clic también ejecuta BINDEVENT() para asegurarse de que cuando se cierra la instancia 1 delformulario 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 Cleanupde la segunda instancia del formulario, gracias al BINDEVENT(). Entre otras cosas, el método ORCleanup de la instancia 2 delformulario establece su propiedad oFormMember a .NULL., liberando la referencia de objeto guardada el txtDemo miembro de lainstancia 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 talforma que el Checkbox.Click haría BINDEVENT() a su propio método ORCleanup en lugar de THISFORM.ORCleanup, haciendo másgranular 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 externosComo se ha descrito antes, toda la limpieza a las referencias de objeto "garbage collection" debe hacerse antes de Form.Destroy, queocurre 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. 20. La solución es codificar el Form.Destroy con mensajes a sus miembros para establecer explícitamente todas las referencias de objetoguardadas 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 baseformulario 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 elForm.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 unmé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 concada uno de sus miembros de la misma forma que el ORCleanup a nivel de formulario solo hace un lazo entre su arreglo de controles yllama al ORCleanup de cada uno de sus miembros directos.Dañar la referencia de objetos cuando se utilizan coleccionesVer la serie de ejemplos ORCleanup2.SCX_Screen.ActiveForm y qué se puede hacer con estoDurante el curso de instanciación del formulario hay un punto en el cual el formulario se convierte en _Screen.ActiveForm. Sabiendocuando esto ocurre nos permite poder hacer cosas muy buenas con la referencia de objeto _Screen.ActiveForm.Cuando THISFORM se convierte en _Screen.ActiveFormComo se demuestra en el ejemplo _ScreenActiveForm.SCX, el formulario que se está instanciando se convierte en _Screen.ActiveForminmediatamente después de ser mostrados (Show())Lo que nunca te dijo tu madreDesafortunadamente, _Screen.ActiveForm no es siempre _Screen.ActiveForm. En muchas ocasiones donde el formulario activo contieneuno o más controles ActiveX, _Screen.ActiveForm puede ser una referencia de objeto a un control ActiveX, NO al formulario que locontiene.La biblioteca en tiempo de ejecución X6SAF.PRG para el framework Visual MaxFrame Profesional está incluida en esta sesión y controlaeste caso. Para tener una referencia de objeto confiable _Screen.ActiveForm, sustituya el código por el siguiente:LOCAL loActiveFormloActiveForm = _Screen.ActiveFormIF TYPE("loActiveForm.BaseClass") = "C"* aquí no es el formulario activoELSE* m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activoENDIFlo que es el equivalente más fiable:LOCAL loActiveFormloActiveForm = X6SAF()IF ISNULL(m.loActiveForm)* aquí no es el formulario activoELSE* m.loActiveForm contiene una referencia de objeto fiable al formulario actualmente activoENDIFGuardar una referencia al formulario y objeto llamadosAl combinar los conocimientos sobre la secuencia de eventos de instanciación de formulario LISAG y el conocimiento de cuandoTHISFORM 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 alformulario y objeto (si existe) llamados.Guardar una referencia al formulario llamadoCuando se ejecuta Form.Load, THISFORM no se ha instanciado todavía, y no es el formulario activo _Screen.ActiveForm. Másimportante, cualquier formulario (si existe) que se ejecuta cuando THISFORM se llama se refleja aun en _Screen.ActiveForm. Así, escomo un pestañeo a obtener una referencia de objeto "libre" al formulario que se está ejecutando cuando se llama THISFORM, y loguarda en una propiedad de usuario disponible durante la vida del THISFORM:LOCAL loActiveFormloActiveForm = X6SAF()IF VARTYPE(m.loActiveForm) = "O"THIS.oCallingForm = m.loActiveFormELSETHIS.oCallingForm = .NULL.ENDIFA 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 otraconfiguració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 unareferencia 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

×