lunes, febrero 06, 2006

Mas de Xailer, paso de parámetros y recuperación de valores

Sigo avanzando con Xailer, ahora sí, cada vez mas rápido ( la documentacion ayuda bastante) y conforme voy avanzando me encuentro con nuevos retos de programación que son sorteados de una manera tan simple, que yo mismo me sorprendo de la manera en que se resuelven estos problemas.

Ahora estoy con el tema del paso de parámetros. Si haz programado en Xailer, te darás cuenta que casi no existen funciones, y que todo se debe realizar por medio una clase, o por un método de la misma. En general, todo lo que tiene que ver con formularios está relacionado con crear un objeto heredado de la clase TForm para luego mostrarlo usando el método ::Show() o ::ShowModal().

Partiendo de este principio, vamos a plantearnos la típica rutina de Altas y modificaciones de datos que lleva cualquier sistema. Básicamente estas 2 rutinas en realidad son la misma, utilizan la misma pantalla de captura, las mismas validaciones y los mismos procesos de reemplazo de datos (hablando de DBFs), y solo varían en 2 cosas: la primera es que cuando vamos a tratar un alta de datos en el formulario se tienen que inicializar las variables a sus valores vacíos o por default y cuando es una modificación, los valores tienen que ser inicializados a los valores existentes, la segunda cosa en al que varían es que un alta lleva un DBAPPEND(), RLOCK() y luego los REPLACE correspondientes, mientras que la rutina de modificación elimina el DBAPPEND() y el resto son las mismas instrucciones. Así pues, salvo un par de detalles, debería ser posible utilizar el mismo formulario para ambos casos.

En el caso de los lenguajes procedurales, y mas especificametne en FiveWin, como todo se maneja a base de funciones, es muy simple utilizar la misma rutina para una modificacion o un alta:


Function Clientes (lNuevo)
   IF lNuevo
      xVar := " "
      nVar := 0
      ...
   ELSE
      xVar := alias->campo
      nVar := alias->campo_num
      ...
   ENDIF
   DEFINE Dialog...... TITLE IIF(lNuevo, "altas de datos", "modificacion de datos")
   ACTIVATE Dialog ....
   IF lNuevo
      alias->(DbAppend())
   ENDIF
   alias->Rlock()
   alias->campo := xVAr
   alias->campo_num := nVar
   ...
Return


Así de fácil, el parametro "lNuevo" nos permite utilizar el mismo dialógo para 2 funciones distintas, pero ¿ qué pasa en Xailer ?, si cuando quieres mostrar un formulario tienes que hacer:

oFormulario():New(Owner):Show()

En el caso de Xailer, no se mandan llamar funciones para desplegar formularios, sino que estos son desplegados llamando directamente al método constructor y al método que los hace aparecer en pantalla.... luego entonces ¿ es posible parametrizar una clase ?.... la respuesta es SI.

Aprendamos la técnica.....

Cuando estamos creando un formulario en Xailer, es muy común que la defincion de la clase nos quede parecida a esto:


CLASS Clientes FROM TForm

   COMPONENT oGroupBox1
   COMPONENT oLabel3
   COMPONENT oCombobox1
   ...
   ...
   ...

   METHOD CreateForm()
   METHOD ConfigAltas( oSender )
   METHOD SetSubTipo( oSender, nIndex, nOldIndex )
   METHOD Salir( oSender )
   ...
   ...
   ...
ENDCLASS


Nosotros podemos agregar nuestras propias DATAS para utilizarlas dentro de la clase como nosotros queramos, asi pues, nuestra definicion de clase puede quedar asi:


CLASS AltasCat FROM TForm

   COMPONENT oGroupBox1
   COMPONENT oLabel3
   COMPONENT oCombobox1
   ...
   ...
   ...
    DATA lNuevo AS LOGICAL INIT .T.
    DATA aTipos AS ARRAY INIT {"Activo","Pasivo","Capital","Resultado","Orden"}
   ...
   ...
   ...

   METHOD CreateForm()
   METHOD ConfigAltas( oSender )
   METHOD SetSubTipo( oSender, nIndex, nOldIndex )
   ...
   ...
   ...
ENDCLASS


Hemos agreado 2 datas, una llamada lNuevo de tipo lógico y otra llamada aTipos de tipo Array, en general, al igual que en FW, nosotros podemos poner tantas datas como necesidades tengamos de las mismas, en este caso, la data lNuevo me va a servir a mi para indicar a la clase, cuando voy a dar un alta y cuando voy a hacer una modificación de datos.

