FoxPress – Febrero 2003

 

Flexibilidad al Máximo: Orientación al dato y aplicaciones basadas en Scripts

         

 

                              Por Doug Hennig

                  http://www.stonefield.com                                                                DataDrivingAppsFiles.zip

 

 

Como desarrollador de VFP estás acostumbrado a guardar los datos de las aplicaciones en tablas. Sin embargo, otro uso de las tablas es guardar información sobre el comportamiento de la aplicación. Esta sesión se centrará en la forma cómo el data-driving pueden hacer que tu aplicación sea más flexible y más mantenible. Además haremos que tu aplicación pueda ser scriptable de forma que los usuarios avanzados puedan provechas estas opciones.

Introducción

   Rick Hodder escribió un interesante artículo en el FoxTalk de Diciembre de 2001 llamado “Subclassing Existing Applications”.  En ese artículo discutió la forma de crear variantes de aplicaciones existentes. Una de las situaciones en las que esto es útil es cuando tienes una aplicación que has creado para un cliente y que debería usar otro cliente pero con unos pequeños cambios. O quizás tienes un producto de software que vendes a muchos clientes pero uno de ellos necesita una versión personalizada. He usado esta tecnica en el pasado para crear variantes de aplicaciones por una variedad de clientes.

Sin embargo, yo ya no uso más esta forma de trabajar y hay varias razones para actuar así:

·   Tienes que crear un nuevo directorio y un nuevo archivo de proyectos, y copiar un montón de archivos en el nuevo directorio. A continuación cambiar algunos de los archivos. Esto lleva a tener un montón de archivos duplicados en tu disco duro.

·   Conseguir mantener esto puede ser dramático cuando tienes que rehacer el código en las diferentes versiones.

·   Tienes que crear un nuevo conjunto para distribuir  el nuevo proyecto.

·   Si tienes muchos clientes  con variantes en las aplicaciones, tendrás muchs directorios y archivos que manipular.

   En su lugar, prefiero usar data-driven y aplicaciones con scripts. Una aplicación es data-driven si las partes de ella que difieren en las distintas versiones se leen de datos en vez de tomarla de código puro y duro. Es de tipo script si parte de el código existe en recursos externos que pueden ser cambiados más que metidos dentro de un .EXE. Los beneficios de usar este sistema son:

·   No tienes que ir a través de todos los pasos de las operaciones de crear un nuevo proyecto, copiar archivos, generar un nuevo EXE, y así en adelante. En su lugar,  simplemente crea un nuevo conjunto de archivos de configuración.

·   Cuando saques una nueva versión de la aplicación, no tienes que hacer un nuevo SETUP.EXE, unicamente los nuevos archivos de configuración.

·   La Instalación de cambios menores pues ser también bastante fácil: no hay necesidad de hechar a todos de la aplicación sino que basta con cambiar los archivos de configuración.

·   Hay unicamente un pequeño conjunto de archivos a manejar para cada variación de la aplicación.

·   Si les facilitas esta posibilidad,  tus clientes podrán hacer cambios en los archivos de configuración para cambiar el comportamiento de la aplicación. Scripting es especialmente útil para esto, ya que permite al usuario personalizar la aplicación a sus necesidades.

   Este documento discute algunas de las formas en el que el data-drive y los scripts se pueden usar en las aplicaciones para hacer la aplicación más flexible y fácil de usar.

 

     Aplicaciones Data-Driving

 

Crear código data-driven es similar a crear código orientado a objeto ya que requiere que examines las areas que necesitas para una operación particular y las abstraigas. En lugar de subclasear para implementar determinado comportamiento, puedes crear registros en una tabla.

 

Miraremos en varias areas donde puedes centrar el data-drive de tus aplicaciones  para hacerlas más flexibles y mantenibles: menus, configuración de la aplicación, y diálogos. Pero antes de nada, discutamos algunas cuestiones.

 

      Cuestiones con el Data-Driven

 

Hay un par de cosas a considerar antes de empezar a realizar aplicaciones data-driving en tus aplicaciones.

 

·   Rendimiento: Tipicamente, el código data-driven necesita macro expansión o funciones como EVALUATE(), TEXTMERGE(), y EXECSCRIPT() (la adición de estas dos últimas funciones en VFP 7 hace mucho más fácil crear apicaciones data-driven y aplicaciones basadas en scripts). Mientras estas aplicaciones son muy flexibles, en la realidad corren más lentas que las instrucciones normales. Muchos de los temas que miraremos respecto del data-driven se usará en los procesos de inicio de las aplicacioines, donde el rendimiento es un tema muy importante.  Asegúrate de que optimizas el código data-driven que corre en un bucle (loop) o en cualquier otra parte sensible de tu aplicación. También, usa técnicas data-driven donde tenga sentido, no sólo porque puedas hacerlo. Por ejemplo, podrías orientar al datos todos tus formularios en tiempo de ejecución, un formulario vacío llenado con controles que mira una tabla de la que toma todos sus controles y propiedades (clase que se debe usar, Top, Left, Width, ControlSource, etc.) y mediante AddObject los añades a tu formulario. Esta forma de funcionar toma sentido para formularios  que necesian diferentes aspectos según los diferentes clientes o donde el usuario pueda personalizar el formulario, pero esto es algo raro, y estos formularios definitivamente se instanciarán mucho más lentamente que sus hermanos hechos a puro código.

·  Seguridad: si tus usuarios son como los míos, seguro que algunos de ellos cumplen el lema de que “el conocimiento es peligroso.” Si entregas una tabla de la aplicación con opciones de configuración corres el riesgo de que alguno borre la tabla o haga cambios inusuales de forma inusual. Necesitarás manejar situaciones como cuando la tabla se pierda o está dañada, contenga valores no correctos, etc... Una opción de recuperar valores por defecto parece algo bueno. Podrías también considerar el hecho de hacer la tabla innaccesible fuera de la aplicación, o incluso encriptar la tabla.

 

      Menues orientados al dato

 

   Uno de los últimos bastiones del código procedural en VFP son el sistema de menues de VFP. Aunque VFP viene con un generador de menues de forma que no tengas que codificarlo, el código generado es estático. Eso significa que los menues no pueden ser reusados, ni pueden ser facilmente cambiados programaticamente en tiempo de ejecución (tal y como cambian las condiciones en la aplicación). Los desarrolladores de VFP han pedido desde hace tiempo por un sistema de menues orientado a objeto. Afortunadamente es posible crear por ti mismo un menú orientado a objeto; yo publicqué un artículo y código fuente sobre esto en el número de Agosto de 2001 de FoxTalk. Debido a las limitaciones de espacio, y debido a que no son el objeto de este documento, no discutiré como se puede implementar esto; siéntete libre de examinar el código fuente incluido con este documento o leete para más detalles el articulo original.

 

   Aunque puedes crear subclases de mi menú o usar código procedural para crear un específico sistema de menues desde esas clases, por qué no usar un menú orientado al dato (data-drive)? Eso hace que el menú se pueda cambiar de una forma mucho más fácil para una variación en la aplicación: simplemente cambia el contenido de la tabla que contiene la información del menú y el menú de la aplicación cambiará.

 

Tabla 1 muestra la estructura de una tabla de menú, que contiene la definición del menú de una aplicación.

Field

Type

Description

RecType

C(1)

The type of menu object to create: “P” for pad or “B” for bar.

Active

L

.T. if this record should be used.

Order

I

The order in which the menu objects should be added to the menu.

Name

C(30)

The name to assign to the menu object (such as “FilePad”).

Parent

C(30)

The name of the parent for this object (for example, the “FileOpen” bar may specify “FilePad” as its parent).

Class

C(30)

The class to instantiate the menu object from; leave this blank to use SFPad for pad objects and SFBar for bar objects.

Library

C(30)

The library containing the class specified in Class; leave this blank to use a class in SFMenu.vcx.

Caption

C(30)

The caption for the menu item; put expressions in text merge delimiters (for example, “Invoicing for <<oApp.cCustomerName>>”).

Key

C(10)

The hot key for the menu item.

KeyText

C(10)

The text to show in the menu for the hot key.

StatusBar

M

The status bar text for the menu item; put expressions in text merge delimiters.

Command

M

The command to execute when the menu item is selected; put expressions in text merge delimiters.

Clauses

M

Additional menu clauses to use; put expressions in text merge delimiters.

PictFile

M

The name and path of an image file to use; put expressions in text merge delimiters.

PictReso

M

The name of a VFP system bar whose image should be used for this bar; put expressions in text merge delimiters.

SkipFor

M

The SKIP FOR expression for the menu item; put expressions in text merge delimiters.

Visible

M

An expression that will determine if the menu item is visible or not; leave it empty to always have the item visible.

Tabla 1. La estructura de la tabla del menú.

 

Esta tabla se usa por CreateMenu.prg para crear el menú de una aplicación. Este PRG espera que se le pase el nombre y el path de la tabla del menú y el nombre o la variable o propiedad que Administrará la referencia al objeto para el menú; mira el artículo sobre menues orientados a objeto  para ver como se puede hacer esto. CreateMenu instancia un item SFMenu, a continuación se abre la tabla especificada usando el tag Order (que ordena la tabla de forma que los pads vengan primero y los bars a continuación, y por el orden de campo). A continuación procesa la tabla de forma que los items activos sean añadidos al menú (pads en el caso de registros “P”, bars dentro de su correspondiente pad en el caso de registros “B”) con los atributos especificados en la tabla.

 

lparameters tcMenuTable, ;

  tcMenuObject

local loMenu, ;

  lnSelect, ;

  lcName, ;

  lcClass, ;

  lcLibrary, ;

  loItem, ;

  lcParent

 

* Create the menu object.

 

loMenu = newobject('SFMenu', 'SFMenu.vcx', '', ;

  tcMenuObject)

 

* Open the menu table and process all active records.

 

lnSelect = select()

select 0

use (tcMenuTable) order Order

scan for Active

 

* If this is a pad, add it to the menu.

 

  if RecType = 'P'

    lcName    = trim(Name)

    lcClass   = iif(empty(Class), 'SFPad', trim(Class))

    lcLibrary = iif(empty(Library), 'SFMenu.vcx', ;

      trim(Library))

    loItem    = loMenu.AddPad(lcClass, lcLibrary, lcName)

 

* If this is a bar, add it to the specified pad.

 

  else

    lcName    = iif(empty(Name), sys(2015), trim(Name))

    lcParent  = trim(Parent)

    lcClass   = iif(empty(Class), 'SFBar', trim(Class))

    lcLibrary = iif(empty(Library), 'SFMenu.vcx', ;

      trim(Library))

    loItem    = loMenu.&lcParent..AddBar(lcClass, ;

      lcLibrary, lcName)

  endif RecType = 'P'

 

* Set the properties of the pad or bar to the values in

* the menu table. Note the use of TEXTMERGE(), which

* allows expressions to be used in any field.

 

  with loItem

    if not empty(Caption)

      .cCaption = textmerge(trim(Caption))

    endif not empty(Caption)

    if not empty(Key)

      .cKey = textmerge(trim(Key))

    endif not empty(Key)

    if not empty(StatusBar)

      .cStatusBarText = textmerge(StatusBar)

    endif not empty(StatusBar)

    if not empty(SkipFor)

      .cSkipFor = textmerge(SkipFor)

    endif not empty(SkipFor)

    if not empty(Visible)

      .lVisible = textmerge(Visible)

    endif not empty(Visible)

    if RecType = 'B'

      if not empty(KeyText)

        .cKeyText = textmerge(trim(KeyText))

      endif not empty(KeyText)

      if not empty(Command)

        .cOnClickCommand = textmerge(Command)

      endif not empty(Command)

      if not empty(Clauses)

        .cMenuClauses = textmerge(Clauses)

      endif not empty(Clauses)

      if not empty(PictFile)

        .cPictureFile = textmerge(PictFile)

      endif not empty(PictFile)

      if not empty(PictReso)

        .cPictureResource = textmerge(PictReso)

      endif not empty(PictReso)

    endif RecType = 'B'

  endwith

endscan for Active

use

select (lnSelect)

return loMenu

 

Para ver esto en acción, ejecuta TestMenu.prg. Se llama a CreateMenu, specifica que Menu1.dbf es el menú de la tabla.

 

      Aplicaciones Data-Driven

 

Con algunos frameworks, como con Visual FoxExpress, creas una aplicación subclaseando una clase aplicación ya existente y poniendo propiedades en la ventana de propiedades o en el código. Hay ventajas en cada uno de estas formas de trabajar, pero algunas veces es más fácil cambiar el contenido de una tabla que subclasear y preocuparse acerca de qué necesidades necesitan ser cambiadas y dónde.

 

El mecanismo de almacenamiento para aplicaciones data-driven podría ser en un arthivo INI, en el Windows Registry, una tabla, o una combinación de estas. Por ejemplo, yo habitualmente almaceno la información específica del usuario en el registry y la global en una tabla. Sin embargo, para aplicaciones donde el usuario necesita configurar algo de forma fácil, especialmente desde fuera de la aplicación, no podrás trabajar con un fichero .INI

 

Para la configuración almacenada en una tabla, la forma de trabajar que he tomado es que algo más que la codificación pura y dura de la lista de todas las propiedades a tomar de una tabla, uso un administrador de configuración de forma que no sean sólo los valores de las propiedades de tipo orientado al dato. He aquí un ejemplo:  Digamos que una de las propiedades en tu objeto aplicación es el nombre de la aplicación (tal como  “Visual Cash Register”).  Eso es algo que siempre has especificado entrándola en la hoja de propiedades,  ya que no es algo que cambiará en tiempo de ejecución. A continuación,  una de las variantes de la aplicación que quieres crear necesita  algo con un nombre diferente (tal como “Visual Cash Register for AccountMate”).  Para recuperar esa propiedad desde la tabla de configuración, necesitarás añadir  código al objeto aplicación para que haga eso. Eso se consigue con una o dos líneas de código, pero necesita ser escrito y testeado.

 

El uso del administrador de configuración te permite manipular la tabla donde está la configuración para recupear la última o poner la de por defecto.

 

SFConfigMgr, en SFPERSIST.VCX, es el administrador de configuración que yo uso. Su propiedad cFileName contiene el nombre de la configuración de la tabla desde la que se configura; el valor por defecto es SFConfig.dbf (miraremos a esta tabla en un momento). SFConfigMgr colabora con otros dos objetos, un objeto persistente (SFPersistentTable, en el mismo VCX), que hace el trabajo actual de obtener un valor desde una tabla y espera a la propiedad de un objeto, y un objeto script, que permite que los valores sean determinados  por la ejecución de un código de script para una rendimiento máximo de la runtime (discutiremos esto más tarde). Los únicos métodos públicos de esta clase son Restore, que restaura todos los parámetros de configuración en la tabla de configuración para un objeto específico, y Reset,  que resetea el manager. Debido a limitaciones de espacio no discutiremos esta clase en detalle. Siéntete libre de examinar el código por ti mismo.

Tabla 2 muestra la estructura de la tabla de configuración, SFConfig.dbf, y la Tabla 3 muestra algunos registros de ejemplo para la tabla. Fíjate que el valor puede ser una expresión entre delimitadores; el administrador de configuración usará TEXTMERGE() en el value, así puede contener cualquier expresión válida de VFP, incluso una llamada a alguna función o método para determinar el valor, tal como leer del Registry o de un archivo INI. También nota que el campo Grupo se puede usar para distinguir la configuración de cada componente. Puedes ver en la Tabla 3 que dos objetos diferentes restaurarán su configuración desde la tabla de configuración, el objeto aplicación y un objeto logo.

 

Field

Type

Description

Key

C(25)

The key for this record.

Target

M

The property in which to store the value.

Value

M

The value for the setting.

Group

C(25)

Used to group settings by type.

Type

C(1)

The data type for the value. Since it’s stored in a memo field, the configuration manager needs to convert the string value to the proper data type.

 

Tabla 2. La estructura de SFConfig.dbf.

 

Key

Target

Value

Group

Type

Application Name

cAppName

My Sample Application

Application

C

Version

cVersion

1.0

Application

C

Data Directory

cDataDir

K:\Apps\Data\

Application

C

Menu Table

cMenuTable

Menu.dbf

Application

C

Logo Image

Picture

<<_samples + 'tastrade\bitmaps\ttradesm.bmp'>>

Logo

C

Table 3. Configuración de SFConfig.dbf para una aplicación de ejemplo.

 

   Para recuperar su configuración, un objeto simplemente llama al método Restore del administrador de configuración, pasándole el valor del grupo que se va a restaurar y una referencia al mismo, de esta forma el administrador de configuración  puede actualizar sus propiedades. El código siguiente, toma desde el método Init Init de SFApplication, instancia SFConfigMgr en oConfigMgr y recupera todos los parámetros de configuración al grupo “Application”.

 

This.oConfigMgr = newobject('SFConfigMgr',  'SFPersist.vcx')

This.oConfigMgr.Restore('Application', This)

 

   La clase SFLogo usa un código similar para obtener el nombre del archivo imagen para usar por el logo.

 

      Diálogos Data-Driven

 

   Muchas aplicaciones tiene tienen una ventana de diálogo de “about”  que muestra cosas como el número de versión, directorio actual, localización de los archivos de datos, nombre del usuario actual, etc.  Puedes crear una clase de dialogo about que tenga el deseado comportamiento y apariencia y a continuación subclasearse para una específica aplicación. Sin embargo, con un diálogo de about de data-driven, no hay necesidad de subclases; simplemente especificarás las cosas que se deben mostrar en el diálogo con una tabla.

 

   La Figura 1 muestra SFAbout, una clase de diálogo data-driven about, tiene el mismo aspecto que cuando muestra la configuración para una aplicación de ejemplo. Esta clase usa SFAbout.dbf para especificar la configuración mostrada en la lista y propiedades del objeto aplicación para otras cosas como el caption, versión, copyright, email address, y así en adelante).

 

   Además de tener una lista de configuración en formato data-driven, esta clase tiene algunas otras interesantes cosas:

 

·   Su tamaño y posición se guarda y se toma desde el Registry (usando la clave guardada en el objeto aplicación de la propiedad cRegistryKey) por el objeto SFPersistentResizer en el formulario.

·   El objeto SFPersistentResizer también controla el tamaño y posición de los controles del formulario cuando el formulario se redimensiona.

·   La anchura de la columna del ListView son también guardados y restaurados desde el Registry.

·   Los Items en el ListView pueden ser “hyperlinked”: cuando el usuario hacer un click en el item, la clase usa la función ShellExecute del Windows API para “run” el valor.  Estos ítems se muestran en azul en la lista. Estas cosas son mucho más utiles para cosas como un nombre de directorio (que lanza Windows Explorer para ese directorio), dirección de email (que lanza el cliente de correo por defecto), y el sitio Web (que muestra ese sitio Web en el browser por defecto). El comportamiento se podría cambiar para soportar funciones que se llamen dentro de la aplicación, tal como mostrar el profile de un usuario cuando el usuario hace click en el nombre del usuario.

·   El botón Copy Settings copia la configuración al Windows clipboard, que se puede entonces pegar en un email para soporte.

·   La ventana de logo en lado de la izquierda es una clase (SFLogo) que obtiene su información desde las propiedades oApp y desde la tabla de configuración (el nombre del archivo imagen a usar).

   El método LoadList, se llama desde el Init, es bastante simple: nos abre un SFAbout.dbf y navega a través de los registros activos, llamando al método AddSettingToList para cada elemento a añadir a la lista.

 

local lnSelect, ;

  lcValue, ;

  lcLink

lnSelect = select()

select 0

use SFAbout order Order

scan for Active

  lcValue = evaluate(ValueExpr)

  lcLink  = iif(empty(LinkExpr), lcValue, ;

    evaluate(LinkExpr))

  This.AddSettingToList(textmerge(trim(Descrip)), ;

    lcValue, Link, lcLink)

endscan for Active

use

select (lnSelect)

 
 
(continuará)

 

 

FoxPress – Febrero de 2003

© de la traducción 2003 FoxPress.