26 agosto 2015

Creando nuestro propio escritorio Omnis

Por defecto, Omnis Studio presenta un escritorio con el fondo gris, es decir, el área de trabajo de la ventana principal es de color gris (al menos en la plataforma Windows). Sin embargo es posible personalizar dicho color de fondo o incluso insertar una imagen. El “truco” que exponemos a continuación, nos puede ayudar a hacer esto con facilidad.

Para lograrlo, empezaremos por añadir una nueva clase-ventana a la librería que estemos construyendo, su nombre será: “wBackground”. Una vez creada, ábrala en modo diseño y (por ejemplo) coloque una foto en ella mediante la opción del menú “Edición”, “Pegar desde archivo”. La imagen seleccionada deberá ser lo suficientemente grande, como para cubrir todo el fondo de la ventana o incluso mayor, la idea es asegurarse de que se siga viendo correctamente aún cuando se amplíe la ventana principal de Omnis, una imagen en formato JPG de 1280x1024 píxeles y a 72 dpi, puede ser suficiente, al final de éste artículo puede un ejemplo. Una vez hecho esto, deberemos modificar las siguientes propiedades de nuestra ventana “wBackground”:

$closebox = kFalse
$growbox = kFalse
$style = kSimple


Además de éstas deberemos cambiar la propiedad “$edgefloat” para fijar su valor a “kEFrightBottom” de éste modo nos aseguraremos de que la imagen en la ventana será automáticamente ajustada, cada vez que cambie su tamaño por acción del usuario. Ahora, y en el método “$construct()” de la ventana “wBackground” deberemos añadir el siguiente código:

Do $cinst.$width.$assign($root.$modes.$width)
Do $cinst.$height.$assign($root.$modes.$height)
Do $cinst.$top.$assign(0)
Do $cinst.$left.$assign(0)


A continuación, añadiremos una segunda clase-ventana con el nombre “wSuper”, a la cual añadiremos un método “$event()”con el código siguiente:

On evWindowClick

   If pClickedWindow.$name='wBackground'
     Quit event handler (Discard event)
   End If


Este código nos permitirá asegurarnos de que la ventana “wBackground” permanezca en todo momento anclada al fondo.

Ahora tan sólo nos faltará asignar la ventana “wSuper”, como superclase en todas y cada una de las clases-ventana que contenga nuestra librería. Podemos optar por hacer esto manualmente, cambiando la propiedad “$superclase” para asignarle el valor “wSuper” o bien (mi preferida) mediante el siguiente comando:

Do $windows.$sendall($Ref.$superclass.$assign('wSuper'))

Finalmente deberemos  añadir el código necesario para hacer visible la ventana “wBackground”, lo cal haremos escribiendo el comando mostrado a continuación, sobre el método “$construct” de la clase “Startup_Task” de nuestra librería en construcción:

Do $windows.wBackground.$openonce()


18 agosto 2015

Polimorfismo en Omnis

Una ventaja de usar encapsulación en objetos con Omnis, es la posibilidad de que éstos puedan reaccionar de manera diferente ante un mismo mensaje. Veamos un ejemplo. Supongamos que disponemos de tres ventanas diferentes con información relativa a la actividad de nuestros clientes y una cuarta ventana con la lista con todos ellos, la idea es que tras pulsar sobre la línea de un cliente cualquiera, pueda reflejarse la información pertinente sobre las restantes tres ventanas. Este evento deberá enviar un mensaje a las otras ventanas con la información del cliente seleccionado, por su parte, cada una de ellas deberán reaccionar de manera diferente: La primera deberá mostrar la dirección del cliente (Address list) y otros detalles, la segunda (Invoices) deberá mostrar todas sus facturas, mientras que la tercera (Delivery notes) deberá mostrar una lista con todos los albaranes entregados.


Lo mejor que podemos hacer para enviar el mensaje a todas las instancias-ventana, (en nuestro caso a todos los objetos del cliente), es empezar primero por construir un método público para cada ventana, el cual será receptor de nuestro mensaje. Para evitar que se nos pueda olvidar alguna de las ventanas implicadas, podríamos optar por crear un clase común con un método público denominado “$setCustomer”, a fin de que sea la superclase de todas las ventanas implicadas. El método no contendrá código alguno, excepto el comentario sobre que éste puede ser anulado (desheredado). Después, (en cada subclase ventana) podremos observar éste mismo método mostrado en color azul y que por tanto podremos “desheredar”. El método deberá contener el parámetro “pCustID”, éste nos permitirá (en función de la ventana de que se trate) cargar los datos generales, datos de facturas y albaranes localizados en la base de datos, según en el identificador (pCustID) pasado a la ventana.


Ahora sólo nos falta saber, cómo enviar el mensaje desde la ventana con la lista de clientes, hacia las otras instancias-ventana. Para ello utilizaremos el método “$sendall” que (como sabemos) permite enviar un mensaje a todos los miembros de un grupo. En nuestro caso, lo escribiremos dentro del método “$event()” correspondiente a la lista de clientes (Customer list):

On evClick
    Do $iwindows.$sendall($ref.$setCustomer(ivCustomerList.ID))

Tras un clic sobre la lista de clientes, se enviará un mensaje a todas las instancias-ventana abiertas, para que sea ejecutado el método “$setCustomer()”, pasándose el ID del cliente seleccionado.

Tenga en cuenta que Omnis podrá mostrar un mensaje de error, en el caso de que alguna de las ventanas abiertas no contenga el método “$setCustomer”. Pero no hay que alarmarse, pues existen varias posibilidades para cambiar este comportamiento. Una modo sencillo, sería ir a las “Prefs” (Preferencias) de la biblioteca y cambiar su propiedad “$reportnotationerror” por “kFalse”.

Otra posibilidad, es enviar el mensaje sólo a los objetos que contengan el método “$setCustomer”. Para ello, deberemos añadir un parámetro adicional al “$sendall” el cual actuará a modo de filtro, enviando el mensaje sólo a las instancias que lo contengan:

On evClick
   Do $iwindows.$sendall($ref.$setCustomer(ivCustomerList.ID),
      $ref.$methods.//$setCustomer//)

Las dos barras invertidas dobles, permite indicar a Omnis, cual es el nombre del método que buscamos.

12 agosto 2015

Objetos externos (External objects)

Los objetos externos pueden ser manejados mediante la creación de un tipo especial de componente referenciado como “no visual”. Todos ellos se encuentran ubicados dentro de la carpeta Xcomp que (a su vez) podemos encontrar dentro del directorio raíz de instalación de Omnis Studio. Pero no todos los Xcomp pueden ser utilizados de éste modo. De hecho, la mayoría de los Xcomp sólo contienen objetos “visuales”, los cuales podemos colocar en ventanas, formularios e informes, pero, incluso algunos de éstos también contienen algún componente “no visual” que podemos utilizar como clase base, en la construcción de la variable objeto (variable de tipo “”Object”) o bien como superclase de una clase de tipo objeto (clase de tipo “Object”).

Existen relativamente pocos de estos Xcomps especiales. Una manera de descubrirlos es navegar a través de la lista de componentes externos disponibles, mediante abrir el enlace correspondiente desde el Explorador de Omnis Studio. Tal y como se muestra el siguiente imagen:



Explorando los componentes externos


Cómo podrá comprobar existe un gran número de componentes externos. Pero sólo el hecho de su aparición, no significa que esté siendo usado por Omnis Studio, ya que primero deberán ser cargados en memoria. La forma más sencilla de hacerlo es mediante indicar su estado de precarga, tal como se puede observar a continuación:



En éste artículo nos centraremos en los componentes recogidos bajo el grupo “External Components”. Si lo ampliamos podremos ver algo similar a los siguiente:



Podríamos mantener componentes alojados en cualquier parte de nuestro sistema, pero lo mejor es agruparlos en la carpeta Xcomp.

Los componentes actualmente cargados se muestran en la lista con un punto verde y los que no, con un punto rojo. Podemos configurar cualquiera de ellos para que sea cargado al iniciarse Omnis o bien al abrirse la librería en uso, mediante hacer clic en el botón de la opción correspondiente. Tenga en cuenta que el color del punto no cambiará inmediatamente, ya que los cambios no se verán reflejados hasta la próxima vez que abramos ésta ventana de configuración. Pero si podremos hacer uso de los cambios introducidos sin necesidad de reiniciar Omnis Studio, no cerrar y volver a abrir nuestra librería.

Los elementos se muestran bajo la denominación “libraries”, y es una denominación apropiada, pues a medida que vamos recorriendo los diferentes elementos de la lista, enseguida nos damos cuenta de que muchos de ellos contienen a su vez varios componentes. Mostrados a la derecha, podemos ver los nombres de dichos componentes, junto con iconos representativos del tipo al que pertenecen. Éstos pueden ser: Ventana, Informe y Objeto. Los componentes “Remote Form”” están localizados en la lista bajo un grupo diferente denominado “Form External Components”, pero en este artículo, deseamos centrarnos en el uso de los componentes de tipo Objeto (Object), son los mostrados mediante un icono en forma de caja y con un enchufe saliendo de ella.




En el dibujo anterior, podemos ver los controles que integran el componente “Graph2”. Observe la existencia de tres controles, uno de tipo ventana, otro de tipo informe y otro de tipo objeto.

Una vez que el componente externo ha sido cargado por Omnis, podrá ser usado con diversos fines dentro de nuestra librería. Un modo de hacerlo, es mediante localizar cualquier componente con interfaz gráfica de usuario, desde el “Component Store” y arrastrarlo sobre la posición que deseemos durante el diseño de una clase apropiada para su uso. Pero cuando se trata de componentes de tipo objeto “no visuales” su modo de uso es diferente.

 

Uso de un objeto externo (External Object)


Existen dos formas básicas de uso. 1) Seleccionar el componente de tipo objeto, como subtipo en una variable de tipo objeto o especificarlo más adelante mediante el método “$new()”. 2) Disponer el componente de tipo objeto, bajo el atributo superclase (superclass) en una “Object Class”. Pueden existir diferentes razones por las que preferir una u otra técnica.

Por el momento, vamos a utilizar la segunda técnica, ya que nos da la oportunidad de explorar fácilmente las propiedades y métodos del objeto. De modo que vamos a crear una nueva “Object Class” con el nombre “timerObject”, después y mediante el “Property Manager”, vamos a asignarle el xcomp “Timer” como superclase de nuestra recién creada “Object Class”. Tras hacer clic sobre el valor para ésta propiedad, aparecerá el diálogo que nos permite realizar su elección de entre la lista de componentes. Observe que sólo se muestran los xcomps de tipo objeto:




Ahora podemos abrir la clase “timerObject” y ver su contenido. Observe que la “Object Class” ha heredado los métodos y propiedades del componente externo. Tras abrir la clase, podemos visualizar dichos métodos en el editor:



Los elementos heredados aparecen (por defecto) en azul. (ver $root.$prefs): 



La “Inheritance”  (herencia) es una característica de la programación orientación a objetos soportada por Omnis Studio. Cada vez que se cree una instancia de nuestro objeto, recibirá los métodos y propiedades de la superclase.

Puede darse el caso de que queramos sustituir un método heredado, por otro nuestro, pero manteniendo su actual nombre, con el único fin de sustituir su código o acción por defecto. Puede que incluso tangamos imperiosas razones para hacerlo así en éste caso, ya que el método denominado “$timer” es el método invocado cuando el cronómetro (en su cuenta atrás) llega a cero. Tras su herencia, está vacío, por lo que debemos hacer un “overriding”, a fin de anular su acción por defecto. A continuación mostramos como hacerlo:




En éste caso el método se hereda vacío, pero también puede suceder que deseemos reemplazar un método que realmente hace algo. Por ejemplo, tal vez necesitemos añadir un par de pasos adicionales al método “$resettimer” a fin de reiniciar otras variables. Tenga en cuenta que aunque hayamos anulado y sustituido el método por el nuestro propio, podemos seguir invocándolo para su ejecución desde cualquier punto del método mediante el comando “Do inherited”, tal y como mostramos a continuación:



El editor de métodos nos permite ver los nombres de los métodos heredados, pero el “Interface Manager” nos permite ver un poco más. Accedemos a la “Interface Manager” desde el menú “View” del editor de métodos. En la siguiente imagen, puede observar como es posible ver los métodos y las propiedades, así como información adicional de ayuda:



Desde aquí, podemos examinar los parámetros disponibles para el método seleccionado y leer su descripción. En el caso de métodos no heredados o anulados, podemos optar por incluir nuestras propias descripciones. La pestaña de propiedades nos permite leer la descripción de la propiedad seleccionada. A continuación veremos un segundo modo de hacer uso de un objeto externo.

Consiste en simplemente heredar (inherit) la funcionalidad del Xcomp directamente sobre una variable de tipo objeto. La forma más sencilla de hacerlo es mediante indicar la xcomp a utilizar, reflejándola como subtipo de la variable de objeto, desde el panel de definición de variables.




Una variable “Object” definida de esta forma, hereda todos los métodos y propiedades que vimos anteriormente para la “Object Class”.

Si deseamos examinar sus métodos y propiedades con mayor detalle, puede optar por abrir el “Interface Manager” desde el menú contextual de la propia variable:




Si lo preferimos, podemos hacer uso del método “$new()” para crear una instancia de la variable objeto, indicando al mismo tiempo el xcomp a utilizar. Dado que la mayoría de éstos xcomps no contienen un método “$construct”, no hay mucha razón para hacerlo, pero ciertamente está permitido. El modo sería la siguiente:


Tenga en cuenta que debemos indicar el nombre de la librería de componentes, seguido de “$objects”, con el fin de poder localizar el nombre del elemento de entre los tres, que la componen.

Por último, indicar también que podríamos hacer uso de una variable de tipo “Object reference” en lugar de una variable “Object”. Recuerde que esta variable (o puntero) deben ser usadas mediante  el método “$newref()” en lugar de “$new()”, tal y como mostramos a continuación:


La única desventaja de esto, es que no se nos permite utilizar el “Interface Manager” para ver los métodos y propiedades del objeto mientras trabajamos.

Gracias por su atención.

05 agosto 2015

Gestión de sub-formularios dentro de un mismo panel o “Paged Pane”.

En éste artículo queremos destacar una, de las muchas cosas que nos ha traído la versión 6 de Omnis, se trata de la posibilidad de gestionar un grupo de subformularios dentro de un mismo contenedor, creando un efecto de paneles plegables al estilo de lo que sucede con el menú de un “Paged Pane”. Los diferentes subformularios quedan dispuestos verticalmente pudiendo ser expandidos o contraídos, haciendo clic/tocando sobre su barra de título o bien sobre el icono dispuesto al efecto


Configuración de los paneles


Para crear el grupo de paneles tendremos que hacer uso del método “clientcommand”  denominado “subformset_add”, junto con algunos nuevos parámetros, cuyas constantes podremos encontrar bajo el grupo ”Subform sets” y con el epígrafe “kSFSflag...”. El comando “subformset_add” creará el conjunto de subformularios dentro de la instancia “remote form” actual, tal y como si se tratase de un “Paged Pane”.

Do $cinst.$clientcommand("subformset_add",row-variable)

La variable de tipo “row” es definida como: row(grupo,padre,flags,orden,lista). A continuación describimos las principales banderas o “flags” que pueden ser usadas con el comando “subformset_add”.

kSFSflagOpenMin

Éste “flag” o bandera causa que el grupo de paneles se abra en estado minimizado. Normalmente, todos los subformularios se abren en estado de no-minimizado, de modo que deberemos hacer uso de ésta bandera para evitar su comportamiento predeterminado.

kSFSflagMinAsTitle

Cuando se minimiza un panel (subformulario) del grupo, se mostrará sólo la barra de título. Esta bandera anula su comportamiento predeterminado y que es ocultar completamente (cerrar) el subformulario. Para añadir el botón de minimizar a cada subformulario, deberá usarse la bandera “kSFSflagMinButton”, lo cual permite al usuario expandir o contraer el panel mediante su pulsación y no sólo mediante hacer clic en su título.

kSFSflagAutoLayout

Dispone automáticamente los paneles (subformularios del grupo) verticalmente dentro de su matriz, ignorando su posiciones originales “left” y “top”. Se activa la bandera  “kSFSflagMinAsTitle” y se desactivan “kSFSflagResize” y “kSFSflagMaxButton”. Si además activamos “kSFSflagAutoLayout”, el usuario podrá arrastrar y soltar la barra de título de los diferentes paneles del grupo, para reordenarlos, colocándolos en el orden que prefiera.

kSFSflagParentWidth

Sólo es aplicable si antes se ha activado “kSFSflagAutoLayout”. Su posición original “width” (ancho) será ignorada, tomándose en su lugar el ancho del subformulario “padre” para todo el grupo. También fija el “edgefloat” de cada subformulario a “kEFright”. De modo que, el uso de “kSFSflagParentWidth” nos permite, la creación de un “Paged Pane” compuesto a modo de varios paneles creados a partir de un grupo de subformularios, que se redimensionan o ajustan automáticamente dentro del panel que los contiene.

kSFSflagSingleOpen

Sólo es aplicable si antes se ha activado “kSFSflagAutoLayout” y “kSFSflagMinButton”. Permite asegura que al menos una ventana o panel esté siempre abierto.

 

Un ejemplo vale más que mil palabras


La imagen que mostramos al inicio de éste artículo, muestra un conjunto de paneles o grupo de subformularios contenidos en un “Paged Pane”, con las opciones “Auto Layout” y “Parent Width” activadas.

El método que mostramos a continuación, construye el grupo de subformularios asignándolo a un “Paged Pane” colcado a su vez dentro de un “remote form”. En el ejemplo, el subformulario tan solo contiene un campo de edición, con un pequeño texto ("This is panel #"). La lista o grupo de subformularios es construida incluyendo dicho texto, además del color de fondo, todo dentro de la variable “row” indicada para en el método “clientcommand” y su opción “subformset_add”.

; defina las variables para “lFormList”, “lSetRow” y para todas sus columnas
; construya el grupo de subformularios sobre “lFormList”
Do lFormList.$define(lFormID,lClassName,lFormParams,lFormTitle,lFormLeft,lFormTop,lFormWidth,lFormHeight)
Do lFormList.$add(1,'jsSubformSetPanelsSubForm',con(1,chr(44),rgb(221,221,255),chr(44),chr(34),"This is panel 1",chr(34)),'Panel 1',,,,160)
Do lFormList.$add(2,'jsSubformSetPanelsSubForm',con(2,chr(44),rgb(204,204,255),chr(44),chr(34),"This is panel 2",chr(34)),'Panel 2',,,,160)
Do lFormList.$add(3,'jsSubformSetPanelsSubForm',con(3,chr(44),rgb(187,187,255),chr(44),chr(34),"This is panel 3",chr(34)),'Panel 3',,,,160)
Do lFormList.$add(4,'jsSubformSetPanelsSubForm',con(4,chr(44),rgb(170,170,255),chr(44),chr(34),"This is panel 4",chr(34)),'Panel 4',,,,160)
; defina la variable “row” para el comando “subformset_add” como “lSetRow”
Do lSetRow.$define(lSetName,lParent,lFlags,lOrderVar,lFormList)
Do lSetRow.$assigncols("SubformPanelsSet",'PagedPane:1',kSFSflagSingleOpen+kSFSflagMinButton+kSFSflagAutoLayout+kSFSflagParentWidth,'iOpenForms',lFormList)
Do $cinst.$clientcommand("subformset_add",lSetRow)

En este caso las banderas “kSFSflagSingleOpen”, “kSFSflagMinButton”, “kSFSflagAutoLayout” y “kSFSflagParentWidth” son sumadas para dotar al grupo en construcción de todas las propiedades simultáneamente.