Y ahora..... la magia..... ¿ como pasar el parámetro a la clase ?. Muy sencillo, utilizando la instrucción WITH OBJECT:


WITH OBJECT oClientes( )
   :lNuevo := .T.
   :New():ShowModal()
END


A simple vista y si vienes del mundo de los objetos de FiveWin, el código anterior nos puede presentar una par de aparentes contradicciones:

1ro. ¿ La instrucción WITH OBJECT está utilizando una llamada a una funcion oClientes() ?
2do. ¿ La DATA lNuevo esta siendo asignada ANTES de llamar al método constructor de la clase ?

Si y no son las respuestas, WITH OBJECT solo opera sobre objetos, con lo cual la llamada a oClientes() no es una llamada a una función, es una llamada al objeto genérico oClientes, que hereda de TForm, por esta simple llamada, ya tenemos acceso al objeto en cuestión.

Quizá esto no es lo mas contradictorio, lo mas contradictorio es que dentro del WITH OBJECT, la DATA lNuevo se inicializa ANTES de llamar al método constructor ::New() y digo que esto puede parecer contradictorio, porque en estricta teoría de objetos, las datas no existen hasta que el objeto es instanciado, sin embargo el poderoso motor de objetos de xHarbour, junto con Xailer, permiten hacer este pequeño truco, de esta manera , puedes mandar parámetros a tu clase, siempre y cuando crees datas y las asignes ANTES de llamar al método constructor.

Una vez inicializadas tus DATAS, el evento ON INITIALIZE puede hacerse cargo de ellas ya sea para inicializar los TextBox, o para cargarlos con los valores de la base de datos:


METHOD Config( oSender ) CLASS Clientes

   (ALI_CATTOD)->(ORDSETFOCUS("NUMCTA"))
   (ALI_CATTOD)->(DBGOTOP())
   IF ::lNuevo
      ::cText := "Registrar cuenta nueva"
      ::oCuenta:Value := "0000"
      ::oSubCuenta:Value := "0000"
      ...
      ...
      ...
   ELSE
      ::cText := "Modificar datos de la cuenta"
      ::oCuenta:Value := (ALIAS_CATALOGO)->cuenta
      ::oSubCuenta:Value := (ALIAS_CATALOGO)->subcuenta
      ...
      ...
      ...
   ENDIF

RETURN Nil


¿ Ves que fácil ?

Y ahora.... ¿ cómo hacemos lo contrario ?, es decir, como recuperar uno o varios valores de la clase.

Durante el desarrollo de mi aplicación, si hay alguna falla en la introducción de datos, aparece este formulario que me pide la acción a tomar:



Aquí el usuario debe de decidir entre 3 opciones posibles: Modificar, Consultar o Alta, pero luego yo tengo que saber cual de los 3 botones pulsó para tomar la acción adecuada.

Para ello, escribí esta pequeña función que además de mostrarme el formulario, me devuelve el valor del botón pulsado:


FUNCTION CatError(cCuenta)
   LOCAL nVret

   WITH OBJECT tCatError()
      :cCuenta := cCuenta
      :OnClose := {|oSender| nil }
      :New():ShowModal()
      nVret := :nOption
   END
RETURN (nVret)


La DATA ::nOption, se asigna en el evento OnClick del formulario, de hecho, la clase es sumamente simple, todo se resuelve directamente en métodos InLine:


CLASS TCatError FROM TForm

   COMPONENT oLabel1
   COMPONENT oBtnBmp1
   COMPONENT oBtnBmp2
   COMPONENT oBtnBmp3
   COMPONENT oLabel2
   COMPONENT oLabel3

   DATA cCuenta
   DATA nOption

   METHOD CreateForm()
   METHOD Configura( oSender )
   METHOD Modifica( oSender ) INLINE ::nOption := 1, ::Close()
   METHOD Consulta( oSender ) INLINE ::nOption := 2, ::Close()
   METHOD Alta( oSender ) INLINE ::nOption := 3, ::Close()

ENDCLASS


Nota como los eventos OnClick ::Modifica(), ::Consulta() y ::Alta(), directamente asignan la data ::nOption y Cierran el formulario usando el método ::Close()... y la magia ocurre aquí....

El hecho de cerrar el formulario, no implica que el objeto se destruya, simplemente deja de estar siendo mostrado, y sus datas permanecen vivas, con lo cual, podemos perfectamente obtener su valor luego de que el formulario deja de estar visible.

De esta forma, podemos recuperar mas de un parametro utilizando una clase.

Como verás, la programacion en Xailer tiene nuevos trucos que aprender, pero sumamente prácticos e interesantes.

No hay comentarios.: