28 de mayo de 2006

Obtener una referencia a un formulario padre

Artículo original: Getting a reference to a parent form
http://weblogs.foxite.com/andykramek/archive/2005/09/04/899.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York

Ocurre a veces que hay dos formularios que dependen en alguna medida uno de otro. Lo más frecuente es que un formulario sea llamado desde otro requiera pasarle múltiples valores o selección de criterios, que son definidos en su "padre". Hay varias posibles soluciones para estos casos; pero veamos una solución muy sencilla, que no es muy conocida, a pesar de que existe en VFP desde la versión 3.0.

Siempre que comienza VFP, ya sea en tiempo de desarrollo o desde sus librerías en tiempo de ejecución, se crea un objeto llamado _Screen, (basado en una clase form - intente escribir ?_Screen.Class desde la ventana de comandos) Vea que no importa si VFP está visible o no. El objeto se crea de todas formas y es un componente fundamental utilizado por el propio VFP.

Una de las propiedades más importantes de _Screen es "ActiveForm". Como implica su nombre, esta propiedad guarda una referencia al formulario activo actualmente y puede ser utilizada cuando escribimos código genérico que necesite acceder a cualquier formulario que esté activo.

Por ejemplo, suponga que tiene un procedimiento que puede ser llamado desde varios formularios diferentes. En ocasiones necesita pasar solamente uno o dos parámetros. Sin embargo, si el procedimiento necesita acceder a propiedades o métodos del formulario llamado, debe entonces darle la referencia al formulario, algo como esto:
DO myproc WITH ThisForm
Y en el procedimiento necesita un parámetro que se corresponda
FUNCTION myProc( toForm)
Esto no es un problema una vez que el procedimiento es llamado desde un formulario como tal (o un objeto que es miembro del formulario). Sin embargo, hay veces cuando un procedimiento necesita acceder a cualquier formulario que esté activo en ese momento, independientemente de si es o no el que ha sido llamado por el procedimiento. Por ejemplo, cuando existen múltiples instancias de un formulario, o debido a que el procedimiento como tal cambia el formulario activo. Este es un escenario donde _Screen.ActiveForm se utiliza debido a que no se tiene que preocupar sobre la instancia del formulario que es llamado y solamente hace referencia al formulario activo:
IF TYPE( "_Screen.ActiveForm" ) = "O"
  *** OK, tenemos realmente un formulario activo
  loForm = _Screen.ActiveForm
ELSE
  *** Ooops! ¿No debía existir un formulario activo?
ENDIF
Ahora puede que se pregunte de porqué he escrito esto. La respuesta es sencilla, si aquí no hubiera formulario activo y trata de acceder a la propiedad, VFP muestra el error 1924 ("ACTIVEFORM no es un objeto"). Esto no es deseable, por tanto es muy importante verificar que existe realmente un formulario antes de tratar de establecer una referencia al mismo. A propósito, vea que esta es también una de las ocasiones cuando no podemos emplear VARTYPE() porque si no hay formulario activo, tratando de acceder a un propiedad también devuelve el error 1924. En este caso TYPE() devolverá "U".

Todo esto es muy interesante; pero escucho que usted dice, ¿Cómo esto ayuda a tomar una referencia al formulario padre? La clave es reconociendo el momento en el cual _ScreenActiveForm se actualiza cuando se instancia un formulario.

La respuesta es que cuando un formulario llama a otro, el valor de _ScreenActiveForm no cambia hasta que el formulario nuevo se ha cargado por completo, sus métodos LOAD(), INIT() y SHOW(). Esto se demuestra al escribir el nombre del formulario activo (Screen.ActiveForm.Name) fuera de un archivo de texto al llamar a un formulario desde otro - he aquí el resultado:
In Parent Form, Active Form is currently [FrmParent]
*** Call child form here ***
STARTING Child Form LOAD, Active Form is currently [FrmParent]
STARTING Child Form INIT, Active Form is currently [FrmParent]
STARTING Child Form SHOW, Active Form is currently [FrmParent]
STARTING Child Form ACTIVATE, Active Form is currently [FrmChild]
*** Release Child Form here ***
STARTING Child Form DESTROY, Active Form is currently [FrmChild]
STARTING Child Form UNLOAD, Active Form is currently [FrmChild]
STARTING Parent Form ACTIVATE, Active Form is currently [FrmParent]
Si pensamos en esto, vemos que tiene sentido. Después de todo, si Load() o Init() devuelven False el formulario hijo no se va a instanciar debido a que _Screen.ActiveForm no ha cambiado al inicio del proceso, sino hasta que Show() no se realice. Una vez que Show() se ejecuta VFP puede estar seguro de que el formulario nuevo no se va a terminar de forma no normal y que el valor de _Screen.ActiveForm podrá ser guardado satisfactoriamente.

De forma análoga, una vez que se realice el Unload() del formulario hijo, el formulario será destruido definitivamente de tal forma el formulario anterior (si existe) se activa y la propiedad se modifica adecuadamente.

Entonces, ¿que es lo que significa esto para nosotros? Bueno, una cosa es que este formulario es llamado por otro formulario, no necesitamos pasar nada para obtener la referencia a su padre. Más importante, puede ser, debido a este comportamiento, que podamos realmente acceder a las propiedades, métodos y eventos del formulario padre desde el Load() del formulario hijo, el que ocurre antes de que sea instanciado cualquier control.

Esto es importante, porque si existen criterios para los controles del formulario hijo que dependen de un valor del formulario padre (típicamente cuando los valores dependen de alguna selección que se haya hecho en el formulario padre) es mucho mejor que controlarlo antes de que el control sea instanciado. Al pasar una referencia como parámetro no permite esto porque el parámetro se recibe en el Init() del formulario, el que sólo se produce después que todos los controles han sido instanciados.

La conclusión de todo esto es, que para proporcionar una referencia a un formulario padre podemos utilizar código genérico, en el evento Load() de una clase formulario. Todo esto es necesario para agregar una propiedad de usuario a un formulario (llámela 'oParentForm') y luego, agregue el siguiente código en el evento Load():
IF TYPE( "_Screen.ActiveForm" ) = "O"
  This.oParentForm = _Screen.ActiveForm
ELSE
  This.oParentForm = NULL
ENDIF 
Cualquier método u objeto, en el formulario, puede tener ahora una referencia al formulario padre verificando simplemente que la propiedad "oParentForm" no esté vacía. Lo otro que hay que hacer (y no es requerido en realidad, pero que yo considero es una buena práctica) es limpiar la referencia en el evento Destroy() - ¡ y esto es una línea !
ThisForm.oParentForm = NULL

Muy buena la idea de Andy. He realizado un ejemplo para probar y funciona muy bien.

Lo veo muy útil para quienes siempre necesitan saber de que formulario fue llamado otro.

Aquí el código de prueba:
PUBLIC ARRAY goForm(1)
goForm(1) = CREATEOBJECT("MiForm")
goForm(1).SHOW
RETURN

DEFINE CLASS MiForm AS FORM
  oParentForm = NULL

  ADD OBJECT cmdParent AS COMMANDBUTTON WITH ;
    HEIGHT=30, TOP=10, LEFT=10, ;
    NAME="cmdParent", CAPTION="¿Padre?"
  ADD OBJECT cmdDoForm AS COMMANDBUTTON WITH ;
    HEIGHT=30, TOP=50, LEFT=10, ;
    NAME="cmdDoForm", CAPTION="Do Form..."

  PROCEDURE LOAD
    IF TYPE( "_Screen.ActiveForm" ) = "O"
      THIS.oParentForm = _SCREEN.ACTIVEFORM
    ELSE
      THIS.oParentForm = NULL
    ENDIF
  ENDPROC

  PROCEDURE INIT
    THIS.CAPTION = SYS(2015)
    IF NOT ISNULL(THISFORM.oParentForm)
      THIS.TOP = THISFORM.oParentForm.TOP + 20
      THIS.LEFT = THISFORM.oParentForm.LEFT + 20
    ENDIF
  ENDPROC

  PROCEDURE DESTROY
    THIS.oParentForm = NULL
  ENDPROC

  PROCEDURE cmdParent.CLICK
    MESSAGEBOX(IIF(ISNULL(THISFORM.oParentForm), ;
      "Formulario sin padre", ;
      "Hijo de " + THISFORM.oParentForm.CAPTION))
  ENDPROC

  PROCEDURE cmdDoForm.CLICK
    LOCAL ln
    ln = ALEN(goForm) + 1
    DIMENSION goForm(ln)
    goForm(ln) = CREATEOBJECT("MiForm")
    goForm(ln).SHOW
  ENDPROC

ENDDEFINE

Luis María Guayán

1 comentario :

  1. Quizas no sea el lugar indicado, pero tengo la siguiente inquietud,
    estoy desarrollando un sistema en el cual defino los formularios como Clases, hasta ahi todo bien, tengo dos grupos de clases formularios, los primeros que administran la información (Maestros) y un segundo grupo que son ayuda, es decir, desde una clase formulario (admProveedores) llamo a otro formulario (ayuda Proveedores), este ultimo esta definido como MODAL, el problema lo tengo al volver donde cierra ambos formularios. en la clase Ayuda probe colocando thisForm.hide()/thisForm.release() de ambas formas produce el mismo efecto, cierra el formulario desde el cual es llamado.
    ambas clases bajo el diseñador de clases.

    ResponderEliminar