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.
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. |
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. |
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 |
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.