Está en la página 1de 171

Instalación

Este capítulo describe el proceso de instalación de su paquete de desarrollo de Xbase++. También lo provee a Ud.
con información importante sobre el entorno del sistema y la configuración.

Removiendo las instalaciones previas de Xbase++


Si tiene una versión previa del Xbase++ ya instalado, debería desinstalarlo vía el panel de control del software:
Hacer Clic en el botón Inicio
Seleccionar Configuración -> Panel de Control
Hacer Clic en el ícono Agregar/remover programas
Seleccionar Xbase++ desde la lista en la solapa Instalar/desinstalar

Note que el programa de desinstalación sólo puede remover los archivos instalados. No remueve los archivos
creados después de la instalación, como los archivos OBJ o EXE en el directorio ..\SAMPLES, por ejemplo. Si
existen tales archivos, se despliega un mensaje de aviso de que el programa no puede removerse. En este caso
particular, puede ignorar el mensaje.

Cuando se remueve una versión previa debería chequear las entradas PATH, LIB e INCLUDE en el
AUTOEXEC.BAT (Windows 95) o en el registro HKEY_CURRENT_USER\Environment (Windows NT), respecti-
vamente, para los antiguos valores.

Después de una instalación nueva, debe recompilar todos los programas que ha compilado con una versión previa
de Xbase++.

Iniciación del programa de instalación


Inserte el CD en la unidad del CD-ROM y cambie al directorio \INTL para instalar la versión internacional de
Xbase++. El programa SETUP.EXE se encuentra en ese directorio. Instala Xbase++ y lo guía a Ud. a través del
procedimiento de instalación iniciando la instalación de las opciones Typical o Custom .
Nota: La instalación de Xbase++ no cambia o reemplaza ningún sistema de archivos DLL. Todos los archivos DLL
requeridos por Xbase ++ se almacenan en los directorios recientemente creados.

Instalación típica
El paquete de desarrollo de Xbase++ se instala por predeterminación en la unidad C: con \ALASKA\XPPW32\
como el directorio raíz <RAIZ>:
<RAÍZ>
+--BIN Compilador y programas de servicio
+--BOOK Ayuda en línea
+--LIB Biblioteca de tiempo de ejecución
+--INCLUDE Archivos #include
+--RESOURCE Archivos de recursos
+--RUNTIME Bibliotecas de tiempo de ejecución
+--SOURCE Código fuente
+--SYS Fuente para los archivos del nivel Sistema
+--COMPAT funciones de compatibilidad para Clipper '87
+--SAMPLES Programas de ejemplo

Durante la instalación la configuración de su sistema se modifica para reflejar los nuevos directorios. Para Win-
dows NT, los cambios se aplican a la Base de Datos del registro bajo \HKEY_CURRENT_USER\Environment ,
mientras AUTOEXEC.BAT se altera para Windows 95. Las siguientes claves de registro o variables de entorno,
respectivamente, se modifican o crean si están ausentes:

PATH = C:\ALASKA\XPPW32\BIN;C:\ALASKA\XPPW32\LIB;
LIB = C:\ALASKA\XPPW32\LIB;
INCLUDE = C:\ALASKA\XPPW32\INCLUDE;
XPPRESOURCE = C:\ALASKA\XPPW32\RESOURCE;

Se definen asociaciones de archivo predeterminadas para los archivos PRG, CH, XPJ y XFF durante una insta-
lación típica. Use la instalación personalizada para suprimir esta característica automática.
Instalación personalizada
La instalación personalizada permite seleccionar los componentes individuales del paquete de Xbase++ a instalar.
Además, la definición de las asociaciones de archivo pueden seleccionarse individualmente.

CH Los archivos Include se asocian con el editor de notas de Windows y con el compilador de Xbase++. El
compilador lleva a cabo un chequeo de sintaxis para un archivo CH cuando se lo invoca.
PRG Los archivos de código fuente se asocian con el editor de notas de Windows y el compilador de Xbase++.
Se compila un archivo PRG.
XFF Los archivos de definición de formulario se asocian con el diseñador de formularios.
XPJ Los archivos Project de Xbase++ se asocian con el ProjectBuilder. Un doble clic con el botón izquierdo
sobre un archivo XPJ construye un proyecto.

Nota: El archivo XPP.REG está disponible en el directorio raíz de la instalación de Xbase++. Si las asociaciones
del archivo no se definen durante la instalación pueden definirse posteriormente importando XPP.REG al registro
de Windows usando REGEDIT.EXE. Las asociaciones del archivo predeterminado pueden modificarse también
editando el archivo XPP.REG. Por ejemplo, si quiere la asociación de archivos de código fuente con el editor de su
elección, reemplace la cadena "notepad.exe" en XPP.REG con el nombre del editor e importe el archivo modifi-
cado al registro.

Sea consciente que XPP.REG contiene una foto de HKEY_CURRENT_USER\Environment como lo era antes de
la instalación de Xbase++. Si posteriormente cambia el registro al instalar otro software, por ejemplo, estos cam-
bios no se reflejan en XPP.REG. Antes de importar XPP.REG al registro, asegúrese que
HKEY_CURRENT_USER\Environment es correcta o remueva esta sección enteramente desde el archivo
XPP.REG.

Antes de continuar
Si ha instalado Xbase++ bajo Windows 95 o trabaja con ambos Clipper y Xbase++ en la misma estación de trabajo,
por favor lea las siguientes secciones antes de comenzar usando Xbase++.

Límite del entorno Windows 95/98


En Windows 95 el espacio para las variables de entorno se limita a 1024 bytes por predeterminación. Esto puede
traer aparejado problemas cuando se definen muchos caminos en AUTOEXEC.BAT apuntando a numerosos
directorios. Si el espacio de memoria para las variables de entorno está exhausto, es más probable que los ca-
minos no se definan completamente o que no estén a disposición en el entorno del sistema. Este es también el
caso si todos los caminos se listan correctamente en AUTOEXEC.BAT, porque la tabla del entorno se encuentra
llena. Cuando se hallan mensajes de error del sistema como "no hay suficiente espacio en el entorno" debe au-
mentar el tamaño de la tabla de entorno usando la declaración SHELL en su archivo CONFIG.SYS. La siguiente
línea consiste en un ejemplo que establece el espacio del entorno en 2048 bytes:

SHELL=C:\windows\command.com C:\windows /E:2048 /P

Note que los cambios en CONFIG.SYS se vuelven efectivos sólo cuando reinicia el equipo. Note también que
C:\windows es sólo un ejemplo donde se localiza el procesador de comandos COMMAND.COM. Puede encon-
trarse en un directorio diferente en su computadora.

Trabajando con Xbase++ y Clipper


Xbase++ necesita ciertas variables del entorno del sistema que se establezcan correctamente. Estas incluyen
PATH, INCLUDE, LIB y XPPRESOURCE que se definen durante la instalación por predeterminación. Sin embargo,
si trabaja con Clipper en la misma computadora, puede haber conflictos con el camino INCLUDE. Ambos com-
piladores buscan este camino para los ficheros de encabezado. Además, ambos productos utilizan los mismos
nombres del fichero de encabezado como por ejemplo STD.CH, pero los contenidos del archivo difieren bastante.
Por tanto, es necesario que el camino INCLUDE apunte al directorio de encabezado correcto cuando uno invoca al
compilador.

Una indicación clara de un fichero de encabezado equivocado que se encuentra en uso se da cuando el enlazador
de Xbase++ reporta una función externa irresuelta que comienza con dos guiones bajos (a saber: __SetFormat(),
__Quit(), __Keyboard()). Estas llamadas a función se producen sólo si se incluye el archivo de Clipper STD.CH. En
general, no hay funciones en Xbase++ que comienzan con un guión doble.
Cambiando el entorno
Para cambiar el entorno de configuración del sistema para Xbase++ o Clipper con mayor facilidad, dos archivos de
lote de comandos están disponibles luego de la instalación de DINO.BAT y AUTOXPP.BAT . El archivo DINO.BAT
se localiza en el directorio raíz de su unidad C: y AUTOXPP.BAT se lo encuentra en el directorio raíz de la ins-
talación de Xbase++. DINO.BAT contiene una pre-instalación instantánea de las variables de entorno del sistema.
Cuando se las invoca, reinicia la configuración del sistema a los valores que eran actuales antes de la instalación
de Xbase++. Ejecutando AUTOXPP.BAT establece los caminos de búsqueda para Xbase++. Los dos archivos de
lote de comandos, por tanto, establecen las variables de entorno apropiadas para Xbase++ o Clipper y le permiten
a Ud. cambiar al instante el entorno para cualquiera de los compiladores.

Después de la instalación
Este capítulo da un rápido vistazo de como podrá prepararse para trabajar con Xbase++. Además, se dan aquí los
consejos de como obtener información sobre Xbase++ que no se incluyen en esta documentación introductoria.

Sus primeros pasos con Xbase++


La forma más fáciln de obtener un vistazo rápido sobre Xbase++ y sus características es confrontar los programas
de ejemplo que vienen junto con Xbase++. Hay una variedad de ejemplos de programación instalados en el di-
rectorio ..\SOURCE\SAMPLES que cubre numerosos aspectos diferentes. Los programas de ejemplo se dividen
en cuatro categorías y se alcanzan rápidamente clickeando sobre el botón de Inicio -> Programas -> Alaska
Xbase++ -> Ejemplos de Xbase++. Esto abre la carpeta de Ejemplos que lista nuevamente cuatro carpetas más,
cada una conteniendo ejemplos de una categoría:

Apps Esta categoría contiene aplicaciones únicas hechas a partir de archivos de código fuente
de múltiples PRG. Cada aplicación apunta al diseño del programa, como Interfaces Sim-
ples o Múltiples, por ejemplo.
Basics Cada programa de ejemplo en la categoría de Conceptos Básicos se focaliza en una ca-
racterística particular de Xbase++. La intención de los ejemplos no es su reutilización, pero
sino mostrar técnicas de programación que resuelven un problema en particular. "Cómo
desplegar texto usando una fuente itálica?", "Cómo crear una ventana modal?" o "Cómo
usar un thread?" son preguntas contestadas por ejemplos de esta categoría.
Migrate El código de Clipper que migra a GUI es uno de los puntos fuertes de Xbase++. Por con-
siguiente, los ejemplos que se incluyen en esta categoría muestran varias posibilidades y
pasos intermedios en la transición de la aplicación del modo de texto a una aplicación de
características GUI completas.
Solution Esta categoría junta programas de ejemplo que proveen soluciones a problemas de pro-
gramación comunes. Todos ellos son programas únicos. Sin embargo, incluyen códigos de
programas que implementan un escenario de prueba para el ejemplo actual. El ejemplo
está listo para la reutilización en sus programas cuando descarta al código para probar el
escenario.

Los ejemplos en la carpeta MIGRATE proveen un buen punto de comienzo cuando ya ha instalado Xbase++ por
primera vez. Entonces abra esta carpeta y cliquee en Login . Ahora puede actualmente ver los contenidos del
directorio ..\SAMPLES\SOURCE\MIGRATE\LOGIN que contienen diferentes tipos de archivo. Los tipos de archivo
se identifican por la extensión del archivo y se visualizan mediante íconos distintos.

Ejecutando programas de ejemplo


El directorio de ejemplo incluye el código fuente PRG y el archivo ejecutable correspondiente. Sólo haga doble-clic
en el ícono del archivo EXE para ver lo que hace un programa de ejemplo. Ud. encontrará tres archivos EXE en la
carpeta Login : LOGIN_T.EXE, LOGIN_H.EXE y LOGIN_G.EXE. Ellos hacen lo mismo (despliegan un diálogo de
acceso y el logo de la compañía), pero lo hacen de manera diferente. LOGIN_T.EXE es una aplicación a modo de
texto, LOGIN_H.EXE agrega elementos gráficos al modo de texto y LOGIN_G.EXE es una solución GUI del mismo
problema. Estos tres programas le dan a Ud. una vista interna de algunas características únicas de Xbase++.

Viendo el código fuente


Cuando vio lo que puede hacer un programa de ejemplo Ud. querrá saber cómo se lo programa. Para esto, sólo
necesita hacer un doble-click en el ícono del archivo PRG. Esto inicia al editor de textos que carga al archivo PRG.
Por predeterminación, se inicia el Notepad de Windows para desplegar el archivo PRG. Note que cada vez que
hace un doble-click en el archivo PRG, una instancia nueva del editor puede iniciarse. Si este es el caso, Ud.
puede arrastrar un ícono del archivo PRG con el botón derecho del mouse y depositarlo sobre el editor. De esta
manera, el editor no se inicia nuevamente. En cambio, sólo carga al archivo PRG correspondiente.

Ejecutando programas de ejemplo en el debugger


Después de estudiar al código fuente de un programa de ejemplo, Ud. puede tener aún más preguntas sobre aquél.
En este caso, es mejor seguir al programa paso a paso a través de su ejecución en el debugger. Puede iniciar al
debugger desde el botón de Inicio -> Programas -> Alaska Xbase ++ -> debugger de Xbase++. Una vez que el
debugger se está ejecutando , Ud. simplemente ingresa el camino y el nombre del archivo EXE, y clickea el botón
OK en la pantalla de inicio del debugger para poder así avanzar paso a paso el programa con la tecla F8. Note que
puede invocar al debugger desde la línea de comando también. Esto se describe en un capítulo separado poste-
riormente en el libro.

Reconstruyendo programas de ejemplo


Una vez que ha comprendido un programa de ejemplo de su elección, probablemente querrá experimentar con él
a realizar modificaciones para ver que pasa si uno cambia esto o aquello en el archivo PRG. Para ver el resultado
de los cambios, deberá reconstruir el archivo EXE correspondiente. Esto se lleva a cabo haciendo un doble-click
sobre el ícono del archivo PROJECT.XPJ con el botón izquierdo del ratón. Esto invoca al ProjectBuilder de Xbase
++ que reconstruye al EXE (referirse al capítulo El ProjectBuilder de Xbase++ - PBUILD.EXE para mayor infor-
mación).

Los próximos pasos con Xbase++


Una vez que vio los programas de ejemplo, tendrá una buena impresión de las propiedades de Xbase++. Luego a
dónde ir y cómo proceder depende de Ud. Lo aconsejamos bien acerca de como leer la documentación intro-
ductoria, pero realizar aún más recomendaciones depende de su conocimiento previo de programación y el len-
guaje que ha estado utilizando hasta ahora. Xbase++ tiene documentación en línea lo cual es una gran fuente de
información para contestar casi cualquier pregunta. Fue diseñado para su comprensión por cualquier novato así
como por un programador avanzado. Dependiendo de su experiencia en programación encontrará debajo algunas
direcciones de dónde buscar en la ayuda en línea para obtener mayor y más apropiada información. Quizá podrá
identificarse con algunos de estos tipos de programación:

Nunca he usado un lenguaje Xbase


Cuando desconoce sobre el lenguaje de Xbase, debería familiarizarse con sus conceptos. Vea los próximos ca-
pítulos en la ayuda en línea:
* Elementos del Lenguaje de Xbase++
* Elementos de un Programa de Xbase++
* Tipos de datos y Literales
* Operadores
* Declaraciones y Sentencias
* Operaciones y Operadores para Tipos de Datos Simples
* Procedimientos, Funciones y Operadores Especiales
* Operaciones y Operadores para Tipos de Datos Complejos

Conozco el lenguaje Xbase pero nunca he usado Clipper


Si está familiarizado con otros dialectos de Xbase como FoxPro o dBase, por ejemplo, Ud. tiene entonces una idea
de las cosas que son posibles en Xbase++. Esto incluye la declaración variables de alcance léxico así como tipos
de datos como bloque de Códigos, Arreglo y Objeto. Estará interesado en el preprocesador, también, puesto que
puede usarse para traducir su código existente a la sintaxis válida de Xbase ++. Vea estos capítulos en la ayuda en
línea:
* Declaraciones y Enunciados
* Operaciones y Operadores para Tipos de Datos Simples
* Operaciones y Operadores para tipos de Datos Complejos
* El Preprocesador de Xbase++
* Conceptos de Error de Manipulación.

Vuélvase familiar con las funciones y comandos disponibles en Xbase++. Los encontrará listados en orden alfa-
bético cuando abra la Tabla de Contenidos en la ayuda en línea.

Soy un programador Clipper


Si Ud. es un programador de Clipper, debería leer el capítulo "Información para programadores de Clippper" en
este libro. Le dará información inicial sobre los temas de migración.
Conozco Clipper pero no conozco GUI
Si Ud. es un programador de Clipper que quiere migrar código existente a Windows, debería familiarizarse con los
programas de ejemplo en los directorios Basics y Migrate. Además, lea el capítulo "Interfaz de Usuario y Con-
ceptos de Diálogo" en este libro. Debería obtener un conocimiento de trabajo de las siguientes funciones (las
encontrará en el índice de la ayuda en línea).
* AppEvent()
* PostAppEvent()
* SetAppWindow()
* XbpCrt()
* XbpDialog()

Ya he programado aplicaciones Windows GUI


Ud. está familiarizado con las aproximaciones a la programación dirigida por evento y no le tiene miedo a la pro-
gramación orientada objeto. Si este es el caso, debería familiarizarse con el concepto de los Componentes de
Xbase. Le permiten ver los controles GUI fácilmente. Además, las características del multi-threading de Xbase++
son de interés para Ud. Lea los siguientes capítulos en este libro:
* Conceptos Básicos de los Componentes de Xbase
* Jerarquía de Clases de los Componentes de Xbase
* Multi-tarea y multi-threading

Sistema de ayuda en línea


Xbase++ incluye una ayuda en línea comprensible en Windows ..\BOOK\XPPREF.HLP. Se la invoca vía el botón
de Inicio o tipeando WINHLP32 XPPREF en la línea de comando o clickeando el ícono respectivo en la carpeta de
Xbase++ o el explorador de Windows.

Para buscar información


Si está buscando una porción particular de información y no sabe dónde encontrarla, pruebe usando las pode-
rosas facilidades de búsqueda provistas por WINHLP32.EXE. El archivo de ayuda provee cuatro maneras de
buscar información:
Tabla de contenidos La tabla de contenidos muestra una estructura jerarquizada de todos los paneles de
ayuda. La primer sección de Conceptos Básicos de Xbase provee conocimiento básico
sobre las características y conceptos de programación mientras que las secciones
apuntan a partes referenciales de la documentación. Se recomienda la sección de
conceptos Básicos de Xbase++ para obtener información fundamental sobre Xbase++.
Indice La solapa de índice provee acceso rápido acerca de un tema particular de ayuda. Si
conoce el nombre de la función o el comando que está buscando, tipeelo en esta solapa
y presione la tecla Enter/Intro.
Búsqueda completa de texto La búsqueda completa de texto para una palabra particular en la documentación
entera. Se listan todos los paneles de ayuda que contienen la palabra especificada.
Cuando esta opción de búsqueda se utiliza por primera vez, WINHLP32.EXE pregunta
por la creación de una lista de palabras para referenciar a los paneles de ayuda. Esto
lleva su tiempo y crea el archivo XPPREF.FTS. Una vez que se crea el archivo la
búsqueda de texto completa está disponible.
Referencia rápida La referencia rápida es una característica especial de la ayuda en línea de Xbase++. Se
la invoca clickeando el botón QuickRef en un panel de ayuda o presionando Alt+Q.
Luego se despliega una ventana que lista temas de ayuda en orden alfabético.
Hay dos ventanas de Referencia Rápida. Una lista funciones y comandos, mientras la
otra lista variables de instancia y métodos. En la parte superior de cada ventana de
Referencia Rápida, se exhibe un botón que permuta ambas ventanas.

Claves de acceso rápido en el sistema de ayuda


Las claves de acceso rápido se agregan a la ayuda en línea de Xbase++ para obtener acceso rápido usando una
palabra clave en vez del ratón.
Alt+C Activa la solapa de la Tabla de Contenidos
Alt+Q Abre la ventana de Referencia Rápida
Alt+S Accede a la búsqueda de Texto Completo
Alt+X Activa el índice de la solapa
Alt+Y Abre la ventana historial
Ctrl+PgDn Despliega el próximo tópico de ayuda
Ctrl+PgUp Despliega temas de ayuda Previos
Todas las selecciones rápidas están disponibles en el menú de contexto. Aparecen cuando se clickea la ventana
de ayuda con el botón derecho del ratón o presionando las teclas Ctrl+Enter.

Aseguramiento de la calidad de Xbase++


Para el aseguramiento de la calidad de sus productos, Alaska Software utiliza un sistema de rastreo de defectos
para el seguimiento y corrección de nuevos errores y anomalías. La "base de datos de defectos" se usa para juntar
información reportada por los usuarios registrados y enviar la información directamente al departamento concer-
niente o inclusive al escritorio de un único desarrollador. Esto permite una respuesta en brevedad entre el reporte
del error y su arreglo.

Cómo contactarse con Alaska


Si la ayuda en línea no puede contestar sus preguntas, nuestro soporte técnico estará a su disposición para
asistirlo y ayudarlo a resolver sus programas. Por favor, use una de las siguientes posibilidades para contactarse
con nuestro soporte técnico:
Dirección de e-mail para clientes en Europa, Medio Oriente, Africa:
support@de.alaska -software.com
Dirección de e-mail para clientes en América, Asia, Pacífico, Australia:
support@us.alaska-software.com
FAX: (+49) 6196 / 779 36-22
Teléfono: (+49) 6196 / 779 36-0

Cómo informar problemas


Los Reportes de Descripción del Problema (PDR) se utilizan para informar problemas a Alaska. Si encuentra al-
guna anomalía en el producto, por favor use el formulario de la próxima página para incluir la información posible
sobre el defecto y enviar un fax a Alaska Software. Ud. puede usar el archivo PDR.TXT que se localiza en el di-
rectorio raíz de su instalación de Xbase++ y enviarlo vía e-mail a nuestro soporte técnico. Enviando un e-mail es
forma preferencial porque el formulario PDR.TXT puede transferirse automáticamente al sistema de rastreo de
defectos.

Qué pasa con los PDRs


Su problema será clasificado cuando lo informa. Si el problema es desconocido y puede reproducirse, será
aprobado por el soporte técnico. Se le asigna luego un PDR-ID al problema que identifica al problema para su
seguimiento.

Ud. será notificado por e-mail si el problema que informó está aprobado. El soporte técnico lo proveerá a Ud. con
información sobre cómo trabajar sobre el problema, si es posible. Todos los PDRs aprobados serán publicados
frecuentemente (aprox. bimensualmente).

Nota: Apreciamos muchísimo la contribución que ayuda a mejorar al producto y su robustez. Sin embargo, si
quiere contribuir con sus experiencias, debe ser un usuario registrado. Sin un número de registro, no es posible
asignarle un PDR-ID a un informe de problema.
El formulario PDR

##################< PDR status - esto es completado por ALASKA >#############

PPN.: ____________ PDR ID: ______________ REF No: _______________

Status : ____________ Severity: ______________

###########################< Datos de Cliente >##############################

Cliente RegNo.: ____________ CIS ID: _______________ Fecha: __.__.____

Version: 1.1.153 / Windows

Compañía / Remitente :
________________________________________________________

Persona para contacto:


________________________________________________________

##############################< Categorías >#################################

Ayuda Enlínea [ ] Función /Comando [ ] Comp.Xbase [ ]


Preprocesador [ ] Objeto/Método [ ] Gráfico [ ]
Compilador [ ] Base de Datos [ ] Otros [ ]

Síntomas :
________________________________________________________

Archivo(s) Adjunto(s):
________________________________________________________

Comentarios :
________________________________________________________

######################< Descripción Detallada >##########################

#################################< Fin >#####################################


Instalación de Aplicaciones Xbase++
Los programas ejecutables creados con Xbase++ pueden ser instalados en otros sistemas de computadoras en
concordancia con el acuerdo de la licencia que Ud tiene con Alaska Software. La instalación requiere al archivo
EXE más las bibliotecas de tiempo de ejecución para ser copiados a otra estación de trabajo. Las bibliotecas de
tiempo de ejecución están contenidas en archivos DLL las cuales están localizadas en el directorio RUNTIME de
su instalación Xbase++. Cuáles archivos DLL copiar depende parcialmente de cuál Motor de Base de Datos usa el
EXE. La siguiente tabla lista todos los archivos posiblemente requeridos:

Archivos necesarios para ejecutar Aplicaciones Xbase++


Archivo Descripción
XPPRT1.DLL 1) Biblioteca Principal de Xbase++
XPPUI1.DLL 1) Eventos y Componentes Xbase
XPPNAT.DLL 1) Biblioteca Pais-específica (módulo nación)
SOM.DLL 1) Biblioteca IBM SOM 2.0

ADAC20B.DLL 2) Clases del Núcleo DAC*


ADAC20C.DLL 2) Clases de Usuario DAC* (ej: DacPagedDataStore())
XPPUI2.DLL 2) Componentes de Aplicaciones, Clases browser y XbpPrintDialog()
DBFDBE.DLL 2) Motor de B.D. para archivos DBF
FOXDBE.DLL 2) Motor de B.D. para archivos FoxPro compatibles DBF
DELDBE.DLL 2) Motor de B.D. para archivos en formato Delimitado
SDFDBE.DLL 2) Motor de B.D. para archivos en formato SDF
CDXDBE.DLL 2) Motor de B.D. para archivos CDX
NTXDBE.DLL 2) Motor de B.D. para archivos NTX
1) Obligatorio 2) Opcional

Todos los archivos DLL marcados con 1) en la tabla deben ser copiados juntos con el EXE a otra estación. De otro
modo el programa ejecutable no puede ser cargado en memoria. Los archivos marcados con 2) son solamente
necesarios si el EXE requiere la funcionalidad de la respectiva DLL. Los archivos DLL deben ser copiados a un
directorio que está listado en la variable de entorno PATH. Si el directorio no es parte del PATH, Ud deberá mo-
dificar esta variable de entorno para que lo incluya.

Nota: La tabla de mas arriba lista todos los archivos de Xbase++ que son de distribución libre para sus clientes.
Todos los archivos creados por Xbase++ también son de libre distribución (Por favor cumpla con el acuerdo de
Licencia).

Información para Programadores de Clipper


Este capítulo provee información para los programadores que planean portar aplicaciones existentes de Clipper a
Xbase++. Si bien Xbase ++ se basa en el lenguaje de programación de Clipper, existen ciertas diferencias debido
a la tecnología del compilador y a diferencias entre DOS y un sistema operativo de 32bit.

Diferencias en el preprocesador y el compilador


Diferencias entre Clipper '87 y Xbase++
Diferencias entre Clipper 5.x y Xbase++
Nuevas funciones en Xbase++ (desconocidas en Clipper)
Diferencias entre Class(y) y Xbase++
Usando el mouse

Diferencias en el preprocesador y el compilador


Los resultados de los preprocesadores de Xbase++ y Clipper difieren en algunas situaciones muy especiales. Los
siguientes ejemplo de código muestran donde se desvían el preprocesador de Clipper o el compilador del pre-
procesador y el compilador de Xbase++.

Opciones de comando
Usa la función de nombres reservada como opciones en los comandos definidos por el usuario conduce a pro-
blemas con el preprocesador en Xbase++. Por ejemplo:

#command @ <fila>, <col> MICOMANDO <expr> [MIN <min>] [MAX <max>] ;


=> MiFuncion( <fila>, <col>, <expr>, <min>, <max>)
Este programa se traduce correctamente salvo que las funciones Min() o Max() se utilicen para los parámetros
opcionales <min> y <max>. Lo siguiente no será permitido en Xbase++:

@ 10,20 MICOMANDO "HacerAlgo" MIN Min(10,x)

En este caso, la palabra clave se emplea como parte de la opción del comando idéntico a la palabra clave de los
parámetros opcionales. Esto no es permitido en Xbase++.

Constantes Numéricas
Se pueden programar las constantes numéricas en Xbase++ usando la notación decimal, hexadecimal o científica.
Clipper comprende solo comprende la notación decimal. Las constantes a continuación son válidas en Xbase++:

3.1415926 // decimal
0xFF // hexadecimal
10.1E-10 // científica

Declaración PARAMETERS
Con la declaración PARAMETERS, la compilación con Xbase++ es más estricta que Clipper. La declaración se
usa para declarar parámetros formales para funciones o procedimientos como variables de la clase de almace-
namiento PRIVATE (Clipper '87). Debido a que PARAMETERS es una declaración ejecutable, puede usarse
solamente después de las declaraciones de variables léxicas como LOCAL o STATIC. Por ejemplo:

// Permitido // No permitido
PROCEDURE MiProc PROCEDURE MiProc
LOCAL cCadena PARAMETERS p1, p2
PARAMETERS p1, p2 LOCAL cCadena

<Código> <Código>
RETURN RETURN

Nota: En Xbase++, los parámetros formales no deben declararse usando PARAMETERS. En cambio, una lista de
parámetros separadas por comas debería escribirse dentro de paréntesis cuando se declaran procedimientos,
funciones o métodos:

PROCEDURE MiProc( p1, p2 )


LOCAL cCadena
<Código>
RETURN

Operador Alias
En Xbase++, sólo una variable de identificación o el operador de macro puede especificarse luego del operador de
alias para variables de memoria dinámica M-> o MEMVAR-> y variables de campo FIELD->. Una expresión, con o
sin paréntesis, no puede usarse en estos casos.

MEMVAR->(&cCampo) // es soportado en Clipper,


FIELD->(&cCampo) // pero no en Xbase++
Correcto en Xbase++:
MEMVAR->&(cCampo)
FIELD->&(cCampo)

Operador Macro &


Cuando el operador de macro se usa dentro de bloques de código, Xbase++ siempre emplea lo que se llama
"evaluación tardía". Esto puede conducir a resultados diferentes en casos especiales. Por ejemplo:

LOCAL oTBrowse, oTBColumn, i


PRIVATE cNombreCampo

USE Clientes NEW


oTBrowse:= TBrowseNew()

FOR i:=1 TO FCount()


cNombreCampo := FieldName(i)
oTBColumn := TBColumnNew( cNombreCampo, {|| &cNombreCampo } )
oTbrowse:addColumn( oTBColumn )
NEXT
La intención de este código del programa es crear un objeto TBrowse que despliega columnas para todos los
campos en la base de datos. El nombre del campo es asignado a una variable PRIVATE que se expande por
macro dentro del bloque de código. En Clipper, ocurre una "evaluación previa" y el bloque de códigos referencia al
campo cuyo nombre esta contenido en la variable PRIVATE cNombreCampo cuando se crea el bloque de códigos.
En Xbase++, el macro de esta expresión se evalúa cuando el bloque de códigos es evaluado. Debido a esto, todas
las columnas en el objeto de TBrowse despliega los contenidos del último campo ya que su nombre esta contenido
en la variable cNombreCampo luego que termina el ciclo FOR...NEXT. Lo siguiente será el código incorrecto para
usarse en esta situación bajo Xbase++:

oTBColumn := TBColumnNew( cNombreCampo, &("{||"+cNombreCampo+"}") )

En este caso, una "evaluación previa" es forzada porque el bloque de códigos se crea primero como una cadena
de caracteres y luego como un macro expandido. El bloque de código contiene entonces una referencia a la va-
riable de campo actual y no a la variable PRIVATE cNombreCampo .

La creación de nombres de archivo con números consecutivos como extensiones puede resolverse en Clipper de
la siguiente manera:

PRIVATE nNumero := "1"


RESTORE FROM ArchResguardo.&nNumero

Esta expresión de macro no es valida en Xbase++. Debe recodificarse, por ejemplo, asignando un nombre de
archivo a una variable y luego usando la variable como un parámetro de comandos:

PRIVATE nNumero := "1"


PRIVATE cNombreArchivo := "ArchResguardo.&nNumero"
RESTORE FROM &cNombreArchivo

Si se reporta un error de sintaxis cuando un programa de Clipper se compila en Xbase++, la salida del prepro-
cesador de Xbase++ debe chequearse para verificar si ha generado el código correcto. La salida del preproce-
sador puede escribirse en un archivo PPO usando la opción del compilador /p.

USE &(Diccionario->cNombreArchivo)

La línea de arriba puede usarse en Clipper para abrir en archivo (cNombreArchivo) cuyo nombre se guarda en una
base de datos (diccionario). El preprocesador de Xbase++ convierte &(...) en una cadena de caracteres. El código
correcto de Xbase++ es:

USE (Diccionario->cNombreArchivo)

Operador de Arreglo [ ]
El operador de arreglo no esta restringido a los valores del tipo de datos Array, pero puede usarse también para las
cadenas de caracteres. En este caso, el operador recupera un único carácter en una posición especifica. Esto es
mucho más rápido que recuperar un único carácter con la función SubStr().
LOCAL cNombre := "Xbase++"

cCaracter := SubStr( cNombre, 3, 1 ) // estilo Clipper


cCaracter := cNombre[3] // estilo Xbase++

Operador de subcadenas $
El operador de subcadenas no está restringido a valores de tipo de datos Carácter, pero pueden usarse para
chequear el valor del tipo de dato Array. El operador busca el valor del operando izquierdo en el arreglo y de-
vuelve .T. (verdadero) si se encuentra el valor, o de lo contrario devuelve .F. (falso). Esto es un código válido en
Xbase++, pero no en Clipper:

? 2 $ {2,4,6} // resulta: .T.


? 4 $ {{1,2},{4,6}} // resulta: .T.
? {1,6} $ {1,2,4,6} // resulta: .T.

Operador Referencia @
Clipper admite lo siguiente:

x := IIf( y, @param, z )

Cuando se compila este código y ejecuta bajo Clipper, x contiene referencias al parámetro si y es igual a .T.
(verdadero). Este no es el caso en Xbase++.
De lo contrario, con Xbase++, cualquier variable puede pasarse por referencia a las funciones, procedimientos y
métodos. Esto incluye elementos de arreglo, variables de miembro y variables de cambios. La siguiente llamada a
función se permite en Xbase++ pero no en Clipper:

MiFunc( @Clientes->Nombre )
MiFunc( @oTBrowse:colorSpec )
MiFunc( @aArreglo[3,12,5] )

#define __XPP__
El compilador de Xbase++ define la constante __XPP__. Esto permite el mantenimiento de diferentes códigos
fuente dentro de un archivo PRG único cuando debe compilarse usando Clipper y Xbase++.

#ifdef __XPP__
<Código Xbase++>
#else
< Código Clipper>
#endif
#pragma

El compilador de Xbase++ conoce pragmas que no existen en Clipper. Un pragma es una directiva que cambia
parámetros de compilación durante el tiempo de compilación. Esto permite que se active o desactive un parámetro
de compilación particular para una única línea en un archivo PRG.

Diferencias entre Clipper '87 y Xbase++


Optimización de expresiones lógicas: Cuando se compila a Clipper '87 los programas de Xbase++ usan la
misma optimización de acceso rápido para expresiones lógicas como Clipper 5.x:

IF <ExprA> .AND. <ExprB>


// <ExprB> no se ejecuta si <ExprA> == .F.
ENDIF
IF <ExprA> .OR. <ExprB>
// <ExprB> no se ejecuta si <ExprA> == .T.
ENDIF

Usar el parámetro /z del compilador para deshabilitar la optimización de acceso rápido cuando se compila con
programas de Clipper '87 con Xbase ++. Como alternativa, la optimización de la clave de acceso rápido puede
activarse|desactivarse para una sola línea de código usando #pragma Shortcut(OFF|ON) .

Nombres de Alias: Con Clipper '87, el nombre de alias que consiste solo de una única letra, puede especificarse
en el comando USE. Con Xbase ++, esto se permite solamente para las letras que le siguen a "M". Por ejemplo, las
siguientes líneas no pueden usarse con Xbase++ porque acarrean un error a partir del tiempo de ejecución:

USE Prueba1 ALIAS A


USE Prueba2 ALIAS K

En cambio, el código correcto del programa para Xbase++ sería:

SELECT A
USE Prueba1
SELECT K
USE Prueba2

En general, se recomienda usar los nombres de alias con más de una palabra.

Diferencias entre Clipper 5.x y Xbase++


El aspecto más importante cuando se migra un programa de Clipper a Xbase++ se da por el hecho que la primera
rutina de una aplicación de Xbase++ debe llamarse MAIN. Sin un procedimiento MAIN, el enlazador no puede
determinar el punto de entrada en el programa y no puede cargarse el archivo ejecutable.
El código del programa localizado fuera de una declaración de FUNCTION o PROCEDURE se asocia automáti-
camente con el procedimiento MAIN por el compilador de Xbase++. Debido a esto, puede haber solo un archivo
PRG con el programa del código localizado fuera de las declaraciones FUNCTION o PROCEDURE. De lo con-
trario el procedimiento MAIN aparece declarado muchas veces y no se crea un archivo ejecutable.
El segundo punto a considerar que los programas de Xbase++ siempre se ejecutan en múltiples planos de eje-
cución (threads). El plano que ejecuta el código del programa implementado tiene la mayor prioridad. Recibe
acceso a la CPU por preferencia por el sistema operativo. Esta es la razón por la que ciclos DO WHILE con en-
cuesta permanente deben evitarse. A saber:

DO WHILE Inkey() <> 0


<código de programa>
ENDDO

Consultando al teclado en esta forma conduce a una encuesta permanente debido a que no hay un estado de
espera en el ciclo. Esto es admitido bajo DOS pero contradice la arquitectura de un sistema operativo con derecho
de prioridad. El ciclo antedicho debe codificarse con Xbase++ así:

DO WHILE Inkey(0.1) <> 0


<código de programa>
ENDDO

Ahora, el ciclo tiene un estado de espera de una décima de segundo. Esto es suficiente para garantizar que todos
los threads de un programa sean ejecutados. Un programa Clipper debe buscarse para ciclos DO WHILE que
hacen encuestas permanentemente y no proveen un estado de espera. Algunas funciones para esto es la función
Sleep() .

La siguiente tabla da una vista general de los comandos que pueden conducir a incompatibilidades cuando un
programa de Clipper se compila usando Xbase ++.

Diferencias en los comandos


Clipper Xbase++
CALL No disponible
COPY..TO..SDF Xbase++ usa SDF como una extensión de archivo
para un archivo SDF de estructura extendida
DIR No disponible
LABEL FORM No disponible
REPORT FORM No disponible
SET FORMAT No disponible
SET FUNCTION No disponible
SET TYPEAHEAD TO 0 No permitido (min 10, max 100)
KEYBOARD Chr(0) Chr(0) será ignorado
RESTORE FROM Archivos MEM existentes no pueden ser leídos
RUN Inicia la ejecución de un nuevo programa

archivo FRM No hay soporte para el formato FRM


archivo LBL No hay soporte para el formato LBL
archivo MEM No hay soporte para el formato MEM

Cuando se usan los comandos para importar y exportar datos, las características de un Motor de Base de Datos
(DataBaseEngine - DBE) de Xbase++ deben tenerse en consideración. Los DBEs se cargan implícitamente
cuando se utilizan los comandos. Esto se aplica a los comandos como COPY TO ... SDF o APPEND FROM ... SDF.
El archivo de importación o exportación, respectivamente, es mantenido por el Motor de Base de Datos que se
carga en la memoria si no se encontraba en ella. En el caso de un formato de SDF éste es el Motor de Base de
Datos de formato SDF. Crea un archivo de estructura extendida con la extensión 'SDF'. Por esta razón, no se
permite el código siguiente:

USE Clientes
COPY TO Clientes.sdf SDF

En este caso, el Motor SDFDBE de Xbase++ crea implícitamente el archivo de estructura extendida 'Cliente.sdf'.
Tiene el mismo nombre que el archivo destino. Como resultado, se produce un error durante el tiempo de ejecu-
ción. En este punto se recomienda a todos los programadores Clipper leer las especificaciones del Conceptos
Básicos de Motores de Bases de Datos en la documentación de Xbase++. Los DBEs de Xbase++ difieren en
muchos aspectos de los RDDs de Clipper.

Xbase++ difiere de Clipper en que las entradas del teclado son registradas en la cola de eventos. El comando SET
TYPEAHEAD TO 0 en Xbase++ causa que todos los eventos de la cola se borren. El numero de eventos en la cola
de eventos no puede establecerse en 0 (cero) ya que esto conduciría a una suspensión del sistema. El mínimo
número de eventos que pueden guardarse en la cola de eventos es 10 (diez).
El comando KEYBOARD se traduce a un evento de teclado que se agrega a la cola de eventos. El carácter Chr(0)
es un evento invalido y se lo ignora. Esto puede conducir a problemas en los programas de Clipper que usan Chr(0)
para establecer el código de retorno de la función Lastkey() en un valor definido. Si este es el caso, debe usarse
otro carácter para este propósito, por ejemplo Chr(255). El preprocesador da una alternativa también:

#xtranslate KEYBOARD( Chr(0) ) => KEYBOARD Chr(255)


#xtranslate KEYBOARD Chr(0) => KEYBOARD Chr(255)
#xtranslate Lastkey() == 0 => Lastkey() == 255
#xtranslate Lastkey() = 0 => Lastkey() == 255

El formato de archivo MEM no esta respaldado por Xbase++. Esto significa que los archivos MEM existentes no
pueden leerse. En Xbase++, los archivos XPF reemplazan a los archivos MEM. Los objetos, arreglos y bloques de
códigos pueden guardarse usando este formato de archivo junto con los tipos de datos carácter, fecha, numérico y
lógico. Esto abre completamente una nueva dimensión para la comunicación entre las estaciones de trabajo en
una red. Empleando el formato de archivo XPF, los bloques de código pueden intercambiarse entre dos estaciones
de trabajo.

Diferencias en las funciones


Clipper Xbase++
AltD() No disponible
AEval() El Quinto parámetro determina si los elementos del
arreglo serán pasados por referencia al bloque de código
CurDir() Devuelve o cambia el directorio actual. No disponible
CurDrive() Devuelve o cambia la unidad de disco actual
File() Incluye un segundo parámetro "D", "H", "S"
FkLabel() No disponible
FkMax() No disponible
NoSnow() No disponible
ReadKey() No disponible
SetBlink() Sólo disponible en modo pantalla completa (modo VIO)
SetColor() Soporte de Atributo de Intensidad para color de fondo
Word() No disponible

En Xbase++, la función AEval() acepta un quinto parámetro que es un valor lógico. Este especifica si un elemento
del arreglo o un valor lógico por referencia. La siguiente línea crea un arreglo y llena los 10 elementos con sus
correspondientes índices:

aArreglo := AEval( Array(10), {|x,i| x:=i },,, .T.)

Las funciones CurDir() y File() se expandieron funcionalmente en Xbase++ (compararla a Clipper). La función
adicional CurDrive() se incluye también en Xbase++. El directorio actual y la unidad pueden modificarse usando
CurDir() y CurDrive() sin usar el comando RUN. Puesto que RUN opera de manera diferente bajo Xbase++ ini-
ciando otro interprete de comando, la técnica de Clipper no cambiara el directorio en la aplicación bajo Xbase++.
La función File() acepta un tipo de archivo como segundo parámetro. A continuación se muestra un ejemplo de la
función del archivo mejorado del código que chequea la existencia del directorio:

cDir := Space(64)
@ 10, 10 GET cDir
READ
IF ! File( Trim(cDir), "D")
Alert( "Directorio No válido")
ELSE
CurDir( cDir )
ENDIF

La función GetDoSetKey() programada en GETSYS.PRG llama a GetPostValidate() antes de evaluar un bloque de


códigos SetKey(). Así, Xbase++ garantiza que ningún dato invalido se escribe en los campos de las bases de
datos.

Palabras claves reservadas


Xbase++ tiene mas claves reservadas que Clipper. El uso una palabra clave reservada de Xbase++ como identi-
ficador de variables, funciones, etc. causa que el compilador de Xbase++ informe un error. Una lista de todas las
palabras reservadas se encuentra en la ayuda en línea en la sección "Elementos del lenguaje de Xbase++".
Diferencias en las variables de instancias y los métodos
Clipper Xbase++
oGet:assign oGet:_assign()
oGet:end oGet:_end()
oGet:badDate oGet:badDate()
oGet:minus oGet:minus()
oGet:picture Cuando no se indica, picture predefinido
oGet:subScript Siempre NIL
No disponible oGet:posInBuffer()
oTbrowse:end() oTBrowse:_end()
Parámetros para bloques de código de navegación:
EVAL( oTBrowse:skipBlock, nSkip, oTBrowse )
EVAL( oTBrowse:goTopBlock, oTBrowse )
EVAL( oTBrowse:goBottomBlock, oTBrowse )
oTBrowse:rowPos:=<n> No sincroniza con el origen de datos
pero mueve el cursor TBrowse
oTBrowse:nTop:=<n> :configure() debe ejecutarse subsecuentemente
oTBrowse:nLeft:=<n> :configure() debe ejecutarse subsecuentemente
oTBrowse:nBottom:=<n> :configure() debe ejecutarse subsecuentemente
oTBrowse:nRight:=<n> :configure() debe ejecutarse subsecuentemente
No disponible oTBrowse:firstScrCol()
No disponible oTBrowse:viewArea()

Se precede con un guión bajo a los nombres de los métodos :assign() y :end() de las clases Get y TBrowse en
Xbase++. Esto se concreta porque se reservan palabras claves para los identificadores previos. No es necesario
hacer cambios en los programas de Clipper porque el preprocesador de Xbase++ hace dicho cambio.

Clipper requiere la variable de instancia oGet:subScript para obtener las referencias correctas a los elementos del
arreglo utilizados como variables en el comando @...GET. Xbase++ usa, en cambio, el operador de referencia @
puesto que los elementos únicos del arreglo pueden pasarse por referencia. Por esta razón, oGet:subScript es
obsoleto y siempre contiene NIL. Para que se produzca en Xbase++ el mismo comportamiento que en Clipper,
debe asignarse un valor apropiado a oGet:subScript . Por ejemplo:

LOCAL aArreglo := {.t., .f.}


CLS
SET CURSOR ON
@ 10, 10 GET aArreglo[1] PICTURE "Y" SEND subScript := {1}
@ 11, 10 GET aArreglo[2] PICTURE "Y" SEND subScript := {2}
READ

Hay una diferencia esencial entre la clase TBrowse en Xbase++ y Clipper. Las asignaciones a la variable de
instancia :rowPos no sincronizan automáticamente con el puntero de registros en la base de datos. Si el cursor
TBrowse se reposiciona cambiando :rowPos , el puntero de registros de la fuente de datos debe moverse explí-
citamente. Además, el objeto TBrowse se pasa como un parámetro a los bloques de cód i-
gos :skipBlock , :goTopBlock y :goBottomBlock .

También se han agregado métodos a la clase Get y a la clase TBrowse, que se requieren para respaldar el control
del mouse.

Diferencias en los comandos y funciones de las bases de datos


Clipper Xbase++
DbSetDriver() DbeSetDefault()
RddList() DbeList()
RddSetDefault() DbeSetDefault()

SET EXCLUSIVE
Valor Predefinido es ON Valor Predefinido en OFF

SET EXACT
No se considera con Se considera con
SEEK / DbSeek() SEEK / DbSeek()

CREATE INDEX
La secuencia de La secuencia de ordenamiento de caracteres
ordenamiento de caracteres depende de SET COLLATION
depende de los módulos y SET LEXICAL
NTX???.OBJ que deben estar
enlazados

Archivo de estructura extendida


FIELD_LEN = 3 FIELD_LEN = 5

Field_len > 999 se Field_dec no se considera


codifica usando al campo para la longitud de un campo caracter
Field_dec

COMMIT se ejecuta
implícitamente al final No se ejecuta implícitamente
del programa

RDD versus DBE


La función de la base de datos DbSetDriver() y todas las funciones Rdd...() de Clipper no están disponibles en
Xbase++. Esto es porque Xbase++ no respalda el concepto propietario de una RDD (Replaceable Database Driver
- Manejador Reemplazable de Bases de Datos) monolítica. En vez de la aproximación a RDD, se utiliza en
Xbase++ el concepto de motor de base de datos que ofrece mucha mas flexibilidad para la manipulación de datos
y archivos. Debido a esta diferencia, Xbase++ ofrece las funciones de Dbe...() en vez de las funciones Rdd...() de
Clipper.

SET EXCLUSIVE
SET EXCLUSIVE está predeterminado en OFF en Xbase++. Esto significa que por predeterminación, s e abren las
bases de datos en modo SHARED para una operación multi-usuario (red) en Xbase++. Si una aplicación de
Clipper que no fue diseñada para acceso multi-usuario se recompila bajo Xbase++, el comando SET EXCLUSIVE
ON necesita insertarse en la rutina de inicio antes que se abra cualquier base de datos.

SET EXACT
Cuando se usan SEEK y DbSeek() para buscar valores de carácter en una base de datos, Xbase++ usa el valor de
SET EXACT para determinar si los espacios en blanco al final de la cadena de caracteres debería ignorarse
cuando se comparan los valores de caracteres.

CREATE INDEX
Xbase++ soporta tablas de peso. Las tablas de peso asignan factores de peso a caracteres simples. Esto permite
que el ordenamiento de los caracteres sea definido por el usuario. Una tabla de peso se utiliza para comparación
de cadenas así como la creación de índices. Una tabla de peso es extremadamente importante cuando se crean
archivos de índice para acceder a los mismos desde Xbase++ y Clipper. En este caso, Xbase++ debe usar la
misma tabla de peso que Clipper. En Clipper, el orden de los caracteres se define en el tiempo de enlace por un
módulo de nación y no puede cambiarse durante el tiempo de ejecución.
Clipper incluye varios archivos NTX???.OBJ que deben enlazarse al EXE con el fin de definir el ordenamiento
pais-específico de caracteres. Para alcanzar el mismo sentido de ordenamiento con Xbase++, la tabla de peso
correspondiente debe activarse usando el comando de SET COLLATION TO. La siguiente tabla lista todas las
diferencias específicas del lenguaje entre Clipper y Xbase++.

Ordenamiento lenguaje-específico de caracteres


Lenguaje Módulo Clipper Comando Xbase++
Inglés Americano No disponible SET COLLATION TO AMERICAN
Inglés Británico No disponible SET COLLATION TO BRITISH
Danés NTXDAN.OBJ SET COLLATION TO DANISH
Holandés NTXDUT.OBJ SET COLLATION TO DUTCH
Finlandés NTXFIN.OBJ SET COLLATION TO FINNISH
Francés NTXFRE.OBJ SET COLLATION TO FRENCH
Alemán NTXGER.OBJ SET COLLATION TO GERMAN
Griego 437 NTXGR437.OBJ SET COLLATION TO GREEK437
Griego 851 NTXGR851.OBJ SET COLLATION TO GREEK851
Islandés 850 NTXIC850.OBJ SET COLLATION TO ICELANDIC850
Islandés 861 NTXIC861.OBJ SET COLLATION TO ICELANDIC861
Italiano NTXITA.OBJ SET COLLATION TO ITALIAN
Noruego NTXNOR.OBJ SET COLLATION TO NORWEGIAN
Portugués NTXPOR.OBJ SET COLLATION TO PORTUGUESE
Español NTXSPA.OBJ SET COLLATION TO SPANISH
Sueco NTXSWE.OBJ SET COLLATION TO SWEDISH
Sistema No disponible SET COLLATION TO SYSTEM
ASCII SET COLLATION TO ASCII
Lo predeterminado para SET COLLATION TO se define en DBESYS.PRG. Depende de la versión especifica de
país de Xbase++. Si los archivos índice deben accederse desde Xbase++ así como desde Clipper, debe incluir las
siguientes líneas en su código:
#ifdef __XPP__
SET COLLATION TO <su país>
#endif

El acceso de archivos de índice desde Xbase++ y Clipper al mismo tiempo requiere usar el mismo sentido de
ordenamiento para los caracteres. Cuando un programa de Clipper usa un sentido de ordenamiento diferente que
el de un programa de Xbase++, archivos de índice se corromperá tarde o temprano.

CREATE FROM
El formato de un archivo DBF de estructura extendida es diferente en Xbase++. La longitud del campo FIELD_LEN
es 5, no 3 como en Clipper. Esto permite el ingreso de una máxima longitud posible de campo directamente. No
será necesario usar el campo espacio decimal para ingresar longitudes de campo mas largas que 999 caracteres.

Archivos Memo
El máximo numero de caracteres que puede guardarse en un campo memo no esta limitado a 64 KB bajo Xbase++.
Si se acceden campos memo en un entorno multi-usuario desde Clipper y Xbase++, la limitación de 64 KB debe
implementarse en Xbase++.

COMMIT al final del programa


Cuando termina un programa de Clipper, encomienda a todas las actualizaciones de registros pendientes a las
bases de datos aun abiertas (el comando COMMIT se ejecuta implícitamente). Este no es el caso con Xbase++.
En cambio, el comportamiento de Clipper se programa en el archivo APPEXIT.PRG que contiene el procedimiento
EXIT PROCEDURE AppExit() implícito. Este último se llama cuando termina un programa y puede modificarse
para alcanzar las necesidades del programador.

RETURN y QUIT
Un parámetro numérico opcional puede especificarse para QUIT. Se lo transfiere a la función ErrorLevel() antes de
que termine una aplicación. Lo mismo puede alcanzarse si la función Main() devuelve in valor numérico con la
declaración RETURN.

Otras diferencias
Clipper Xbase++
Sin procedimiento Main PROCEDURE Main debe ser el primer
procedimiento de la aplicación
Procedimientos INIT Implícitos en:
APPSYS.PRG
ERRORSYS.PRG ERRORSYS.PRG
RDDSYS.PRG DBESYS.PRG

Procedimientos EXIT Implícitos en:


No disponible APPEXIT.PRG

función @E en PICTURE Los Números se muestran en el formato específico


del país según el sistema operativo

Transform(1.23,"@N") Coma Decimal y Miles


el separador es configurable
Time() Los separadores entre HH:MM:SS
son configurables

No disponible SetLocale() configura datos específicos del país

Constantes para la función Set()


_SET_DEBUG Estas constantes no
_SET_SCROLLBREAK son soportadas

Nuevas constantes para Set()


No disponible _SET_CHARSET
_SET_COLLATION
_SET_LEXICAL
_SET_TIME
Se utiliza la configuración del sistema operativo para determinar los separadores usados en el formato numérico y
de fecha, tanto como el valor devuelto por la función Time(). Entonces, el formato @E de PICTURE es obsoleto e
ignorado por Xbase++. Sin embargo, la puesta de numerosos específicos de país pueden configurarse con la
función SetLocale().

Nuevas funciones en Xbase++ (desconocidas en Clipper)


Esta sección lista todas las funciones, comandos y declaraciones que son nuevas en Xbase++ comparadas con
Clipper.

Nuevas funciones en Xbase++


Nombre Descripción
AppDesktop() Devuelve la ventana del escritorio de Windows
AppEvent() Lee eventos de la cola de eventos
AppKeyState() Determina el estado de teclas especiales
AppName() Devuelve el nombre del EXE
AppType() Determina el tipo de aplicación
Bin2f() Convierte una cadena binaria a un numero FLOAT
Bin2u() Convierte una cadena binaria a un entero LONG sin signo
Bin2Var() Convierte una cadena binaria a cualquier tipo de dato
Char2Var() Convierte una cadena binaria a otro tipo de dato
ClassCreate() Crea una clase dinámicamente durante el tiempo de ejecución
ClassDestroy() Libera una clase de objeto dinámica
ClassObject() Devuelve la clase de objeto de cualquier clase
ConfirmBox() Despliega la caja de diálogo GUI para confirmación del usuario
ConvToAnsiCP() Convierte una cadena a ANSI
ConvToOemCP() Convierte una cadena a OEM
CurDrive() Devuelve la letra actual de la unidad
DispOutAt() Salida de la pantalla sin cambio de la posición del cursor
DllCall() Llama a una función contenida en un DLL
DllExecuteCall() Llama una función DLL usando una plantilla de llamadas
DllInfo() Devuelve información sobre uno o mas DLLs
DllLoad() Carga un DLL dinámicamente durante el tiempo de ejecución
DllPrepareCall() Prepara una plantilla de llamadas para una función DLL
DllUnLoad() Descarga un DLL
EnableClipRect() Permite el manejo de modo de pantalla de texto virtual
F2bin() Convierte un numero FLOAT a binario
GetEnableEvents() Habilita al mouse para GETs
GetEventReader() GETs usa AppEvent() en vez de Inkey()
GetHandleEvent() Manejo de evento predeterminado para GETs
GetKillActive() Toma foco de GET activo
GetList() Devuelve el arreglo GetList actual
GetListPos() Devuelve la posición del GET actual en GetList
GetToMousePos() Mueve el cursor para el mouse dentro de GETs
IsFieldVar() Chequea si existe el campo
IsFunction() Chequea si existe función
IsMemberVar() Chequea si el objeto tiene una variable de miembro específico
IsMemvar() Chequea si existe la variable de memoria
IsMethod() Chequea si el objeto tiene un método específico
LastAppEvent() Devuelve el último evento
LChar2Var() Convierte una cadena local a otro tipo de dato
MsgBox() Despliega una caja de mensajes GUI
NextAppEvent() Devuelve el próximo evento
NumButtons() Numero de botones del mouse
PostAppEvent() Envía un evento a la cola de eventos
PValue() Recupera el valor del n-simo parámetro
RunRexx() Ejecuta un archivo de comando REXX
RunShell() Abre un nuevo interprete de comando
SetAppEvent() Asocia un evento con un bloque de códigos
SetAppFocus() Establece foco a una ventana o control GUI
SetAppWindow() Devuelve la ventana de aplicación
SetClipRect() Define el área de manejo para modo de texto
SetEnv() Define una variable de entorno
SetLexRule() Define reglas de comparación léxica para los caracteres
SetLocale() Función para localización
SetMouse() Permite los eventos del mouse en modo de texto
SetTimerEvent() Inicia un temporizador de thread
Sleep() Detiene el thread actual por un tiempo específico
SymbolInfo() Devuelve información sobre símbolos de función
TBApplyKey() Manipulador predeterminado Inkey() para Tbrowse
TBHandleEvent() Manipulador predeterminado AppEvent() para Tbrowse
TBtoMousePos() Mueve el cursor de TBrowse al puntero del mouse
ThreadID() Devuelve ID del thread actual
ThreadObject() Devuelve el objeto Thread actual
ThreadWait() Espera para que termine un thread
ThreadWaitAll() Espera para que terminen múltiples threads
U2bin() Convierte un entero LONG a binario
Var2Bin() Convierte cualquier tipo de dato a binario
Var2Char() Convierte cualquier tipo de dato a una cadena de caracteres
Var2LChar() Convierte cualquier tipo de datos a una cadena local

Nuevas funciones relacionadas


de bases de datos
Nombre Descripción
DbCargo() Añade un valor arbitrario a un área de trabajo usada
DbClientList() Devuelve todos los clientes registrados de un área de trabajo
DbContinue() Equivalente funcional de CONTINUE
DbCopyExtStruct() Equivalente funcional de COPY STRUCTURE EXTENDED
DbCopyStruct() Equivalente funcional de COPY STRUCTURE
DbCreateExtStruct() Equivalente funcional de CREATE STRUCTURE EXTENDED
DbCreateFrom() Equivalente funcional de CREATE FROM
DbDeregisterClient() Remueve un cliente registrado de un área de trabajo
DbeBuild() Construye un Motor de Base de Datos compuesto
DbeInfo() Devuelve información sobre un Motor de Base de Datos
DbeList() Devuelve los nombres de Motor de Base de Datos cargados
DbeLoad() Carga un Motor de Base de Datos
DbeSetDefault() Selecciona el Motor de Base de Datos predeterminado
DbeUnload() Libera un Motor de Base de Datos de la memoria
DbExport() Equivalente funcional de COPY TO
DbGoPosition() Mueve el puntero de registros usando un valor porcentual
DbImport() Equivalente funcional de APPEND FROM
DbInfo() Devuelve información sobre un área de trabajo
DbJob() Asocia un área de trabajo con un bloque de códigos
DbJoin() Equivalente funcional de JOIN
DbList() Equivalente funcional de LIST
DbLocate() Equivalente funcional de LOCATE
DbPack() Equivalente funcional de PACK
DbPosition() Devuelve la posición del puntero de registros como un valor
porcentual
DbRegisterClient() Registra un cliente en un área de trabajo
DbRelease() Libera a un área de trabajo del espacio de trabajo actual
DbRequest() Transfiere un área de trabajo a un espacio de trabajo actual
DbResetNotifications() Permite notificaciones desde áreas de trabajo a los clientes
DbRList() Devuelve una lista de relaciones
DbROrdName() Devuelve el nombre de índice de una relación
DbSkipper() Función skipper DBF predeterminada para TBrowse
DbSort() Equivalente funcional de SORT
DbSuspendNotifications() Inhabilita notificaciones desde áreas de trabajo a clientes
DbTotal() Equivalente funcional de TOTAL
DbUpdate() Equivalente funcional de UPDATE
DbZap() Equivalente funcional de ZAP
FieldInfo() Devuelve información del campo
OrdIsDescend() Chequea si el índice esta descendiendo
OrdIsUnique() Chequea si el índice es único
WorkSpaceEval() Evalúa un bloque de códigos en todas las áreas de trabajo
usadas
WorkSpaceList() Devuelve el nombre de alias de todas las áreas de trabajo
usadas
Nuevas Directivas, Declaraciones y Comandos
Nombre Descripción
#pragma Permuta parámetros del compilador durante el tiempo de compilación
ACCESS | ASSIGN Declara métodos de acceso/asignación
APPBROWSE Parte de aplicación (GUI browser)
APPDISPLAY Despliega partes de aplicación
APPEDIT Parte de aplicación (ventana de Edición)
APPFIELD Declara un campo para una parte de aplicación
CLASS Declara una clase
CLASS METHOD Declara un método de clase
CLASS VAR Declara una variable de clase
DEFERRED Declara un método como diferido
DLLFUNCTION Crea una función que llama a una función DLL
FINAL Declara un método como final
INLINE Declara un método en línea
METHOD Declara un método
SET CHARSET Selecciona conjuntos de caracteres ANSI u OEM
SET COLLATION Establece tabla de ordenamiento especifica de país
SET LEXICAL Permite una comparación léxica de reglas para cadenas
SET TIME Establece un formato de despliegue para el tiempo del sistema
SYNC Declara un método como sincronizado
VAR Declara una variable de instancia

Xbase++ Funciones de clase


Nombre Descripción
DacPagedDataStore() Provee cache de acceso a bases de datos
DacSession() Establece conexiones con servidores de bases de datos
DataRef() Clase para referenciar datos
Error() Clase Error
Get() Clase Get
TBrowse() Clase TBrowse
TbColumn() Clase TBColumn
Thread() Clase Thread
Signal() Clase Signal
VCrt() Clase Pantalla Virtual para modo de Texto
Xbp3State() Clase Botón Tres Estados
XbpBitmap() Clase Bitmap
XbpBrowse() Clase GUI browser
XbpCheckBox() Clase Casilla de Confirmación
XbpClipBoard() Clase Papelero
XbpColumn() Clase Columna para el Browser Gráfico
XbpComboBox() Clase Combobox
XbpCrt() Clase Ventana híbrida
XbpDialog() Clase Ventana GUI
XbpFileDev() Clase Dispositivo de archivo
XbpFileDialog() Diálogo de selección de archivo
XbpFont() Clase Fuente Tipográfico
XbpFontDialog() Diálogo de selección de Fuente Tipográfico
XbpHelp() Clase Ventana de Ayuda en Línea
XbpHelpLabel() Clase Ayuda sensible de contexto
XbpIWindow() Ventana implícita
XbpListBox() Clase Lista Desplegable
XbpMenu() Clase Menú
XbpMenuBar() Clase Barra del Menú
XbpMetaFile() Clase Metarchivo
XbpMLE() Clase Edición Multi Línea
XbpPartHandler() Clase Padre para Componentes Xbase
XbpPresSpace() Espacio de Presentación
XbpPrinter() Clase Impresora
XbpPrintDialog() Clase Diálogo de Selección de la impresora
XbpPushButton() Clase Botón
XbpQuickBrowse() Clase GUI browser
XbpRadioButton() Clase Botón
XbpScrollBar() Clase Barra de desplazamiento
XbpSetting() Clase Abstracta para parámetros
XbpSLE() Clase Editor de una sola línea
XbpSpinButton() Clase Botón Giratorio
XbpStatic() Clase Control Estático GUI
XbpSysWindow() Clase Abstracta para diálogos del sistema
XbpTabPage() Clase Control de Solapas
XbpTreeView() Clase Vista en Arbol
XbpTreeViewItem() Elemento de una Vista en Arbol
XbpWindow() Clase Abstracta de ventana

Funciones del Motor Gráfico - GraphicsEngine


Nombre Descripción
GraArc() Dibuja un arco, circulo o elipses
GraBitBlt() Copia un bitmap
GraBox() Dibuja un rectángulo
GraError() Devuelve el último código de error
GraGetRGBIntensity() Determina intensidades de color RGB
GraIsRGBColor() Chequea si un valor de color es un color RGB
GraLine() Dibuja una línea
GraMakeRGBColor() Crea un valor de color RGB
GraMarker() Dibuja un color de marcador
GraPathBegin() Inicia un trazado gráfico
GraPathClip() Recorta un trazo gráfico
GraPathEnd() Cierra un trazado gráfico
GraPathFill() Rellena un trazado gráfico
GraPathOutline() Bordea un trazado gráfico
GraPos() Establece la posición del cursor gráfico
GraQueryTextBox() Calcula las coordenadas para cadenas
GraRotate() Rota la salida gráfica
GraScale() Escala la salida gráfica
GraSegClose() Cierra un segmento gráfico
GraSegDestroy() Libera un segmento gráfico
GraSegDraw() Dibuja un segmento gráfico
GraSegDrawMode() Establece el modo dibujar para segmentos de gráfico
GraSegFind() Encuentra segmentos gráficos
GraSegOpen() Abre un segmento gráfico
GraSegPickResolution() Establece sensibilidad para hallar segmentos gráficos
GraSegPriority() Establece prioridad para segmentos gráficos
GraSetAttrArea() Establece atributos para áreas de dibujos
GraSetAttrLine() Establece atributos para líneas de dibujo
GraSetAttrMarker() Establece atributos para marcadores de dibujo
GraSetAttrString() Establece atributos para cadenas gráficas
GraSetColor() Establece el color para todas las funciones gráficas
GraSetFont() Establece la fuente para cadenas gráficas
GraSpline() Dibuja una curva
GraStringAt() Dibuja una cadena
GraTranslate() Desplaza la salida gráfica

Diferencias entre Class(y) y Xbase++


La Biblioteca Class(y) es el producto líder de terceros para programación orientada a objeto bajo Clipper. La
sintaxis de Xbase++ para el modelo de objetos es similar a la de Class(y), pero no copia la sintaxis porque el
modelo de objetos y las implementaciones son diferentes. La diferencia más importante es que el compilador de
Xbase++ cuida las declaraciones de clase en vez del preprocesador de Clipper, como es utilizado por la Class(y).
Debido a como el preprocesador es empleado por la Class(y), solo una clase puede declararse en cada archivo
PRG. Xbase++ permite que cualquier numero de clases sea declarada en un único archivo. La biblioteca Class(y)
traduce cada METHOD como una STATIC FUNCTION y la declaración CREATE CLASS a una FUNCTION.
Xbase++ maneja las cosas de manera diferente. El compilador crea una función de clase desde la declaración
CLASS y crea referencias para implementaciones de métodos, y no STATIC FUNCTIONs. En Xbase++, un mé-
todo difiere de una función estática porque la variable self referencia implícitamente al objeto que esta ejecutando
el método. La variable self no puede declararse o modificarse dentro de los métodos. La asignación de un valor a
self o el pase de self a una función usando el operador de referencia genera en error del compilador de Xbase++.
Una variable de instancia importante en la Class(y) es ::super . Esta variable de instancia no existe en Xbase++
puesto que no es apropiada para identificar claramente objetos de la superclase en escenarios de herencia múl-
tiple. Xbase++ accede a las variables de miembro y a los métodos de cada superclase usando el nombre de la
superclase. Ejemplo:
CLASS DataDialog FROM XbpDialog
EXPORTED:
METHOD init
<...>
ENDCLASS

METHOD DataDialog:init( parámetros )


::xbpDialog:init( parámetros )
<...>
RETURN self

El método :init() de la superclase debe llamarse por este "molde" explícito en Xbase++. Cualquier método visible
en cualquier superclase puede llamarse explícitamente especificando el nombre de la clase en la llamada al
método.

Otra diferencia importante entre Class(y) y Xbase++ es en efecto que los métodos de clase y las variables de clase
pueden accederse a través del objeto de clase bajo la Class(y). Bajo Xbase++, cada instancia de una clase tiene
acceso a métodos de clase y a variables de clase. La llamada a un método de clase puede darse vía un objeto
(instancia) de una clase en Xbase++. Las variables de clase son accesibles a través de instancias. Xbase++ no
requiere la declaración de MESSAGE...TO CLASS en la clase, para delegar variables de clase y llamadas de
métodos a la clase objeto.

Ya que la sintaxis de Xbase++ para declarar clases y métodos es tan similar a la sintaxis de la biblioteca Class(y),
la migración del código de la Class(y) a Xbase++ es relativamente fácil con ayuda del preprocesador. Como
ejemplo, las directivas del preprocesador incluidas debajo traducen el código de la Class(y) a Xbase++:

/* Class(y) Xbase++ */
#ifdef __XPP__

#command CREATE CLASS <x> [FROM <y>] => CLASS <x> [FROM <y>]
#command MESSAGE <x> METHOD <y> => METHOD <x> IS <y>
#command CLASS MESSAGE <x> METHOD <y> => CLASS METHOD <x> IS <y>
// Nombre de la superclase
#xtrans :super => :<superClassName>

#else
#include "Class(y).ch"
#endif

Si el código de la biblioteca Class(y) ha de ser portado a Xbase++, el preprocesador puede usarse para acomodar
diferencias en la sintaxis. La directiva #ifdef admite la compilación del mismo código usando tanto Clipper/Class(y)
o Xbase++. El archivo PRG conteniendo la declaración de la Class(y) solo necesita compilarse usando las direc-
tivas del preprocesador que traducen el código existente al código valido de Xbase++.

Usando el mouse
El primer paso para migrar programas existentes de Clipper a un sistema operativo de 32 bit es integrar el mouse
para la navegación del programa. En Xbase++, todas las funciones y comandos del diálogo pueden controlarse
usando el mouse. Esto incluye @...SAY...GET, MENU TO, Alert(), TBrowse(), y Memoedit(). Para proveer soporte
para el mouse la función SetMouse(.T.) necesita insertarse en la rutina de inicio del programa. Si se incluye esto, la
función AppEvent() se utiliza para recuperar eventos en vez de la función Inkey(), que lee solo lo que ingresa del
teclado. Ambas funciones devuelven un valor numérico correspondiente tanto al código de tecla de la tecla pre-
sionada (Inkey()) o la identidad del último evento que ocurrió (AppEvent()). Con Clipper, Inkey() es la función usada
generalmente para recuperar lo que ingresa a través del teclado. Esta función sólo existe para compatibilidad en
Xbase++. Xbase++ reemplaza a Inkey() con la función AppEvent() para leer eventos de la cola de eventos.
Los valores de retorno de AppEvent() e Inkey() pueden ser diferentes cuando se presiona la misma tecla, porque el
código de DOS Inkey() no es compatible con el código de AppEvent(). Por esta razón, la transición al soporte del
mouse usando SetMouse(.T.) también requiere que las llamadas a SetKey() sean reemplazadas por llamadas a la
función SetAppEvent(). Los Bloques de códigos que están asociados a teclas usando SetKey() deben asociarse a
la misma tecla usando SetAppEvent(). La constante #define utilizada para identificar teclas también es diferente.

Por ejemplo:
SetKey( K_F10, {|| HacerAlgo() } ) // Clipper
SetAppEvent( xbeK_F10, {|| HacerAlgo() } ) // Xbase++
La primera línea contiene al código Clipper. Esta llamada asocia a la tecla de función F10 a un bloque de códigos.
Este código también trabaja correctamente en Xbase++ mientras el mouse no este activo. Luego que se llama a
SetMouse(.T.) para activar al mouse, el bloque de códigos {|| HacerAlgo() } no será ejecutado cuando se presiona
la tecla F10. Para garantizar la ejecución del bloque de códigos apropiado después de que se haya presionado la
tecla F10, el bloque de códigos debe reasociarse al evento de código xbeK_F10 mas que al código de tecla K_F10
usando SetAppEvent().

El diseñador de formularios de Xbase++ - XPPFD.EXE


El diseñador de formularios de Xbase++ es una herramienta poderosa diseñada para darle soporte al programador
en el desarrollo de aplicaciones GUI (Interfaz Gráfica del Usuario). El diseñador de formularios está escrito com-
pletamente en Xbase++ lo que demuestra la versatilidad de este paquete de desarrollo. Este capítulo describe
cómo utilizar y trabajar con el diseñador de formularios y cómo el código generado puede integrarse en un pro-
grama de aplicación.

Componentes del diseñador de formularios


Trabajando con el diseñador de formularios
El código FUNCTION
El código CLASS
Usando el código CLASS

Componentes del diseñador de formularios


El diseñador de formularios se inicia haciendo un doble-click en el ícono correspondiente en la carpeta de
Xbase++ o ingresando XPPFD en la línea de comandos. La ventana principal del diseñador de formularios se
despliega inicialmente junto con un formulario en blanco. El nuevo formulario es una ventana XbpDialog en la que
los controles GUI o los Componentes de Xbase se insertan. Los Componentes de Xbase se seleccionan clic-
keando los íconos correspondientes en la ventana principal del diseñador de formularios.

Ventana principal del diseñador de formularios

La ventana principal se divide en tres secciones: barra del menú, paleta de herramientas y línea de estado. Todas
las funciones del diseñador de formularios pueden seleccionarse vía la barra del menú. Las funciones principales
se integran en la paleta de herramientas y son accesibles clickeando los íconos correspondientes con el ratón.
Para un uso confortable, los íconos están contenidos en tres páginas de solapa (XbpTabpage).
La ventana principal se complementa por ventanas suplementarias que se abren vía el sistema del menú y no se
visualizan permanentemente. La siguiente tabla da un vistazo:

Ventanas adicionales del diseñador de formularios


Menu item Ventana
Edit
- Sequence Cambia las secuencias de los Componentes de Xbase
en un formulario

- Symbols for iVar Define símbolos para variables de instancia


usadas en códigos orientados a objeto
Assistants
- Fields Selecciona archivos y campos de bases de datos

Options
- Settings Define las opciones predeterminadas para un formulario y
para el diseñador de formularios

- Alignment pallette Contiene íconos para la alineación predeterminada de


los Componentes de Xbase dentro del rectángulo de marcación
- Property monitor Cambia las propiedades de los Componentes de Xbase contenidos
en un formulario

- Resolution monitor Exhibe el formulario en comparación con


las diferentes resoluciones de pantalla

Help
- Help index (F1) Despliega la ayuda en línea del diseñador de formularios

Nota: el diseñador de formularios tiene ayuda en línea sensible al contexto que se activa presionando la tecla F1.
Para obtener información detallada, la ventana debe tener foco antes que se presione F1.

Trabajando con el diseñador de formularios


Trabajando con el diseñador de formularios involucra siempre los siguientes pasos distintivos: selección de los
controles o los campos de una base de datos, arreglo en el formulario, guardar el formulario, y la creación del
código de un programa. La forma más fácil para la selección de controles de Componentes de Xbase, respecti-
vamente, es clickeando un ícono correspondiente en la paleta de herramientas del diseñador de formularios.
Luego un recuadro de inserción se despliega en el formulario, que debe posicionarse con el ratón. Un clic con el
botón derecho marca la posición de inserción y crea un Componente de Xbase en este punto, en el formulario. Si
deben insertarse los campos de bases de datos, la ventana de selección de campos debe abrirse vía los ítems del
menú "Assistents->Fields". Los nombres de múltiples campos pueden seleccionarse en la ventana. Dependiendo
del tipo de dato del campo, los diferentes Componentes de Xbase que acceden al campo de una base de datos se
crean en el formulario. Deben posicionarse con el recuadro de inserción.

El diseñador de formularios tiene dos modos de posicionar al recuadro de inserción: orientado-a-pixel y orienta-
do-a-grilla. El modo de posicionamiento se selecciona en el diálogo de opciones: "Use grid" ("Usar grilla") que se
abre vía los ítems del menú "Options->Settings". Este diálogo define opciones para configurar al diseñador de
formularios. Una opción, por ejemplo, es el tamaño de la grilla usado para el posicionamiento. La grilla debe
desactivarse para posicionar un Componente de Xbase exactamente en la punta del puntero del ratón.

Mientras se diseña un formulario, normalmente es necesario reposicionar los Componentes de Xbase insertados,
agruparlos usando los elementos estáticos como las líneas o los marcos, o llevar a cabo otros cambios. El for-
mulario puede modificarse confortablemente por medio de un recuadro de selección que permite modificar la
apariencia completa del formulario con sólo algunos clics del ratón. El recuadro de selección selecciona uno o más
Componentes de Xbase para ser modificados. La posición y el tamaño del recuadro de selección definen la po-
sición y el tamaño de los elementos marcados. Si se le altera el tamaño al recuadro, diferentes opciones estarán
disponibles para definir cómo los Componentes marcados de Xbase deben escalarse. Estas opciones se selec-
cionan en la ventana "Options->Settings". Por ejemplo, el espacio entre los Componentes de Xbase puede es-
calarse, o su tamaño, o ambos. Si los controles empotrados en los Componentes de Xbase (hijos) deberían o no
escalarse, es configurable también. Una variedad de opciones de alineamiento estandarizadas puede seleccio-
narse en la ventana "Alignment" (Alineación). Por ejemplo, el espaciado entre Componentes Xbase puede ser
escalado, o su tamaño, o ambos. Todos los Componentes de Xbase marcados pueden centrarse en el recuadro
de selección o dárseles el mismo ancho sólo clickeando al ícono correspondiente.

Las relaciones padre-hijo entre los Componentes de Xbase pueden definirse o modificarse fácilmente. Las op-
ciones varias existen para este propósito en el menú de contexto del formulario que se abre por un clic con el botón
derecho del ratón.

Opciones en el menú de contexto


Opción Descripción
Group XBPs Crea un Recuadro de Grupo como el padre para todos los Componentes de
Xbase marcados
Ungroup XBPs Remueve el Recuadro de Grupo y define el formulario como el padre para
los Componentes de Xbase contenidas en el Recuadro de Grupo
Assign parent Define un Componente de Xbase como el padre para todos los elementos
marcados
Release parent Libera una relación padre-hijo sin borrar al padre
To back Mueve todos los Componentes de Xbase a segundo plano
To front Mueve todos los Componentes de Xbase marcados a primer plano
Repaint all Redibuja el formulario completo

Los Componentes de Xbase se agrupan usando un Recuadro de Grupo por predeterminación (un elemento
XbpStatic del tipo XBPSTATIC_TYPE_GROUPBOX). Se lo utiliza como el padre para los Componentes de Xbase.
Después de la definición de un grupo sólo el padre puede marcarse de esta manera, y no los Componentes
empotrados de Xbase++. Para seleccionar o modificar los hijos de un Componente de Xbase, es necesario liberar
la relación padre-hijo.

Clickeando los ítems del menú "Options->Property Monitor" se abre una ventana que se utiliza para la visualiza-
ción de la jerarquía padre- hijo y se la equipa con un browser que despliega las propiedades del Componente
actual de Xbase. Si se modifican las propiedades en el browser el Componente de Xbase se actualiza con datos
nuevos.

Cuando se diseña completamente un formulario, puede almacenarse opcionalmente o puede generarse un código
en ambos estilos: funcional u orientado a objeto. Este código puede modificarse aún más usando un editor de texto.
En general se recomienda almacenar el formulario en caso que necesite modificarse posteriormente, o para que
sirva de templado para otros formularios. El diseñador de formularios almacena un formulario en archivos binarios
que pueden cargarse en un punto tardío en el tiempo. Se selecciona en el sistema del menú ("Form ->FUNCTION
Code" (Código orientado a Funciones) o "Form->CLASS Code" (Código Orientado a Objetos)) el tipo de código
fuente que genera el diseñador de formularios. Como alternativa, el tipo predeterminado se crea clickeando un
ícono en la paleta de herramientas. El tipo de código fuente predeterminado se selecciona en la ventana Settings.

El código FUNCTION
Antes que el diseñador de formularios genere el código fuente, es necesario ordenar los Componentes de Xbase
en el formulario. Deben aparecer en el orden que corresponda a la secuencia a la que accede un usuario usando
la tecla TAB. Normalmente el código fuente para los Componentes de Xbase++ se crea en la misma secuencia en
la que se lo inserta en el formulario. Esta secuencia puede no ser equivalente a la interacción con el usuario.
Puede chequearse o modificarse en la ventana de Secuencia (ítems del menú: Edit->Sequence). El código fuente
debería crearse sólo si la secuencia de Componentes de Xbase corresponde con la interacción con el usuario.

Un formulario de ejemplo en el directorio ..\SAMPLES\BASICS\XPPFD que fue creado por el diseñador de for-
mularios. Se lo utiliza como ejemplo para dar más explicaciones y se lo muestra debajo:

Ejemplo para un formulario de cliente

El formulario contiene dos XbpSLEs agrupadas para editar los campos de bases de datos. botones están orga-
nizados debajo de la caja Grupo para la navegación de bases de datos y para cerrar el formulario. Ambos tipos de
código fuente para este formulario, funcional y orientado a objeto, existen en el directo-
rio ..\SAMPLES\BASICS\XPPFD. Puede compilarse y ejecutarse. Para que el código fuente funciones correcta-
mente el diseñador de formularios hace algunas suposiciones simplificantes que deben considerarse cuando se
integra el código a un programa de aplicación.

El código funcional comienza con todas las directivas #include necesarias y declara el procedimiento Principal más
algunas variables LOCAL. Luego todas las bases de datos que son accesibles por un formulario se abren con el
comando USE. El formulario de ejemplo usa sólo una base de datos:

#include "Gra.ch"
#include "Xbp.ch"
#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvent, mp1, mp2
LOCAL oDlg, oXbp, DrawingArea, oXbp1, aEditControls := {}
// Path está abreviado solamente en la documentación.
USE ..\SAMPLES\BASICS\XPPFD\CLIENTE.DBF NEW EXCLUSIVE
La variable LOCAL aEditControls se inicializa con un arreglo vacío. Se la utiliza para recolectar todos los Com-
ponentes de Xbase que acceden a los campos de una base de datos. Provee una forma cómoda de acceder a
todos los Componentes de Xbase en una función definida por el usuario que edita datos en el formulario.

Ya que diseñador de formularios no puede hacer suposiciones sobre las estrategias de bloqueo de un registro en
un entorno multi-usuario, se abren las bases de datos para acceso exclusivo. Por tanto, las líneas del programa
que abren las bases de datos con el comando USE están más inclinadas a modificarse cuando el código fuente
está integrado a un programa de aplicación.

Después de abrirse una base de datos, se crea el formulario como una instancia de la clase XbpDialog y se le
asigna a la variable LOCAL una referencia al área de dibujo.

oDlg := XbpDialog():new( AppDesktop(), , {148,98}, {334,182}, , .F.)


oDlg:border := XBPDLG_SIZEBORDER
oDlg:title := "New Form"
oDlg:create()
drawingArea := oDlg:drawingArea
drawingArea:setFontCompoundName( "8.Helv.normal" )

Estas líneas de código definen padre, posición y tamaño del formulario más el tipo de borde, título de la ventana y
fuente. Este código necesita ser modificado probablemente, excepto por la posición y el tamaño. Especialmente la
ventana padre debería chequearse si es correcta. El diseñador de formularios asume que la ventana desktop será
empleada como padre y escribe la función AppDesktop() para recuperarlo.

La variable LOCAL drawingArea hace referencia al área de dibujo del formulario. Se la utiliza como padre para los
Componentes de Xbase subsecuentes:

oXbp1 := XbpStatic():new( drawingArea, , {12,48}, {300,96} )


oXbp1:caption := "Cliente"
oXbp1:type := XBPSTATIC_TYPE_GROUPBOX
oXbp1:create()

Estas cuatro líneas de código crean la caja Grupo usada para agrupar la entrada a los campos en el formulario.
Como sirve de padre para el XbpSLE, se le asigna al objeto XbpStatic a una variable LOCAL separada oXbp1 .
Esta variable se usa en el código fuente para definir el padre para los objetos XbpSLE:

oXbp := XbpStatic():new( oXbp1, , {12,48}, {72,24} )


oXbp:caption := "Apellido:"
oXbp:options := XBPSTATIC_TEXT_VCENTER+XBPSTATIC_TEXT_RIGHT
oXbp:create()
oXbp := XbpSLE():new( oXbp1, , {96,48}, {192,24} )
oXbp:bufferLength := 20
oXbp:tabStop := .T.
oXbp:dataLink := {|x| IIf( PCount()==0, ;
Trim( CLIENTE->APELLIDO ), ;
CLIENTE->APELLIDO := x ) }
oXbp:create():setData()
AAdd( aEditControls, oXbp )

Cuando los Componentes de Xbase acceden a los campos de una base de datos pero no tienen descripción, un
objeto XbpStatic se crea siempre para desplegar texto a la izquierda del campo de entrada. El texto predetermina
al nombre del campo alineado a la derecha en caso mixto.

Para la lógica del programa, el bloque de códigos :dataLink es esencial ya que lleva a cabo el acceso a lectura y
escritura de un campo único. En el código fuente de estilo funcional, los literales aparecen en el bloque de códigos
para los nombres de campo y de alias. Por tanto, el código debe adaptarse, por ejemplo, cuando se especifica un
nombre de alias diferente en el comando USE.

La variable de instancia :bufferLength limita el número de caracteres que pueden ingresarse en el buffer de edición
de un objeto XbpSLE. Iguala la longitud del campo de la base de datos correspondiente, así un XbpSLE no podrá
editar más caracteres de los que pueden ser almacenados en un campo. Debido a que los caracteres blancos son
caracteres válidos para un XbpSLE, el bloque de códigos :dataLink debe remover los espacios precedentes
cuando lee el campo de una base de datos. Sino el buffer de edición está ya lleno luego del acceso a escritura y no
serán posibles los cambios cuando XbpSLE se encuentra en modo de inserción. Los caracteres pueden sobre-
escribirse solamente en este caso.
Nota: En el código funcional, la variable LOCAL oXbp siempre recibe la referencia al Componente de Xbase que
no se emplea como padre. En el ejemplo arriba, referencia primero un objeto XbpStatic. Después de la ejecución
del método :create() , se le asigna una referencia de un objeto nuevo a esta variable (oXbp := XbpSLE():new(...)).
Esto no sobreescribe o destruye el objeto XbpStatic. Cuando el método :create() finaliza, se le agrega el objeto
XbpStatic a la lista de hijos del padre, que es la caja Grupo referenciada en oXbp1 . Una referencia al objeto
XbpStatic puede recuperarse con la expresión oXbp1:childList()[1] . Luego de la creación de un objeto XbpSLE, se
le añade al arreglo aEditControls . Todos los Componentes de Xbase que acceden a los campos de bases de
datos vía el bloque de código :dataLink se le agregan a este arreglo y pueden accederse en un programa usando
esta variable.

El código fuente generado para la creación de los Componentes de Xbase puede ser bastante extenso y parecer
complejo al comienzo. No obstante, consiste sólo de un patrón simple que se repite:

oXbp := Xbp...():new( <padre>, , <posicion>, <tamaño> )


oXbp:<configuración> := <valor>
oXbp:create()

Este patrón corresponde al "ciclo de vida" de los Componentes de Xbase. Se crea la instancia de una clase se crea
con el método :new() . La instancia (objeto) obtiene valores de configuración asignados. De acuerdo a la confi-
guración los recursos del sistema son requeridos desde el sistema operativo con el método :create() . Después
de :create() , el Componente de Xbase funciona y se lo referencia en la lista de hijos de su padre. Por tanto, la
variable LOCAL oXbp puede reutilizarse.

El diseñador de formularios genera códigos fuente estandarizados y libera al programador del cuantioso trabajo de
tipeo. El código fuente para el despliegue y la creación de un formulario, o para acceder a campos de bases de
datos (:dataLink ), puede estandarizarse. El límite del diseñador de formularios se alcanza, sin embargo, cuando
vuelve al flujo del programa o lógico. Esto no puede estandarizarse y constituye una tarea del programador
adaptar el código fuente generado con el fin de integrarlo en un programa de aplicación.

Pero en programación, hay situaciones comunes a muchas aplicaciones. Mover el puntero de registros es un
ejemplo. Para ello, un conjunto de Botones predefinidos pueden seleccionarse en el diseñador de formularios e
insertarse en el formulario. Las siguientes líneas de código son un ejemplo para un botón XbpPushbutton que
mueve el puntero de registros al siguiente registro:

oXbp := XbpPushButton():new( drawingArea, , {12,12}, {72,24} )


oXbp:caption := "Próximo"
oXbp:create()
oXbp:activate := {|| Gather( aEditControls ), ;
CLIENTE->( DbSkip( 1) ), ;
Scatter( aEditControls ) }

En este caso, el programa se controla por el bloque de código :activate que se evalúa cuando se hace clic en el
botón. La función Gather() escribe los valores actuales desde el formulario en la base de datos. Después, el
puntero de registros se mueve por la función DbSkip() y finalmente los valores del nuevo registro se transfieren al
formulario con la función Scatter(). Los valores nuevos son visibles, entonces, en la pantalla.

Los aspectos más importantes del diseñador de formularios pueden resumirse así:
1. El diseñador de formularios genera el código fuente basado en la representación visual.
2. La secuencia de los Componentes de Xbase en el formulario debe ajustarse para que corresponda con la
interacción con el usuario.
3. El código fuente que controla el flujo del programa se crea en una extensión muy limitada. Hay sólo un
conjunto de botones que llevan a cabo tareas comunes con su bloque de códigos :activate .
4. El código fuente generado debe modificarse cuando se lo integra en un programa de aplicación.

El código CLASS
El diseñador de formularios de Xbase++ puede crear un código fuente para un formulario en el estilo orientado a
objeto. Esto incluye la declaración de una clase (cuyas instancias despliegan el formulario) más el código para los
métodos :init() y :create() . Para la creación del código orientado a objeto, las mismas reglas son válidas como para
el código funcional (referirse a la sección previa). Debe chequearse la secuencia de Componentes de Xbase en el
formulario. Además, los símbolos para las variables de instancia de la clase deben definirse. Si no están definidas,
el diseñador de formularios crea identificadores predeterminados para las variables de instancia desde el nombre
de la clase de un Componente de Xbase más un dígito, o usa los nombres de los campos como símbolos cuando
un Componente de Xbase accede a un campo de base de datos. Con el fin de obtener un código legible, los
nombres (símbolos) de las variables de instancia deberían editarse antes de generar el código fuente. esto se lleva
a cabo en la ventana de símbolos (ítems del menú: Edit->Symbols para iVar).

El código orientado a objeto saca ventaja de la herencia. Se crean dos archivos PRG, cada uno conteniendo el
código para una clase. El primer archivo contiene el nivel de implementación del formulario mientras que el se-
gundo provee al nivel de utilización. El código se divide en dos archivos y una clase (nivel de utilización) se deriva
a partir de la otra (nivel de implementación). Para el formulario del ejemplo discutido en la sección previa, existen
dos archivos en el directorio ..\SAMPLES\BASICS\XPPFD: SAMPLE2.PRG y _SAMPLE2.PRG. Ambos contienen
el código para crear una clase.

Nivel de Implementación
-----------------------
Archivo : _SAMPLE2.PRG
Declaración: CLASS _CustomerForm FROM XbpDialog

Nivel de Utilización
--------------------
Archivo : SAMPLE2.PRG
Declaración: CLASS CustomerForm FROM _CustomerForm

Para la creación del código de ejemplo se emplearon el nombre del archivo SAMPLE2.PRG y el nombre de la
clase CustomerForm . El diseñador de formularios prefija ambos nombres con un guión bajo y usa los identifi-
cadores resultantes para el código fuente que construye el nivel de implementación de un formulario. La clase del
nivel de implementación tiene variables de instancia opera cada Parte de Xbase contenida en el formulario.
Además, se declaran las variables de instancia para cada base de datos accedida más la variable de instan-
cia :editControls . el código fuente del formulario de ejemplo para el nivel de implementación se da a continuación:

CLASS _CustomerForm FROM XbpDialog


EXPORTED:
VAR editControls

// Elementos Control Contenidos


VAR GroupBox
VAR Static1
VAR Apellido
VAR Static2
VAR Nombre
VAR ButtonNext
VAR ButtonPrevious
VAR ButtonOK

// Area de trabajo / alias


VAR CLIENTE

METHOD init
METHOD create
ENDCLASS

Además de las variables de instancia, se declaran los métodos :init() y :create() . Ellos inicializan al formulario y
requieren recursos del sistema para él. El código fuente para dichos métodos se crea por el diseñador de for-
mularios. Ambos métodos tienen el mismo perfil de parámetros como otros Componentes de Xbase. Los valores
predeterminados para todos los parámetros se definen en el método :init() .

METHOD _CustomerForm:init( oParent, oOwner, aPos, aSize, aPP, lVisible)

DEFAULT oParent TO AppDesktop(), ;


aPos TO {148,98}, ;
aSize TO {334,182}, ;
lVisible TO .F. , ;
aPP TO {}

AAdd ( aPP, { XBP_PP_COMPOUNDNAME, "8.Helv.normal" } )


::XbpDialog:init( oParent, oOwner, aPos, aSize, aPP, lVisible )
::XbpDialog:border := XBPDLG_SIZEBORDER
::XbpDialog:title := "New Form"
El valor predeterminado para oParent (la ventana padre) se da llamando a AppDesktop() , que devuelve la ventana
desktop. En este punto, se hace la misma suposición que con el código funcional. Si la ventana desktop no debiera
usarse como la ventana padre, o bien debe especificarse el padre explícitamente o ajustarse el código del mé-
todo :init() (Nota: si el formulario debería usarse como ventana de aplicación, una instancia de una clase debería
crearse en AppSys() y pasarse a SetAppWindow()).

La tarea principal del método :init() es inicializar la superclase (XbpDialog) y todos los Componentes de Xbase
contenidos. El código para la inicialización de los Componentes de Xbase es similar al código funcional como
están descriptos en la sección previa. La única diferencia es que las referencias a los objetos se los asigna a las
variables de instancia en vez de a las variables LOCAL. Las siguientes líneas de código muestran una compara-
ción entre códigos funcionales y orientados a objeto que crean la caja Grupo en el formulario de ejemplo:

Código orientado a Funciones

oXbp1 := XbpStatic():new( drawingArea, , ;


{12,48}, {300,96} )
oXbp1:caption := "Cliente"
oXbp1:type := XBPSTATIC_TYPE_GROUPBOX

Código orientado a Objetos

::GroupBox := XbpStatic():new( ::drawingArea, , {12,48}, {300,96} )


::GroupBox:caption := "Cliente"
::GroupBox:type := XBPSTATIC_TYPE_GROUPBOX

En el estilo funcional se utilizan las variables LOCAL oXbp1 y drawingArea , mientras que el código orientado a
objeto usa las variables de instancia ::GroupBox y ::drawingArea para almacenar referencias a Componentes de
Xbase. Se declara a ::GroupBox en la clase _CustomerForm y ::drawingArea es una variable de instancia de la
superclase XbpDialog (Nota: ::GroupBox es una abreviación de self:GroupBox , ::drawingArea es la abreviación de
self:drawingArea ).

el código que inicializa los Componentes de Xbase, que acceden a los campos de las bases de datos también es
similar en el estilo funcional y orientado a objeto:

Código orientado a Funciones

oXbp := XbpSLE():new( oXbp1, , {96,48}, {192,24} )


oXbp:bufferLength := 20
oXbp:tabStop := .T.
oXbp:dataLink := {|x| IIf( PCount()==0, ;
Trim( CLIENTE->APELLIDO ), ;
CLIENTE->APELLIDO := x ) }
oXbp:create():setData()
AAdd( aEditControls, oXbp )

Código orientado a Objetos

::Apellido := XbpSLE():new( ::GroupBox, , {96,48}, {192,24} )


::Apellido:bufferLength := 20
::Apellido:tabStop := .T.
::Apellido:dataLink := {|x| IIf( PCount()==0, ;
Trim( (::CLIENTE)->APELLIDO ) , ;
(::CLIENTE)->APELLIDO := x ) }
AAdd( ::editControls, ::Apellido )

En el código orientado a objeto, el objeto XbpSLE se referencia en la variable de instancia ::Apellido . Tiene el
mismo nombre, o símbolo, que el campo de base de datos editado por el objeto XbpSLE. Cuando una Parte de
Xbase accede a un campo, el diseñador de formularios usa el nombre del campo como identificador para la co-
rrespondiente variable de instancia por predeterminación.

El código para el bloque de códigos :dataLink también es diferente en el código orientado a objeto. El acceso a un
campo se lo programa como (Alias)->Fieldname y el área de trabajo se determina por los contenidos de una va-
riable de instancia. El código funcional usa al nombre de campo literal y alias como Alias->Fieldname .
La diferencia final entre el código funcional y orientado a objeto consiste en la llamada a los métodos :create()
y :setData() . En el código funcional, ambos métodos se llaman en la misma línea de programa. En el código
orientado a objeto, estos métodos se llaman en dos clases diferentes: el método :create() se lo llama en clase
_CustomerDialog (Nivel de implementación, _SAMPLE2.PRG) y :setData() se ejecuta en la clase CustomerDialog
(Nivel de utilización, SAMPLE2.PRG).

Archivo: _SAMPLE2.PRG (Nivel de Implementación)

CLASS _CustomerForm FROM XbpDialog

METHOD _CustomerForm:create( ... )


::XbpDialog:create( ... )
<...>
::Apellido:create()
<...>
RETURN self

Archivo: SAMPLE2.PRG (Nivel de Utilización)

CLASS CustomerForm FROM _CustomerForm

METHOD CustomerForm:create( ... )

// Ejecutar método de la superclase


::_CustomerForm:create( ... )

// El Directorio está abreviado sólo en la documentación.

// Abrir bases de datos y asignar área de trabajo.


USE ..\XPPW32\SOURCE\SAMPLES\DATA\CLIENTE.DBF NEW EXCLUSIVE

::CLIENTE := Select()

// Transferir valores a EditControls


AEval ( ::EditControls, { | oXbp | oXbp:SetData() } )

// Desplegar el formulario
::show()

RETURN self

El archivo _SAMPLE2.PRG contiene la declaración de clase para el formulario de ejemplo más el código requerido
para crear todos los Componentes de Xbase contenidos en el formulario. El archivo es el nivel de implementación
del formulario. Para utilizar un formulario es necesario abrir las bases de datos y transferir los valores desde el
registro actual al formulario. Esta es la tarea de una clase derivada. Se la programa en el archivo SAMPLE2.PRG
y provee el nivel de utilización del formulario.

El código fuente para el nivel de utilización puede (y debería) modificarse cuando se lo integra en un programa de
aplicación. Por ejemplo, el nombre del archivo de la base de datos aparece como el nombre literal luego del co-
mando USE e incluye la unidad y el camino. Esta línea definitivamente debe cambiarse para reflejar la situación en
un programa de aplicación. Si las bases de datos están o no abiertas en el método :create() , o ya estaban abiertas
cuando se llama a este método, debe ser una decisión del programador. Sólo asegura que el código generado
puede compilarse con el fin de probar el formulario en un archivo ejecutable aislado.

Para probar el código orientado a objeto de un formulario, es necesario declarar el procedimiento Principal (Main).
Esto lo realiza opcionalmente el diseñador de formularios cuando el parámetro correspondiente se establece en la
ventana de Settings (cuadro de confirmación: procedimiento Main para el código de clase). En este caso el pro-
cedimiento Main se escribe en el archivo que contiene el nivel de utilización de un formulario. En el ejemplo, este
es el archivo SAMPLE2.PRG. El procedimiento Main crea un formulario y procesa los eventos dentro de un ciclo
DO WHILE:
******************************************************************
* Procedimiento Principal para probar el formulario
******************************************************************
PROCEDURE Main
LOCAL nEvent, oXbp, mp1, mp2

CustomerForm():New():Create()

DO WHILE nEvent != xbeP_Close

nEvent := AppEvent ( @mp1, @mp2, @oXbp )


oXbp:HandleEvent ( nEvent, mp1, mp2 )

IF nEvent == xbeP_Quit
QUIT // AppQuit()
ENDIF
ENDDO
RETURN

// Incluir código de programa para el nivel de implementación


// del formulario.

#include "_SAMPLE2.PRG"

// EOF

La intención del procedimiento principal, como el generado por el diseñador de formularios, es probar un formu-
lario. Antes del ciclo de eventos, se instancia la clase sin siquiera asignar la referencia del objeto a una variable. La
expresión CustomerForm():New():Create() crea el formulario como fue definido en la clase y almacena la refe-
rencia del objeto en la lista hija de la ventana de aplicación (-> SetAppWindow():childList()[1]).

Para crear un archivo ejecutable que prueba un formulario, dos archivos PRG deben compilarse y enlazarse
(SAMPLE2.PRG y _SAMPLE2.PRG). Esto se consigue incluyendo al archivo que contiene el nivel de imple-
mentación (_SAMPLE2.PRG) en el archivo con el nivel de utilización (SAMPLE2.PRG). Cuando el compilador
compila al archivo SAMPLE2.PRG, el preprocesador ya ha procesado la directiva #include "_SAMPLE2.PRG" y
sólo un archivo necesita compilarse. Este archivo contiene la declaración para ambas clases, CustomerForm y
_CustomerForm .

Usando el código CLASS


Puede (y debería) modificarse el código orientado a objeto para integrarlo a un programa de aplicación. Es re-
comendable hacer cambios sólo en el archivo que contiene al código fuente para el nivel de utilización de un
formulario. Luego es posible cambiar el formulario posteriormente y tener el diseñador de formularios generando
un nuevo código fuente que refleje cualquier cambio. Si se cambia un formulario después que se ha generado el
código fuente, el diseñador de formularios genera código sólo para la implementación del formulario. El archivo
que contiene el código a nivel de utilización o específico de la aplicación, permanece intacto.

Un ejemplo de como el código orientado a objeto puede modificarse es el archivo SAMPLE3.PRG que se localiza
en el directorio ..\SAMPLES\BASICS\XPPFD. La clase CustomerForm desde el SAMPLE2.PRG se modifica de
forma tal que el formulario puede usarse en un entorno multi-usuario. Se agregan tres métodos a la declaración de
clase y se implementa el código para ellos.

CLASS CustomerForm FROM _CustomerForm


EXPORTED:
METHOD init
METHOD create

// Métodos específicos de la Aplicación (Son definidos por el usuario)


METHOD skip
METHOD setData
METHOD getData
ENDCLASS

Antes que el puntero de registros se mueva, todos los datos cambiados en el formulario deben escribirse en la
base de datos. En un entorno multiusuario esto requiere que el puntero de registros sea bloqueado antes que
pueda escribirse en él. Esta lógica del programa se realiza como un código de aplicación específico en los nuevos
métodos :skip() , :setData() y :getData() y es la única diferencia entre los archivos SAMPLE2.PRG y
SAMPLE3.PRG. El método :init() asegura que se ejecuten los nuevos métodos:

METHOD CustomerForm:init( oParent, oOwner, aPos, aSize, aPP, lVisible )

// Ejecuta el método de la superclase


::_CustomerForm:init( oParent, oOwner, aPos, aSize, aPP, lVisible )

// Cambia los bloques de código para los eventos de los botones.


::ButtonOK:activate := {|| ::getData(),PostAppEvent( xbeP_Close ) }
::ButtonPrevious:activate := {|| ::skip(-1) }
::ButtonNext:activate := {|| ::skip( 1) }
RETURN self

Los botones contenidos en el formulario son referenciados en las variables de instancia que se declaran en la
clase _CustomerForm (_SAMPLE2.PRG). Se modifican los bloques de código :activate , entonces los nuevos
métodos serán ejecutados cuando un usuario clickea un botón. La implementación de los métodos nuevos re-
quiere sólo unas pocas líneas de código:

METHOD CustomerForm:skip( nSkip )


IF ::getData() // Escribir registro
(::CLIENTE)->(DbSkip( nSkip )) // Mover el puntero de registro
::setData() // Leer registro
ENDIF
RETURN self

METHOD CustomerForm:setData
AEval( ::editControls, {|o| o:setData() } )
RETURN self

METHOD CustomerForm:getData
LOCAL lOk := ( AScan( ::editControls, {|o| o:changed } ) == 0 )

IF ! lOk // Los datos fueron cambiados


lOk := (::CLIENTE)->(DbRLock()) // Bloquear registro
IF lOk // Registro está Bloqueado
AEval( ::editControls, {|o| o:getData() } )
(::CLIENTE)->(DbUnlock()) // Desbloquear registro
ELSE
MsgBox( "Actualmente el Registro está bloqueado" )
ENDIF
ENDIF
RETURN lOk

El método :skip() mueve el puntero de registros en este ejemplo sólo si los datos modificados en el formulario se
escriben a la base de datos. Esto, inclusive, depende del bloqueo exitoso de registros que se establece en el
método :getData() . Si los datos permanecen intactos, no se produce el bloqueo y el puntero de registros se mueve
siempre. Cuando el puntero de registros se modifica, el método :setData() transfiere los datos desde la base de
datos al formulario.

El compilador de Xbase++ - XPP.EXE


El compilador de Xbase++ puede iniciarse cuando se ingresa XPP en la línea de comandos. La sintaxis general de
la llamada al compilador es como sigue:

XPP [<Opciones>] <archivo-PRG> // o


XPP <archivo-PRG> [<Opciones>]

<archivo-PRG> indica el nombre de archivo del código fuente a compilar. Si el nombre del archivo se especifica sin
una extensión, se usa .PRG como una extensión de archivo. <Opciones> establece parámetros para controlar el
proceso de compilación. Un parámetro del compilador siempre comienza siempre con una barra ("/") o con un
guión ("-") (ver debajo).
El programa XPP.EXE contiene dos componentes: el preprocesador y el compilador actual. XPP.EXE traduce un
archivo de código fuente en formato ASCII a un archivo OBJ en formato binario. La traducción del código fuente
ocurre en dos pasos: Primero el preprocesador traduce el código del programa dentro del archivo PRG a código
modificado que el compilador "entiende". Luego el compilador crea un archivo OBJ que el enlazador puede enlazar
a un programa ejecutable.

El resultado del preprocesador puede salir como un archivo separado con la extensión PPO (PPO significa Salida
PreProcesador - PreProcessed Output) usando el parámetro de compilación /p. Un archivo PPO es un archivo
ASCII conteniendo el código que el compilador realmente compila. El preprocesador prepara el código del pro-
grama para el compilador, y éste último convierte el código preparado en un archivo OBJ.

Parámetros del Compilador


Ejemplos para el compilador de Xbase++
XPPLOAD Información de la versión

Parámetros del Compilador


La compilación de códigos de programación ASCII (tanto un archivo PRG o PPO) a códigos de programación
binario pueden controlarse de formas específicas usando parámetros de compilación. Estos parámetros deben
especificarse junto con el nombre del archivo cuando se llama a XPP.EXE. Los siguientes parámetros (<Opcio-
nes>) son válidos para el compilador:

/? Despliega información sobre todos los parámetros de compilación. El parámetro /? so-


lamente provee información y no puede combinarse con otros parámetros.
/a Declaración MEMVAR automática
Cuando se usa este parámetro, todas las variables especificadas con PRIVATE, PUBLIC
o PARAMETERS están declaradas explícitamente como MEMVAR.
/b Insertar información de debug
Cuando se usa /b, se escribe información de debug en el archivo OBJ resultante. Esto
incluye número de filas, nombres de las variables léxicas, nombre del archivo de código
fuente y otra información.
Si el comando /b no está incluido, el debugger no puede desplegar información sobre el
código fuente del programa durante el tiempo de ejecución del programa de Xbase++
resultante. La información de debug aumenta el tamaño del archivo OBJ y el archivo
ejecutable EXE. Por esta razón, la versión final del programa debe compilarse y enlazarse
sin /b.
/coff Crea archivos de objeto en Archivo Objeto de Formato Común (COFF)
El compilador de Xbase++ crea un archivo de objeto COFF si se establece el parámetro
/coff. En las plataformas de Windows, este parámetro se activa por predeterminación.
Para crear archivos de objeto OMF, debe usarse el parámetro /omf.
/com Usar modo de compatibilidad
En el modo de compatibilidad, todos los identificadores (nombres) para las funciones,
procedimientos, métodos y variables se consideran significativos por el compilador sólo
hasta un máximo de 10 caracteres. El parámetro /com activa este modo. Sólo debe es-
tablecerse cuando se compila con programas de Clipper con nombres de funciones que
son más largos que 10 caracteres, pero abreviados usando los primeros 10 caracteres.
Los efectos colaterales del parámetro /com es que todas las funciones de Xbase++ con
identificadores de más de 10 caracteres de largo no pueden llamarse otra vez.
/d<id>[=<val>] #define <id>
El parámetro /d especifica la constante #define con el valor <id> al compilador en la línea
de comandos. La constante #define es válida dentro del archivo de código fuente. Op-
cionalmente, puede asignársele a la constante el valor <val>.
/dll[:DYNAMIC] Crear un archivo OBJ para un archivo DLL
El parámetro /dll dirige al compilador para que incluya información adicional necesaria
para la creación de un archivo DLL en el archivo OBJ. Todos los archivos OBJ que deben
enlazarse al archivo DLL deben compilarse usando /dll. Si el enlazador creara un archivo
EXE, el parámetro no debe utilizarse.
Importante: Si un archivo DLL debe ser cargado y liberado durante el tiempo de ejecu-
ción de una aplicación de Xbase++ usando las funciones DllLoad() y DllUnload(), la pa-
labra clave adicional :DYNAMIC debe especificarse.
/err:<número> Terminar la compilación luego de cierta cantidad <número> de errores.
Por omisión, el compilador termina el proceso de compilación ni bien registra 20 errores
de programa. Este valor puede cambiarse usando el parámetro /err:<número> especifica
un conteo máximo de errores antes de la terminación de la compilación.
En algunos casos el número de 20 errores se alcanza rápidamente. Cuando el compilador
detecta un error de sintaxis trata de encontrar el próximo punto de ingreso en el código
fuente para continuar el proceso de compilación con el código remanente. Si no se en-
cuentra un punto de ingreso luego de un error inicial, el compilador reporta errores de
sintaxis adicionales inclusive si el código es el correcto.
/es Crea un código de error del sistema operativo para dar advertencias.
Cuando se establecen los parámetros, el compilador crea un código de error OS para dar
advertencia, pero no cuando se detecta un error de sintaxis.
/ga Convierte cadenas literales desde ANSI a OEM
Cuando se establece el parámetro /ga, todas las cadenas literales de caracteres en el
código fuente del PRG se convierten desde ANSI hasta OEM antes de que el compilador
cree el archivo OBJ.
/go Convierte cadenas literales desde OEM hasta ANSI
Cuando se establece el parámetro /go, todas las cadenas literales de caracteres en el
código fuente del PRG se convierten desde OEM hasta ANSI antes que el compilador
cree el archivo OBJ.
/i<directorio> Directorio de búsqueda para archivos #include
El parámetro /i especifica un directorio de búsqueda <directorio> adicional para el uso del
compilador cuando está localizando archivos #include. Normalmente, el compilador sólo
busca éstos archivos en el directorio actual y aquellos directorios especificados por la
variable de entorno INCLUDE.
/l No inserta números de línea
Si se emplea el parámetro /l, no se incluyen números de línea en el archivo OBJ. Esto
disminuye el tamaño del archivo EXE ejecutado pero tiene la desventaja que deshabilita a
la función ProcLine() a retornar los números de línea. Si se usa /l y ocurre un error durante
el tiempo de ejecución, no será posible determinar que línea en el código fuente causo el
error.
/link[:"opciones"] Crea un archivo EXE desde el archivo de objeto.
El parámetro /link hace que el compilador inicie al enlazador cuando el archivo OBJ es
creado con éxito. De esta manera es posible crear un archivo EXE desde un único archivo
PRG invocando al compilador únicamente. Las opciones para el enlazador también
pueden especificarse con el parámetro /link. Deben incluirse en doble comillas y se
transfieren luego al enlazador.
/m Ignora SET PROCEDURE TO (ProcRequest)
El parámetro /m previene la inserción automática de los archivos del código fuente adi-
cional (archivos PRG) que se especifican usando el comando SET PROCEDURE TO.
Puesto que SET PROCEDURE TO es un comando de compatibilidad pura, el parámetro
/m no necesita usarse generalmente.
/n No hay procedimiento de inicio implícito (MAIN)
Si código del programa se encuentra en el archivo del código fuente fuera de una de-
claración FUNCTION, PROCEDURE o METHOD este código del programa se condensa
implícitamente en el procedimiento MAIN(). Este comportamiento predeterminado puede
suprimirse por el parámetro /n. El compilador luego crea un mensaje de error si encuentra
al código del programa fuera de la declaración FUNCTION, PROCEDURE o METHOD.
/nod No hay una biblioteca predeterminada en el archivo OBJ
Si se usa el parámetro /nod (biblioteca predeterminada ausente), el nombre del archivo
XPPRT1.LIB no esta incluido implícitamente en el archivo OBJ. El enlazador solo usa los
archivos LIB especificados con el parámetro /r.
/o<nombre> Renombra el archivo OBJ
Normalmente, el compilador crea un archivo OBJ que tienen el mismo nombre de archivo
que el archivo PRG a partir del que se crea. El parámetro /o se usa para renombrar el
archivo OBJ resultante a <nombre> . Si se usa un path diferente en <nombre> ,especificar
el nombre del path es suficiente. El nombre del path debe terminarse usando una barra \.
El archivo OBJ se crea luego en el path especifico con el mismo nombre de archivo que el
archivo PRG.
/omf Crea archivos de objeto en Formato de Módulo Objeto (OMF)
El compilador de Xbase++ Compilador crea objetos OMF si se establece el parámetro
/omf. En la plataforma OS/2 este parámetro se activa por predeterminación. Para crear
archivos de objeto COFF, el parámetro /coff debe establecerse.
/p Crea lo que sale del preprocesador (archivo PPO)
Si se incluye el parámetro /p, el compilador crea un archivo PPO conteniendo lo que sale
del preprocesador junto con la creación del archivo OBJ. El archivo tiene el mismo nom-
bre que el código fuente, pero tiene como extensión del archivo PPO en vez de PRG. Un
archivo PPO puede usarse para determinar si los comandos definidos por el usuario están
correctamente traducidos por el preprocesador.
/pre[:<min>] Carga archivos DLL desde compilador
El parámetro /pre (precargar) indica que todos los archivos DLL usados por el compilador
se cargan en la memoria principal y permanecen disponibles en la memoria luego que el
compilador finaliza. Un intervalo de tiempo en minutos puede especificarse opcional-
mente usando :<min>. Cuando no se produjo proceso de compilación alguno, luego
de :<min>, el archivo DLL es removido automáticamente de la memoria. El valor prede-
terminado para :<min> es 10 minutos. Esto significa que si no se especifica un intervalo
de tiempo, los archivos DLL se remueven de la memoria después de 10 minutos.
/pre se usa para evitar el cargado y la remoción de los archivos DLL en el path ..\BIN y
acelerar la compilación repetitiva.
/q No hay despliegue de pantalla durante la compilación (silencioso)
El parámetro /q suprime el despliegue de los números de las líneas durante la compilación.
Esto permite que el compilador trabaje mas rápidamente.
/r<nombrebibl> Especifica archivo LIB para el enlazador
El parámetro /r se usa para especificar una biblioteca <nombrebibl> adicional para el
enlazador. Este ultimo recurre a ella con el fin de resolver referencias externas cuando
enlaza al archivo OBJ. Por predeterminación el enlazador busca el archivo XPPRT1.LIB y
no necesita ser especificado explícitamente salvo que el parámetro /nod sea utilizado.
/s Solo testea sintaxis
Si se usa el parámetro /s, el compilador solo chequea la sintaxis del código fuente y no
crea un archivo OBJ.
/u[<nombre>] Usa STD.CH definido por el usuario
El parámetro /u define el archivo #include <nombre> como un reemplazo para el archivo
STD.CH que se incluye por predeterminación en todos los archivos de código fuente
durante la compilación. Un programador que solo usa comandos con limitación puede
copiar estos comandos desde el archivo STD.CH a otro archivo y especificar este archivo
como el predeterminado usando /u. Esto ahorra tiempo cuando el preprocesador lee el
archivo #include. Si no se especifica <nombre>n no se usa un archivo #include prede-
terminado.
/v Trata variables no declaradas como MEMVAR
Por omisión el compilador trata las variables no declaradas que no son precedidas por el
nombre de alias como variables de campo. El parámetro /v causa que el compilador trate
las variables no declaradas como variables dinámicas de memoria. Estas variables re-
ciben el nombre de alias MEMVAR->.
/w Despliega advertencias
El parámetro /w causa que el compilador despliegue advertencias en la pantalla cuando
detecta variables no declaradas que no están precedidas por los nombres de alias.
/wi Despliega advertencias para variables no inicializadas
El parámetro /wi produce advertencias para todas las variables que no han sido iniciali-
zadas y aparecen en expresiones en las que las variables deben tener un valor.
/wl Despliega advertencias para variables no léxicas
El parámetro /wl produce advertencias para todas las variables que no son declaradas
como LOCAL o STATIC o incluidas en la lista de parámetros formales de funciones,
procedimientos y métodos.
/wu Despliega advertencias para variables léxicas sin uso.
El parámetro /wu produce advertencias para todas las variables léxicas declaradas y que
no se han utilizado.
/z Apaga optimizaciones de acceso rápido
El parámetro /z apaga por predeterminación las optimizaciones "de acceso rápido" para
los operadores lógicos .AND. y .OR.. Sin optimización, todas las expresiones que se
combinan usando operadores lógicos se evalúan. Con la optimización las expresiones
combinadas se evalúan sólo hasta que el resultado de toda la expresión de determine
claramente.

Ejemplos para el compilador de Xbase++.


El siguiente ejemplo hace un chequeo de sintaxis para el nombre de archivo MAIN.PRG y la salida del prepro-
cesador en el archivo MAIN.PPO sin desplegar el número de línea.

XPP MAIN /s /p /q

El próximo ejemplo crea el archivo TEST.OBJ incluyendo información del debug. Se escribe el archivo OBJ en el
path \XPP\OBJ\. No se crea un procedimiento implícito MAIN() y se despliegan advertencias en la pantalla durante
la compilación:

XPP TEST /b /n /w /o\XPP\OBJ\


Constantes definidas por el Compilador
Por omisión, el compilador de Xbase++ define dos constantes: una para identificar al compilador y la otra para
identificar al sistema operativo. Esto permite que el código del programa especifico para el compilador o para la
exclusión de un sistema operativo de la compilación, usando las directivas #ifdef , #ifndef , #else y #endif . Las
constantes de #define son:

#define __XPP__ // Xbase++


#define __OS2__ // versión OS/2
#define __WIN32__ // Windows versión 32 bits

Por ejemplo, si el mismo archivo PRG debe compilarse por Xbase++ y Clipper, pueden darse situaciones en las
que ambos compiladores se comporten de manera diferente. Estos conflictos pueden resolverse de la siguiente
forma:

#ifdef __XPP__
<Código Xbase++>
#else
<Código Clipper>
#endif

De esta manera se pueden mantener códigos del programa para diferentes compiladores en el mismo archivo
PRG.
Las constantes adicionales definidas por el compilador están disponibles para debugging y/o con propósito de
hacer versiones. Pueden usarse de la siguiente manera:

? __FILE__, __LINE__, __DATE__, __TIME__

El compilador reemplaza estas constantes con la información correspondiente, o sea los valores de estas cons-
tantes se modifican cuando se compilan los archivos del código fuente.

__FILE__ Una cadena de caracteres conteniendo el nombre del archivo de código fuente.
__LINE__ Un valor numérico que indica el número de línea de la fuente donde aparece la constante.
__DATE__ Una cadena de caracteres conteniendo la fecha de compilación en el formato "MMM DD YYYY", con
MMM siendo el nombre abreviado del mes, DD es el dia y YYYY es el año.
__TIME__ Una cadena de caracteres conteniendo el tiempo de compilación en el formato HH:MM:SS.

XPPLOAD Información de la versión


el programa de utilidades XPPLOAD.EXE puede chequear la información de las versiones de archivos EXE y DLL.
La sintaxis para llamar a este programa de utilidades es:

XPPLOAD [versión] [[<directorio>]<archivo1>] [<archivo2>] [@<lista-de-archivos>]

XppLoad trabaja ahora también con Windows 95/98.

El modo automático se inicia al ejecutar


XPPLOAD version

Xppload mostrará el nombre de los archivos y la ubicación de los directorios de Xbase++ - archivos de ejecución
relevante.

En el modo de comando versión, XPPLOAD acepta los nombres de los archivos o listas de archivos.
XPPLOAD version XppRt1.dll

Busca a Xpprt1.dll, imprime el directorio en donde se usara el DLL y la versión. Si el directorio es suministrado
explícitamente, XPPLOAD tratara de encontrar el archivo allí y si encuentra la versión la imprimirá.

Una lista de archivos es un archivo de texto que contiene un nombre de archivo por línea, y pasado a XppLoad con
un signo precedente:

XPPLOAD version @ListaDLL.lst

Si se ejecuta "Xppload Version" desde un archivo de lote o archivo make pueden verificarse sus códigos de re-
torno:
0 - ok
1 - error general
2 - error de línea de comandos
3 - error de versión DLL
4 - DLL no encontrada

El Debugger de Xbase++ - XPPDBG.EXE


Este capítulo describe el debugger provisto con Xbase++. El debugger es una poderosa herramienta para el
desarrollo de programas y ayuda al programador a encontrar errores en las aplicaciones ejecutadas en los modos
VIO, híbrido, o GUI. El debugger mismo se ejecuta en una ventana XbpCrt y está escrito primariamente en
Xbase++.

Conceptos Básicos del Debugger


El Menú de Sistema del Debugger
Menú File
Menú Run
Menú Monitor
Menú Commands
Menú Options
Menú Help
Trabajando con el debugger
Estableciendo puntos de interrupción
Inspeccionando errores

Conceptos Básicos del Debugger


El debugger de Xbase++ se inicia cuando se selecciona XPPDBG en la línea de comandos y se ejecuta como un
proceso independiente. Si se especifica el nombre de archivo de una aplicación de Xbase++ como un argumento
de la línea de comandos, esta aplicación se inicia como otro proceso y se muestra información de debug sobre la
aplicación de Xbase++. Para que se encuentre disponible la información de debug, todos los archivos PRG de la
aplicación deben haber sido compilados usando la opción del compilador /b y los archivos OBJ resultantes en-
lazados usando la opción de enlace /DE. Sino no estará disponible ningún tipo de información de debug.

El debugger exhibe primordialmente información de debug sobre el código fuente de la aplicación que está siendo
ejecutada y controlada por el debugger. Puede mostrarse cualquier variable de memoria y sus contenidos editados
durante el tiempo de ejecución usando el debugger. Esto es posible incluso cuando se ha declarado a la variable
LOCAL, STATIC, PRIVATE, o PUBLIC. El debugger puede mostrar la pila de llamadas que lista todas las fun-
ciones predeterminadas que define el usuario, procedimientos y métodos.

El debugger puede interrumpir la ejecución de una aplicación y controlarla por medio de los puntos de interrupción
o usando un modo de paso único (esto significa que el código fuente de una aplicación es procesado línea por
línea). Los puntos de interrupción detienen automáticamente la ejecución de la aplicación y reactivan el debugger.
Esto permite que se la ejecute paso a paso para limitar y localizar el lugar donde se produjo el error. El debugger
incluye también una línea de comandos donde pueden ejecutarse expresiones de Xbase++ dentro de una apli-
cación.

Se navega dentro del debugger usando el mouse o teclas de acceso rápido. De éstas últimas, las más importantes
son F8, F5 y la combinación de teclas Ctrl+S. F8 ejecuta la próxima línea del código fuente de la aplicación cuando
se está en el modo de paso único. La tecla F5 termina el modo de paso único del debugger y devuelve el control a
la aplicación hasta que el debugger se reactive usando Ctrl+S (aplicación stop) o se alcance algún punto de in-
terrupción. Todos los demás aspectos del debugger los maneja el menú. La actividad actual del debugger se
muestra por un área coloreada dentro de la barra del menú. El rojo significa que el debugger está inactivo y que la
aplicación está en ejecución. El verde significa que el debugger está activo y controla la aplicación.

Si la línea de código fuente excede el ancho de la ventana de código fuente, presione la barra espaciadora para
ver la línea completa.

Nota Importante: Cuando una aplicación se encuentra activa, ésta puede ser interrumpida usando la combinación
de teclas Ctrl+S. Si una aplicación está en un estado de espera no está ejecutando el código. Esto ocurre durante
WAIT, Inkey(0), y AppEvent(). Mientras la aplicación está esperando alguna entrada desde el teclado o algún
evento, el debugger no puede intervenir en la aplicación puesto que no se está ejecutando código en la aplicación.
En este caso, la ventana de la aplicación debe traerse al primer plano y apretarse una tecla si es necesario, así la
aplicación termina con el estado de espera. Sólo después el debugger puede ganar el control nuevamente.
El Menú de Sistema del Debugger

El menú principal del debugger es siempre visible como la línea superior de la ventana del debugger. Contiene los
siguientes ítems del menú:

File Activa el menú de archivos. Esto permite la navegación dentro de la ventana de código, seleccionar
archivos de aplicación del código fuente para mostrar en la ventana de código o terminar la función
del debugger.
Run Activa menú de ejecución. Las opciones del menú de ejecución controlan cómo se ejecuta la
aplicación, Reiniciar y cómo seleccionar otra aplicación de Xbase++ para debug.
Monitor Activa el menú del monitor. Esto abre o cierra la ventana del monitor para exhibir variables de
memoria y la ventana de la pila de llamadas para exhibir las funciones y los procedimientos activos.
Commands Activa el menú de comandos. Las funciones del menú de comandos controlan las ventanas de los
comandos del debugger y permiten que se establezcan o borren los puntos de interrupción.
Options Activa el menú de opciones. Este menú configura el debugger y permite que se guarde la confi-
guración actual.
Help Muestra una ventana de ayuda.

Menú File
El menú File (Archivos) contiene opciones que especifican el archivo del código fuente exhibidos en la ventana del
debugger y qué sección del código fuente es visible.

Open module Esta opción abre una ventana de selección de archivos que lista todos los archivos PRG
conteniendo el código fuente. Se hace una selección en la ventana usando la tecla Enter o
haciendo un doble clic sobre el ítem subrayado. El archivo PRG seleccionado es presen-
tado en la ventana código del debugger y pueden establecerse los puntos de interrupción
en este archivo usando la tecla F9 o Enter.
Los nombres de archivos serán desplegados con su camino completo (vista completa). La
tecla TAB conmuta entre esa vista y una vista de nombres de archivos (vista reducida).
Presionando una letra correspondiente a la primera letra de un archivo listado en esta vista
causará que el cursor salte al próximo nombre de archivo coincidente.
Puede abrirse también una ventana de selección. Contiene una lista de todas las funciones,
procedimientos y métodos comprendidos en un archivo PRG. Esta ventana de selección se
abre haciendo un clic en el botón derecho del mouse o usando la combinación de teclas
Ctrl+Return luego de la selección de un archivo PRG.
Goto Line Esta opción abre una ventana de entradas que permite la especificación de una línea de
números para que el debugger muestre en la ventana de código.
Goto Function Esta opción muestra el código fuente de cualquier función o procedimiento en la ventana de
código. Si el código fuente de la función no se encuentra en el archivo PRG que esta ex-
hibiéndose en ese momento en la ventana de código, el debugger abre el archivo PRG
apropiado. Si varias funciones existen con el mismo nombre (STATIC FUNCTION) el de-
bugger muestra el código fuente de la primer función que encuentra con el nombre espe-
cificado.
Si existen bastantes funciones con el mismo nombre (FUNCIONES STATIC) el debugger
desplegará una lista de archivos posibles donde la función conteniendo estas implemen-
tadas. Lo mismo se aplica al monitor de Pila de Llamadas.
Para ir a un método de una clase, ingrese el nombre de clase seguido de dos puntos y el
nombre del método, tal como "MiXbpMLE:init". Adicionalmente, para saltar a un número de
línea puede especificarse agregando el caracter "#" y el número de línea, tal como
"MAIN#567".
Where Esta opción muestra la línea de programa actual que se está ejecutando en la ventana de
código. Se carga al archivo PRG apropiado si ya no es visible. Esta opción cumple las
mismas funciones que "Open module" y "Goto ..." para reposicionar el código fuente en la
línea actual luego que la ventana de código es desplazada o se abre otra ventana.
Exit La opción termina con el debugger.
Menú Run
Este menú contiene opciones que controlan la aplicación de Xbase++. Esto incluye seleccionar y Reiniciar la
aplicación.

Restart Esta opción termina y luego reinicia la aplicación de Xbase++ actual. Todos los puntos de
interrupción establecidos y monitores activados permanecerán visibles luego del Reinicio.
Startup La opción termina la aplicación actual y abre una ventana por la que es posible entrar al
nombre de archivo de la aplicación. Esto permite hacer debugger de aplicaciones de Xbase++
diferentes sin terminar ni Reiniciar el debugger.
Step La tecla de acceso rápido para esta opción es F8. Tanto seleccionar la opción del menú cómo
presionar F8 ejecutan la línea del programa en el código fuente de la aplicación.
Trace La tecla de acceso rápido para esta opción es F10. Presionar F10 y seleccionar esta opción
del menú causan que todas las funciones y procedimientos llamados por la línea del programa
actual sean ejecutados completamente antes de que el debugger haga una pausa durante la
ejecución del programa en la próxima línea. Esto evita el paso a paso del código fuente de la
función o procedimiento llamador.
Go F5 es la tecla de acceso rápido para esta opción. Devuelve el control a la aplicación y apaga al
debugger. Este ultimo puede recuperar el control al ser reactivado con la combinación de te-
clas Ctrl+S o si se da un punto de interrupción. Si se presiona Ctrl+S, la ventana del debugger
recibe el foco, lo que significa que debe estar al frente. Si el debugger no muestra una "luz
verde" luego de Ctrl+S, la aplicación esta en la condición de estado de espera y no ejecuta
código alguno. En este caso la aplicación debe recibir un evento de forma tal que abandone el
estado de espera. La manera mas fácil de llevar esto a cabo es hacer un clic en la ventana de
la aplicación con el mouse y si es necesario presionar una tecla.
Goto Cursor F7 es la tecla de acceso rápido para esta opción. El debugger permite al código del programa
de la aplicación ejecutar hasta la línea en la ventana de código y luego interrumpe la ejecución
del programa. Esta opción del menú y la tecla F7 establecen la línea actual en la ventana de
código como un punto de interrupción temporario.
Step Back La tecla de acceso rápido para esta opción es F4. Esta opción emplea efectivamente la línea
después del procedimiento actual o función como punto de interrupción. Cuando se la pre-
siona, el procedimiento actual o función se ejecuta hasta el final. El debugger interrumpe el
programa tan rápido como termine la función actual. Luego posiciona el código en la ventana
de código en la próxima línea del código fuente que llamó a la función o procedimiento que
estuvo activo cuando se presionó F4.

Menú Monitor
Este menú incluye opciones para abrir ventanas para ver variables de memoria y/o la pila de llamadas. La in-
formación en las ventanas se actualiza automáticamente mientras que se está ejecutando la aplicación. Cuando la
ventana del monitor está activa, se puede seleccionar una variable con un doble clic y luego editarla. El archivo
PRG correspondiente se carga automáticamente en el código de la ventana cuando se hace doble clic con el botón
derecho a cualquiera de las funciones o procedimientos en la ventana de la pila de llamadas.

Local Habilita o Deshabilita el despliegue de variables de la clase de almacenamiento LOCAL.


Static Habilita o Deshabilita el despliegue de variables de la clase de almacenamiento STATIC.
Private Habilita o Deshabilita el despliegue de variables de la clase de almacenamiento PRIVATE.
Public Habilita o Deshabilita el despliegue de variables de la clase de almacenamiento PUBLIC.
Field Habilita/Deshabilita el despliegue de variables de almacenamiento de la clase FIELD.
Callstack Habilita o Deshabilita el despliegue de pila de llamadas.

Un arreglo desplegará sus elementos, la salida es creada de acuerdo con Var2Char(), sin mostrar los tipos de
datos de cada elemento. Un bloque de código mostrará su representación literal y un objeto mostrará su nombre
de clase.

Menú Commands
La ventana de comandos se abre o cierra usando opciones de este menú. Esta ventana presenta el resultado de
todas las expresiones ingresadas en la línea de comandos del debugger. También registra la expresión ingresada
y su resultado. La flecha hacia arriba puede utilizarse para recuperar una expresión ingresada previamente que
puede ejecutarse nuevamente presionando Enter. Además del manejo de la ventana de comandos, el menú
también maneja puntos de interrupción. Un punto de interrupción designa una línea en el código fuente de la
aplicación en la que el programa se interrumpe automáticamente. Si se alcanza un punto de interrupción mientras
se esta ejecutando la aplicación, el debugger se reactiva y toma otra vez el control.
Toggle Breakpoint F9 es la tecla de acceso rápido de esta opción. Alternativamente se puede
presionar la tecla Enter. La opción define la línea actual en la código fuente
de la aplicación como un punto de interrupción o elimina todos los puntos de
interrupción previamente definidos en esta línea. Independientemente de que
se defina una línea como un punto de interrupción se la identifica con el ca-
racter ¯ , que aparece al comienzo de la línea del código fuente frente al
número de línea.
Delete All Breakpoints Esta opción borra todos los puntos de interrupción definidos en todos los
archivos de código fuente.
List Breakpoints Esta opción lista todos los puntos de interrupción definidos en todos los ar-
chivos de código fuente.
Open Command Window Esta opción abre la ventana de comandos. Las expresiones pueden ingre-
sarse luego en la línea de comandos del debugger. Cuando la ventana de
comandos fue abierta recientemente, esta opción del menú es reemplazada
con "Close Command Window".
Clear Command Window Esta opción borra todas las líneas previamente registradas en la ventana de
comandos.

Menú Options
Este menú admite la configuración de varios aspectos del debugger o que se guarde la actual configuración de
ventana. Los datos de la configuración se guardan en un archivo con la extensión .@@@ y el mismo nombre de
archivo que el archivo EXE. Si este archivo existe en el directorio actual lo lee el debugger y es empleado para su
configuración.

Debug Program Startup Si se selecciona esta opción el debugger también despliega el código del
programa ejecutado antes de la llamada al procedimiento Principal. Este des-
pliega las funciones AppSys(), DbeSys() y cualquier otra INIT PROCEDURE.
Check Time Stamps Esta función puede encenderse o apagarse. Si se la activa, el debugger
compara la estampa del tiempo de los archivos de código fuente de la aplica-
ción con la marca de tiempo en los archivos ejecutables y da consejos si estos
últimos (archivos DLL y EXE) son mas antiguos que alguno de los archivos de
código fuente.
Set Tab Width esta opción especifica el número de espacios en blanco utilizados para re-
emplazar caracteres de tabulación cuando se exhiben los códigos fuente en la
ventana de código del debugger.
Comandos Ejecutados al Arrancar!EF Si se establece esta opción, todos los
comandos ingresados en la ventana de comandos serán automáticamente
ejecutados la próxima vez que arranque usando la información guardada para
Reinicio (Save Restart Info).
Save Restart Info Esta opción guarda la configuración actual del debugger y todos los actuales
puntos de interrupción. El archivo creado tiene el mismo nombre de archivo
que la aplicación y la extensión ".@@@". Si se especifica XPPDBG -n <ar-
chivo EXE> en la línea de comandos, el debugger ignora la información
guardada y no carga al archivo @@@.

Menú Help
Este menú contiene opciones que muestran información de ayuda para el debugger.

Short Help Esta opción abre una pequeña ventana de ayuda y da teclas de acceso rápido para controlar
al debugger.

Opciones para el Debugger


La sintaxis general para la llamada es:

XppDbg [<opción>] <archivoEXE> [<parámetros>]

<archivoEXE> El nombre del archivo Xbase++ ejecutable. Se asume la extensión .EXE.


<parámetros> Los parámetros para el programa a ser depurado pueden ser pasados justo después del
nombre del <archivoEXE>. Si un parámetro incluye espacios, la cadena tiene que ser
enmarcada por comillas de acuerdo con las reglas para pasaje de cadenas del procesador
de comandos que sea utilizado.
/? Muestra información acerca de las opciones del debugger.
/i La primera detención del debugger será en ErrorSys() en vez de Main() o los procedi-
mientos INIT. Esto permite la depuración de funciones ErrorSys(), AppSys() y DbeSys().
Esta opción de la línea de comandos tiene el mismo efecto que la opción "Debug Program
Startup".
/n La información de Reinicio no será usada.
/s:<directorio de código fuente> El directorio del código fuente puede ser agregado a los archivos de vista si
la ubicación original (en tiempo de compilación) no está disponible cuando esté depurando.

Trabajando con el debugger


El objetivo del debugger es ayudar al programador a encontrar y resolver errores del programa. Esta sección
discute las capacidades que ofrece el debugger y da consejos sobre como localizar los errores. Los errores del
programa que conducen a la terminación del mismo deben diferenciarse de los que no pueden identificarse du-
rante el tiempo de ejecución del sistema. Los errores que causan la terminación del sistema son relativamente
fáciles de hallar ya que el error predeterminado por la manipulación de rutinas da información sobre el tipo y
ubicación del error en pantalla. La información mas importante para localizar un error durante el tiempo de eje-
cución es el nombre de la rutina y el número de la línea del código fuente en el archivo PRG donde ocurrió el error.
Si la razón para el error no es obvia, un punto de interrupción puede establecerse en esta línea en el debugger y
las condiciones que conducen al error durante el tiempo de ejecución examinadas por el debugger.

Estableciendo puntos de interrupción


Se inicia el debugger ingresando XPPDBG <archivo EXE> en la línea de comandos. Carga el archivo ejecutable
<archivo EXE> y detiene la aplicación Xbase++ en la primer línea ejecutable del programa. se establece un punto
de interrupción implícito en esta línea. Si ocurre un error durante el tiempo de ejecución el debugger identifica el
nombre de la rutina donde ocurrió el error junto con el número de línea en el archivo PRG. Para establecer el punto
de interrupción en esta línea la opción "Open Module" se selecciona en el menú de archivo. Este despliega una
ventana de selección conteniendo los nombres de todos los archivos PRG. Luego de resaltar el nombre del ar-
chivo donde se encontró la rutina causante del error, hacer un clic en el botón derecho del mouse sobre el archivo
resaltado (o la combinación de teclas Ctrl+Return) abre una segunda ventana que lista todas las rutinas conte-
nidas en el archivo PRG. La rutina que genero el error puede seleccionarse de esta lista. Hacer un doble clic con el
botón izquierdo o presionar la tecla Enter abre el archivo PRG correspondiente y posiciona la ventana de código en
la rutina seleccionada.

Dentro de la ventana de código debería resaltarse la línea donde ocurrió el error. Se establece un punto de inte-
rrupción haciendo doble clic en el botón izquierdo del mouse sobre la línea resaltada o presionando la tecla F9 o la
tecla Enter.

Una sesión de debug generalmente comienza con el establecimiento de puntos de interrupción. Puede estable-
cerse cualquier número de puntos de interrupción. La opción "Save Restart Info" puede seleccionarse en las op-
ciones del menú para guardar los puntos de interrupción desde la ejecución de una ejecución hasta la siguiente. El
número de líneas de los puntos de interrupción se guardan luego con el mismo nombre de archivo de la aplicación
y la extensión ".@@@".

Luego de establecer de puntos de interrupción la aplicación se ejecuta presionando la tecla F5. El área coloreada
en la barra del menú del debugger cambia de verde a roja. Esto indica que la aplicación y no el debugger tiene foco.
El debugger interrumpe la aplicación y recupera el control tan rápido como se alcance el punto de interrupción. El
área coloreada resaltada cambia entonces de rojo nuevamente a verde.

Inspeccionando errores
Ni bien el debugger interrumpió la aplicación en el punto de interrupción el estado actual de la aplicación puede
examinarse más de cerca. La ventana del monitor se abre a partir del menú Monitor para exhibir los contenidos de
todas las variables visibles. Las variables están organizadas basadas en su clase de almacenamiento (LOCAL,
STATIC, PRIVATE y PUBLIC). Si una variable posee un valor incorrecto puede cambiarse usando el debugger.
Para hacer esto, la ventana del monitor se elige haciendo clic con el botón izquierdo del mouse o utilizando la tecla
TAB. Dentro de la ventana Monitor debe posicionarse resaltando en la variable correspondiente. Una ventana de
edición se abre luego de hacer clic con el botón izquierdo del mouse o presionando Enter. El valor de la variable
puede editarse en esta ventana. La ventana de edición se cierra seleccionando OK.

Las variables con el tipo caracter, numérico, fecha o lógico pueden mostrarse en un campo de entradas en el cual
pueden editarse. Lo exhibido usa sintaxis de Xbase++. Los bloques de código no pueden desplegarse pero sí
modificarse. Cuando se cambia un valor, debe prestarse especial atención a la sintaxis. Por ejemplo, deben estar
presentes los delimitadores para una cadena de caracteres cuando se edita un valor del tipo de dato caracter. Si la
variable es un bloque de código, los caracteres {|| y } deben incluirse. Los caracteres ingresados son compilados
usando el operador de macro y se le asigna el resultado a la variable.

Si la variable que se edita es un arreglo, los contenidos de los elementos del arreglo pueden verse usando el botón
Ver. El máximo número de elementos desplegados es 64. Los elementos en un arreglo mas allá de este límite de
64 elementos puede examinarse ingresando el índice numérico para el primer elemento del arreglo que se
muestra en el campo de entrada "Start".

La ventana de edición presenta solo la información esencial sobre un objeto como el nombre de la clase al que el
objeto pertenece. Los contenidos de las variables de instancia de un objeto se exhiben presionando el botón Ver.
Solo se muestran las variables de instancia declaradas en la clase. Si la clase se deriva de otras clases, los
nombres de las clases de las superclases también se exhiben. Seleccionar una superclase despliega una ventana
de edición que contiene las variables de instancia para dicha clase.

Sólo las variables de instancia de los objetos pueden examinarse en la ventana de edición del debugger. En
muchos casos el valor de retorno de un método necesita chequearse. Por ejemplo, la pregunta podría ser "¨Cual
es el nombre de la clase del padre de esta Componente de Xbase?" El padre solo puede determinarse usando el
método.:setParent() : no aparece en la lista de variables de instancia. Los problemas como este se resuelven
usando la ventana de comandos del debugger. Para ver la ventana de comandos la opción "Open Command
Window" se selecciona para abrirla. Debajo de esta ventana se exhibe la línea de comando del debugger y puede
ingresarse cualquier expresión. Luego de presionar la tecla Enter se ejecuta la expresión y el resultado de la
misma se despliega en la ventana de comandos.

El padre de un Componente particular de Xbase puede determinarse en la línea de comandos del debugger de dos
maneras. Si la variable oXbp contiene un Componente de Xbase cuyo información paternal es requerida, cual-
quiera de la siguientes dos expresiones pueden emplearse en la línea de comandos del debugger para determinar
la clase de padre:

oXbp:setParent():className()
dummy := oXbp:setParent()

La primera expresión despliega el nombre de la clase del padre en la ventana de comandos. La segunda expresión
crea una variable PRIVATE referenciando al padre. Seleccionar una opción "Private" para la ventana del monitor
despliega variables de la clase de almacenamiento PRIVATE. El padre de oXbp se exhibe como la variable
"dummy". Esta variable puede examinarse con el debugger de la manera descripta anteriormente.

El ProjectBuilder de Xbase++ - PBUILD.EXE


El ProjectBuilder de Xbase++ es una herramienta para manejar proyectos enteros de software. Un proyecto con-
siste de por lo menos un archivo EXE pero puede conformarse de varios archivos EXE y/o DLL también. Este es un
archivo ASCII con la extensión XPJ (Xbase++ ProJect). Hace una lista de todos los datos necesarios para construir
un proyecto. Por ejemplo, los nombres de los archivos, información para el compilador y el enlazador o qué archivo
ejecutable debe crearse de qué archivos fuentes.

Creando un archivo de proyecto (archivo XPJ)


Creando un proyecto
La sección [PROJECT] en un archivo XPJ
Secciones Definidas por el Usuario en un archivo XPJ
Opciones para PBUILD.EXE

Creando un archivo de proyecto (archivo XPJ)


La forma más fácil de crear un archivo de proyecto es usando un archivo ASCII que lista los nombres de todos los
archivos PRG que son parte de un proyecto. Ese tipo de archivo puede crearse con el comando DIR:

DIR /b *.prg > proyecto.txt


PBUILD @proyecto.txt

La salida del comando DIR se dirige al archivo PROYECTO.TXT. Este archivo puede modificarse con facilidad
usando un simple editor de textos para remover los nombres de los archivos PRG que se localizan en el directorio
actual pero no son parte del proyecto, por ejemplo. El ProjectBuilder (PBUILD.EXE) lee ese archivo y crea a partir
de él una plantilla para un archivo de proyecto con la extensión XPJ (esto significa Xbase++ ProJect).
Para pequeños proyectos, la estructura básica de un archivo de proyecto puede crearse también con un editor. El
siguiente ejemplo muestra un archivo de proyecto para el programa CLIENTE.EXE que necesita sólo cuatro ar-
chivos PRG:

01: [PROJECT]
02: DEBUG = yes // Definiciones que alcanzan a todo el proyecto
03: GUI = no
04: CLIENTE.XPJ // La raíz del proyecto
05:
06: [CLIENTE.XPJ] // Enunciar todos los archivos EXE y DLL
07: CLIENTE.EXE // del proyecto aquí
08:
09: [CLIENTE.EXE] // Listar todos los fuentes para cada
10: CLIENTE.PRG // archivo EXE y/o DLL separadamente
11: GETCLIE.PRG
12: PRINT.PRG
13: VIEWCLIE.PRG

El ejemplo muestra las principales características de un archivo XPJ: está dividido en diferentes secciones, cada
una comenzando con un nombre simbólico entre corchetes cuadrados. La primer sección debe siempre nom-
brarse [PROJECT]. Es el punto de entrada para el ProjectBuilder y lista las definiciones válidas para el proyecto
completo. En este ejemplo, un programa que contiene información del debug se crea como una aplicación a modo
de texto (líneas 2 y 3). Al final de las definiciones de lista (línea 4), aparece el nombre de la próxima sección
analizada por el ProjectBuilder. Esta sección es la raíz del proyecto y lista todos los archivos ejecutables que son
parte del proyecto, o que deben redistribuirse más tarde a los clientes, respectivamente. Esto incluye ambos ar-
chivos EXE y DLL. Cada nombre de un archivo ejecutable de los archivos fuentes se emplea nuevamente como el
nombre de otra sección que lista los nombre de los archivos fuente a partir de los cuales se crea el archivo eje-
cutable.

Como resultado la estructura de un archivo XPJ representa las dependencias que existen entre los archivos fuente
y objetivo de un proyecto. Por ejemplo, el archivo mencionado anteriormente CLIENTE.EXE es un archivo objetivo
que depende de cuatro archivos PRG, o fuentes, respectivamente. Cuando se modifica un archivo, el objetivo
debe actualizarse. El ProjectBuilder analiza las dependencias entre fuente y objetivo usando un archivo de pro-
yecto y lo actualiza para reflejar los últimos cambios.

El ejemplo arriba muestra la estructura básica de un archivo XPJ como puede crearse fácilmente con un editor. No
obstante, muchas más dependencias pueden existir entre los archivos del mismo proyecto. Esto incluye, por
ejemplo, las dependencias entre CH->PRG, PRG->OBJ, OBJ->EXE u OBJ->DLL y los archivos DEF->LIB. Pro-
jectBuilder puede detectar todas las dependencias automáticamente una vez que se crea la estructura básica de
un archivo de proyecto. Para ello, se inicia a PBUILD.EXE con el parámetro /g (g por "g"enerar):

PBUILD CLIENTE.xpj /g

El parámetro /g causa que PBUILD.EXE analice todas las dependencias que existen dentro de un proyecto.
ProjectBuilder expande entonces un archivo de proyecto (rudimentario) y agrega toda la información faltante al
archivo XPJ. Esto libera al programador de bastante tipeo, especialmente en grandes proyectos. Por ejemplo,
PBUILD.EXE expande al archivo XPJ de ejemplo por 20 líneas. Como resultado, el archivo de proyecto crece
desde 11 a 31 líneas:

01: [PROJECT]
02: COMPILE = xpp // Compilador faltante y enlazador
03: COMPILE_FLAGS = /q // Se agrega información
04: DEBUG = yes
05: GUI = no
06: LINKER = alink
07: LINK_FLAGS =
08: RC_COMPILE = arc
09: RC_FLAGS =
10: CLIENTE.XPJ
11:
12: [CLIENTE.XPJ]
13: CLIENTE.EXE
14:
15: [CLIENTE.EXE] // Creado automáticamente
16: // $START-AUTODEPEND // dependencias
17: COLLAT.CH
18: GET.CH
19: MEMVAR.CH
20: PROMPT.CH
21: SET.CH
22: STD.CH
23: CLIENTE.OBJ
24: GETCLIE.OBJ
25: PRINT.OBJ
26: VIEWCLIE.OBJ
27: // $STOP-AUTODEPEND
28: CLIENTE.PRG
29: GETCLIE.PRG
30: PRINT.PRG
31: VIEWCLIE.PRG

Los cambios aparecen en la sección [PROJECT], donde se agrega toda la información faltante para compilar y
enlazar. Además, la sección [CLIENTE.EXE] contiene ahora una lista de los archivos para el que se ha detectado
el ProjectBuilder. Esta lista automáticamente creada está encerrada entre los marcadores //
$START-AUTODEPEND y // $STOP-AUTODEPEND en las líneas 16 y 27. Ambos marcadores indican un área en
el archivo de proyecto que debe cambiarse automáticamente cuando se llama a PBUILD.EXE subsecuentemente
junto con el parámetro /g. Por tanto, un archivo de proyecto no debería alterarse entre los marcadores porque los
cambios en esta área pueden perderse.

Creando un proyecto
Cuando se genera un archivo de proyecto incluyendo todas las dependencias, se crea un proyecto, o actualiza,
respectivamente, invocando PBUILD.EXE y especificando al archivo de proyecto apropiado, si es necesario. Por
omisión, ProjectBuilder busca al archivo PROJECT.XPJ en el directorio actual. Por tanto, cada una de las si-
guientes posibilidades crea un proyecto:

1) PBUILD
2) PBUILD CLIENTE
3) PBUILD CLIENTE.prj

En la primer llamada, el ProjectBuilder crea al proyecto que se describe en el archivo PROJECT.XPJ. La segunda
llamada requiere que un archivo CLIENTE.XPJ esté disponible en directorio actual (XPJ es la extensión prede-
terminada para los archivos proyecto), mientras en la tercer llamada, el nombre de archivo completo especifica el
archivo de proyecto. Después de invocarlo, ProjectBuilder analiza las dependencias entre los archivos fuente y
final. Si se modifica un archivo fuente desde la última llamada a PBUILD.EXE, por ejemplo. si los archivos fuente
son más nuevos que los archivos objetivo, el ProjectBuilder crea los objetivos correspondientes una vez más
(archivos EXE o DLL). Puesto que sólo los fuentes son tratados por el ProjectBuilder, el tiempo para actualizar los
proyectos complejos es por tanto reducido en una gran extensión.

La sección [PROJECT] en un archivo XPJ


Toda la información que puede listarse en la sección [PROJECT] se describe debajo. Cada archivo de proyecto
debe comenzar con la sección [PROJECT] que contiene las definiciones válidas para el proyecto entero (defini-
ciones amplia de proyecto):

COFF_LINKER= Esta definición opcional puede usarse para definir al enlazador que debe invocarse por el
ProjectBuilder cuando se establece OBJ_FORMAT= en COFF (Formato Común de Ar-
chivo Objeto). Si COFF_LINKER= no se define en este caso, el ProjectBuilder utiliza al
enlazador definido con LINKER=.
COMPILE= Esto indica el nombre del compilador de Xbase++. Es siempre XPP.
COMPILE_FLAGS= Todos los parámetros de los compiladores a establecer para la compilación se definen aquí.
Note las definiciones separadas OBJ_DIR= y DEBUG= existen para los parámetros /o y /b.
DEBUG= Esta definición puede establecerse en YES (versión con debug) o NO (versión sin debug).
Un archivo ejecutable se crea así con o sin información del debug. Un archivo ejecutable
debe crearse con DEBUG=YES para monitorearse con el debugger.
GUI= Si un programa debe ejecutarse como una aplicación a modo de texto, debe establecerse
GUI=NO. cuando un programa lleva a cabo una salida gráfica, ya sea funciones Gra..() o
usando los Componentes de Xbase, debe definirse GUI=YES.
LINKER= Esta definición indica el nombre del enlazador que se emplea para crear los archivos EXE o
DLL desde los archivos OBJ. Este es el enlazador enviado con la versión de Xbase++
específica del sistema operativo.
LINK_FLAGS= Todas las banderas del enlazador que no están cubiertas por GUI= y DEBUG= se listan en
esta definición. Sin embargo, si se emplean las banderas /PM y /DE, ellas sustituyen a las
definiciones correspondientes GUI= y DEBUG=.
OBJ_DIR= Opcionalmente, puede definirse el directorio para los archivos OBJ. El compilador crea los
archivos OBJ en este directorio y el enlazador hace una búsqueda en el mismo de los
archivos. Sólo un directorio puede establecerse para cada objetivo.
Nota: La configuración OBJ_DIR afecta sólo el archivo OBJ listado en la sección del ar-
chivo de proyecto delimitado con // $START-AUTODEPEND y // $STOP-AUTODEPEND.
PBuild completa estos nombres de archivo OBJ con el directorio OBJ_DIR para que nin-
guna información de camino pueda especificarse para los archivos listados en la sección de
auto- dependencia. Archivos OBJ adicionales pueden especificarse con un camino com-
pletamente calificado afuera de la sección de auto-dependencia.
OBJ_FORMAT= pueden usarse dos valores para esta definición para seleccionar el formato de archivo
objeto: COFF (Formato Común de Archivo Objeto) o OMF (Formato del Módulo Objeto). El
ProjectBuilder luego inicia el Parámetros del Compilador e invoca El programa de utilidades
AIMPLIB.EXE, si es necesario, usando los parámetros correspondientes /coff o /omf.
Además, se emplea el enlazador especificado con COFF_LINKER= u OMF_LINKER= en el
proceso de construcción.
OMF_LINKER= Esta definición opcional puede usarse para definir al enlazador que el ProjectBuilder invoca
cuando se establece OBJ_FORMAT= a OMF (Formato de Módulo Objeto). Si no se define
OMF_LINKER= en este caso, el ProjectBuilder utiliza al enlazador definido con LINKER=.
RC_COMPILE= Esta definición contiene el nombre del compilador de recursos de la manera en que se lo
envía con Xbase++. Normalmente, recursos adicionales se utilizan para aplicaciones GUI
solamente.
OS/2 - Estas son notas adicionales al final de esta sección para la plataforma de OS/2.
RC_FLAGS= Las banderas para el compilador de recursos se establece con esta definición.
<SECCION> Todas las entradas en la sección [PROJECT] sin los signos iguales se usan como refe-
rencia para las secciones subsecuentes definidas por el usuario que analiza el Project-
Builder. Normalmente, sólo se hace referencia a una sección adicional (la sección de raíz).
Debe haber por lo menos una sección definida por el usuario.

Nota: Las definiciones que se listan en la sección [PROJECT] son válidas para el proyecto de entrada. Sin em-
bargo, ellas deben aparecer también en las secciones definidas por el usuario. En este caso, son válidas para una
sola sección. Si se pasan las definiciones a PBUILD.EXE sobre la línea de comandos usando el parámetro /d, se
ignoran todas las definiciones en un archivo de proyecto con el mismo nombre.

Compilador de Recursos para OS/2


Dependiendo si los recursos se declaran en los archivos ARC o RC, la opción RC_COMPILE= debe establecerse
en arc o rc . Si se usan los archivos ARC, el Project Builder invoca primero al ARC.EXE (Compilador de Recursos
de Alaska) y luego RC.EXE (Compilador de Recursos de OS/2). ARC.EXE traduce los archivos ARC a archivos
RC que pueden compilarse por RC.EXE.

Secciones definidas por el usuario en un archivo XPJ


Luego de la sección [PROJECT], las secciones definidas por el usuario se listan en un archivo de proyecto. El
nombre de la primer sección definida por el usuario (la sección raíz) debe indicarse en la sección [PROJECT] como
se muestra en el siguiente ejemplo:

[PROJECT]
DEBUG = YES // definición a nivel de proyecto

RAIZ // primera sección definida por el usuario

[RAIZ]
archivo1.EXE
archivo2.DLL

[archivo1.EXE]
<archivos PRG para crear el EXE>

[archivo2.DLL]
DEBUG = NO // definición a nivel de sección
<archivos PRG para crear la DLL>
La primer sección definida por el usuario lista todos los archivos ejecutables (objetivos) que ProjectBuilder ha de
crear. Para cada objetivo, existe una sección definida por el usuario con el mismo nombre. Lista todos los archivos
PRG (fuentes) para ese objetivo. Los fuentes son normalmente archivos PRG, pero pueden ser archivos fuente así
como los que necesita una aplicación GUI. Los archivos de recursos se compilan con un compilador de recursos y
enlaza al archivo ejecutable.

Cuando un archivo objetivo se crea como una biblioteca dinámica (archivo DLL), el nombre del archivo corres-
pondiente debe incluirse en una extensión DLL. ProjectBuilder automáticamente crea todos los archivos necesa-
rios para crear una DLL. Esto incluye al archivo de definición de exportación (archivo DEF) y la biblioteca de im-
portación (archivo LIB). La última debe estar enlazada a un EXE que usa las funciones contenidas en un DLL.

Cuando un archivo de proyecto contienen secciones definidas por el usuario múltiples, es suficiente escribir sólo
los archivos fuente necesarios para cada sección (archivos PRG y archivos de recurso, si es necesario). El archivo
de proyecto puede expandirse después para describir todas las dependencias entre los fuentes y objetivos in-
vocando a PBUILD.EXE con el parámetro /g.

Opciones para PBUILD.EXE


ProjectBuilder se inicia desde la línea de comando ingresando a PBUILD. Si no se especifica un archivo de pro-
yecto el ProjectBuilder busca al archivo PROJECT.XPJ y crea el proyecto descripto en ese archivo. La sintaxis
general para la llamada es:

PBUILD @<archivo>

PBUILD [<archivo XPJ>] [<opciones>]


@<file> El caracter @ indica a un archivo que lista todos los recursos que son parte del proyecto.
<file> es un archivo ASCII que contiene el nombre de un archivo fuente en cada línea.
Puede crearse muy fácilmente ingresando DIR /b *.PRG > PROJECT.LST en la línea de
comando, por ejemplo. Desde este archivo, ProjectBuilder crea un archivo de proyecto con
el mismo nombre del archivo pero una extensión diferente del archivo (XPJ). El nombre
<file> se utiliza también para la creación del archivo ejecutable.
<archivo XPJ> Cuando no se describe un proyecto en el archivo PROJECT.XPJ, el nombre del archivo de
proyecto debe especificarse en la línea de comando. La extensión predeterminada es XPJ.
/? o /h La información de despliegue sobre opciones de PBUILD.EXE. el parámetro /? sólo provee
información y no puede combinarse con otros parámetros.
/a El parámetro /a provoca que ProjectBuilder lleve a cabo un ciclo completo de compilación y
enlace para todos los fuentes de un proyecto. Esto reconstruye un proyecto entero inde-
pendientemente de si los archivos fuente han sido modificados (o no) desde la última ac-
tualización del proyecto.
/c El parámetro /c (clean:limpiar) borra todos los archivos binarios de un proyecto entero que
crea ProjectBuilder (archivos OBJ, EXE, EXP, DEF, LIB, DLL y RES).
/d<id>[=<val>] La definición <id> en un archivo de proyecto puede establecerse en el valor <val> usando el
parámetro /d en la línea de comandos. Por ejemplo, ingresando PBUILD /dDEBUG=NO
construye un proyecto sin información de debug inclusive si se especifica DEBUG=YES en
la sección [PROJECT]. El parámetro /d, por tanto, modifica temporariamente las defini-
ciones sin cambiar el archivo de proyecto.
/g[<nombre>] Especificar el parámetro /g provoca que ProjectBuilder analice las dependencias de un
proyecto. Genera entonces una lista de todos los archivos de los cuales depende el archivo
objetivo. Esta opción debe usarse para expandir la estructura básica de un archivo de
proyecto o cuando se alteran las dependencias. La estructura básica de un archivo XPJ
cubre sólo a los archivos PRG y objetivos (EXE o DLL). Un archivo XPJ completo, sin
embargo, lista archivos con la extensión CH, OBJ, ARC, DEF y LIB (archivos DEF y LIB se
utilizan para crear archivos DLL).
Opcionalmente, un archivo nuevo de proyecto puede crearse especificando <name> . Si se
omite el nombre del archivo un archivo de proyecto existente se sobrescribe.
/k Se crean archivos temporarios en el curso de un proceso build que se borran por prede-
terminación cuando ProjectBuilder ha terminado. Usando la opción /k (por keep:mantener )
previene ProjectBuilder de borrar los archivos temporarios.
/l Cuando los archivos DLL son parte de un proyecto, ProjectBuilder automáticamente crea
los archivos DEF correspondientes (Archivos de definición de exportación) y archivos LIB
(bibliotecas de importación). Normalmente, esto no es necesario cuando se modifica un
archivo PRG sino únicamente cuando se alteran el número, secuencia e identificadores de
funciones de exportación en el DLL.
Por ejemplo, si una función en un archivo PRG se modifica insertando unas pocas líneas de
código, el archivo PRG debe compilarse y el DLL enlazarse. Sin embargo, las definiciones
de exportación y las bibliotecas de importación no necesitan ser reconstruidas en este caso.
Por tanto, el parámetro /l suprime la creación automática de los archivos DEF y LIB.
/n Con /n, el ProjectBuilder sólo despliega todos los pasos necesarios para actualizar un
proyecto sin llevar a cabo las acciones correspondientes (no se invoca al compilador ni al
enlazador).
/v El parámetro /v activa el modo verboso de ProjectBuilder.

El enlazador Alaska - ALINK.EXE


El enlazador ALINK crea un archivo único conteniendo un código ejecutable de 32 bits desde archivos OBJ y LIB
(bibliotecas). Los archivos OBJ son los módulos del programa de una aplicación creada por el compilador, mien-
tras que los archivos LIB contienen colecciones de varios archivos OBJ. El archivo resultante del enlazador puede
ser o bien un archivo EXE o bien uno DLL. Todos los archivos a enlazar por ALINK deben cumplir con el Formato
Común de Archivo Objeto (Common Object File Format (COFF)).

Nota: Si se crea una aplicación con ProjectBuilder (PBUILD.EXE) no es necesario llamar al enlazador explícita-
mente. ProjectBuilder invocará al enlazador automáticamente.

Llamando al enlazador desde la línea de comando


Opciones del enlazador
Variables ambientales para el enlazador
Creando archivos DLL
El programa de utilidades AIMPLIB.EXE

Llamando al enlazador desde la línea de comando


Es posible comenzar ALINK desde la línea de comando y todos los archivos a enlazar deben ser especificados en
la línea de comando. Alternativamente se puede emplear un archivo script que controla el proceso de enlazado. La
sintaxis general para la llamada es:

ALINK [<opciones>] <OBJ> [<LIB>] [<RES>] [/OUT:<EXE>]


o
ALINK @<Script1> [@<ScriptN>]

Cuando se llama a Alink, se deben especificar uno o más archivos OBJ como archivos de entrada. El enlazador
crea un archivo EXE como un programa ejecutable desde los archivos OBJ. Por omisión, el nombre del primer
archivo OBJ se emplea para nombrar el archivo EXE resultante. Los nombres de archivo de entrada se encuentran
separados por espacios en blanco y pueden aparecer en orden en la línea de comando. Por predeterminación
ALINK usa .OBJ como una extensión de archivo. Ello significa que es posible especificar archivos OBJ sin una
extensión al enlazador. ALINK sólo emplea extensiones para buscar archivos. No hace suposición alguna acerca
del contenido de los archivos desde sus extensiones.

Además de archivos OBJ, ALINK acepta archivos LIB y sólo un archivo RES como archivos de entrada. Se utilizan
archivos LIB para crear archivos DLL (ver Creando archivos DLL), y un archivo RES contiene recursos binarios,
como bitmaps o íconos, que deben estar disponibles durante la ejecución (ver El compilador de recursos de Alaska
- ARC.EXE).

Se puede hacer un listado de archivos de entrada para el enlazador en un archivo ASCII <Script> que debe ser
especificado con el signo @ precediéndolo, en la línea de comando. El enlazador lee este archivo y enlaza todos
los archivos listados con un archivo de salida ejecutable.

Opciones del enlazador


Las opciones del enlazador brindan información a ALINK especificando cómo se crea un archivo ejecutable. Usa
palabras claves siempre precedidas por un guión. Es posible especificar las opciones del enlazador en cualquier
lugar de la línea de comando, y algunas de las palabras claves pueden abreviarse.

/BASE:<n>
Con la opción /BASE:<n>, el enlazador establece la dirección de base especificada para cargar el
archivo ejecutable. Los valores predeterminado son 0x400000 para archivos EXE y 0x10000000
para archivos DLL. Si un sistema operativo no está disponible para cargar un archivo en la loca-
lización especificada, calcula una nueva dirección y reubica al programa. La información respecto a
direcciones de base puede obtenerse usando la opción /MAP. Aparecen listas de direcciones de
base en un archivo MAP. El enlazador redondea el valor <n> a un múltiplo de 64kB.
/DE[BUG]
Con la opción /DEBUG, el enlazador introduce información de depuración con nombres simbólicos
y números de línea al archivo EXE. El archivo EXE puede ejecutarse aun fuera del debugger.
Dentro de éste último, tal información se encuentra disponible cuando la aplicación ha sido enla-
zada con la opción /DEBUG.
Importante: Para recibir la información debug desde el enlazador, se deben compilar archivos PRG
con la opción /b. Crear un archivo EXE habilitado para debug es posible únicamente con la opción
de compilado /b en conjunto con la opción /DEBUG del enlazador.
/DEFAULTLIB:<archivo>
Esta opción especifica un archivo LIB adicional en el cual el enlazador sirve para buscar como
resolver referencias externas. El enlazador primero busca las bibliotecas especificadas en la línea
de comando, luego la biblioteca predeterminadas con /DEFAULTLIB, y por ultimo aquellas nom-
bradas en los archivos OBJ.
/DLL
Con esta opción, el enlazador crea un archivo DLL en vez de un EXE. Todos los archivos OBJ a ser
enlazados a una DLL deben ser compilados usando el interruptor /DLL del compilador.
Nota: Cuando se emplea la opción /DLL, no sólo los archivos OBJ sino también archivos de defi-
nición para exportar (EXP) (EXPort definition file) deben especificarse para el enlazador. (ver
Creando archivos DLL).
/FORCE:MULTIPLE | UNRESOLVED
La opción /FORCE obliga al enlazador a producir un archivo ejecutable inclusive si ha detectado
símbolos externos irresueltos y/o declaraciones múltiples del mismo símbolo. El archivo ejecutable
no es cor recto por consiguiente, y sólo aquellas partes del programa que no usan los símbolos en
cuestión podrán ejecutarse sin errores.
Si el enlazador se ve en la necesidad de tolerar ambos, los símbolos no resueltos y declaraciones
múltiples de un único símbolo, el interruptor /FORCE debe emplearse dos veces (Alink <archivo>
/FORCE:MULTIPLE /FORCE:UNRESOLVED).
Precaución: Realmente lo hemos estimulado a evitar usar la opción /FORCE. Si la utiliza tenga en
cuenta que los archivos de salida pueden estar incorrectos, y que ejecutar un programa incorrecto
puede conducir al colapso del mismo y/o del sistema operativo.
/MAP[:<archivo>]
La opción /MAP causa que el enlazador cree un archivo MAP. Este archivo contiene información
sobre el archivo ejecutable. Opcionalmente, se puede especificar el nombre del archivo MAP con
<archivo> . Si se omite, el nombre del archivo de salida también sirve para el archivo MAP.
/NOD[EFAULTLIB]
Esta opción previene a ALINK de buscar bibliotecas de importación predeterminado que resuelven
referencias externas, como por ejemplo, el archivo XPPRT1.LIB. Cuando se usa esta opción, el
enlazador sólo recurre a los archivos LIB en la línea de comando.
/NOL[OGO]
Esta opción suprime el despliegue del logo del programa y el num ero de versión.
/OUT:<archivo>
Con esta opción es posible definir el nombre del archivo ejecutable. Por omisión, el nombre del
archivo se crea desde el primer archivo OBJ especificado al enlazador.
/PM[TYPE]:VIO|PM [<mayor>[.<menor>]]
/SUBSYSTEM:CONSOLE|WINDOWS [<mayor>[.<menor>]]
Las opciones /SUBSYSTEM y /PMTYPE tienen el mismo significado para el enlazador. Ambas
definen el tipo de aplicación. El predeterminado es VIO o CONSOLE, respectivamente, lo que in-
dica una aplicación a modo de texto. Si se especifica PM o WINDOWS, el enlazador crea una
aplicación GUI.
Opcionalmente se puede especificar el mínimo número de versión del sistema operativo requerido
para ejecutar una aplicación Xbase++. Si se configura <mayor> como 4 y <menor> como .1, por
ejemplo, un programa Xbase++ no podría iniciarse en un sistema operativo versión 3.x, pero lo
haría en una versión 5.x.
/ST[ACK]:<max>[,<min>]
Esta opción configura el tamaño de stack de una aplicación Xbase++. <max> es un valor numérico
indicativo del máximo tamaño de stack en bytes (predeterminado es 1 MB), mientras <min> op-
cionalmente define el tamaño de stack al comienzo del programa. Esto significa que el mínimo
tamaño de stack es <min> bytes y que puede crecer dinámicamente durante la ejecución hasta
<max> bytes.
/VERBOSE
La opción /VERBOSE causa que el enlazador exhiba información adicional durante el proceso de
enlace.
/VERSION <mayor>[.<menor>]
El enlazador coloca esta información de la versión en el titulo de los archivos EXE o DLL. <mayor>
y <menor> pueden contener un valor entre o y 65535.

Variables ambientales para el enlazador


ALINK.EXE busca la variable ambiental "ALINK" y emplea sus contenidos para opciones predeterminado. Un
ejemplo de definición para la variable ambiental es:

SET ALINK=/DEBUG /PMTYPE:PM

Esto predetermina la creación de aplicaciones con interfaz gráfica habilitadas para debug. Todas las opciones
especificadas en la línea de comando son procesadas luego de las opciones SET ALINK=. Por lo tanto, las op-
ciones de la línea de comando se superponen con las opciones establecidas por la variable ambiental.
Además, el enlazador usa SET LIB= como un camino de búsqueda para archivos OBJ y LIB. ALINK busca ar-
chivos OBJ y LIB en el siguiente orden:

1. El directorio especificado como parte del nombre del archivo.


2. El directorio actual.
3. Todos los directorios están definidos en la variable ambiental SET LIB=.

Si no se encuentra un archivo OBJ o LIB, ALINK emite un mensaje de error y concluye el proceso de enlace.

Creando archivos DLL


Las Bibliotecas de Enlace Dinámico (archivos DLL) constituyen la base del sistema operativo y deberían ser
consideradas generalmente como parte de una aplicación durante el desarrollo de un programa bajo Xbase++.
Este capitulo discute como construir archivos DLL y emplea como ejemplo los tres procedimientos siguientes,
cada uno presumiblemente programado en un archivo PRG separado:

** Archivo MAIN.PRG ** // Este archivo se usa para


PROCEDURE Main // crear un archivo EXE.
Hola() // Los procedimientos están contenidos
DigaHola() // en un archivo DLL.
RETURN

** Archivo HOLA.PRG ** // Estos dos archivos se usan


PROCEDURE Hola // para crear un archivo DLL.
? "Hola mundo!"
RETURN

** Archivo DIGAHOLA.PRG **
PROCEDURE DigaHola
? "Hola gente!"
RETURN

El ProjectBuilder de Xbase++ provee la forma más fácil para la creación de un archivo DLL puesto que puede
desarrollar todos los pasos automáticamente. Sólo se requiere para crear un archivo de proyecto que contiene
secciones separadas para archivos EXE y DLL. Una plantilla de proyecto apropiado para el ProjectBuilder puede
verse así:

// Archivo: PROJECT.XPJ
[PROJECT]
PRINCIPAL
[PRINCIPAL]
MAIN.EXE
MISFUNCS.DLL
[MAIN.EXE]
MAIN.PRG
MISFUNCS.LIB
[MISFUNCS.DLL]
HOLA.PRG
DIGAHOLA.PRG
El Proyecto consiste en su totalidad de un archivo EXE y otro DLL. En la plantilla se listan los archivos fuentes PRG
correspondientes en dos secciones separadas. La sección para el archivo EXE incluye la biblioteca de importación
MISFUNCS.LIB necesaria para usar las funciones DLL. Esta plantilla debe ser expandida llamando a PBUILD con
la opción /g option y una segunda llamada - sin la opción /g - finalmente crea ambos EXE y DLL con su archivo de
biblioteca de importación.

Si se crea un archivo DLL sin el ProjectBuilder, usando por ejemplo una utilidad Make, han de seguirse un total de
cinco pasos diferentes:

1. Compilar todos los archivos PRG requeridos para el archivo DLL con la opción /DLL del compilador.
2. Crear un archivo con las definiciones para los módulos en el archivo DLL (archivo DEF). Este archivo con-
tiene una lista de todas las funciones o procedimientos que son exportadas desde el archivo DLL y que
pueden ser importadas a un archivo EXE. Esta tarea es llevada a cabo por el programa utilitario
XPPFILT.EXE.
3. Crear una biblioteca de importación LIB y un archivo de exportación (EXP) desde el archivo DEF. Esto lo
lleva a cabo el programa utilitario AIMPLIB.EXE.
4. Enlazar archivos OBJ con el archivo EXP a uno DLL.
5. Para poder usar un nuevo archivo DLL con un archivo EXE recientemente creado, la biblioteca de impor-
tación (LIB) debe enlazarse al archivo EXE.

El ejemplo a continuación describe los cinco pasos antedichos. Emplea los tres archivos PRG MAIN.PRG,
HOLA.PRG y DIGAHOLA.PRG:

Paso 1: Compilando

Los archivos fuente para un archivo EXE o una DLL deben ser compilados diferentemente:
XPP main /q /b

XPP Hola /q /b /dll


XPP DigaHola /q /b /dll

Se crean tres archivos OBJ. El archivo MAIN.OBJ sólo puede enlazarse a un archivo EXE y los archivos
HOLA.OBJ y DIGAHOLA.OBJ sólo pueden enlazarse a un archivo DLL.

Paso 2: Crear al archivo DEF


Para usar un archivo DLL, todas las funciones o procedimientos que pueden llamarse desde fuera del DLL deben
conocerse (definiciones de exportación). Se listan las definiciones para funciones de exportación o procedimientos
en un archivo DEF creado por el programa utilitario XPPFILT.EXE. Este genera un archivo DEF desde una lista de
archivos OBJ que pueden enlazarse a una DLL:

XPPFILT hola.obj DigaHola.obj /f:misfuncs.def

XPPFILT.EXE crea el archivo MISFUNCS.DEF que contiene toda la información para crear el archivo
MISFUNCS.DLL. El archivo DEF que ejemplifica lo antedicho luce así:

01: LIBRARY misfuncs INITINSTANCE TERMINSTANCE


02: DATA MULTIPLE NONSHARED READWRITE LOADONCALL
03: CODE LOADONCALL
04:
05: EXPORTS
06:
07: ;Del archivo objeto: hola.obj
08: HOLA
09:
10: ;Del archivo objeto: DigaHola.obj
11: DIGAHOLA

En un archivo DEF, las líneas destinadas a los comentarios comienzan con punto y coma. Las demás líneas
contienen declaraciones para el enlazador. La declaración LIBRARY declara el nombre de archivo del archivo DLL
e indica si las rutinas de iniciación son ejecutados sólo una vez durante la carga, o cada vez que un proceso re-
quiere un archivo DLL. Todos los archivos DLL creados con Xbase++ deben ejecutar sus rutinas de especializa-
ción para cada proceso (cada programa) e INSTANCE (línea 1) debe especificarse siempre.

Cuando múltiples programas Xbase++ acceden al mismo archivo DLL, ellos sólo pueden compartir el código del
programa y no las variables declaradas en él. Deben darse los atributos MULTIPLE NONSHARED READWRITE a
un archivo DLL. Esto se concreta usando la declaración DATA (línea 2).
La opción LOADONCALL especificada en las declaraciones DATA y CODE especifican que un archivo DLL se
carga en la memoria sólo cuando se ejecuta un módulo contenido en el archivo DLL. Alternativamente, se usa la
opción PRELOAD que especifica que la carga se realiza al comienzo del archivo EXE (líneas 2 y 3).

Siguiendo la declaración EXPORTS (línea 5) se listan todos los identificadores (nombres) para funciones de ex-
portación y procedimientos. Cada identificador debe aparecer en una línea por si solo. Sólo es posible llamar
desde un archivo DLL a las funciones o procedimientos especificados siguiendo la declaración EXPORTS.

Nota: Si se declaran clases en un archivo DLL, sólo los nombres de las clases deben listarse, y no los nombres de
los métodos declarados para una clase. EL nombre de la clase es también el nombre de la función clase y esto
debe definirse como exportado.

Paso 3: Crear la biblioteca de importación


El programa utilitario AIMPLIB.EXE utiliza el archivo DEF para crear una biblioteca de importación (biblioteca LIB)
y un archivo de exportación (archivo EXP):

AIMPLIB misfuncs.def

Como resultado, se crean los archivos MISFUNCS.LIB y MISFUNCS.EXP. El archivo LIB contiene información
sobre qué puede importarse por un archivo EXE, y el archivo EXP define qué se exporta de un archivo DLL.

Paso 4: Crear el archivo DLL


Cuando existe un archivo EXP, el archivo DLL puede crearse por medio del enlazador. Todos los archivos OBJ
más el archivo EXP han de especificarse:

ALINK /DLL hola.obj digahola.obj misfuncs.exp /OUT:misfuncs.dll

La opción /DLL se utiliza para enlazar archivos OBJ. El nombre del archivo DLL se define usando la opción /OUT.

Paso 5: Crear el archivo EXE


El ultimo paso crea el archivo ejecutable EXE. Todas las bibliotecas de importación conteniendo referencias a
archivos DLL adicionales deben especificarse para el enlazador:

ALINK main.obj misfuncs.lib

Este llamado a ALINK crea el archivo ejecutable MAIN.EXE como una aplicación en modo texto. No contiene
código desde el archivo DLL, pero referencia la biblioteca dinámica MISFUNCS.DLL. El código desde este archivo
se carga cuando una función contenida en la DLL es llamada desde el MAIN.EXE.

El programa de utilidades AIMPLIB.EXE


El programa AIMPLIB.EXE crea bibliotecas de importación y archivos de exportación desde archivos de definición
de exportación (archivos DEF). Estos archivos se requieren para la creación de archivos DLL para ser enlazados
estáticamente a un EXE (ver la sección previa). AIMPLIB acepta las siguientes opciones de líneas de comando:

/? | /h Despliega información sobre opciones de líneas de comandos.


/coff Crea una biblioteca de importación en Formato Común de Archivo Objeto (COFF). Esta es la
opción predeterminada para las plataformas de Windows.
/omf Crea una biblioteca de importación en Formato de Módulo Objeto (OMF). Esta es la opción
predeterminada para la plataforma de OS/2.
/o:<archivo> Especifica el nombre del archivo <archivo> para la biblioteca de importación.
/q Suprime lo que sale en pantalla cuando el programa está siendo ejecutado (modo silen-
cioso).

El compilador de recursos de Alaska - ARC.EXE


En las aplicaciones GUI, es una práctica común usar recursos externos para desplegar información gráfica que no
puede incluirse en archivos fuente de PRG, como las imágenes bitmap. Una forma de suministrar recursos ex-
ternos a una aplicación es enlazando los recursos al archivo ejecutable. De esta manera, se garantiza que los
recursos estén disponibles para el programa durante el tiempo de ejecución.
Los recursos externos deben existir en un formato binario para que el enlazador pueda unirlos a un archivo eje-
cutable. La conversión a formato binario es la tarea del compilador de recursos ARC.EXE que usa un archivo de
descripción para recursos externos (archivo ARC) con el fin de crear un archivo de recursos binarios (archivo
RES).
Declarando recursos externos - El archivo ARC
Directivas para ARC.EXE
Opciones para ARC.EXE

Declarando recursos externos - El archivo ARC


Cuando una aplicación de Xbase++ requiere recursos externos, se los declara en un archivo ARC. Este es un
archivo en formato ASCII que se compila a un archivo de recursos binarios (archivo RES) por el compilador de
recursos ARC.EXE. Los recursos externos son bitmaps, iconos y punteros. En un programa de aplicación se los
identifica por un ID numérico. El valor del ID debe estar entre 1 y 16384. Por tanto, el archivo ARC debe proveer
datos sobre el tipo de recursos, un ID numérico para cada recurso y el nombre del archivo que contiene el recurso:

Tipos de Recursos
Palabra Descripción
BITMAP Recurso Imagen como Bitmap, ver XbpBitmap()
ICON Recurso Icono, usado por XbpDialog() o XbpTreeViewItem()
POINTER Recurso Puntero de Ratón, ver XbpWindow():setPointer()
CHARACTER Recurso Cadena, ver LoadResource()
La longitud está limitada a 65535 caracteres por cadena.

*
* TEST.ARC
*

/* Declaración de diferentes
tipos de recursos
*/

BITMAP 110 = "Logo.bmp" // Bitmap


ICON 120 = "Folder.ico" // Icono
POINTER 130 = "Arrow.ptr" // Puntero
CHARACTER 200 = "Question." // Recurso de Cadena
* EOF
Estas líneas muestran los elementos sintácticos más importantes para declarar recursos en un archivo ARC. La
declaración comienza con una palabra clave indicativa del tipo de recurso. Esta es seguida por un ID numérico
para la identificación del recurso dentro de una aplicación. Después del ID, debe aparecer un signo igual y el
nombre del archivo entre comillas completa la declaración. Es posible incluir comentarios en el archivo ARC (como
en el archivo PRG) usando los caracteres /* y */ o una doble barra para comentarios dentro de líneas. Además, el
asterisco indica una línea de comentarios cuando es el primer caracter en la línea.

OS/2 - El compilador de recursos de OS/2, RC.EXE, requiere que se declaren los recursos de manera diferente.
Sin embargo, si se invoca a ARC.EXE con el parámetro /rc de la línea de comandos, éste traduce un archivo ARC
a un archivo RC que puede compilarse por otros compiladores de recursos como RC.EXE en la plataforma de
OS/2. De ésta manera, las declaraciones de recursos pueden hacerse independientemente del compilador de
recursos.

Directivas para ARC.EXE


En vez de IDs numéricos, pueden usarse constantes #define en un archivo ARC así como en un archivo PRG. Esto
tiene la ventaja que los recursos pueden identificarse en los archivos ARC y PRG usando las mismas constantes.
Para llevar a cabo ésto, las constantes se definen en un archivo CH. El compilador de recursos comprende las
directivas #inlude , #define , y #ifdef , #ifndef , #else #endif , y las trata de la misma forma que el compilador de
Xbase++. Por tanto, es posible declarar los recursos de la siguiente manera:

** archivo CH: BITMAPID.CH // Archivo de Inclusión define


// constantes para
#define ID_BMP_LOGO 10 // XPP.EXE y ARC.EXE
#define ID_BMP_FONDO 11
#define ID_BMP_PROXIMO 12
#define ID_BMP_PREVIO 13

** EOF

-----------------------------------------------------------------
** archivo ARC: TEST.ARC // Declaración de recursos

#include "BitmapID.ch" // Archivo de Inclusión CH

BITMAP // Palabra Reservada introduce


ID_BMP_LOGO = "Logo.bmp" // un bloque de recursos
ID_BMP_FONDO = "Backgrd.bmp" // del mismo tipo
ID_BMP_PROXIMO = "Next.bmp"
ID_BMP_PREVIO = "Prev.bmp"

#ifdef __OS2__ // Condicional


100 = "\bitmaps\os2\test.bmp" // Compilación debido al
// condicional
#else // constante implícita
100 = "\bitmaps\w32\test.bmp"
#endif

** EOF

Este ejemplo muestra el código para un archivo CH que define constantes para el recurso numérico de IDs. Las
constantes se usan en el archivo ARC puesto que ARC.EXE puede resolver la directiva #include.

Sólo se declaran los recursos bitmap en el ejemplo. La palabra clave BITMAP se escribe en una línea separada y
seguida por un bloque de declaraciones de recursos del mismo tipo. ARC se apoya en esta forma sintáctica para
declarar recursos que resulta en una mejor lectura del archivo ARC. Si se escribe una palabra clave en una línea
por sí sola, todas las líneas siguientes declaran el mismo tipo de recurso sin repetir la palabra clave.

Las ultimas líneas en el ejemplo demuestran como los recursos específicos del sistema operativo pueden de-
clararse en uno y en el mismo archivo ARC. Se usan las directivas #ifdef , #else y #endif , permitiendo así la
compilación adicional. ARC.EXE utiliza las mismas constantes #define implícitas como el compilador de Xbase ++:
__OS2__ and __WIN32__.

Opciones para ARC.EXE


El compilador de recursos se inicia en la línea de comandos usando la siguiente sintaxis:

ARC [<opciones>] <archivo1> [<archivoN>]

ARC.EXE crea un archivo RES desde todos los archivos ARC especificados en la línea de comandos. Si la di-
rectiva #include aparece en un archivo ARC, el compilador de recursos busca los archivos #include en el directorio
actual y en los directorios listados en la variable de entorno INCLUDE. Se busca en los archivos que contienen los
recursos externos - como bitmaps o iconos - en el directorio actual y en los directorios listados en la variable de
entorno XPPRESOURCE.

El compilador de recursos acepta las siguientes opciones de la línea de comandos:

/? | /h Despliega información sobre las opciones de ARC.


/d<id>[=<val>] La opción /d especifica la constante #define especifica la <id> al compilador de recursos
en la línea de comandos. La constante #define es válida dentro de un archivo ARC. Op-
cionalmente, puede asignársele el valor <val> a la constante.
/ga Cuando se usa la opción /ga todas las cadenas de caracteres literales en el archivo ARC
se convierten a ANSI antes que el compilador de recursos cree el archivo RES.
/go Cuando se usa la opción /go todas las cadenas de caracteres literales en el archivo ARC
se convierten a OEM antes de que el compilador de recursos cree un archivo RES.
/i:<path> La opción /i especifica el directorio de búsqueda <path> para que el compilador de re-
cursos utilice cuando localiza a los archivos #include. Normalmente, ARC.EXE solo busca
para estos archivos en los directorios especificados por las variables de entorno
INCLUDE.
/o:<nombre> Normalmente el compilador de recursos crea un archivo RES que tiene el mismo nombre
de archivo que el archivo ARC a partir del cual se crea. El parámetro /o se utiliza para
renombrar el archivo RES resultante como <nombre> .
/q Suprime lo que sale en la pantalla durante la compilación (modo silencioso).
/rc Traduce un archivo ARC a uno RC que puede compilarse por el compilador de recursos
RC.EXE de OS/2.
/v El parámetro /v activa el modo verboso del compilador de recursos

Conceptos Básicos de Programación de Bases de Datos


Este capítulo describe las bases de programación de Bases de Datos. Explica aspectos fundamentales, a saber:
términos mayores usados en el contexto de Bases de Datos y acceso a Bases de Datos.

Qué es una Base de Datos


Creando una Base de Datos
Guardando datos
área de trabajo y Alias
El espacio de trabajo de Xbase++
Puntero de registros y campos de Base de Datos

Qué es una Base de Datos


Teóricamente la palabra "Base de Datos" se refiere al conjunto completo de datos pertenecientes al dominio de un
problema. Estos datos pueden ser divididos en muchos archivos. En el contexto de dialectos de Xbase, se usa
"Base de Datos" cuando se discuten archivos individuales existentes en el formato DBF. Por esta razón histórica,
la palabra "Base de Datos" se emplea en la documentación como sinónimo para un único archivo DBF.

El formato de archivo DBF es la base para todos los dialectos Xbase y permite la manipulación directa de con-
juntos de datos extensos. Los datos dentro de cada archivo DBF están organizados en forma de tabla organizada.
Cada columna corresponde a un campo de Base de Datos (campo, para sintetizar), y cada fila a un registro de
datos (registro, para sintetizar). El titulo (o fila superior) contiene los nombres de los archivos que identifican los
campos. La siguiente tabla ilustra como se ordenan los datos en un archivo DBF:

NRCLIENTE APELLIDO NOMBRE TELEFONO


40001 Montenegro Marcelo (054)11-4674-3464
40002 Fisher Fred (614)713-4578
50013 Anderson Robert (819)567-9832
50021 Long Barbara (402)715-4321
50043 Baker Christine (517)454-3356
50057 Kemper Joseph (414)234-5678
50100 Smith Richard (303)614-4321

El archivo DBF (tabla) en este ejemplo contiene siete registros (filas). Cada registro incluye cuatro campos (co-
lumnas). Cada campo contiene información especifica sobre cada cliente.

En un archivo DBF la estructura de la tabla se define junto con los datos. La definición de la tabla incluye los
nombres de los campos (títulos de las columnas), su longitud (ancho de columna), tipos de datos y cantidad de
espacios decimales para los campos numéricos. Un archivo contiene datos tipeados precisamente, o sea los datos
de una columna han de tener siempre el mismo tipo datos. El formato de archivo DBF permite guardar un total de
cinco clases de datos diferentes: caracter, fecha, lógico, memo y numérico.

Es posible guardar la definición inicial de la estructura de una tabla DBF en la propia tabla DBF. Para ser empleada
de esta manera, la tabla debe contener los nombres de los campos, su longitud y el número de espacios decimales
(un total de cuatro columnas). La definición de la estructura de la tabla para el ejemplo antedicho seria el siguiente:

FIELD_NAME FIELD_TYPE FIELD_LEN FIELD_DEC


NRCLIENTE C 8 0
APELLIDO C 20 0
NOMBRE C 20 0
TELEFONO C 15 0

Esta tabla contiene la información necesaria para describir la estructura de un archivo DBF. Cada registro contiene
la definición para un campo determinado. Los títulos de las columnas en la tabla son los nombres de los campos
conocidos como un archivo DBF "de estructura extendida". Un archivo DBF "de estructura extendida" contiene las
definiciones para otro archivo DBF. Las definiciones de los campos comprenden la definición de la estructura de un
archivo DBF y son el punto de partida para la utilización de Bases de Datos.
Creando una Base de Datos
Un archivo DBF puede ser creado únicamente a partir de una definición de estructura. En Xbase++ los comandos
y funciones especificados en la próxima tabla pueden ser empleados para crear la definición a continuación:

Funciones y comandos para la creación de una Base de Datos


Función Comando
DbCreateExtStruct() CREATE
DbCreateFrom() CREATE FROM
DbCopyStruct() COPY STRUCTURE
DbCopyExtStruct() COPY STRUCTURE EXTENDED
DbCreate()

Las primeras cuatro definiciones y comandos en la tabla existen únicamente debido a razones de compatibilidad y
no deberían continuar siendo usadas. La creación de un archivo DBF en Xbase++ debería hacerse utilizando la
función DBCreate(). DBCreate() toma como parámetro la definición de estructura para el archivo DBF en la forma
de un ordenador bidimensional. Aquello evita el mecanismo indirecto que pone en práctica el empleo de un archivo
de estructura extendida. El siguiente código demuestra el uso óptimo de la función DbCreate():

cNombreArchivo := "CUST.DBF"
aEstructura := { { "NRCLIENTE" , "C", 8, 0 }, ;
{ "APELLIDO" , "C", 20, 0 }, ;
{ "NOMBRE" , "C", 20, 0 }, ;
{ "TELEFONO" , "C", 15, 0 } }
DbCreate( cNombreArchivo, aEstructura )

El nombre del archivo en conjunto con la estructura del archivo DBF se los guarda en variables pasadas a la
función DbCreate(). La estructura del archivo es definida en un orden bidimensional. Todos los campos en este
ejemplo tiene el tipo de dato "caracter", identificado por la letra "C" (caracter). La longitud de los campos son 8, 20,
20 y 15 caracteres respectivamente, y todos los campos carecen de espacios decimales.

El nombre de un archivo puede contener hasta un máximo de 10 caracteres. Esta misma regla se aplica también a
nombres de campos que demandan nombres de variables. La primer letra debe ser un caracter alfabético un
subrayado y todos los demás caracteres deben ser alfanuméricos o un subrayado.

El tipo de dato de un campo se define por una única letra. Se reconocen la letra "C" (caracter),"D" (fecha),
"L"(lógico), "M" (memo) y "N" (numérico).

La máxima longitud del nombre de un campo depende del tipo de dato.


Caracter ("C") Máximo 64 KBytes
Fecha ("D") Siempre 8 Bytes
Lógico ("L") Siempre 1 Byte
Memo ("M") Siempre 10 Bytes
Numérico ("N") Máximo 19 Bytes

Un campo memo siempre ocupa 10 bytes, pero puede guardar un número ilimitado de caracteres. Esto es porque
los contenidos de campos memo se guardan en un archivo separado llamado archivo DBT.
Los campos numéricos guardan números con una máxima longitud de 19 lugares. El decimal y el prefijo cada uno
toma un byte. El número más largo que puede ser guardado es 2^32-1 y el máximo número de espacios decimales
es 15.

Guardando datos
Luego que un archivo DBF es creado utilizando DBCreate(), debe ser abierto antes de que se pueda guardar datos
o leerlos a partir de el. La siguiente tabla da una vista general de las funciones y comando que pueden emplearse
para las operaciones simples de base de datos "USE", "CLOSE", "APPEND BLANK" y "REPLACE".

Funciones y comandos operaciones simples de Base de Datos


Función Comando Descripción
DbUseArea() USE Abre una Base de Datos
DbAppend() APPEND BLANK Anexa registro nuevo
FieldPut() REPLACE Cambia los contenidos de los campos
DbCloseArea() CLOSE Cierra Base de Datos
Independientemente de que operaciones de Base de Datos sean programadas usando funciones o comandos es
simplemente cuestión de preferencia personal del programador. Al tiempo de corrida de un programa no hay di-
ferencia entre una operación de Base de Datos que es programada como comando y otra que es programada
como función. En muchos casos es más fácil programar con la sintaxis de comando. El código de programa a
continuación muestra operaciones usando sintaxis de comando que puede ser contrastada con el otro ejemplo a
continuación que usa sintaxis como función.

USE Cliente // abrir Base de Datos


APPEND BLANK // anexar registro
REPLACE NrCliente WITH "50112" , ; // entrar datos
Apellido WITH "Miller" , ; // campos del nuevo
Nombre WITH "Karl" , ; // registro
Telefono WITH "(713)517-6554"

CLOSE Cliente // cerrar Base de Datos

En el ejemplo, un nuevo registro es añadido a una Base de Datos conteniendo información del cliente. Las mismas
operaciones aparecen debajo usando la función sintaxis:

DbUseArea(, , "Cliente") // abrir Base de Datos


DbAppend() // anexar registro
FieldPut( 1 , " 50112") // entrar datos en los
FieldPut( 2 , "Miller" ) // campos del nuevo
FieldPut( 3 , "Karl" ) // registro
FieldPut( 4 , "(713)517-6554" )
DbCloseArea() // cerrar Base de Datos

En este ejemplo, los datos del cliente son incorporados a la Base de Datos por la función FieldPut() que identifica
el campo por su posición original. Al hacer esto el programa pierde claridad. Una mejor solución en este caso
consiste en dirigirse a los campos mediante el nombre de alias FIELD y realizar tareas directas:

DbUseArea(, , "Cliente") // abrir Base de Datos


DbAppend() // anexar registro
FIELD->NrCliente := " 50112" // entrar datos en los
FIELD->Apellido := "Miller" // campos del nuevo
FIELD->Nombre := "Karl" // registro
FIELD->Telefono := "(713)517-6554"
DbCloseArea() // cerrar Base de Datos

En conjunto con estas operaciones elementales de Base de Datos Xbase++ provee funciones que obtienen in-
formación sobre Base de Datos o campos individuales. Una Base de Datos debe ser abierta en orden para las
funciones contenida en la siguiente tabla:

Funciones que devuelven información sobre una Base de Datos


Función Descripción
Header() Devuelve el tamaño de la cabecera de archivo en bytes
RecSize() Devuelve el tamaño de un registro en bytes
RecCount() Devuelve la cantidad de registros en el archivo
LastRec() Devuelve el número de registros en el archivo
LUpdate() Devuelve la fecha del último acceso a escritura

Estas funciones pueden ser empleadas para calcular el tamaño de una base de datos (las funciones RecCount() y
LastRec() ambas proporcionan el mismo valor). La formula para calcular el tamaño de una Base de Datos es:

(RecSize() * LastRec()) + Header() + 1

Se usa frecuentemente esta formula junto con la función DiskSpace() durante rutinas backup, para determinar si
un archivo DBF cabe en la disquetera blanco a la cual ha de ser copiada.
Es importante la información de los campos en la programación de Base de Datos. No solamente son los conte-
nidos de un campo utilizados cuando se procesan datos sino también el tipo de dato, posición ordinal dentro de un
archivo DBF y el nombre del campo. Por ejemplo, esta información se torna útil cuando al escribir rutinas genéricas
o lecto-escritura dirigida por dato.
Funciones retornando información sobre campos en una Base de Datos
Función Descripción
FCount() Devuelve el número de campos en el archivo
FieldName() Devuelve el nombre de campo basado en la posición ordinal
FieldPos() Devuelve la posición ordinal del campo basado en nombre de campo
FieldGet() Devuelve el contenido de un campo basado en la posición ordinal
Type() Devuelve el tipo de datos de un campo basado en el nombre de campo
DbStruct() Devuelve el la estructura de archivo como un arreglo bidimensional

El siguiente código presenta un ejemplo utilizando una de las funciones de esta tabla. Este código incluye dos
funciones definidas por el usuario. Una de ellas lee todos los campos de un registro en un ordenador y la otra
escribe el ordenador nuevamente como archivo. Esta es una técnica común de programación para amortiguar la
edición de registros y es frecuentemente usada en programas diseñados para operaciones multi-uso en un am-
biente de red:

PROCEDURE Main
LOCAL aRegistro := LeeRegistro() // leer registro

// <editar datos>

IF RLock() // Bloquear registro


GrabaRegistro( aRegistro ) // Escribir registro
DbUnlock() // Liberar registro
ENDIF
RETURN

FUNCTION LeeRegistro() // Leer registro


RETURN AEval( Array( FCount() ), {|x,i| x:= FieldGet(i) },,, .T. )

FUNCTION GrabaRegistro( aRegistro ) // Escribe registro


RETURN AEval( aRecord, {|x,i| FieldPut(i, x) } )

Area de trabajo y Alias


En programación de Base de Datos, es una regla más que una excepción, que un programa de aplicación use
múltiples bases de datos (archivos DBF) al mismo tiempo. El acceso a diferentes Bases de Datos es posible
mediante áreas de trabajo mientras un programa esta en funcionamiento. Un área de trabajo se identifica por un
índice numérico y tiene dos estados diferentes, free y used . Cuando se abre una Base de Datos en un área de
trabajo con el comando USE, se la utiliza. Sólo es posible abrir una Base de Datos en un área de trabajo y el
acceso a una Base de Datos puede ocurrir únicamente en el área de trabajo seleccionada o en uso. En la pro-
gramación de Base de Datos, las áreas de trabajo juegan un rol central, puesto que encapsulan el acceso a las
Bases de Datos.
Cuando se abre una Base de Datos, se le da a un área de trabajo un identificador simbólico: un nombre de alias. Si
no es especificado explícitamente, se crea de manera implícita el nombre de alias desde algún archivo de una
Base de Datos. Por consiguiente, un área de trabajo tiene tres atributos importantes y hay tres funciones que
devuelven tal información.

funciones para áreas de trabajo


Función Descripción
Select() Devuelve el número de un área de trabajo
Used() Determina cuando un área de trabajo está en uso
Alias() Devuelve el nombre de alias de un área de trabajo

Es posible recuperar información desde diversas áreas de trabajo mediante el operador alias (->) y desde el
número de áreas de trabajo o su nombre de alias. Inclusive, se puede acceder a los campos de múltiples Bases de
Datos vía la referencia al alias. Referenciar áreas de trabajo con alias es una operación fundamental y progra-
mación de uso frecuente con múltiples Bases de Datos o áreas de trabajo, respectivamente. Las líneas de código
a continuación nos muestran algunas operaciones básicas:

PROCEDURE Main
? Select() // Resulta: 1
? Used() // Resulta: .F.
? Alias() // Resulta: "" (cadena nula)

USE Cliente // Abre Base de Datos sin alias


USE Facturas ALIAS Fac NEW // Abre Base de Datos en nueva
USE Ordenes ALIAS Ord NEW // área de trabajo con nombre de alias

? Select() // Resulta: 3
? Used() // Resulta: .T.
? Alias() // Resulta: Ord

? (1)->(Alias()) // Resulta: Cliente


? (2)->(Alias()) // Resulta: Fac

? Cliente->Apellido // Resulta: Miller


? (2)->FechaFac // Resulta: 06.12.1994
? Ord->(Select()) // Resulta: 3
RETURN

El programa presenta los aspectos más importantes para el uso de múltiples bases de datos. Al comienzo del
Principal procedimiento, se selecciona un área de trabajo número 1; es el área de trabajo actual. Ya que ninguna
Base de Datos se encuentra abierta desde el inicio la función Used() devuelve el valor .F. (falso), y el nombre de
alias correspondiente a la primer área de trabajo es una cadena vacía (""). Solo después de usar el comando USE
para abrir la Base de Datos, el área de trabajo recibe un nombre de alias. El valor de retorno .T. (verdadero) de la
función Used() luego indica el área de trabajo actual a ser empleada. Cuando se programa el comando USE con la
opción NEW, selecciona primeramente un área de trabajo y seguidamente abre una Base de Datos. Por lo tanto, el
valor de retorno de la función Select() es ahora 3 y no más 1 como en la primer llamada. Se abren tres Bases de
Datos y la tercer área de trabajo es actual. Su nombre de alias es "Ord".

Cuando se llama una función sin una referencia al alias es ejecutada en el área de trabajo actual. Una referencia al
alias causa que una función sea ejecutada en el área de trabajo correspondiente (o con mayor precisión el área de
trabajo se torna temporariamente la actual; entonces la función es ejecutada y al área de trabajo anterior es se-
leccionada otra vez. La llamada a función (1)->(Alias()) devuelve el nombre de alias de la primer área de trabajo.
Es la cadena "Cliente" la que se crea implícitamente desde el nombre del archivo.

El ejemplo también nos muestra diferencias de sintaxis que se requieren para referencias al alias para campos de
Bases de Datos (o símbolos) y llamadas a función (o expresiones). Si una expresión aparece sobre el lado derecho
del operador de alias, a ser escrita en paréntesis. Se programa un símbolo sin paréntesis. Sobre el lado izquierdo
del operador de alias se deben usar paréntesis cuando se utiliza un número o variable en vez de los nombres de
alias literales. Ergo, se codifican referencias al alias de formas diferentes:

cAlias := "Cliente"
nArea := 1

xValor := Cliente->Apellido // Referencia de Alias a la variable de campo


xValor := (1)->Apellido
xValor := (cAlias)->Apellido
xValor := (nArea)->Apellido

xValor := Cliente->(Select()) // Ejecuta una expresión en


xValor := (1)->(Select()) // un área de trabajo
xValor := (cAlias)->(Select())
xValor := (nArea)->(Select())

Se garantiza la unicidad de nombres de alias de áreas de trabajo, así como lo son los números de áreas de trabajo.
Si se abre varias veces el mismo archivo de la Base de Datos en diferentes áreas de trabajo, cada área de trabajo
recibirá un nombre de alias distinto, pero referenciará la misma Base de Datos física.

USE Cliente NEW


USE Cliente NEW
USE Cliente ALIAS Clie NEW
USE Cliente ALIAS Clie NEW

? (1)->(Alias()) // Resulta: Cliente


? (2)->(Alias()) // Resulta: Cliente_2
? (3)->(Alias()) // Resulta: Clie
? (4)->(Alias()) // Resulta: Clie_4

Cuando se intenta usar más de una vez el mismo nombre de alias, Xbase++ crea de manera implícita un identi-
ficador único al unir un caracter guión bajo "_" y el número de área de trabajo al nombre de alias original.
El espacio de trabajo de Xbase++
El concepto de áreas de trabajo existe en cada dialecto de Xbase++. Se extiende en Xbase++ por un espacio de
trabajo que provee un nivel de abstracción más elevado. El concepto de áreas de trabajo se describe así:

1. Un espacio de trabajo contiene áreas de trabajo.


2. El número de áreas de trabajo dentro de un espacio de trabajo se limita a 65.000.
3. Un espacio de trabajo define el área de trabajo actual. Todas las operaciones de Bases de Datos son pro-
gramadas sin referencias al alias se ejecutan en esta área de trabajo.
4. Seleccionar el área de trabajo es una operación del espacio de trabajo. Cada espacio de trabajo siempre tiene
un área de trabajo actual.
5. Cada thread tiene por lo menos un espacio de trabajo.

Un espacio de trabajo contiene áreas de trabajo y permite que se abran múltiples Bases de Datos en tiempo de
ejecución. El acceso a campos de Bases de Datos en un programa sigue un patrón jerárquico como el descripto
debajo:

Espacio de Trabajo Un espacio de trabajo por cada thread


|
+- área de Trabajo Un máximo de 65.000 áreas por espacio de trabajo
|
+- Nombre de Alias Referencia una Base de Datos abierta
|
+- Nombre de Campo Referencia un campo de Base de Datos (variable field)

Un espacio de trabajo y áreas de trabajo contenidas siempre existen durante el tiempo de ejecución de un pro-
grama, independientemente de si las Bases de Datos están o no abiertas. La operación fundamental en un es-
pacio de trabajo es la selección del área de trabajo actual. Mientras un área de trabajo no esta siendo utilizada
puede ser seleccionada por su número. Se pasa un número entre 1 y 65.000 al comando SELECT o función
DbSelectArea() con el fin de elegir un área de trabajo particular como la actual. Alternativamente, se especifica el
valor 0 (cero). Esto actualiza la próxima área de trabajo libre y con el número más pequeño. Por ejemplo,

PROCEDURE Main
USE Cliente
? Select() // Resulta: 1

SELECT 100
? Select() // Resulta: 100

SELECT 0
? Select() // Resulta: 2
RETURN

Cuando comienza el programa, se selecciona al área de trabajo 1 y la base de datos Cliente. Se abre
CLIENTE.DBF en este ejemplo con el comando USE en la primer área de trabajo. Luego, se selecciona el área de
trabajo 100 que se actualiza. Por ultimo, el comando SELECT 0 selecciona la próxima área de trabajo libre. Tiene
la posición ordinal 2.
Cuando se abre una Base de Datos en un área de trabajo no solo puede seleccionársela por su número sino por su
nombre de alias. Este ultimo es un nombre simbólico que facilita la programación con múltiples Bases de Datos:

PROCEDURE Main
USE Cliente
USE Facturas NEW
USE Partes NEW
? Select() // Resulta: 3
? Alias() // Resulta: Partes
SELECT 2
? Alias() // Resulta: Facturas
SELECT Cliente
? Select() // Resulta: 1
DbCloseAll() // Cierra todas las Bases de Datos
RETURN

En este ejemplo se abren tres Bases de Datos en tres áreas de trabajo diferentes. Demuestra como seleccionar
una Base de Datos utilizando un número o un nombre de alias. Al final todas las Bases de Datos se cierran.
Operaciones en un espacio de trabajo
La función DbCloseAll() constituye un ejemplo para las funciones que desarrollan operaciones en áreas de trabajo.
Afecta todas las áreas de trabajo usadas, no así las libres. Hay un conjunto de funciones en Xbase++ que opera en
una única área de trabajo y desarrolla operaciones con un área de trabajo única o todas las áreas de trabajo
usadas. La tabla siguiente da una lista de las estas funciones:

Funciones y comandos para un área de trabajo:


Función Comando Descripción
Afecta un área de trabajo

DbSelectArea() SELECT Selecciona el área de trabajo actual

Afecta todas las áreas de trabajo en uso

DbCloseAll() CLOSE ALL Cierra los archivos de Base de Datos


DbCommitAll() COMMIT Escribe los buffers permanentemente al disco
DbUnlockAll() UNLOCK Libera los bloqueos de registros y archivos
WorkspaceEval() Evalúa un bloque de código en todas
las áreas de trabajo en uso
WorkspaceList() Devuelve los nombres de alias de todas
las áreas de trabajo en uso en un arreglo

La función WorkspaceEval() de la tabla es la más poderosa puesto que evalúa un bloque de código en todas las
áreas de trabajo. Todas las funciones Db...All() podrían ser emuladas con esta función y las funciones nuevas para
todas las áreas de trabajo programadas.

Operaciones entre dos espacios de trabajo


En Xbase++, un espacio de trabajo esta ligado a un thread. Como consecuencia, las áreas de trabajo son también
recursos locales del thread porque están contenidas en un espacio de trabajo. Ello implica que el acceso a Bases
de Datos está limitado al thread donde es posible abrir una Base de Datos. Sin embargo, un programa mul-
ti-threaded debe acceder Bases de Datos en diversos threads, lo cual se logra abriendo la misma Base de Datos
en threads diferentes o por intercambio de la referencia a una base de datos abierta entre threads o espacios de
trabajo respectivamente. Las funciones en la próxima tabla sirven a este último propósito:

Funciones para espacios de trabajo múltiples


Función Descripción
DbRelease() Transfiere el alias de un área de trabajo al Espacio Cero
DbRequest() Transfiere un alias desde el Espacio Cero en un área de trabajo

En Xbase++, un nombre de alias referencia a una Base de Datos. El nombre de alias de un área de trabajo puede
ser intercambiado entre dos espacios de trabajo. Transferir nombres de alias requiere de un espacio de trabajo
virtual llamado el espacio Cero. Se usan para el intercambio las dos funciones DbRelease() y DbRequest() :

USE Cliente
? Used() // Resulta: .T.

DbRelease()
? Used() // Resulta: .F.

DbRequest()
? Used() // Resulta: .T.

CLOSE Cliente

Luego de llamar a DbRelease() , el área de trabajo actual se encuentra libre. No obstante, el archivo de Base de
Datos aun esta abierto. Referencia a la base de datos (el nombre de alias) que no existe mas en el espacio de
trabajo actual. Se la transfiere al espacio Cero y puede transferirse de vuelta a un área de trabajo de un espacio de
trabajo llamando a la función DbRequest() . Cuando se termina DbRequest() , un área de trabajo recibió un nombre
de alias y es utilizada nuevamente. Es importante comprender que el archivo de la Base de Datos permanece
abierto durante estas operaciones. Solo la referencia al archivo abierto cambia su lugar desde el espacio de tra-
bajo actual al espacio Cero de vuelta al mismo u otro espacio de trabajo.
Puntero de registros y campos de Base de Datos.
Cuando se usa un área de trabajo todos los datos guardados en un archivo de base de datos son accesibles.
Luego de abrir una Base de Datos el puntero de registros apunta inicialmente el primer registro. Durante el tiempo
de ejecución las variables de campo de un programa luego contienen los valores guardados en el primer registro.
Esto significa que un programa solo puede acceder a campos del registro actual. Con el fin de acceder a datos
guardados en diferentes registros, se debe mover el puntero de registros.La navegación por Base de Datos debe
ocurrir en un área de trabajo.

USE Cliente
? Recno() // Resulta: 1
? Cliente->Apellido // Resulta: Miller

GOTO 10 // selecciona registro nro. 10

? Recno() // Resulta: 10
? Cliente->Apellido // Resulta: Brown

Este ejemplo demuestra que los contenidos de variables de campo cambian cuando se mueve el puntero de
trabajo. Se seleccionan los registros al navegar por el puntero de registros, y esta es la única forma de acceder a
todos los datos guardados en un archivo de Base de Datos. La próxima tabla da una lista de las funciones mas
importantes utilizadas en el contexto de navegación en la Base de Datos.

Funciones y comandos para navegar en Base de Datos:


Función Comando Descripción
Bof() Se ha alcanzado el Comienzo de Archivo?
Eof() Se ha alcanzado el Fin de Archivo?
DbSkip() SKIP Mueve al puntero de registro
DbGoTo() GOTO Mueve al puntero de registro a un
registro particular de la Base de Datos
DbGoTop() GO TOP Mueve al puntero de registro al
primer registro de la Base de Datos
DbGoBottom() GO BOTTOM Mueve al puntero de registro al
último registro de la Base de Datos
DbGoPosition() Mueve al puntero de registro un porcentaje determinado
DbPosition() devuelve la posición del puntero de registros
como un valor porcentual
RecNo() Devuelve el puntero de registro

Las funciones Recno() y DbSkip() son lejos las más importantes para la navegación en Base de Datos. La primera
regresan el puntero de registros y la segunda lo cambian. Además, las dos funciones Bof() and Eof() se emplean
muy a menudo. Indican si el puntero de registro ha alcanzado los limites de una Base de Datos (superior o inferior)
de un archivo de Base de Datos. El siguiente ejemplo de código muestra una construcción típica utilizada para
obtener información de todos los registros de un archivo de la Base de Datos:

USE Cliente // Abre Base de Datos

DO WHILE .NOT. Eof() // Sale del ciclo al Final del archivo


// Despliega campos simples
? Recno(), FIELD->Nombre, FIELD->Apellido
DbSkip(1) // Próximo registro
ENDDO

CLOSE Cliente // Cierra Base de Datos

El Motor de Base de Datos de Xbase++ (DBE)


Este capítulo describe los conceptos del Motor de Base de Datos de Xbase++ y muchos aspectos de su uso.
Contrariamente a otros entornos de desarrollo de Xbase, no hay un único manejador de base de datos monolítico
en Xbase++ (inclusive uno que sea reemplazable como un Manejador de Bases de Datos Reemplazable, o "RDD").
En cambio, Xbase++ usa el concepto de un "motor de base de datos (DBE)", que consiste de componentes indi-
viduales. Estos componentes son objetos que ponen a disposición servicios (métodos) para la manipulación de
datos y archivos. Los componentes individuales pueden ensamblarse en un Motor de Base de Datos compuesto.
Con este concepto modular el programador tiene la flexibilidad de acceder a varios modelos de bases de datos
disponibles hoy en día, así como cualquier modelo en el futuro.

Conceptos Básicos de Motores de Bases de Datos


Motores de Base de Datos y lenguaje de programación
Determinar información sobre los Motores de Bases de Datos
La función DbeInfo()
La función DbInfo()
La función FieldInfo()
Especificaciones del Motor de Base de Datos - formato de archivos

Conceptos básicos de Motores de Bases de Datos


La arquitectura de los Motores de Bases de Datos (DBE) de Xbase++ está diseñada para ser aplicable a cualquier
modelo de base de datos. Esto incluye ambas las bases de datos orientadas a registros (por ejemplo, bases de
datos de Xbase) y orientadas a conjunto y bases de datos relacionales (por ejemplo, bases de datos de SQL). Una
base de datos puede describirse de manera abstracta como constituida por cuatro componentes: orientado a
conjuntos y Bases de Datos Relacionales (por ejemplo, Bases de Datos SQL). Una Base de Datos puede ser
descripta abstractamente como si tuviera cuatro componentes:

Componente DATA Este componente describe el modelo de base de datos o cómo se alma-
cenan los propios datos. "Base de Datos" aquí puede considerarse análoga
a una "tabla" en el modelo de relaciones o de una "estructura" en el modelo
de bases de datos jerárquica.
Componente ORDER Este componente pone a disposición un mecanismo para permitir la defini-
ción del ordenamiento lógico basado en las expresiones. Esto permite dejar
intacto el ordenamiento físico preexistente de las tablas.
Componente RELATION Este componente define las relaciones entre los datos. Enlaza las tablas de
manera física o lógica basada en expresiones. La tarea de este compo-
nente puede considerarse análoga al "operador JOIN" en el modelo de re-
laciones o el "puntero de conjuntos" en el modelo de relaciones o el "pun-
tero de Conjuntos" en el modelo de bases de datos en red basada en ar-
chivos.
Componente DICTIONARY Este componente describe la estructura de bases de datos y/o las reglas de
integridad.

Una Motor de Base de Datos representa uno o más de los cuatro componentes y define como interaccionan con la
base de datos subyacente. El Motor de Base de Datos está contenido en un archivo DLL y se lo carga durante el
tiempo de ejecución del programa. Por ejemplo, el archivo DBFDBE.DLL contiene el Motor de Base de Datos que
provee el componente para manipular archivos DBF (componente DATA). El archivo NTXDBE.DLL contiene un
Motor de Base de Datos que representa un componente para los órdenes (componente ORDER) que es válido
para las bases de datos orientadas a registros (por ejemplo, archivos DBF). Este DBF maneja archivos índice en el
formato NTX. DBFDBE y el NTXDBE son Motores de Base de Datos los cuales contienen cada uno sólo un
componente para la manipulación de datos y archivos. Están designados cada uno como un "componente DBE".
Dos componentes DBEs que proveen diferentes componentes pueden ensamblarse en un nuevo Motor de Base
de Datos que controla entonces a ambos componentes y combina todas las características de los componentes
DBEs dentro del nuevo DBE. Tal Motor de Base de Datos ensamblado se lo llama "DBE compuesto". Un DBE
compuesto existe sólo en la memoria y no se guarda en un archivo en el Disco rígido. Los D BEs componentes, sin
embargo, se almacenan en archivos.

Los DBEs compuestos son necesarios generalmente cuando se programa con las bases de datos y deben crearse
al inicio del programa. Los componentes DBEs apropiado se cargan en la memoria usando la función DbeLoad() y
el DBE compuesto se crea usando la función DbeBuild(). El siguiente programa de ejemplo muestra la aproxi-
mación básica:

IF .NOT. DbeLoad( "DBFDBE", .T. ) // carga componente DBE


Alert( "Motor de Base de Datos DBFDBE no cargado", {"OK"} )
ENDIF

IF .NOT. DbeLoad( "NTXDBE", .T. ) // carga componente DBE


Alert( "Motor de Base de Datos NTXDBE no cargado", {"OK"} )
ENDIF
// crea DBE componente
IF .NOT. DbeBuild( "DBFNTX", "DBFDBE", "NTXDBE" )
Alert( "DBFNTX Motor de Base de Datos no creado", {"OK"} )
ENDIF
En el ejemplo, dos componentes DBEs almacenados en los archivos DBFDBE.DLL y NTXDBE.DLL se guardan en
la memoria. A partir de estos dos DBEs componentes un DBE compuesto se crea con el nombre "DBFNTX". Este
DBE compuesto puede manejar archivos DBF (componente DATA) así como archivos NTX (componente
ORDER).

El ejemplo usa dos de las cuatro funciones básicas disponibles para la programación usando Motor de Base de
Datos. La siguiente tabla lista las cuatro funciones:

Funciones para la preparación de Motor de Base de Datos


Función Descripción
DbeLoad() Carga componente DBE en la memoria
DbeBuild() Crea componente DBE y lo define como DBE actual
DbeSetDefault() Define DBE predeterminado para operaciones de archivos
DbeUnLoad() Remueve DBE de la memoria

La función DbeLoad() carga un componente DBE en la memoria principal. Ese DBE puede cargarse de dos ma-
neras: oculto y visible. Un DBE componente que se carga como "oculto" sólo puede usarse para crear DBEs
compuestos y no directamente usados para la manipulación de datos y archivos.

DbeLoad( "DBFDBE", .F. ) // cargar componente DBE como visible


USE Cliente // abrir archivo DBF
Browse() // lleva a cabo operaciones de bases de datos

En este ejemplo, el componente DBE se carga como visible (.F. significa no "oculto" aquí). Por tanto, el DBE puede
usarse para abrir un archivo DBF, y para ejecutar operaciones de archivos. El ejemplo a continuación muestra lo
opuesto:

DbeLoad( "DBFDBE", .T. ) // carga componente DBE como oculto


USE Cliente // error durante tiempo de ejecución

Aquí el componente DBE se carga como "oculto" y cualquier intento de usarlo para manipulación de archivos
conduce a errores durante el tiempo de ejecución. El Motor de Base de Datos sólo puede usarse para crear DBE
compuestos usando la función DbeBuild().

Nota
El DBE compuesto "DBFNTX" se crea por predeterminación cada vez que un programa de Xbase++ se inicia. Esto
significa que todos los archivos y comandos de índice y funciones trabajan con este Motor de Base de Datos por
predeterminación. El DBFNTX Motor de Base de Datos se crea en el archivo DBESYS.PRG. Se llama a la función
DbeSys() contenida en este archivo cada vez que se inicia un programa de Xbase++.

El Motor de Base de Datos DBFNTX consiste de sólo dos componentes y puede hacer una mímica del modelo de
base de datos de Clipper de la forma en la que manipula archivos DBF y NTX. El componente RELATION es único
en este modelo de base de datos en que ningún DBE se requiere para el componente RELATION. Además, no hay
componente DICTIONARY, porque este no está respaldado por el modelo de base de datos.

Motores de Base de Datos y lenguaje de programación


Para una comprensión completa de los Motores de Base de Datos, han de explicarse los mecanismos internos que
crean la relación entre el lenguaje de programación y el modelo de base de datos. Como por ejemplo, el comando
USE y la función DbUseArea() abren uno o más archivos en un área de trabajo. La apertura del archivo actual se
lleva a cabo por un Motor de Base de Datos, en este caso por el componente DATA del DBE. El comando INDEX
ON crea un índice para un archivo abierto en el área de trabajo. A esta operación también la lleva a cabo el DBE,
pero el DBE utiliza el componente ORDER.

Ciertos comandos y funciones existen dentro de los confines del lenguaje de programación y sólo pueden eje-
cutarse por componentes específicos del Motor de Base de Datos. Con estos comandos y funciones, el pedido
debe encaminarse al DBE apropiado capaz de ejecutar el comando. Esta tarea es llevada a cabo por un meca-
nismo interno llamado "Agente de Manipulación del Lenguaje de Base de Datos", abreviado como DMLB. El DMLB
encamina los pedidos desde el programa a un componente específico de una DBE.

El Motor de Base de Datos DBFDBE, por ejemplo, no es capaz de crear un índice. Debe cargarse el Motor de Base
de Datos NTXDBE para concretar esto. Asimismo, este DBE no puede abrir un archivo DBF. Para que el comando
USE y el comando INDEX ON sean ejecutables, debe estar disponible un DBE compuesto que contenga todos los
componentes requeridos por el programa. La tarea del DMLB es dirigir los pedidos que pueden llevar a cabo la
tarea pedida. Si ningún componente correspondiente está disponible el DMLB intenta llevar a cabo la tarea por sí
mismo basado en sus propias habilidades. Si esto no tiene éxito, se produce un error durante el tiempo de eje-
cución. Este modelo provee la habilidad de excluir los componentes que no son necesarios. Por ejemplo, es po-
sible escribir un programa que no use un índice. En este caso, el Motor de Base de Datos NTXDBE (más preci-
samente el componente ORDER) no necesita cargarse.

La distribución de funcionalidades específicas para componentes DBEs y la habilidad para hacer que todas las
funcionalidades estén disponibles dinámicamente usando un componente DBE ofrece mayor flexibilidad en la
manipulación de datos y archivos. Por ejemplo, puede crearse un índice para un archivo que existe en el formato
SDF (Sistema de Formato de Datos). El NTXDBE (componente ORDER) necesita principalmente combinarse con
el Motor de Base de Datos SDF (componente DATA). El resultado es un Motor de Base de Datos SDFNTX que
permite el ordenamiento lógico de los archivos ASCII que se encuentran en el formato SDF. El siguiente código del
programa ilustra esto (Nota: el ejemplo asume que el DBE DBFNTX compuesto ya se ha creado).

USE Cliente VIA DBFNTX // abre archivo DBF


COPY TO Direccion.txt SDF // copia la base a un archivo ASCII
CLOSE Cliente
DbeLoad( "SDFDBE", .T.) // carga al motor SDF
// (Formato de Datos de Sistema)
// y crea un DBE compuesto
DbeBuild( "SDFNTX", "SDFDBE", "NTXDBE" )

USE Direccion.txt VIA SDFNTX // abre archivo ASCII


INDEX ON Upper(Nombre) TO Temp // crea un índice

El hecho que los componentes DATA puedan combinarse con los componentes ORDER para crear DBE com-
puestos permite la funcionalidad de varias operaciones de archivo esté a disposición de los diferentes formatos de
archivo. En el ejemplo, un archivo TXT (archivo ASCII) se abre usando el comando (USE) previamente utilizado
para abrir un archivo DBF. Además las operaciones con bases de datos como DbAppend() y DbSeek(), pueden
ahora llevarse a cabo en el archivo TXT. La adición de un nuevo registro de datos usando DbAppend() se concreta
vía el componente SDF y la búsqueda de los datos en el archivo TXT usando DbSeek() ocurre vía el componente
NTX.

Hay, sin embargo, una limitación de que debe ser posible llevar a cabo una operación particular con archivos del
formato de archivos. En el caso de Motor de Base de Datos SDF, borrar registros de datos usando la función
DbDelete() no es posible. Estas funciones marcan un registro para borrarlo y no hay provisión en el formato de
archivo SDF para las marcas de borrado. Consecuentemente, llamar a la función DbDelete() para un archivo bo-
rrado por el Motor de Base de Datos SDF conducirá a un error durante el tiempo de ejecución porque esta ope-
ración no se encuentra respaldada.

Determinar información sobre los Motores de Bases de Datos


Durante el tiempo de ejecución de un programa es a veces necesario determinar información sobre los DBEs
cargados, áreas de trabajo, o campos individuales en áreas de trabajo. Xbase++ provee las cuatro funciones
listadas en la siguiente tabla para esto:

Funciones para información sobre los Motores de Base de Datos


Función Descripción
DbeList() Determina cuáles DBEs se cargan
DbeInfo() Devuelve información sobre el DBE actual
DbInfo() Devuelve información sobre un área de trabajo
FieldInfo() Determina información sobre el campo a partir del cual
se basa la posición ordinal

Para una comprensión más profunda de estas funciones, se necesita conocimiento de los mecanismos internos
utilizados para abrir archivos con USE o DbUseArea(). Estos mecanismos no son reconocidos inmediatamente en
el nivel del lenguaje. Un archivo puede abrirse solamente cuando por lo menos se carga sólo un DBE que provee
al componente DATA para el formato de archivo pedido. Únicamente después que ese componente esté dispo-
nible puede abrirse un archivo en el área de trabajo, o de lo contrario se produce un error durante el tiempo de
ejecución. Si está o no disponible un DBE actual puede determinarse durante el tiempo de ejecución usando la
función DbeList(). DbeList() devuelve un arreglo de dos columnas: la primer columna contiene los nombres de los
DBEs y la segunda contiene los valores lógicos representando la "marca oculta" (ver la sección previa).

Abrir un archivo usando el DBE actual crea una instancia del DBE que se llama "objeto de base de datos" (DBO).
El DBO hace uso de todas las características del DBE actual y es el objeto de la base de datos realmente el que
abre y manipula los archivos. Un DBO representa el área de trabajo donde se abren los archivos.
Es de gran ayuda distinguir entre los Motores de Base de Datos, que proveen la funcionalidad que permite que los
archivos se abran en el área de trabajo y el objeto de base de datos que existe sólo cuando el archivo está abierto
en el área de trabajo. El objeto de base de datos es creado por el DBE cuando se abre el archivo. Manipula los
archivos y se lo descarta automáticamente cuando se cierra al archivo.

La función DbeInfo()
La función DbeInfo() provee información sobre el DBE actual. Por tanto, puede ejecutarse solamente cuando se
carga al DBE. Es similar a las funciones DbInfo() y FieldInfo() que puede llamarse cuando un archivo está abierto
en el área de trabajo actual. De lo contrario se produce un error durante el tiempo de ejecución.

Además de devolver información, DbeInfo() también permite que un DBE sea configurado de maneras específicas.
Varias configuraciones de un DBE son intercambiables y pueden redefinirse usando DbeInfo(). Como ejemplo, la
extensión predeterminada para los archivos puede alterarse:

#include "Dmlb.ch"
#include "DbfDbe.ch"

DbeLoad( "DBFDBE", .T.)


DbeLoad( "NTXDBE", .T.) // crea un DBE compuesto
DbeBuild( "DBFNTX", "DBFDBE", "NTXDBE" ) // DBFNTX
// extensión por omisión DBF->FBD
// para archivos de bases de datos
DbeInfo( COMPONENT_DATA , DBE_EXTENSION, "FBD" )
// extensión por omisión NTX->XTN
// para archivos de índice
DbeInfo( COMPONENT_ORDER, DBE_EXTENSION, "XTN" )

DbCreate( "Temp", { { "Apell", "C", 20, 0 }, ;


{ "Nomb" , "C", 20, 0 }, } )

USE Temp // crea base de datos y


INDEX ON Field->Apell TO TempA // archivos índice
INDEX ON Field->Nomb TO TempB
CLOSE Temp
// despliega nombres de archivos.
AEval( Directory( "Temp*.*" ), ; // Resulta: Temp.FBD
{|a| QOut(a[1]) } ) // TempA.XTN
// TempB.XTN

En este ejemplo, las extensiones predeterminadas para dos tipos diferentes de archivos se redefine usando
DbeInfo(). Esto afecta a los archivos manipulados por el componente DATA del DBFNTX DBE (el archivo DBF se
crea como un archivo FBD) y los archivos que son manipulados por el componente ORDER (los archivos NTX se
crean como archivos XTN). Todo esto ocurre en sólo las dos líneas del ejemplo:

DbeInfo( COMPONENT_DATA , DBE_EXTENSION, "FBD" )


DbeInfo( COMPONENT_ORDER, DBE_EXTENSION, "XTN" )

Se muestra un aspecto especial de DbeInfo(): si se llama a la función con los parámetros, el primer parámetro
debe ser siempre una constante #define del archivo DMLB.CH especificando uno de los cuatro componentes que
pueden contenerse en un DBE. Además, el segundo parámetro debe ser una constante #define. Hay algunas
constantes que son universalmente válidas y pueden usarse con cada DBE y otras constantes que pueden usarse
sólo con un DBE específico. La diferencia puede reconocerse por el prefijo de la constante #define. El prefijo DBE_
identifica las constantes universalmente válidas y el prefijo DBFDBE_ identifica las constantes que son sólo vá-
lidas para el DBFDBE (más precisamente para el componente DATA que manipula los archivos DBF). Las
constantes que contienen el prefijo DBFDBE_ se definen en el archivo #include DBFDBE.CH. El tercer parámetro
especifica el valor que ha de establecerse para la configuración específica (en el ejemplo, la extensión del archivo)
de un DBE.

No todas las configuraciones de un DBE son alterables, así el tercer parámetro se lo procesa únicamente por
DbeInfo() cuando el DBE permite que se modifique la configuración correspondiente. La siguiente tabla da un
vistazo de las configuraciones universalmente válidas que existen para todos los DBEs. La única configuración
que puede modificarse es la extensión de archivo:
Constantes universales para las características de los DBEs
Constante *) Tipo de dato Descripción
DBE_DATATYPES ro C Tipos de datos soportados
DBE_EXTENSION a C Extensión de archivo predeterminado
DBE_MANUFACTURER ro C Productor del DBE
DBE_NAME ro C Nombre del DBE
DBE_VERSION ro C Versión del DBE
*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

Junto con estas constantes generales, la mayoría de los DBEs tienen constantes #define específicas que sólo
pueden usarse para el DBE específico (más precisamente, para un componente específico). En la sección Es-
pecificaciones del Motor de Base de Datos - formato de archivos, se describen constantes específicas del DBE
DbeInfo().

La función DbInfo()
Cuando se abre un archivo en un área de trabajo, un objeto de base de datos (DBO) es creado por el DBE actual
para manipular la apertura del archivo en el área de trabajo. El DBO representa al área de trabajo y la función
DbInfo() puede leer información sobre el DBO y puede modificar la configuración del DBO. DbInfo() requiere que
un archivo esté abierto en el área de trabajo correspondiente.
DbInfo(), así como DbeInfo(), recibe parámetros que son constantes definidas en el archivo #include. Las cons-
tantes universalmente válidas pueden encontrarse en el archivo DMLB.CH y se listan en la próxima tabla:

Las constantes universales para los objetos de base de datos (áreas de trabajo)
Constante *) Tipo de Dato Descripción
DBO_ALIAS ro C Nombre de alias
DBO_FILENAME ro C Nombre del archivo abierto
DBO_ORDERS ro N Número ordinal (índices)
DBO_RELATIONS ro N Número de relaciones
DBO_SHARED ro L .T. si el archivo está abierto en modo SHARED
DBO_REMOTE ro L .T. si el archivo no está almacenado
en la estación de trabajo local
DBO_DBENAME ro L Nombre del DBE que ha
abierto un archivo de la base de datos
*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

Las constantes #define universalmente válidas para DbInfo() comienzan con el prefijo DBO_ (por Objeto de Base
de Datos - DataBase Object). Hay además constantes que pueden usarse sólo con los DBOs creados por un DBE
específico). Un ejemplo de las constantes que pueden pasarse a DbInfo() se da en los siguientes códigos del
programa:

#include "Dmlb.ch"
#include "DbfDbe.ch"

DbeLoad( "DBFDBE", .T.)


DbeLoad( "NTXDBE", .T.) // crea DBFNTX
DbeBuild( "DBFNTX", "DBFDBE", "NTXDBE" ) // DBE compuesto

USE Cliente ALIAS Clie

? DbInfo( DBO_ALIAS ) // resulta: Clie


? DbInfo( DBO_FILENAME ) // resulta: C:\DATA\Cliente.DBF

// manejadores de archivos
? DbInfo( DBFDBO_DBFHANDLE ) // resulta: 8
? DbInfo( DBFDBO_DBTHANDLE ) // resulta: 9

El ejemplo ilustra que el primer parámetro pasado a DbInfo() es una constante #define que es o bien una constante
universalmente válida (prefijo DBO_) o una constante DBE específica. En el caso del DBFDBE, las constantes
específicas para la función DbInfo() comienzan con el prefijo DBFDBO_ y están contenidas en el archivo #include
DBFDBE. (Para resumir: las constantes que contienen DBE_ son válidas para un DBE, y para la función DbeInfo().
Las constantes que contienen DBO_ son válidas para los objetos de base de datos y para la función DbInfo()). Las
constantes para DbInfo() que son dependientes del DBE actual se listan en la sección Especificaciones del Motor
de Base de Datos - formato de archivos,
Se inicializa el DBO con todas las configuraciones actuales del DBE cuando el archivo está abierto. Si se hacen los
cambios más tarde al DBE usando DbeInfo(), todos los DBOs permanecen sin efecto por el cambio. Esto significa
que todas las áreas de trabajo donde los archivos están abiertos no están afectados por los cambios hechos a un
motor de base de datos. Cambios como aquellos afectan sólo las áreas de trabajo donde el archivo está abierto
luego de que se produjo el cambio.

La función FieldInfo()
Ni bien se abre un archivo en un área de trabajo, las variables de campo (campos) existen dentro de este área de
trabajo. Similar a Clipper, la información sobre un campo puede determinarse usando las funciones FieldName() o
FieldPos(). Xbase++ también incluye a la función FieldInfo() para leer o cambiar información sobre un campo in-
dividual en un área de trabajo. La función FieldInfo() se comporta de manera similar a DbeInfo() y DbInfo(), y toma
una constante #define como segundo parámetro. Las constantes válidas para FieldInfo() se listan en la siguiente
tabla:

Constantes universales para las variables de campo en un área de trabajo


Constante *) Tipo de Dato Descripción
FLD_LEN ro N Longitud del campo
FLD_DEC ro N Número de espacios decimales
FLD_TYPE ro C Tipo de datos de las variables de campo en el nivel
de lenguaje de Xbase++
FLD_TYPE_AS_NUMERIC ro N Como valor de caracter y numérico
FLD_NATIVETYPE ro C Tipo de datos original de la variable de campo como
es definida en el DBE
FLD_NATIVETYPE_AS_NUMERIC ro N como valor de caracter y numérico

*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

FieldInfo() provee información importante sobre los campos en la base de datos como la longitud y el número de
espacios decimales. Ejemplo:

(En este ejemplo, se asume que está cargado el DBFDBE)


#include "Dmlb.ch"
// crea base de datos
DbCreate( "Partes", { { "ParteNro" , "C", 6, 0 }, ;
{ "Parte" , "C", 20, 0 }, ;
{ "Precio" , "N", 8, 2 } } )

USE Partes

? FieldInfo( 3, FLD_LEN ) // resultado: 8


? FieldInfo( 3, FLD_DEC ) // resultado: 2

? FieldInfo( 1, FLD_LEN ) // resultado: 6


? FieldInfo( 2, FLD_LEN ) // resultado: 20

El primer parámetro de la función FieldInfo() es la posición ordinal de un campo (como fue retornada por FieldPos())
y el segundo parámetro es una constante #define que designa qué información es pedida. Para determinar la
longitud de un campo o sus espacios decimales, pueden definirse dos pseudofunciones simples para su traduc-
ción por el preprocesador de llamadas a FieldInfo():

#xtranslate FieldLen( <nPos> ) => FieldInfo( <nPos>, FLD_LEN )


#xtranslate FieldDec( <nPos> ) => FieldInfo( <nPos>, FLD_DEC )

El tipo de datos de un campo es también información importante y puede determinarse pasando la constante
FLD_TYPE o FLD_NATIVETYPE. En ambos casos, FieldInfo() devuelve un valor de caracter identificando al tipo
de dato. Usando las dos constantes el tipo de dato que está disponible para manipular por los comandos y las
funciones apropiadas de Xbase++ puede distinguirse del tipo de datos original almacenado en la base de datos.
Ellos son a veces, pero no siempre, idénticos. Por ejemplo, en el nivel de lenguaje de Xbase++ sólo existe un tipo
de dato numérico. Cuando se almacenan los números en los campos, sin embargo, los números enteros y de
coma flotante podrían tratarse de manera diferente. Xbase++ reconoce a los números y otros datos internamente
y distingue entre tipos de datos que pueden existir en el nivel del lenguaje y en el nivel de la base de datos. Co-
rrespondientemente, FieldInfo() puede leer los tipos de datos de un campo como existen en el nivel de lenguaje
(FLD_TYPE) o en el nivel de base de datos (FLD_NATIVETYPE). Para determinar el tipo de datos de un campo a
nivel de lenguaje de Xbase++, la constante FLD_TYPE se pasa a FieldInfo(). El tipo de datos es devuelto por
FieldInfo() equivalente a los valores de retorno de Valtype() o Type().

La identificación numérica de los tipos de datos usa constantes definidas en el archivo #include TYPES.CH. Las
constantes de la siguiente tabla están disponibles para determinar los tipos de datos usando FieldInfo() junto con
FLD_TYPE_AS_NUMERIC y FLD_NATIVETYPE_AS_NUMERIC:

Las constantes para los tipos de datos (valores de retorno de FieldInfo())


Constante Descripción
XPP_CHARACTER valor Caracter
XPP_DATE valor Fecha
XPP_LOGICAL valor Lógico
XPP_MEMO Campo Memo
XPP_NUMERIC valor Numérico
XPP_ARRAY Arreglo *)
XPP_BLOCK Bloque de Código *)
XPP_DOUBLE Valor Numérico como doble *)
XPP_ILLEGAL Tipo de Dato No válido *)
XPP_OBJECT Objeto *)
XPP_UNDEF Valor Indefinido (NIL) *)
*) Los valores devueltos con FieldInfo() son dependientes del DBE

Especificaciones del Motor de Base de Datos - formato de archivos


Esta sección describe las especificaciones de los DBEs dadas junto con Xbase++. Da además información sobre
las operaciones con bases de datos que no están soportadas por cada uno de los DBEs.

SDFDBE (Componente DATA)


DELDBE (Componente DATA)
DBFDBE (Componente DATA)
FOXDBE (Componente DATA)
NTXDBE (Componente ORDER)
CDXDBE (Componente ORDER)

SDFDBE (componente DATA)


El SDFDBE manipula archivos ASCII en el Formato de Datos de Sistema. La extensión del archivo predeterminado
es ".TXT". Cada línea en el archivo tiene la misma longitud y contiene al mismo registro. Los tipos de datos ca-
racter, fecha, lógico y numérico están soportados, pero el tipo de dato memo no existe bajo el SDFDBE. La es-
pecificación para un archivo TXT es así:

Especificaciones para el System Data Format (archivo TXT)


Elemento Especificación
Extensión de Archivo TXT *)
Tamaño de Archivo Limitado sólo por los recursos del sistema
Fin de Archivo Chr(26)
Fin de Registro Retorno de carro + avance de línea = Chr(13)+Chr(10), todos los
registros tienen el mismo número de caracteres Caracteres
separadores de campos Ninguno
Tipos de Datos C, D, L, N, sin memo
Valores Carácter Compensados con espacios blancos a la derecha
Valores de Fecha AAAAMMDD
Valores Lógicos T o F *)
Valores Numéricos Compensados con ceros a la izquierda
Caracter Decimal Punto *)
SET CHARSET Se ignora

*) configurable

Cuando se crea un archivo TXT en el formato SDF, o bien con COPY TO...SDF, DbExport(), o DbCreate(), se crea
un segundo que contiene la descripción de la estructura para los campos en el archivo TXT. Por omisión este
archivo tiene la extensión SDF y es un archivo ASCII que podría también crearse usando en editor de texto simple.
Se soporta a la definición de la estructura en el formato archivo familiar INI que puede escribirse en su forma
generalizada como sigue:
[<Sección>]
<Clave>=<Valor>

En un archivo SDF hay dos secciones: [INFO] y [FIELDS]. El siguiente ejemplo de programa crea un archivo TXT
en el formato SDF que ilustra lo siguiente:

PROCEDURE Main
LOCAL i
DbeLoad( "SDFDBE" ) // crea archivo TXT
DbCreate( "TEST", { {"CARACTER" , "C", 10, 0}, ;
{"FECHA" , "D", 8, 0}, ;
{"LOGICO" , "L", 1, 0}, ;
{"NUMERICO" , "N", 6, 2} },"SDFDBE" )

USE Test VIA SDFDBE // abre usando Motor SDF

FOR i:=1 TO 10 // agrega 10 registros


DbAppend()
REPLACE FIELD->caracter WITH Replicate( Chr(64+i), i ), ;
FIELD->Fecha WITH Date()+i , ;
FIELD->Logico WITH (i % 2 == 0) , ;
FIELD->Numerico WITH (i ^ 2) / 2
NEXT
CLOSE Test // Cierra el archivo
RETURN

El ejemplo crea un archivo TXT con cuatro campos y añade diez registros al archivo. El archivo TEST.SDF se crea
como un archivo de estructura junto con el archivo de datos TEST.TXT que contiene los datos creados en el ciclo
FOR..NEXT. El archivo de estructura TEST.SDF se ve así:

[INFO]
file=TEST.TXT
fieldcount=4
recsize=27
reccount=10

[FIELDS]
CARACTER=C,10,0
FECHA=D,8,0
LOGICO=L,1,0
NUMERICO=N,6,2
[END]

La primer sección [INFO] describe al archivo TEST.TXT que contiene los datos. Esto incluye el nombre del archivo
sin el directorio (archivo), el número de campos (fieldcount), la longitud de un registro (recsize) y el número de
registros (reccount). La segunda sección [FIELDS] describe los campos en el archivo TEST.TXT. Los nombres de
los archivos aparecen sobre el lado izquierdo del operador igual y el tipo de datos, la longitud del campo y los
espacios decimales se especifican como una lista separada por comas en el lado derecho. La definición se termina
con la sección [END]. Note que ambos archivos (archivos SDF y TXT) deben localizarse en el mismo directorio.
Luego de la ejecución del código de ejemplo citado anteriormente, el archivo TEST.TXT contiene diez registros
con el siguiente contenido:

A 19950822F000.50
BB 19950823T002.00
CCC 19950824F004.50
DDDD 19950825T008.00
EEEEE 19950826F012.50
FFFFFF 19950827T018.00
GGGGGGG 19950828F024.50
HHHHHHHH 19950829T032.00
IIIIIIIII 19950830F040.50
JJJJJJJJJJ19950831T050.00

El DBE SDF manipula y permite que las operaciones con bases de datos se lleven a cabo en el archivo. Esto in-
cluye definir las condiciones de filtro usando SET FILTER o DbSetFilter(), así como crear las relaciones con
DbSetRelation() o SET RELATION. Si el SDFDBE está conectado a un DBE compuesto que contiene el NTXDBE
(SDFNTX), los índices pueden crearse también para un archivo ASCII que existe en el formato SDF.
El formato SDF impone ciertas restricciones en la programación. Por ejemplo, no es posible borrar registros,
porque no se proveen marcas de borrado en el formato de archivo SDF. Por esta razón, una llamada a función
DbDelete() provoca un error durante el tiempo de ejecución usando el SDFDBE. Además, el SDFDBE abre el
archivo exclusivamente lo cual significa que los archivo ASCII en el formato SDF no pueden usarse simultánea-
mente en la operación concurrente de varios programas. La siguiente tabla lista todas las operaciones comunes
que se encuentran soportadas por el SDFDBE:

Las operaciones de base de datos que el SDFDBE no soporta


Función/Comando Resultado de la llamada
DbDelete() Error de Tiempo de Ejecución
DbRecall() Error de Tiempo de Ejecución
DbPack() Error de Tiempo de Ejecución
DbRLock() Error de Tiempo de Ejecución
DbSort() Error de Tiempo de Ejecución
RLock() Error de Tiempo de Ejecución
DbRLockList() Devuelve un arreglo vacío
Deleted() Siempre devuelve .F.
FLock() Siempre devuelve .T.
Header() Devuelve 0
USE...READONLY READONLY es ignorado
USE...SHARED SHARED es ignorado

Configuración de SDFDBE
El DBE SDF puede configurarse específicamente usando la función DbeInfo(). El formato de archivo SDF y el
mecanismo de acceso puede afectarse cuando se crean el archivo SDF o TXT. La siguiente tabla da un vistazo de
las constantes especiales #define que pueden pasarse a la función DbeInfo() cuando el DBE es SDFDBE:

Las constantes para DbeInfo() con el SDF-DBE


Constante *) Valor Tipo de Dato Descripción
SDFDBE_AUTOCREATION a .F. L archivo TXT es creado automáticamente creado por
DbCreate()
SDFDBE_DECIMAL_TOKEN a . C Caracter para punto decimal
SDFDBE_LOGICAL_TOKEN a TF C Caracter para valores lógicos
SDFDBE_STRUCTURE_EXT a SDF C Extensión para el archivo de estructura

*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

Los valores predeterminados se muestran en la columna "Valor".

SDFDBE_AUTOCREATION
Esta constante determina si sólo el archivo SDF o el archivo SDF y el archivo TXT son creados por
DbCreate(). El valor predeterminado para esta configuración es .F. (falso) y la función DbCreate() crea
únicamente el archivo SDF (archivo de estructura) y no al archivo TXT por predeterminación. Si el archivo
TXT no existe cuando USE o DbUseArea() abre al archivo, se lo crea.
Si esta configuración es .T. (verdadera, DbCreate() genera al archivo SDF así como un archivo TXT vacío.
Cualquier archivo TXT existente con este nombre está sobrescrito.
SDFDBE_DECIMAL_TOKEN
El delimitador de los espacios decimales para los valores numéricos puede establecerse usando este valor.
Lo que está predeterminado es el período. La coma se especifica como el separador de los espacios de-
cimales en los valores numéricos por el siguiente código:

DbeInfo( COMPONENT_DATA, SDFDBE_DECIMAL_TOKEN, "," )

Cuando se especifica al delimitador como el caracter ASCII 0 (= Chr(0)) el SDFDBE ignora cualquier deli-
mitador para los campos numéricos. Usa entonces las especificaciones de los campos como se lo encuentra
en el archivo de estructura extendida (archivo SDF). Por ejemplo:

archivo SDF:
CARACTER=C,4,0
NUMERICO=N,6,2
archivo TXT:
AAAA004321
BBBB987654
En este caso el campo NUMERICO tendrá el valor 43.21 para el primer registro y 9876.54 para el segundo.
para proveer una conversión de datos con facilidad entre los hosts de Xbase++ y VMS.

SDFDBE_LOGICAL_TOKEN
La configuración especifica los dos caracteres usados para los valores lógicos y especifica los valores que
representan verdadero y falso. Lo predeterminado es "TF". Una cadena de caracteres que contiene dos
caracteres se especifica para esta configuración. el primer caracter representa verdadero y el segundo re-
presenta falso. El siguiente código de programa especifica el caracter "1" para el valor lógico verdadero y al
caracter "0" para el valor lógico falso.

DbeInfo( COMPONENT_DATA, SDFDBE_LOGICAL_TOKEN, "10" )

SDFDBE_STRUCTURE_EXT
Esta constante determina la extensión del archivo para el archivo de estructura (archivo SDF). Lo prede-
terminado es .SDF.

DELDBE (componente DATA)


El DBE DELDBE manipula los archivos ASCII en formato delimitado. La extensión del archivo predeterminado es
TXT. El formato delimitado es único en que permite que los registros y los campos dentro de los registros tengan
longitud variable. los campos están separados dentro de los registros por delimitadores y el separador predeter-
minado de campos es la coma. Los archivos en el formato delimitado no utilizan una definición de estructura y no
hay un tipo explícito o estandarización de campos.
Un tipo implícito se realiza cuando los campos se leen basados en el formato en que se almacenan varios datos.
Los datos del tipo de dato caracter están comprendidos entre caracteres delimitadores (doble comilla por prede-
terminación). Los valores numéricos no están comprendidos entre caracteres delimitadores, y se los reconoce
como numéricos porque comienzan con los dígitos 0 a 9, o un prefijo más/menos. Los datos numéricos pueden
interpretarse como valores de Fecha. Los valores Lógicos son caracteres alfabéticos únicos que no están com-
prendidos entre caracteres delimitadores. Los valores predeterminados son T y F. El DELDBE no soporta los tipos
de dato Memo.

Especificaciones para el formato delimitado (archivo TXT)


Elemento Especificación
Extensión de Archivo TXT *)
Tamaño de Archivo Limitado sólo por los recursos del sistema
Máx.Long.de Registro Predeterminado a 128 Kilobytes *)
Fin de Archivo Chr(26)
Fin de Registro Retorno de carro + avance de línea = Chr(13)+Chr(10), Los
registros pueden tener un número variable de caracteres
Caracter separador de campos Coma
Tipos de Datos C, D, L, N, sin memo
Valores Carácter Valores entre comillas
Valores de Fecha Si hay valores numéricos a ser interpretados como fecha,
deben aparecer en formato AAAAMMDDValores Lógicos T o F
como un solo caracter alfabético
Valores Numéricos Compensados con ceros a la izquierda
Caracter Decimal Punto *)
SET CHARSET Se lo ignora

*) configurable

El DELDBE manipula archivos ASCII cuyas filas o registros tienen longitudes variables. Este DBE se utiliza con los
comandos COPY TO...DELIMITED y APPEND FROM...DELIMITED. Soporta también operaciones con base de
datos básicas, como DbCreate(), DbUseArea(), y DbSkip(). Los filtros y las relaciones pueden establecerse tam-
bién si el DELDBE está acoplado con el NTXDBE. El comando DELETE y la función DbDelete() borran registros
pero tienen apenas un significado diferente. El borrado no se produce vía una marca de borrado (como con
DBFDBE), sino que resulta en un borrado inmediatamente físico. Luego de llamar a DbDelete(), un registro no
puede llamarse nuevamente cuando el DELDBE manipula a un archivo. La siguiente tabla da un vistazo de las
operaciones con base de datos que no están soportadas por el DELDBE:

Operaciones con base de datos sin soporte del DELDBE


Función/Comando Resultado de la llamada
DbRecall() Error de Tiempo de Ejecución
DbPack() Error de Tiempo de Ejecución
DbRLock() Error de Tiempo de Ejecución
DbSort() Error de Tiempo de Ejecución
DbSeek() Error de Tiempo de Ejecución
RLock() Error de Tiempo de Ejecución
FLock() Error de Tiempo de Ejecución
INDEX ON Error de Tiempo de Ejecución
SET INDEX Error de Tiempo de Ejecución
DbRLockList() Devuelve un arreglo vacío
Deleted() Siempre devuelve .F.
Header() Devuelve 0
USE...READONLY READONLY será ignorado
USE...SHARED SHARED será ignorado

Un archivo ASCII en el formato delimitado no contiene datos de estructura. Por tanto, los nombres de los archivos
y la estructura del archivo (más precisamente, el arreglo DbStruct()) también se comportan de manera muy es-
pecial bajo DELDBE. El arreglo DbStruct() sólo determina el número de campos y los tipos de datos. Puesto que la
longitud de los archivos es variable en el formato delimitado, el arreglo de estructura no puede establece las
longitudes de los campos o el número de espacios decimales para los campos numéricos. Los campos de un
archivo delimitado pueden accederse usando un nombre de alias. Los nombres de los campos comienzan con
"FIELD" y se los enumera desde 1 hasta FCount(). El siguiente código del programa ilustra esto:

USE Cliente ALIAS Clie VIA DBFNTX // abre archivo DBF


COPY TO Direccion.txt DELIMITED // crea archivo TXT

USE Direccion ALIAS Dire VIA DELDBE

Dire->( DbAppend() ) // agrega registro

REPLACE Dire->FIELD1 WITH "Miller" , ; // almacena datos


Dire->FIELD2 WITH "John" , ; // al archivo TXT
Dire->FIELD3 WITH "0800-12345"

en este ejemplo un archivo TXT en el formato delimitado se crea usando el comando COPY TO. La descripción de
la estructura se toma desde un archivo DBF.

Configuración de DELDBE con DbeInfo()


El DBE DEL puede configurarse de específicamente usando la función DbeInfo(). Específicamente el formato de
archivo DEL y el modo operativo del DELDBE pueden configurarse usando DbeInfo(). La siguiente tabla da un
vistazo de la constante especial #define que puede pasarse a la función DbeInfo() para el DELDBE:

Las constantes para DbeInfo() con el DELDBE


Constante *) Valor Tipo de Datos Descripción
DELDBE_MODE a #define NModo Operativo (vea debajo)
DELDBE_RECORD_TOKEN a CRLF C Caracter separador para registro
DELDBE_FIELD_TOKEN a , C Caracter separador para campos
DELDBE_FIELD_TYPES a "" C Cadena describiendo tipos de datos para
campos
DELDBE_DELIMITER_TOKEN a " C Caracteres incrustados para valores ca-
racter
DELDBE_DECIMAL_TOKEN a . C Caracter para punto decimal
DELDBE_LOGICAL_TOKEN a TF C Caracteres para valores lógicos
DELDBE_MAX_BUFFERSIZE a 64 N Max. tamaño para un registro en KB

*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

Los valores predeterminados se listan en la columna "Valor".


DELDBE_MODE
Esta constante se utiliza para establecer el modo operativo del DELDBE. Hay tres modos operativos: au-
to-campo, multi-campo y único campo. El modo operativo predeterminado es auto-campo. En la siguiente
línea el modo operativo auto-campo se modifica al modo único campo:

DbeInfo( COMPONENT_DATA, DELDBE_MODE, DELDBE_SINGLEFIELD )

Los tres modos operativos posibles se describen en las secciones a continuación.

Modo: DELDBE_AUTOFIELD
El modo DELDBE_AUTOFIELD es el modo operativo predeterminado. El DELDBE se encuentra en este
modo luego de que se lo carga usando DbeLoad(). Si un archivo TXT se crea usando DbCreate() para un
archivo en formato delimitado, puede llenarse con los datos usando los comandos APPEND BLANK y
REPLACE.

#include "DelDbe.ch"

aStruct := { {"CARAC1","C",10,0} , ; // crea arreglo de estructura


{"CARAC2","C",10,0} , ; // para DbCreate()
{"NUM" ,"N", 6,2} , ;
{"LOGICO","L", 1,0} }

DbCreate( "Auto", aStruct, "DELDBE" ) // crea archivo TXT


// configura tipos de datos
DbeInfo( COMPONENT_DATA, DELDBE_FIELD_TYPES, "CCNL" )
USE Auto VIA DELDBE
FOR i:=1 TO 3 // agrega tres registros
APPEND BLANK
REPLACE FIELD->FIELD1 WITH Replicate(Chr(64+i),i), ;
FIELD->FIELD2 WITH Replicate(Chr(96+i),i), ;
FIELD->FIELD3 WITH 10^i, ;
FIELD->FIELD4 WITH (i%2 == 1)
NEXT

En este código, un archivo TXT con cuatro campos se crea en formato delimitado. Los campos no corres-
ponden a los nombres del arreglo de estructura pero se enumeran y prefijan con "FIELD". Además, se
configuran los tipos de datos para los campos. Luego que el código citado anteriormente se ejecuta, el ar-
chivo "Auto.txt" contiene:

"A","a",10.00,T
"BB","bb",100.00,F
"CCC","ccc",1000.00,T

Estas líneas demuestran los datos en formato delimitado: los valores de caracter están comprendidos entre
comillas, los valores numéricos consisten sólo de dígitos, y los valores lógicos consisten de un caracter
alfabético.

Modo: DELDBE_MULTIFIELD
El modo DELDBE_MULTIFIELD es un modo operativo que fue creado especialmente para controlar ar-
chivos para formularios carta (form letters). Los archivos de control son empleados por los programas
procesadores de texto y contienen los datos de las variables para los formularios carta. Generalmente la
primera línea de un archivo de control contiene los nombres de los campos, o los nombres de las variables
definidos en la primera línea de un formulario carta que se reemplazan por valores desde el archivo control.
En el modo operativo multi-campo, la primera línea del archivo TXT contiene los nombres de los campos. El
siguiente ejemplo crea un típico archivo de control en el que se modifican los caracteres delimitadores para
los valores de caracter y los separadores para los campos:
#include "DelDbe.ch"

? DbeSetDefault( "DELDBE" )

aStruct := { {"CARAC1","C",10,0} , ; // crea arreglo de estructura


{"CARAC2","C",10,0} , ; // para DbCreate()
{"NUM" ,"N", 6,2} , ;
{"LOGICO","L", 1,0} }

// conmuta a multi-campo
DbeInfo( COMPONENT_DATA, DELDBE_MODE, DELDBE_MULTIFIELD )
// punto y coma en vez de
// coma
DbeInfo( COMPONENT_DATA, DELDBE_FIELD_TOKEN, ";" )
// sin separador para
// valores caracter
DbeInfo( COMPONENT_DATA, DELDBE_DELIMITER_TOKEN, Chr(0) )
DbCreate( "Multi", aStruct, "DELDBE" )

USE Multi VIA DELDBE


FOR i:=1 TO 3 // los campos tienen nombre
DbAppend()
REPLACE FIELD->CARAC1 WITH Replicate(Chr(64+i),i), ;
FIELD->CARAC2 WITH Replicate(Chr(96+i),i), ;
FIELD->NUM WITH 10^i, ;
FIELD->LOGICO WITH (i%2 == 1)
NEXT

En este código un archivo TXT en formato delimitado se crea conteniendo cuatro campos. Los nombre de
las variables corresponden a los nombres del arreglo de estructura y se almacenan en la primera línea del
archivo TXT. Se utiliza un punto y coma como separador de campos y los valores de los caracteres no están
comprendidos en los caracteres delimitadores. Después de la ejecución de este ejemplo, el archivo "Mul-
ti.txt" contiene:

CARAC1;CARAC2;NUM;LOGICO
A;a;10.00;T
BB;bb;100.00;F
CCC;ccc;1000.00;T

La primera línea contiene los nombres de los campos que corresponden a los nombres de las variables del
archivo control. Las líneas subsecuentes contienen datos para el formulario carta. La acción predeterminada
de delimitar los valores de caracteres se apaga. Esto se hace especificando al caracter Chr(0) como el
caracter delimitador. El punto y coma se define como el separador de campos.

En el modo operativo "multi-campo", los campos de un archivo TXT en formato delimitado pueden especi-
ficarse por sus nombres de campo, ya que los nombres de campo están definidos en el archivo TXT. Este es
un caso especial y es generalmente usado para crear archivos de control para el procesamiento de texto
desde los archivos DBF.

Modo: DELDBE_SINGLEFIELD
El modo DELDBE_SINGLEFIELD es un modo operativo que permite que cualquier archivo ASCII sea
abierto y manipulado por el DELDBE. En este modo, el DELDBE trata a un archivo ASCII como si el archivo
contuviera sólo un campo. El campo se trata como carente de delimitadores, pero los caracteres que indican
el final del registro indican el final del registro requerido. Para un archivo ASCII, esto corresponde gene-
ralmente a los caracteres del final de línea (Retorno de carro + avance de línea = Chr(13)+Chr(10)) En modo
"único campo", un campo es idéntico a un registro y el DELDBE lee un archivo ASCII línea por línea. Esto
permite que operaciones de una base de datos como DbSkip(), DbGoTop() o DbGoBottom() puedan eje-
cutarse en un archivo ASCII. en este modo operativo el DELDBE puede manipular muchos archivos ASCII.
Por ejemplo:

#include "DelDbe.ch"

? DbeSetDefault( "DELDBE" )

aStruct := { {"CARAC1","C",10,0} , ; // crea arreglo de estructura


{"CARAC2","C",10,0} , ; // para DbCreate()
{"NUM" ,"N", 6,2} , ;
{"LOGICO","L", 1,0} }

// conmuta a campo simple


DbeInfo( COMPONENT_DATA, DELDBE_MODE, DELDBE_SINGLEFIELD )

DbCreate( "Single", aStruct, "DELDBE" )


USE Single VIA DELDBE

FOR i:=1 TO 3
DbAppend()
REPLACE FIELD->FIELD WITH Replicate(Chr(64+i),i)
NEXT
DbCloseArea()

Independientemente del arreglo de estructura provisto por DbCreate(), un archivo TXT en el modo de único
campo se crea se crea como un archivo vacío que carece de información. El DELDBE trata al archivo TXT
como si fuera un archivo con un único campo. El nombre del archivo es FIELD. Con excepción de los ca-
racteres que representan el final del registro, el DELDBE no reconoce cualquier otro caracter delimitador en
el modo operativo. Los contenidos del archivo "Single.txt" creados en el ejemplo citado anteriormente será
así:
A
BB
CCC

Cada línea de archivo contiene sólo los datos escritos en el archivo usando REPLACE luego que DbAppend()
agregó un nuevo registro.

El modo operativo de "único campo" es ideal para tener una vista de cualquier archivo ASCII con filas que
pueden identificarse por caracter(es) de fin de línea distintos.

DELDBE_RECORD_TOKEN
El final de un registro en un archivo TXT se lo identifica generalmente por los dos caracteres retorno de
carro+avance de línea (Chr(13)+Chr(10). El delimitador para un registro puede modificarse usando la fun-
ción DbeInfo(). Además debe contener al menos un caracter y un máximo de dos caracteres.

DELDBE_FIELD_TOKEN
En el formato delimitado (modo auto-campo y multi-campo), los campos están separados entre sí por un
separador. La coma es el separador por predeterminación. La siguiente línea de programa especifica al
punto y coma como el separador:

DbeInfo( COMPONENT_DATA, DELDBE_FIELD_TOKEN, ";" )

DELDBE_FIELD_TYPES
En el modo auto-campo, los tipos de datos de los campos se reconocen de acuerdo a si al formato de los
datos están almacenados en un archivo TXT. El reconocimiento tipo de datos predeterminado puede ser
omitido cuando se especifica el tipo de dato para cada campo:

cSave := DbeInfo( COMPONENT_DATA, DELDBE_FIELD_TYPES, "DCNLC" )


USE Mydata.txt VIA DELDBE
DbeInfo( COMPONENT_DATA, DELDBE_FIELD_TYPES, cSave )

En este ejemplo, los cinco campos están a disponibles en el archivo delimitado y sus valores se recuperan
teniendo los tipos de datos Fecha, Caracter, Numérico y Lógico . Note que los valores del tipo de Fecha sólo
pueden recuperarse cuando los datos consisten de 8 dígitos en el formato YYYYMMDD.

DELDBE_DELIMITER_TOKEN
Los valores del tipo "caracter" se encuentran entre ciertos caracteres con función de paréntesis en el formato
delimitado. La doble comilla está predeterminada. La siguiente llamada especifica la comilla simple como el
caracter delimitador para los valores de caracteres:

DbeInfo( COMPONENT_DATA, DELDBE_DELIMITER_TOKEN, "'" )

DELDBE_DECIMAL_TOKEN
El caracter utilizado para marcar los espacios decimales en los valores numéricos pueden especificarse
también. El punto está predeterminado. La siguiente línea del programa especifica a la coma como el se-
parador de espacios decimales en los valores numéricos:

DbeInfo( COMPONENT_DATA, DELDBE_DECIMAL_TOKEN, "," )

Cuando el caracter para los espacios decimales se modifica, no debe ser el mismo que el separador de
campos. Si es el mismo, los lugares después de la coma se los ve como un campo separado que contiene
otro valor numérico.
DELDBE_LOGICAL_TOKEN
Para los valores lógicos, los caracteres que representan verdadero o falso pueden especificarse. Lo que
está predeterminado es "TF". si esta configuración se modifica, una cadena de caracteres que consiste de
dos caracteres debe especificarse. El primer caracter representa verdadero y el segundo caracter repre-
senta falso. La siguiente llamada especifica al caracter "Y" para el valor lógico verdadero y al caracter "N"
para el valor lógico falso:

DbeInfo( COMPONENT_DATA, DELDBE_LOGICAL_TOKEN, "YN" )

Los caracteres alfabéticos deben indicarse para los valores lógicos utilizados con el DELDBE. Los carac-
teres "1" y "0" para "verdadero" y "falso" no están permitidos porque se los interpreta como valores numé-
ricos.
DELDBE_MAX_BUFFERSIZE
El formato delimitado permite que los campos y registros se agranden o alarguen. Un campo de caracteres
que contiene 10 caracteres puede asignársele una cadena de caracteres conteniendo caracteres de 20K. El
espacio de memoria ocupado por un campo en un registro debe agrandarse dinámicamente (y también
reducirse) después de ser agrandado. Esto ocurre automáticamente con el DELDBE. La ubicación dinámica
de espacio de memoria en un archivo (no en la memoria) implica una consideración limitada de la velocidad
en las operaciones con base de datos con DELDBE. Para optimizar la velocidad del DELDBE, el buffer de
lectura para un registro cargado usando el DELDBE está limitado a 1 KB (1024 bytes). Ni bien un registro
más largo que 1024 byte se lee, el buffer de lectura se duplica. Este comportamiento requiere que se defina
un límite superior, o la máxima longitud de un registro que no puede exceder ningún registro. El límite su-
perior para la longitud de un registro se establece en 64KB por predeterminación. Cuando se anticipa a
cualquier línea de un archivo ASCII manipulado por el DELDBE de no ser más larga que caracteres de 64KB,
el tamaño máximo del buffer de lectura debe incrementarse. La siguiente línea del programa aumenta el
máximo tamaño del buffer de lectura del DELDBE a 256KB:

DbeInfo( COMPONENT_DATA, DELDBE_MAX_BUFFERSIZE, 256 )

DBFDBE (Componente DATA)


El DBE DBFDBE manipula archivos en el formato DBF. Este formato de archivo lo usan todos los dialectos de
Xbase para almacenar datos. En un archivo DBF, los datos se guardan en forma de tabla donde un registro re-
presenta a una fila y donde un campo representa una columna. Las filas en la tabla tienen una longitud fija, y cada
campo almacena los valores que tienen el mismo tipo de dato siempre.

El DBFDBE se usa como un componente DATA del DBE DBFNTX compuesto que se crea cada vez que se inicia
un programa de Xbase++. Esto ocurre en la función DbeSys() cuyo código fuente está contenido en el archi-
vo ..\SOURCE\SYS\DBESYS.PRG. El DBFDBE lleva a cabo todas las operaciones con las bases de datos que
pueden ejecutarse en un archivo DBF. Esto incluye la navegación usando DbSkip() así como la definición de los
filtros y la marcación de registros para borrado usando DbDelete(). Además, el DBFDBE manipula archivos de
memo que contienen el texto de campos de memo.

Las operaciones con base de datos que requieren un índice no pueden llevarse a cabo por el DBFDBE. Para estas
operaciones el DBE debe combinarse con el componente ORDER para crear un DBE compuesto. Xbase++
provee al NTXDBE que manipula los archivos índice (ver la próxima sección).

El DBFDBE tiene ciertas limitaciones causadas por el formato de los archivos DBF o por los factores asociados
con la operación concurrente con las aplicaciones de Clipper. La siguiente tabla da un vistazo:

Especificaciones para los archivos DBF


Elemento Especificación
Tamaño de Archivo Limitado al área de bloqueo de registros es 1 GB (10^9 bytes) por
predeterminación
Cantidad de Campos No limitado
Cantidad de Re- (Area para bloqueo - Header() - 1) / RecSize()
gistros
Tipos de Datos C, D, L, N, M
Valores Carácter Max. 64 KB
Valores de Fecha Campos siempre 8 bytes
Valores Lógicos Campos siempre 1 byte
Valores Numéricos Campos contienen max. 19 bytes, incluyendo punto decimal y prefijo
de signo negativo
Decimal character Punto
Memo Campos siempre 10 bytes; los contenidos de los campos (texto)
almacenados en archivos memo y la longitud del texto en campos memo
no están limitados Tamaño del archivo memo Limitado sólo por los
recursos del sistema
SET CHARSET los valores Caracter son almacenados siempre usando el conjunto de
caracteres OEM

Los campos en un archivo DBF se tipean fuertemente y tienen una longitud fija. Esto está predefinido en la es-
tructura del archivo. El valor máximo que puede almacenarse en los campos numéricos depende de la longitud del
campo y el punto decimal. Para los valores negativos un signo menos limita al valor más pequeño adicionalmente.
Cualquier intento de guardar datos numéricos demasiado grandes o demasiado chicos para caber en un campo de
una base de datos resulta en un error durante el tiempo de ejecución. Por tanto, es necesario considerar el rango
de los datos numéricos que se almacenan cuando los campos numéricos están especificados.

En el caso de los caracteres de campo, la longitud del campo limita el máximo número de caracteres que puede
almacenarse. Si el número de caracteres en una cadena excede la longitud del campo, la cadena de caracteres
está truncada y no se eleva ningún error durante el tiempo de ejecución.

Usando el DBFDBE no hay limitación respecto de la longitud de los campos y de los archivos de memo, como hay
con el limite basado en DOS de 64 KB. Los campos de memo pueden almacenar texto más largo que caracteres
de 64 KB, y los archivos de memo (archivos DBT) pueden volverse más grandes que 32 MB. No obstante, estos
temas están aún en consideración cuando las aplicaciones de Xbase++ utilizan la misma base de datos que las
aplicaciones de DOS Xbase.

Las cadenas de caracteres se almacenan en los campos de caracteres o de memo usando al conjunto de ca-
racteres OEM. Si se selecciona al conjunto de caracteres ANSI en un programa empleando SET CHARSET , se
produce una conversión automática ANSI-OEM cuando un valor del campo se lee desde o escribe a un archivo
DBF.

El tamaño máximo para un archivo depende del desplazamiento utilizado para el bloqueo de registros durante la
operación concurrente (ver la descripción de DBFDBE_LOCKOFFSET abajo). Bajo Clipper, el desplazamiento
varía desde la versión 5.01 a 5.2 y puede establecerse a un valor coincidente en Xbase++. Esto permite que
Xbase++ opere concurrentemente con las aplicaciones existentes de Clipper en un entorno de red heterogéneo en
el que algunas de las estaciones de trabajo se ejecutan bajo DOS y otras bajo un sistema operativo de 32 bit. El
número máximo de registros depende del desplazamiento de registros y de su longitud. Generalmente, el número
máximo de registros puede calcularse como el desplazamiento para los bloqueos de registros divididos por la
longitud de un registro.

El DBFDBE usa dos identificadores reservados para las variables de campo: _LOCK y _NULL. _LOCK se utiliza
para el bloqueo automático de registros y _NULL se reserva para propósitos futuros. Si el DBFDBE se ejecuta en
modo de autobloqueo, un archivo DBF tendrá el campo _LOCK (ver debajo).

Configuración de DBFDBE con DbeInfo()


El DBE DBF puede configurarse de maneras específicas usando la función DbeInfo(). Por ejemplo, la extensión
del archivo para los archivos de base de datos puede especificarse o el modo operativo para los bloqueos de re-
gistros pueden establecerse. La siguiente tabla da un vistazo de las constantes #define que pueden pasarse a la
función DbeInfo() para el DBFDBE:

Las constantes para DbeInfo() con el DBFDBE


Constante *) Valor Tipo de Datos Descripción
DBE_EXTENSION a DBF C Extensión para archivos DBF
DBFDBE_MEMOFILE_EXT a DBT C Extensión para archivos memo
DBFDBE_MEMOBLOCKSIZE ro 512 N Tamaño de bloque para campos memo
DBFDBE_LOCKOFFSET a 1*10^9 N Area de archivo para bloqueo de registros
DBFDBE_LOCKMODE a DBF_NOLOCK N Modo para bloqueo de registros

*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

Los valores predeterminados se muestran en la columna "Valor".


DBE_EXTENSION
Esta constante es válida para todos los DBEs y define la extensión de archivo para los archivos manipulados
por el DBE. La extensión de archivo DBF predeterminada para el DBFDBE puede modificarse. Esto es
especialmente útil para la protección de datos. Por ejemplo, algunos usuarios usan el comando DIR para la
búsqueda de todos los archivos con la extensión DBF. Simplemente basta cambiar la extensión para que
estos usuarios no intenten manipular el archivo fuera de la aplicación que lo utiliza.

DbeInfo( COMPONENT_DATA, DBE_EXTENSION, "FBD" )

Esta llamada a DbeInfo() establece la extensión de archivo predeterminada para los archivos DBF en "FBD".

DBFDBE_MEMOFILE_EXT
Esta constante permite que se determine la extensión de archivo predeterminada para los archivos de memo
(archivos DBT). La extensión de archivo predeterminado por el formato de archivo DBT y no pueden modi-
ficarse.

DBFDBE_MEMOBLOCKSIZE
Esta constante devuelve el tamaño mínimo de bloque utilizado para almacenar texto en un archivo de memo.
El tamaño de bloque predeterminado para el DBFDBE es 512 bytes. No puede modificarse. Dentro de un
archivo de memo los textos o una cadena de caracteres se almacenan en bloques múltiples cada uno de 512
bytes. Cuando el texto que tienen sólo 100 bytes de longitud se almacena, aún ocupa 512 bytes en el archivo
de memo.

DBFDBE_LOCKOFFSET
Esta constante devuelve o modifica el desplazamiento del área para los bloqueos de registros. El despla-
zamiento predeterminado es 2 gigabytes. Puede modificarse con el fin de garantizar la operación concu-
rrente de las aplicaciones de Xbase++ con las aplicaciones de Clipper. Lo predeterminado es 1 gigabyte
bajo Clipper 5.01 y 2 gigabytes bajo Clipper 5.2 cuando el archivo NTXLOCK2.OBJ está enlazado con la
aplicación. Cambiar el desplazamiento de los bloqueos de registros debería llevarse a cabo solamente con
un conocimiento detallado del mecanismo de bloqueo para los registros. Por ejemplo:

DbeInfo( COMPONENT_DATA, DBFDBE_LOCKOFFSET, 2 * 10^9 )

En esta línea de programa el desplazamiento para los bloqueos de registro manipulados por el componente
DATA del DBE DBFNTX compuesto se establece en 2 gigabytes.
Cuidado: Si el DBFDBE se establece como el componente DATA de un DBE compuesto, la alteración del
desplazamiento debe llevarse a cabo también para el componente ORDER. Por ejemplo, en el DBE
DBFNTX compuesto, el DBFDBE y el NTXDBE deben tener el mismo desplazamiento para los bloqueos de
registro. El desplazamiento para el NTXDBE se establece usando la constante NTXDBE_LOCKOFFSET.

DBFDBE_LOCKMODE
El DBFDBE soporta bloqueos de registros para permitir la operación concurrente de las aplicaciones de
base de datos. El DBFDBE tienen dos modos operativos para bloquear los registros. Un modo operativo es
compatible con Clipper y requiere el que se lleve a cabo el bloqueo y desbloqueo explícito de registros
cuando se actualizan los registros. El otro modo usa el mecanismo de bloqueo extendido de Xbase++ que
soporta el bloqueo automático de registros.

Modo: DBF_NOLOCK
El modo DBF_NOLOCK es el modo operativo por predeterminación. El DBFDBE está en este modo ope-
rativo luego de que se carga usando DbeLoad(). Este es también el modo del DBE DBFNTX compuesto
disponible cada vez que se inicia un programa de Xbase++. En la operación concurrente en este modo
operativo un bloqueo de registros debe establecerse explícitamente usando RLock() o DbRLock() antes de
que un valor en un campo se modifique. Si se asignan nuevos valores a los campos o las variables de campo
durante la operación concurrente sin el establecimiento de un bloqueo de registros, se produce un error
durante el tiempo de ejecución. Este comportamiento es compatible con Clipper.

Modo: DBF_AUTOLOCK
En el modo DBF_AUTOLOCK, el DBFDBE ejecuta automáticamente un bloqueo de registro cuando se
modifica un registro o un campo durante la operación concurrente. El bloqueo se remueve automáticamente
luego que se ha escrito el cambio a un archivo. Este modo no es compatible con los mecanismos de bloqueo
implementados en Clipper. Esto significa que el modo operativo no puede usarse en la operación concu-
rrente entre los programas de Xbase++ y Clipper. El problema surge porque el modo DBF_AUTOLOCK
emplea los mecanismos de bloqueo desde el sistema operativo en vez de un bloqueo de registros lógico
usando un desplazamiento virtual. El sistema operativo permite el bloqueo directamente.

El bloqueo de registros automático manipula suavemente un conflicto potencial que puede ocurrir durante la
operación concurrente: el problema de la "actualización perdida". Este problema puede darse cuando un
registro se procesa al mismo tiempo por dos programas A y B. El programa A lee un registro en la memoria,
edita los valores de los campos y escribe los cambios nuevamente a la base de datos. El programa B lee al
registro al mismo tiempo que el programa A, cambia los valores y escribe su propio conjunto de cambios
nuevamente a la base de datos luego que el programa A ya ha escrito sus datos. Esta situación causa un
escenario de "actualización perdida" porque los datos escritos en la base de datos por el programa A están
sobrescritos por los datos del programa B, y consecuentemente se pierden.

El problema de la "actualización perdida" se resuelve en el modo DBF_AUTOLOCK porque sólo el primer


conjunto de datos está escrito automáticamente al archivo cuando dos programas procesan al mismo re-
gistro al mismo tiempo. Cuando el programa B lee a un registro al mismo tiempo que el programa A, sólo los
datos desde el programa que primero escribe sus cambios de vuelta a la base de datos es válido gene-
ralmente.

Con el fin de que el DBFDBE reconozca y evite el escenario de la "actualización perdida", una base de datos
debe tener un campo _LOCK de tipo "C" con la longitud del campo 4. Se usa para almacenar el contador en
un formato binario. El modo operativo DBF_AUTOLOCK no funciona sin el campo _LOCK. El DBFDBE usa
el valor de un campo _LOCK para reconocer este problema potencial y evitar el escenario de "actualización
perdida". Por ejemplo:

El programa A lee un registro, el valor de _LOCK debe ser 100. Al mismo tiempo el programa B lee al mismo
registro. El valor de _LOCK para el programa B es siempre 100. el programa A cambia los datos y escribe los
cambios nuevamente en el registro. Cuando se lleva a cabo esta actualización, el DBFDBE bloquea al re-
gistro, escribe los valores en los campos e incrementa los valores en un campo _LOCK. El DBFDBE luego
desbloquea al registro. el valor de _LOCK es ahora 101. Cuando el programa B intenta actualizar al registro
con sus propios cambios, la operación falla porque el valor de _LOCK es aún 100 para el programa B. No
coincide el valor de _LOCK en la base de datos. El programa B quiere cambiar los datos que han sido re-
cientemente modificados por el programa A. Los datos desde el programa A están retenidos y cualquier
intento del programa B de ganar acceso a escritura causa un error recuperable durante el tiempo de eje-
cución.

Este error durante el tiempo de ejecución siempre debería manipularse usando la estructura de control
BEGIN SEQUENCE...ENDSEQUENCE y un bloque de código de error. En este caso es la responsabilidad
del programador decidir el primer o el último cambi o que ha de ser válido en el escenario de la "actualización
perdida". Un ejemplo de como el programa de la "actualización perdida" puede manipularse se muestra en
las siguientes líneas de programa:

bError := ErrorBlock( {|e| Break(e)} )

USE Cliente

cApell := Cliente->Apell // lee campos y los pone en


cNomb := Cliente->Nomb // variables de memoria

@ 10,10 SAY "Apellido" GET cApell // editar variables


@ 11,10 SAY " Nombre" GET cNomb // de memoria
READ

DO WHILE .T.
BEGIN SEQUENCE
REPLACE Cliente->Apell WITH cApell , ;
Cliente->Nomb WITH cNomb
COMMIT
RECOVER
i := Alert( "Los datos han sido cambiados por otra estación", ;
{"No guardar", "Guardar"} )
IF i==2
LOOP
ENDIF
ENDSEQUENCE
EXIT
ENDDO

ErrorBlock( bError )
en este ejemplo, el error durante el tiempo de ejecución se produce en el caso que se capture el escenario
de la "actualización perdida" usando RECOVER, y la decisión acerca de si los datos modificados deberían
guardarse es responsabilidad del usuario. Si se produce el error durante el tiempo de ejecución de la "ac-
tualización perdida", se relee el campo _LOCK y el valor del buffer de datos (memoria) coincide con el valor
en el campo de base de datos. Es luego suficiente reescribir los valores modificados en la base de datos. En
el ejemplo, esto se lleva a cabo utilizando la declaración LOOP que sólo se alcanza si hay un error.

Obtener información con la función DbInfo()


Cuando se abre un archivo DBF se crea una instancia de DBFDBE. La instancia es un objeto de base de datos
(DBO) e incluye todas las configuraciones posibles del DBFDBE. El objeto de base de datos DBF representa el
área de trabajo en la que se abre al archivo DBF y trata de abrir al archivo DBF. La función DbInfo() puede utili-
zarse para recuperar información sobre el DBF DBO. Una constante #define que identifica que información es-
pecífica se requiere, ha de pasarse a la función. Las constantes que pueden pasarse a la función DbInfo() para el
DBFDBE se listan en la siguiente tabla:
Las constantes para el DbInfo() con el DBFDBE
Constante *) Valor Tipo de Datos Descripción
DBO_FILENAME ro C Nombre completo de archivo
DBFDBE_LOCKMODE ro #define N Modo para bloqueo de registros
DBFDBE_LOCKOFFSET ro 1*10^9 N Area de archivo para bloqueo de registros
DBFDBO_DBFHANDLE ro 0 N Manejador del archivo DBF
DBFDBO_DBTHANDLE ro 0 N Manejador del archivo DBT
*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

La función DbInfo() devuelve las configuraciones para la instancia de un DBE DBFDBE activo en un área de
trabajo. En vez de un DBFDBE_, las constantes #define para DbInfo() comienzan con DBFDBO_. En el caso de un
objeto de base de datos estas configuraciones pueden leerse únicamente y no redefinirse (READONLY en vez de
ASSIGNABLE).

FOXDBE (componente DATA)


El DBE FOXDBE manipula archivos de base de datos compatibles con FoxPro. Su estructura cumple con el
formato DBF pero pueden contener tipos de campos adicionales, otros que Caracter , Fecha , Lógico , Numerico o
Memo . Los tipos de campos específicos de FoxPro se convierten automáticamente a tipos de datos compatibles
de Xbase++ cuando una aplicación de accede a las bases de datos creadas por FoxPro.

La mayor diferencia con el DBFDBE es el formato de archivo para los archivos de memo que se manipula más
eficientemente. Los archivos de memo del FOXDBE tienen FPT como su extensión de archivo predeterminada, no
DBT. Con el formato de archivo FPT, es posible almacenar cualquier dato binario en los campos de memo,
mientras que sólo texto puede almacenarse en los campos de memo usando al formato de archivo DBT. Además,
el tamaño de bloque que representa el espacio mínimo utilizado para los datos almacenados puede definirse en un
campo de memo. En el formato DBT el tamaño del bloque es de 512 bytes mientras el FOXDBE soporte los ta-
maños de bloque en el rango de 33 bytes a 64 Kb.

El FOXDBE tiene en cuenta las características específicas de FoxPro del formato DBF además de otros factores
asociados con la operación concurrente con las aplicaciones de FoxPro. La siguiente tabla da un vistazo al res-
pecto:

Especificaciones para archivos DBF compatibles con FoxPro


Elemento Especificación
Tamaño de Archivo Limitado a 2^31 bytes (2 gigabytes)

Max.cantidad de campos 255

Max.cantidad de registros (2^31 - Header() - 1) / RecSize()

Tipos de datos para campos


FoxPro B, C, D, F, G, I, L, N, M, T, Y
Xbase++ F, C, D, N, O, I, L, N, M, T, Y, V, X

Tamaño de Archivo Memo Limitado a 2^31 bytes (2 gigabytes)

Tamaño de bloque de Memo 64 Bytes (ajustable entre 33 bytes y 64 kb)

SET CHARSET El conjunto de caracteres seleccionado con SET CHARSET


sobre la creación del archivo define cómo los datos
de texto serán almacenados

FoxPro soporta hasta once tipos diferentes de datos para los campos cuando crea bases de datos. En el lenguaje
de definición de datos (DDL), cada tipo de campo se lo identifica con una letra. El DDL de Xbase++ usa las mismas
letras para la mayoría de los tipos de datos. Sin embargo, en algunos casos son diferentes a FoxPro:

Mapeo de los tipos de campos de FoxPro en el DDL de Xbase++


Descripción Tipo de Campo Ancho Tipo de Dato Valtype()
FoxPro Xbase++

Doble B 8 F N
Caracter (texto) *) C 1-254 C C
Caracter (binario) C 1-254 X C
Fecha D 8 D D
De coma flotante F 1-20 N N
Genérico G 4 O M
Entero largo con signo I 4 I N
Lógico L 1 L L
Memo (texto) *) M 4 o 10 M M
Memo (binario) M 4 o 10 V M
Numérico N 1-20 N N
Marca de Tiempo T 8 T C
Monetario Y 8 Y N
*) Los datos son convertidos de acuerdo con SET CHARSET

Si un campo numérico del tipo de Doble ha de crearse en un nuevo archivo de la base de datos, su tipo de campo
se especifica con la letra B en FoxPro, mientras Xbase++ utiliza la letra F para declarar un campo como Double en
la llamada a la función DbCreate(). Estos campos almacenan valores numéricos en el formato binario y requieren
siempre 8 bytes en la base de datos. De esta manera, ellos difieren de los campos numéricos teniendo el tipo DDL
"N" donde los valores numéricos se almacenan como una secuencia de dígitos y donde el rango está limitado por
la longitud del campo. Sin embargo, ambos tipos de campos almacenan datos numéricos y la función Valtype()
devuelve a la letra N en ambos casos. Por tanto, uno debe distinguir entre la definición de un campo (DDL) y el tipo
de dato almacenado en un campo. El FOXDBE extiende lenguaje de definición de datos para los archivos DBF
pero no añade nuevos tipos de datos utilizados para las operaciones durante el tiempo de ejecución de un pro-
grama de Xbase++.

El conjunto de caracteres utilizado para almacenar datos de texto depende de la configuración SET CHARSET
cuando el FOXDBE crea un archivo DBF. Esta información se almacena en el encabezado del archivo y no puede
modificarse después de la creación del archivo. Si un caracter diferente se selecciona posteriormente en un pro-
grama con SET CHARSET, los datos de texto están sujetos a una conversión automática del código de página
ANSI-OEM de acuerdo a la configuración modificada. Note que el FOXDBE distingue texto de los datos binarios
para los campos de caracteres y memo. Sólo el texto se lo convierte automáticamente, mientras los datos binarios
se leen desde o escriben a una base de datos sin esta conversión.

Configuración de FOXDBE con DbeInfo()


El DBE puede configurarse específicamente usando la función DbeInfo(). Por ejemplo, puede determinarse la
extensión de archivo para los archivos de base de datos o establecerse el tamaño del bloque para los campos de
memo. La siguiente tabla da un vistazo de las constantes #define que pueden pasarse a la función DbeInfo() por el
FOXDBE:

Las constantes para DbeInfo() con el FOXDBE


Constante *) Valor Tipo de Datos Descripción
DBE_EXTENSION a DBF C Extensión para archivos DBF
DBFDBE_MEMOFILE_EXT a FPT C Extensión para archivos memo
DBFDBE_MEMOBLOCKSIZE ro 64 N Tamaño de bloque para campos memo
DBFDBE_LOCKMODE a #define N Modo para bloqueo de registros
*) ro=sólo lectura (READONLY) , a=asignable (ASSIGNABLE)

Los valores predeterminados se muestran en la columna "Valor".


DBE_EXTENSION
Esta constante es válida para todos los DBEs y define la extensión de archivo predeterminada para los
archivos que manipula el DBE. La extensión de archivo DBF predeterminada para el FOXDBE puede mo-
dificarse.
DbeInfo( COMPONENT_DATA, DBE_EXTENSION, "FOX" )
Este llamado a DbeInfo() establece la extensión de archivo predeterminada para los archivos DBF a "FOX".
FOXDBE_MEMOFILE_EXT
Esta constante se usa para determinar a cambiar la extensión predeterminada para los archivos de memo
(archivos FPT).
FOXDBE_MEMOBLOCKSIZE
Esta constante devuelve o modifica el tamaño de bloque mínimo utilizado para almacenar datos en un ar-
chivo de memo. El tamaño de bloque predeterminado para el FOXDBE es 64 bytes. Dentro de un archivo de
memo, los textos o las cadenas de caracteres se almacenan en bloques múltiples cada uno de 64 bytes.
Cuando el texto que es sólo de 100 bytes de longitud se almacena, ocupa aún 128 bytes en el archivo de
memo.
FOXDBE_LOCKMODE
Esta configuración permite la selección de un esquema de bloqueo compatible cpara el acceso a un archivo
concurrente cuando las aplicaciones de Xbase++ comparten bases de datos con las aplicaciones de FoxPro.
Las constantes de la siguiente tabla pueden usarse para alterar la configuración predeterminada:
Las constantes para los esquemas de bloqueo
Constante Descripción
FOXDBE_LOCKMODE_AUTO *) Xbase++ detecta el esquema de bloqueo apropiado
FOXDBE_LOCKMODE_2X Refuerza el esquema de bloqueo de FoxPro 2.x
FOXDBE_LOCKMODE_VISUAL Refuerza el esquema de bloqueo de Visual FoxPro
*) predeterminado

Por omisión, Xbase++ detecta qué esquema de bloqueo debe utilizarse. Sin embargo, bajo determinadas
raras ocasiones puede ocurrir que se seleccione un esquema de bloqueo automático. En este caso el pro-
gramador puede reforzar un esquema de bloqueo particular que sea compatible con la versión de FoxPro
existente.

Obtener información con la función DbInfo()


Cuando se abre un archivo DBF, se crea una instancia del FOXDBE. La instancia es un objeto de base de datos
(DBO) e incluye todas las configuraciones del FOXDBE. El objeto DBF representa al área de trabajo en la cual el
archivo DBF se abre y manipula al archivo DBF abierto. La función DbInfo() puede usarse para recuperar infor-
mación sobre el DBO (el área de trabajo). una constante #define que identifica que información específica se
requiere ha de pasarse a la función. Las constantes que pueden pasarse a la función DbInfo() para el FOXBE se
listan en la siguiente tabla:

Constantes para DbInfo() con FOXDBE


Constante *) Valor Tipo de Dato Descripción
DBO_FILENAME ro C Nombre de archivo Completo
FOXDBO_MEMOBLOCKSIZE ro 64 N Tamaño de bloque para campos memo
*) ro=READONLY , a=ASSIGNABLE

La función DbInfo() regresa la configuración para la instancia del DBE FOXDBE activo en el área de trabajo. En
vez del FOXDBE_, las constantes #define para DbInfo() comienzan con FOXDBO_. En el caso de un objeto de
base de datos, estas configuraciones sólo pueden leerse y no redefinirse (READONLY en vez de ASSIGNABLE).

NTXDBE (componente ORDER)


El Motor de Base de Datos NTXDBE manipula archivos de índice en el formato NTX. Este es el formato nativo de
archivo índice de Clipper que se usa como el formato predeterminado para la construcción de índices en Xbase++.
Lo predeterminado puede modificarse al cambiar el archivo DBESYS.PRG en el directorio ..\SOURCE\SYS. La
función DbeSys() en este archivo se la llama automáticamente al inicio del programa, antes del procedimiento
Principal. Un DBESYS.PRG personalizado debe enlazarse posteriormente en forma explícita al archivo EXE.
El formato de archivo NTX ha sufrido cambios. El NTXDBE soporta al formato de archivo NTX como se lo define en
Clipper 5.2. Un vistazo de sus características se lista debajo:

Especificaciones para los archivos NTX


Elemento Especificación
Tamaño de archivo Limitado al área para bloqueo de registros,
el valor predeterminado es 1 GB (10^9 Bytes)

Tipo de Datos C, D, L, N, sin memo

Máx. longitud para:


expresión INDEX 255 caracteres
expresión FOR 255 caracteres
nombre TAG 10 caracteres

Indices por archivo Uno

FOR expresión Soportado


TAG expresión Soportado
WHILE expresión Ignorado
cláusula EVAL...EVERY Ignorado
cláusula RECORD Ignorado
cláusulas ALL..NEXT..REST Ignorado

SET CHARSET El índice está basado en el conjunto ANSI de caracteres

El NTXDBE utiliza el valor predeterminado del nombre del archivo índice sin extensión para la opción TAG en el
comando INDEX. Como consecuencia, los nombres del archivo índice no difieren sólo por la extensión salvo que la
opción TAG se use en el comando INDEX. Por ejemplo, esto es ilegal:

INDEX ON Apellido TO Nombre.nt1


INDEX ON Nombre TO Nombre.nt2
SET INDEX TO Nombre.nt1, Nombre.nt2

En este caso "Nombre" se usa implícitamente como el nombre de TAG para dos índices que resultan en un error
durante el tiempo de ejecución. Esto puede resolverse especificando dos nombre de tag diferentes:

INDEX ON Apellido TAG Apell TO Nombre.nt1


INDEX ON Nombre TAG Nombre TO Nombre.nt2
SET INDEX TO Nombre.nt1, Nombre.nt2

La única configuración que puede establecerse usando DbeInfo() para el NTXDBE es el desplazamiento de los
bloqueos de registro. Puesto que los bloqueos de registro pueden establecerse en las bases de datos que se
ordenan por un índice, el archivo índice debe bloquearse también. Un bloqueo de registros comienza en un des-
plazamiento lógico y el desplazamiento predeterminado es 1 gigabyte. Este desplazamiento puede modificarse.
Cuando se lo modifica para el DBFDBE, debe ingresarse el mismo valor para el NTXDBE. un ejemplo del DBE
DBFNTX compuesto es:

DbeInfo( COMPONENT_ORDER, NTXDBE_LOCKOFFSET, 2*10^9 )

En esta línea de código el desplazamiento para los bloqueos de registro manipulados por el componente ORDER
del DBE DBFNTX compuesto se establece en 2 gigabyte.

CDXDBE (componente ORDER)

El Motor de Base de Datos CDXDBE manipula a los archivos índice en el formato CDX. Este formato de archivo
originado en FoxPro ha sufrido modificaciones. Por omisión el CDXDBE soporta al formato de archivo CDX como
se lo definió en FoxPro. Además, el CDXDBE provee la interoperabilidad completa con Clipper RDDs Six y Comix
(ver debajo).

La mayor ventaja del formato de archivo CDX es su capacidad de mantener índices múltiples en un archivo índice.
Soporta también índices condicionales donde sólo se referencia en el índice un subconjunto de registros de base
de datos. Esto se lleva a cabo por una condición FOR que puede especificarse en la creación de un índice. Se da
un vistazo de las características de CDXDBE en la tabla a continuación:

Especificaciones para los archivos CDX


Elemento Especificación
Tamaño de archivo Limitado al área para bloqueo de registros,
el valor predeterminado es 2 GB (2 * 10^9 Bytes)

Tipo de Datos C, D, L, N, sin memo

Máx. longitud para ambos


INDEX más expresión FOR 512 caracteres
nombre TAG 10 caracteres

Indices por archivo No limitado

FOR expresión Soportado


TAG expresión Soportado
WHILE expresión Ignorado
cláusula EVAL...EVERY Ignorado
cláusula RECORD Ignorado
cláusulas ALL..NEXT..REST Ignorado

SET CHARSET El índice está basado en el conjunto ANSI de caracteres

Si se selecciona una tabla de ordenamiento (ver SET COLLATION) , se la almacena en el archivo CDX. Por ello el
orden de los caracteres se define durante la creación del índice. Este es un cambio importante del formato de
archivo CDX que ha introducido el formato de Visual FoxPro. Xbase++ soporta esta característica así Visual
FoxPro y las aplicaciones de Xbase++ pueden compartir los mismos archivos CDX.
Note que la tabla de ordenamiento definida por Visual Foxpro difiere de la tabla de ordenamiento definida por el
sistema operativo. Cuando una tabla de ordenamiento está activa, el CDXDBE es compatible con la tabla de or-
denamiento de Visual Foxpro mientras el NTXDBE usa a la tabla de ordenamiento del sistema operativo. Por tanto,
el orden de los caracteres puede diferir apenas entre CDXDBE y NTXDBE si bien (aparentemente) la misma tabla
de ordenamiento se selecciona con SET COLLATION.

Además note que los índices en los caracteres de expresión se vuelven no sensibles a las mayúsculas una vez
que la tabla de ordenamiento se selecciona para el CDXDBE. Este comportamiento, nuevamente, es compatible
con Visual Foxpro.

Clipper y CDX: todas las unidades de las bases de datos de CDX (RDD) disponibles para Clipper usan el formato
de archivo CDX como se lo definió bajo FoxPro 2.x. Este formato de archivo no almacena una tabla de ordena-
miento. Por tanto, las aplicaciones de Clipper no pueden usar los archivos CDX creados por el Visual Foxpro o los
programas de Xbase++. Xbase++, sin embargo, puede leer correctamente y actualizar los archivos CDX creados
por Clipper. Si los programas de Clipper se utilizan para compartir los archivos CDX con las aplicaciones de
Xbase++, El programa Clipper debe crear los archivos índices .

Multi-tarea y Multi-threading (multi-enhebrado)


Este capítulo describe funciones que permiten al programador Xbase++ acceder a las capacidades especiales del
sistema operativo a nivel de lenguaje Xbase++. Estas funciones proveen acceso a multi-tarea y multi- threading.
"Multi-tarea" es la capacidad del sistema operativo para ejecutar varios programas de aplicación simultáneamente.
Cada programa de aplicación se encuentra embutido en un proceso. Un proceso contiene al menos un "thread"
(hebra) en la que se ejecuta el código del programa. El programa se ejecuta en un thread, no en un proceso.
Múltiples threads pueden encontrarse activos en un proceso. Esta capacidad multi-threading del sistema operativo
permite a diversos componentes de la misma aplicación ejecutarse al mismo tiempo.

Iniciar múltiples procesos - multi-tarea


Usando múltiples threads
Caminos de Ejecución en un programa
Visibilidad de variables en threads
Prioridades de los threads
Obteniendo información acerca de los threads
Los objetos Thread saben la hora
Rutinas de (Des)Inicialización para Threads
Clases de Thread definidas por el usuario
Controlando threads usando estados de espera
Controlando threads usando señales
Exclusión mutua de threads

Iniciar múltiples procesos - multi-tarea


La función RunShell() ejecuta cualquier programa desde una aplicación Xbase++ como un proceso nuevo. El
número de procesos que pueden iniciarse depende de la memoria disponible (incluyendo el espacio libre en el
disco rígido), pero está limitado por el sistema operativo. Para tomar la máxima ventaja de RunShell(), se requiere
un conocimiento y comprensión del comando START y de los comandos asociados con el procesador de co-
mandos. Cuando se pasan comandos al procesador de comandos, debe considerarse siempre el parámetro /C.
Puede obtenerse información detallada en la ayuda en línea del sistema operativo.

La función RunShell() inicia una nueva instancia del procesador de comandos y le pasa una cadena de caracteres
que es ejecutada en la línea de comando. Bajo Windows 95 el procesador de comandos predeterminado es
COMMAND.COM y para OS/2 y Windows NT es CMD.EXE. La función RunShell() debe recibir al menos un pa-
rámetro para ser pasado al procesador de comandos, que puede ser una cadena nula (""):

xResultado := RunShell( "" )

En la línea de arriba, RunShell() inicia una nueva instancia del procesador de comandos, se abre una nueva
ventana y la aplicación Xbase++ se detiene hasta que la ventana se cierra. Este es el resultado de los valores
predeterminados para otros parámetros que pueden pasarse a RunShell(). Estos parámetros adicionales pueden
especificarse cuando una aplicación Xbase++ es dependiente o independiente del procesador de comandos re-
cientemente iniciado.

// llamar al programa sin especificar


// parámetros de línea (""), pero especificando
// ejecución asíncrona (.T.)

RunShell( "", "PROGRAMA.EXE", .T.)

Esta línea de código ejecuta un programa asincrónicamente en una nueva ventana. Se especifica el nombre del
programa como el segundo parámetro y no se pasan comandos de línea. La ejecución de la aplicación Xbase++
(la cual invocó a RunShell()) continúa al hacer clic con el ratón en la ventana de la aplicación Xbase++. Cuando el
tercer parámetro contiene el valor lógico .T. (verdadero), el nuevo programa se inicia y la aplicación Xbase++
continúa ejecutando independientemente. Si el valor es .F. (falso), la aplicación Xbase++ espera hasta que el
programa recientemente iniciado haya terminado antes de continuar su ejecución.

El programa puede también iniciarse como un proceso de segundo plano y, en tal caso, la aplicación Xbase ++
permanece activa.

// llamar al programa sin especificar


// parámetros de línea (""),
// Use ejecución asíncrona (.T.)
// y ejecutarlo como un proceso en segundo plano (.T.)

RunShell( "", "PROGRAMA.EXE", .T., .T.)

El cuarto parámetro determina cuando el nuevo proceso será iniciado en el segundo plano o en el frente de la
aplicación Xbase++. Por omisión, RunShell() inicia un nuevo proceso en el frente. Esto significa que la nueva
ventana se trae al frente y recibe el foco de entrada. Si una sesión de "pantalla completa" se inicia usando
RunShell(), la función conmuta automáticamente a modo caracter. Cuando el valor lógico .T. (verdadero) se es-
pecifica como el cuarto parámetro, el nuevo proceso inicia en el segundo plano de la aplicación Xbase++ y ésta
retiene el foco.

Los ejemplos de llamadas a programa muestran cómo una aplicación puede iniciarse pasando el nombre del
archivo. El nombre del archivo inclusive puede pasarse a la función RunShell() como un parámetro en la línea de
comandos. Las siguientes dos líneas son equivalentes:

RunShell( "", "PROGRAMA.EXE", .T.)


RunShell("/C PROGRAMA.EXE", , .T.)

En ambos casos, el programa se inicia asincrónicamente. En la segunda línea, el nombre de archivo está conte-
nido en el parámetro de la línea de comando. Para que esto trabaje, la línea de comando debe comenzar con "/C".
El siguiente código muestra ejemplos adicionales para el uso de la función RunShell():

// ejecuta al programa REVERSI.EXE asincrónicamente en


// segundo plano

RunShell( "", "REVERSI.EXE", .T., .T. )

// sesiones DOS (/DOS) en el primer plano (/F) en una ventana (/WIN)


// con el título de ventana "DOS session" bajo OS/2

RunShell( '/C START "DOS session" /F /WIN /DOS' )

// Sesión DOS de Pantalla Completa (/DOS) en segundo plano (/B) bajo OS/2

RunShell( '/C START /B /DOS' )

// Llamada Indirecta del programa EXPLORER.EXE de Windows


// usando el comando START.
// El programa Explorer muestra el directorio actual.

? RunShell( "/C START EXPLORER.EXE ." )

El comando START invoca a un nuevo procesador de comandos y le pasa una línea de comandos. Esto permite la
ejecución de cualquier programa incluyendo el pasaje de parámetros. Cuando se usa START, RunShell() ofrece
una flexibilidad considerable para la iniciación de cualquier programa desde una aplicación de Xbase++ así como
para el pasaje de parámetros (Nota: ingrese "Help Start" o "Start /?" en la línea de comandos para obtener in-
formación detallada sobre el comando START desde la ayuda en línea del sistema operativo).
Usando múltiples threads
Multi-threading es una característica especial del sistema operativo que permite que un programa de aplicación
sea dividido en varios componentes que pueden ejecutarse de manera independiente y simultánea. El ejemplo
clásico para ello es una aplicación que provee la habilidad de evaluar datos y de imprimir informes de una base de
datos mientras se ingresan datos en otra base de datos. La idea es que el usuario comienza una tarea que con-
sume mucho tiempo, e inmediatamente empieza a trabajar con otro procedimiento mientras se ejecuta el primero.
En este ejemplo, la evaluación y el informe de una base de datos se ejecuta en un thread diferente que la rutina
para el ingreso de datos. No obstante, la evaluación de la base de datos y el procedimiento de informe, y el de
ingreso de datos son componentes de la misma aplicación. Otro ejemplo es el programa para la colección de datos
en el que estos últimos se colectan de varias fuentes con cada fuente siendo controlada por un thread separado.
Inclusive cuando los datos se reciben simultáneamente de diferentes fuentes, puede registrarse con confianza.

Caminos de ejecución en un programa


Un programa de aplicación (el archivo EXE) se inicia como un proceso. Un proceso consiste de uno o más threads.
Dentro de un proceso un thread puede pensarse como un camino de ejecución separado donde se ejecutan donde
las funciones y procedimientos independientemente de otros threads. Cuando un proceso consiste de diversos
threads, el sistema operativo asigna tiempo del microprocesador (CPU) para los diferentes threads. El thread que
gana acceso a la CPU (qué thread se ejecuta) depende primero de la prioridad del thread y luego de si debe
ejecutar instrucciones o si se encuentra actualmente en un modo ocioso. En multi- threading, el sistema operativo
le da a cada thread una cantidad limitada de tiempo de CPU (llamado porción de tiempo), y cada thread es
otorgado su tiempo. De esta manera varios procesos se ejecutan al mismo tiempo (multi-tareas), y dentro de un
proceso pueden ejecutarse varios threads (multi-threading). Sin embargo, desde el punto de vista de la CPU, sólo
un thread se ejecuta en cualquier punto del tiempo.

La clase Thread de Xbase++ ofrece al programador una herramienta para sacar ventaja de multi-threading de una
manera simple y directa. Un objeto thread debe crearse y el objeto debe recibir información acerca de qué código
del programa se ejecuta en el thread. El siguiente ejemplo muestra la aproximación básica:

PROCEDURE Main
LOCAL oThread := Thread():new() // crear objeto thread

CLS
oThread:start( "Suma", 10000 ) // suma números desde 1 hasta 10000
DO WHILE .T. // muestra caracteres durante
?? "." // el cálculo
Sleep(10)
IF ! oThread:active // Verifica si el thread aún se ejecuta
EXIT
ENDIF
ENDDO

? "La suma es:", oThread:result


RETURN

FUNCTION Suma( nNumero )


LOCAL i, nSuma := 0

FOR i:=1 TO nNumero


nSuma += i
IF i % 100 == 0 // muestra el progreso
DispOutAt( MaxRow(), 0, i )
ENDIF
NEXT
RETURN nSuma

El programa tiene el único propósito de demostrar el uso un objeto thread en un corto ejemplo (de otra manera
sería insignificante). El objeto thread se crea usando el método :new() de la clase Thread. Ni bien se creó el objeto
thread, está disponible un thread nuevo. El thread está entonces listo para la ejecución. El código del programa a
ejecutarse en el thread nuevo se especifica llamando al método :start() del objeto thread. El primer parámetro es
una cadena de caracteres que especifica al identificador de la función o procedimiento que ha de ejecutarse en el
thread. En el ejemplo, se especifica la función definida por el usuario Suma(). Todos los demás parámetros (10000
en este caso) se pasan como argumentos a la función que está siendo ejecutada. Luego de llamar al método :start()
se ejecuta el código del programa en el thread nuevo.
En el programa de ejemplo se ejecutan dos ciclos simultáneamente. El ciclo DO WHILE en el procedimiento
Principal exhibe un punto en la pantalla por cada vez que se pasa por el ciclo. El ciclo FOR...NEXT en la función
Sum() calcula una suma simultáneamente. Cuando el ciclo FOR...NEXT en el thread nuevo se termina, el ciclo DO
WHILE se termina también porque la variable de instancia :active señala que un thread nuevo no ejecuta más
códigos. El resultado del cálculo está contenido en la variable de instancia :result del objeto thread.

Se crea el thread cuando se crea el objeto thread. La ejecución del código en el thread comienza usando el mé-
todo :start() . El identificador para la función o procedimiento a ejecutarse en el thread se pasa a este método como
una cadena de caracteres. El símbolo o identificador para la función o procedimiento debe estar disponible durante
el tiempo de ejecución. Esto significa que la función o el procedimiento comenzado en el thread nuevo se declara
como una STATIC FUNCTION o un STATIC PROCEDURE. Mientras el thread está ejecutando un código de
programa, la variable de instancia :active tiene el valor .T. (verdadero). Cuando el thread ha terminado, el valor de
retorno de la última función o procedimiento ejecutado en el nuevo thread se le asigna a la variable de instan-
cia :result .

Visibilidad de variables en threads


La habilidad para dividir un programa en threads diferentes presenta una dimensión nueva para los programa-
dores que no han programado previamente en un entorno multi-threading. Si bien crear threads es simple en
Xbase++, la programación de una aplicación multi-threaded agrega nueva complejidad y requerimientos para el
diseño de los programas. Además, nuevas fuentes de error deben considerarse ya que pueden resultar de las
distintas partes de una aplicación que está siendo ejecutada al mismo tiempo. Primero, multi-threading afecta la
visibilidad de las variables en threads diferentes. La siguiente tabla muestra las diferencias:

Visibilidad de las variables en threads diferentes


Visibilidad Clase de Almacenamiento
Visible en los procesos PUBLIC
(todos los threads) STATIC

Visible en el thread LOCAL


(este Thread) PRIVATE
FIELD

Las variables declaradas como LOCAL o PRIVATE son visibles en el thread donde ocurrió la declaración. Las
variables declaradas con PUBLIC o STATIC son visibles en todos los threads de un proceso (programa de apli-
cación). Las variables de campo (FIELD) son visibles en un área de trabajo de un espacio de trabajo. Un espacio
de trabajo está ligado a un thread. Puesto que los espacios de trabajo pueden moverse entre threads, las variables
de campo pueden volverse visibles en threads diferentes. En un punto dado en el tiempo, una variable de campo
es visible sólo en un thread.

Cuando se divide el código de un programa en diferentes threads, debe evitarse la posibilidad de múltiples threads
de tener acceso simultáneo a la misma variable (PUBLIC o STATIC) y su modificación. Si múltiples threads están
modificando la misma variable, el valor de la variable no es predecible. El siguiente ejemplo demuestra esta si-
tuación:

STATIC snNumero := 0 // STATIC visible en todo el programa

PROCEDURE Main
LOCAL i, oThread := Thread():new() // crea objeto thread

oThread:start( "Decremento" ) // decrementa snNumero

FOR i:=1 TO 10000


snNumero ++ // incrementa snNumero
NEXT

? "Resultado:", snNumero // teóricamente esto es 0


RETURN

PROCEDURE Decremento
LOCAL i
FOR i:=1 TO 10000
snNumero -- // decrementa snNumero
NEXT
RETURN
En el ejemplo, se ejecutan dos ciclos FOR...NEXT simultáneamente en dos threads diferentes. La misma variable
STATIC se accede en ambos threads. El primer thread incrementa la variable 10000 veces y el segundo thread
disminuye la variable 10000 veces. Teóricamente el resultado será el valor cero. En la práctica rara vez se llega a
este valor. Generalmente el valor de snNumero al final del programa es mayor que cero. Esto es porque el sistema
operativo ubica el tiempo del procesador independientemente para los dos threads. El ciclo FOR...NEXT en el
procedimiento Principal comienza incrementando ni bien se inicia el primer thread. Cambiar entre threads lleva su
tiempo y la variable STATIC aumenta varias veces antes de que el segundo thread comience realmente. Cuando
el ciclo FOR...NEXT en el primer thread termina el proceso entero se termina, incluyendo el segundo thread. Esto
significa que el ciclo FOR...NEXT en el segundo thread se cancela antes de que la variable i alcanza el valor 10000.
Por esta razón, el valor de snNumero al final del programa es casi siempre mayor que cero.

Este programa demuestra que la programación con múltiples threads requiere consideración de acontecimientos
especiales. El acceso simultáneo a las mismas variables o archivos por múltiples threads debería evitarse.
Cuando dos threads se encuentran usando las mismas variables, el resultado (o el valor de la variable) no es
predecible, puesto que el sistema operativo ubica que thread recibirá el tiempo disponible del procesador. El
thread que desarrolló una tarea por último establece el valor de la variable. Como regla general, la parte del pro-
grama que debe ejecutarse en un thread separado debería estar escrito de tal forma que pueda ser compilado y
enlazado como un programa independiente. Todas las variables en un thread deberían protegerse del acceso por
otros threads.

Prioridades de los threads


El multi-threading permite que diferentes programas sean ejecutados al mismo tiempo o que el mismo código del
programa sean ejecutados de manera simultánea múltiples veces en threads diferentes. Cuando se le asigna un
valor a una variable el valor de la variable depende del thread al que se el sistema operativo le asignó tiempo del
procesador. El tiempo del procesador asignado a un thread puede influenciarse por su prioridad definida. Por ello
el resultado del programa de ejemplo puede cambiarse si se le añade una única línea de programa:

STATIC snNumero := 0 // STATIC visible en todo el programa

PROCEDURE Main
LOCAL i, oThread := Thread():new() // crea objeto thread

// incrementa nivel de prioridad


oThread:setPriority( PRIORITY_ABOVENORMAL )

oThread:start( "Decremento" ) // decrementa snNumero

FOR i:=1 TO 10000


snNumero ++ // incrementa snNumero
NEXT

? "Resultado:", snNumero // siempre esto es 0


RETURN

PROCEDURE Decremento
LOCAL i
FOR i:=1 TO 10000
snNumero -- // decrementa snNumero
NEXT
RETURN

En este ejemplo, la prioridad del nuevo thread se incrementa en relación al thread actual usando el méto-
do :setPriority() . Esto causa que el tiempo del procesador sea preferencialmente asignado al nuevo thread. Esto
significa que el ciclo FOR...NEXT en el procedimiento Decremento se procesa primero porque el thread en el que
este ciclo se ejecuta tiene una prioridad más alta que el thread en el que se ejecuta el procedimiento Principal. En
este caso, el ciclo FOR...NEXT en el procedimiento Decremento se ejecuta antes que el ciclo FOR...NEXT en el
procedimiento Principal. El resultado del programa es siempre cero porque snNumero disminuye 10000 veces y
luego aumenta 10000 veces.

El ejemplo representa un caso extremo en el cual el orden de la ejecución puede controlarse precisamente al
aumentar la prioridad de threads individuales. Generalmente, el orden de ejecución de los threads (la asignación
de tiempo del procesador) depende de varios factores que son controlados por el sistema operativo.
Por omisión los threads en los programas de Xbase++ tienen prioridad PRIORITY_NORMAL y esto es general-
mente adecuado. Esto resulta en la asignación de tiempo del procesador a una aplicación de Xbase++ con igual
precedencia que con la mayoría de los demás programas. En situaciones normales, la prioridad no debería alte-
rarse. Modificar la prioridad requiere un conocimiento detallado de la manera en la que el sistema operativo dis-
tribuye el tiempo del procesador. Los threads reciben tiempo del procesador basados en prioridad. Los threads de
baja prioridad reciben acceso al CPU si ningún thread con prioridad más alta está siendo ejecutado o si un thread
de prioridad más alta ha entrado en estado de espera. Temporalmente puede asignarse una prioridad alta a un
thread con prioridad baja para permitir que se ejecute.

El objeto thread permite que la prioridad de los threads sea modificada y es la responsabilidad del programador
usar este poder responsablemente. Aumentar la prioridad de los threads sólo provee más tiempo del procesador al
thread desde el sistema operativo. No causa que el programa se ejecute más rápido. En el peor de los casos, si la
prioridad se establece demasiado alto no es posible llevar a cabo los procesos multitarea y multi-threading porque
se le asigna todo el tiempo del procesador a una aplicación de Xbase++ (o un único thread en una aplicación de
Xbase++). En este caso los otros programas no pueden ejecutarse hasta que haya finalizado la aplicación de
Xbase++. Modificar la prioridad de los threads demanda un cuidado especial. Estos mandatos influencian direc-
tamente la ejecución por prioridad de varios programas, o procesos, respectivamente (multi-tareas). Ellas no
afectan el desempeño de una aplicación de Xbase++ individual.

Obteniendo información acerca de los threads


Existen dos funciones en Xbase++ que son útiles en el contexto de multi- threading. Se utilizan en la implemen-
tación del código del programa donde el objeto thread que ejecuta este código es desconocido. Las funciones son
ThreadID() y ThreadObject() .

Cada thread manejado por un objeto Thread puede identificarse por un ID numérico. Los IDs del Thread son
números consecutivos, o sea el primer thread tiene el ID 1 y ejecuta el procedimiento Principal. Un objeto Thread
almacena el ID del thread en su variable de instancia :threadID . Cuando se llama a la función ThreadID(), recupera
el objeto Thread del thread actual y devuelve el valor de la variable de instancia :threadID . Esto nuevamente es el
ID numérico del thread actual.

La función ThreadObject() se utiliza de manera similar. Pero en vez del ID numérico, devuelve el objeto Thread
completo que ejecuta la función. Por tanto, el resultado de las siguientes expresiones es siempre idéntico:

ThreadID() // Devuelve el valor un ID numérico


ThreadObject():threadID // (Valor de una variable de instancia)

Los objetos Thread saben la hora


Se inicia un thread llamando al método :start() de un objeto Thread. Normalmente, la ejecución del código del
programa dentro del thread comienza inmediatamente luego que se llama al método. Sin embargo, es posible
definir el tiempo exacto cuando el thread debe comenzar con la ejecución del programa. Esto se realiza con el
método :setStartTime() que debe recibir el valor numérico indicando "segundos desde medianoche". Por ejemplo:

oThread:setStartTime( 12*60*60 ) // 12 en punto.


oThread:start( {|| MedioDia() } )

Un objeto Thread monitorea el reloj del sistema. Por consiguiente, la rutina MedioDia() en el ejemplo se ejecuta a
las doce en punto a pesar de que el thread se inicia más temprano. El thread actual que ha llamado al méto-
do :start() continua ejecutándose.

Otra forma de ejecución dependiente del tiempo del código del programa lo provee el método :setInterval() . Define
un intervalo para la ejecución repetida del código del programa por un objeto Thread. Cada vez que el intervalo
expira el objeto Thread automáticamente reinicia su thread. Esta funcionalidad también es provista de forma
simplificada por la función SetTimerEvent(). Un ejemplo típico para esto es la exhibición continua de la hora actual,
que puede programarse de diferentes manera:

// ------------------- Ejemplo 1 ---------------------------


SetTimerEvent( 100, {|| DispOutAt( 0, 0, Time() ) } )

// ------------------- Ejemplo 2 ---------------------------


oThread:start( "MuestraHora_A" )
PROCEDURE MuestraHora_A
DO WHILE .T.
DispOutAt( 0, 0, Time() )
Sleep( 100 )
ENDDO
RETURN

// ------------------- Ejemplo 3 ---------------------------


oThread:setInterval( 100 )
oThread:start( "MuestraHora_B" )

PROCEDURE MuestraHora_B
DispOutAt( 0, 0, Time() )
RETURN

El resultado de los tres ejemplo es idéntico: el tiempo se despliega una vez que el segundo en la esquina superior
de la izquierda de la pantalla (la unidad para el intervalo es 1/100mo de segundo). La implementación más fácil es
dada por la función SetTimerEvent() que evalúa repetidamente al bloque de códigos.

Una comparación de los procedimientos MuestraHora_A() y MuestraHora_B() revela una importante implicación
que resulta de usar el método :setInterval() . Ejemplo nro. 2 utiliza el ciclo DO WHILE y un estado de espera ex-
plícito (función Sleep()) para el despliegue continuo, mientras que el ejemplo nro. 3 trabaja continuamente sin un
ciclo DO WHILE. En el ejemplo nro. 3, se define un intervalo de tiempo monitoreado por el objeto Thread. Con-
secuentemente, el procedimiento MuestraHora_B() se ejecuta cada vez que el intervalo ha transcurrido y que el
thread entra de manera implícita en el estado de espera entre dos ciclos de ejecución.

Rutinas de (Des)Inicialización para Threads


El código del programa invocado en un thread llamando al método :start() puede diferenciarse en mayor detalle por
el agregado de rutinas de (des)inicialización que se ejecutan una vez al comienzo de un thread y una vez antes de
que termine. Las variables de instancia :atStart y :atEnd de un objeto Thread son de utilidad para este propósito
particular. Es posible designar a ambos nombres de funciones o bloques de códigos:

oThread:atStart := {|| DbUseArea( .T., , "CLIENTES" ) }


oThread:atEnd := {|| DbCloseArea() }
oThread:setInterval( 0 )
oThread:start( "ListaDeClientes", {|| FIELD->CIUDAD = "New York" } )

<otro código de programa que ejecuta en paralelo>

PROCEDURE ListaDeClientes( bCondicionFor )


IF Eval( bCondicionFor )
QOut( FIELD->APELLIDO )
ENDIF

SKIP

IF Eof()
ThreadObject():setInterval( NIL )
ENDIF
RETURN

En este ejemplo, se programa una base de datos de consulta que lista los datos de todos los clientes que viven en
Nueva York. El archivo se abre cuando empieza un thread o sea antes de que comience el filtro, y se cierra antes
de que termine el thread. Esto ocurre en los bloques de código :atStart y :atEnd . El código del programa para la
evaluación de la base de datos se implementa sin el típico ciclo DO WHILE .NOT. El ciclo Eof() se vuelve posible
porque el intervalo de tiempo para la ejecución repetida de este código se establece en cero. Como resultado se
nincia el código inmediatamente una vez más cuando se alcanza la declaración RETURN. Cuando el puntero de
registros se mueve hasta el final del archivo (Eof() == .T.), se establece el intervalo en NIL lo que causa que el
thread no repita el código pero que termine.

Clases de Thread definidas por el usuario


La clase Thread puede servir como superclase de las clases Thread definidas por el usuario cuyas instancias
tienen su propio thread. Se proveen tres métodos para su uso en clases de Thread derivadas. Tienen el atributo de
visibilidad PROTECTED: y por tanto pueden usarse solamente en subclases. Estos métodos
son :atStart() , :execute() y :atEnd() , de los cuales al menos el método :execute() debe programarse en una clase
Thread definida por el usuario. Contiene el código a ejecutarse en el thread separado luego de que es llamado el
método :start() .

El ejemplo para la Base de Datos de consulta en la sección previa se utiliza como base para la siguiente clase de
Thread que lleva a cabo las mismas operaciones de base de datos:

oThread := ListaDeClientes():new()
oThread:start( , {|| FIELD->CIUDAD = "New York" } )

<otro código de programa que ejecuta en paralelo>

******************************
CLASS ListaDeClientes FROM Thread
PROTECTED:
METHOD atStart, execute, atEnd
ENDCLASS

// Abrir Base de Datos cuando el thread comience


METHOD ListaDeClientes:atStart
USE CLIENTES
::setInterval( 0 )
RETURN self

// Ejecute filtro a la Base de Datos


METHOD ListaDeClientes:execute( bCondicionFor )
IF Eval( bCondicionFor )
QOut( FIELD->APELLIDO )
ENDIF
SKIP
IF Eof()
::setInterval( NIL )
ENDIF
RETURN self

// Cerrar Base de Datos antes que termine el thread


METHOD ListaDeClientes:atEnd
CLOSE CLIENTES
RETURN self

La clase definida por el usuario es instanciada y un bloque de códigos con la condición para la consulta se pasa al
método :start() . A los tres métodos :atStart() , :atEnd() y :execute() se los invoca automáticamente y ejecuta
dentro del thread. El bloque de códigos que se le pasa a :start() también se le pasa al método :execute() . El código
de este método se ejecuta repetidas veces hasta que se alcanza el final del archivo.

El ejemplo muestra los métodos que pueden o deben implementarse en una clase Thread definida por el usuario.
Usa un mecanismo de ejecución repetida de tiempo controlado del código en un thread. En vez de :setInterval( 0 |
NIL ) , también puede usarse un ciclo DO WHILE.

Controlando threads usando estados de espera


En programas multi-threaded, cada thread puede verse como un camino de ejecución separado en el que dife-
rentes partes de un programa pueden ejecutarse al mismo tiempo. También es posible ejecutar una y la misma
parte de un programa simultáneamente en múltiples threads. Un ciclo DO WHILE, por ejemplo, puede progra-
marse una vez pero ejecutarse diez veces al mismo tiempo. Por tanto, no todos los elementos del lenguaje que
controlan el flujo de programa en un thread son apropiados para controlar el flujo del programa entre múltiples
threads. Esto se aplica a las declaraciones como FOR..NEXT, DO WHILE..ENDDO, IF..ENDIF, DO
CASE..ENDCASE o BEGIN SEQUENCE..ENDSEQUENCE. Todas estas estructuras de control las traduce el
compilador al tiempo de compilación y son sólo válidas para un thread.

Las posibilidades para coordinar threads diferentes comienza con la detención del thread actual hasta que uno o
más threads hayan terminado. Las funciones ThreadWait(), ThreadWaitAll() y el método :synchronize() de la clase
Thread se utilizan con éste propósito. Cuando se llama a cualquiera de estas funciones o al método, el thread
actual detiene la ejecución del programa y entra en estado de espera. El thread espera para la finalización de uno
u otros threads y retoma la ejecución luego. Mientras espera, el thread no consume recursos de CPU. El siguiente
esquema demuestra esto:

Thread A Thread B

ejecutando
|
oThreadB:start()
| ejecutando
| | // ejecución simultánea
| | // del programa
oThreadB:synchronize(0) |
| // thread B ejecuta código
estado de espera |
RETURN // thread B termina
ejecutando // thread A retoma
|
El Thread A inicia al thread B y espera para su finalización en un punto particular en el programa llamando al
método :synchronize() . No es posible terminar el thread B explícitamente a partir del thread A.

Normalmente, la coordinación de threads vía los estados de espera es necesaria si un thread A necesita el re-
sultado de otro thread B. Por ejemplo, el cálculo de estadísticas extensivas puede hacerse en múltiples threads
donde cada uno recopila los datos desde una base de datos particular y calcula sólo una parte de la estadística. La
consolidación de la estadística entera luego se produce en un único thread que necesita esperar los resultados de
todos los demás threads. Así todos los threads ejecutan diferentes partes del programa al mismo tiempo y deben
coordinarse o sincronizarse en un punto particular del programa. La coordinación puede implementarse por un
thread que se encuentra esperando a los demás, o por un thread que les dice a los demás que abandonen el
estado de espera. La última posibilidad requiere de un objeto Señal para la comunicación entre threads.

Controlando threads usando señales


Hay una posibilidad de coordinar threads que no requiere que un thread termine antes de que asuma otro thread.
Sin embargo, esto requiere el uso de un objeto de la clase Señal. El objeto Señal debe ser visible en dos threads al
mismo tiempo. Con la ayuda de un objeto Señal, un thread puede informarle a uno o más threads que abandonen
el estado de espera y que asuman la ejecución del programa:

Thread A Thread B

ejecutando ejecutando
| | // ejecución simultánea
| | // del programa
| oSignal:wait() // thread B se detiene
| |
| estado de espera // thread A ejecuta código
oSignal:signal() |
| | // thread B continúa ejecutando
ejecutando ejecutando

Cada vez que un thread ejecuta al método :wait() de un objeto Señal entra en un estado de espera y detiene la
ejecución del programa. El thread abandona su estado de espera sólo después de que otro thread llama al mé-
todo :signal() del mismo objeto Señal. De esta manera se establece una comunicación entre threads. Un thread le
informa a los demás threads que abandonen su estado de espera y reasuman la ejecución del programa.

Exclusión mutua de threads


Mientras los múltiples threads ejecuten diferentes códigos de programa al mismo tiempo, la coordinación de los
threads es posible usando los estados de espera como se llegó a hacer con :synchronize() o ThreadWait(). No
obstante, los estados de espera no son posible si el mismo código del programa está ejecutándose simultánea-
mente en múltiples threads. Un ejemplo común para esta situación es sumar/borrar elementos de/desde un
Arreglo globalmente visible:

PUBLIC aQueue := {}

FOR i:=1 TO 10000 // Este ciclo no puede


Add( "Prueba" ) // ser ejecutado en
Del() // múltiples threads
NEXT

**********************
FUNCTION Add( xValor )
RETURN AAdd( aQueue, xValor )

**************
FUNCTION Del()
LOCAL xValor

IF Len(aQueue) > 1
xValor := aQueue[1]
ADel ( aQueue, 1 )
ASize( aQueue, Len(aQueue)-1 )
ENDIF
RETURN xValor

En este ejemplo, el arreglo aQueue se usa para almacenar temporariamente valores arbitrarios. Los valores re-
cuperados desde el arreglo de acuerdo al principio FIFO (Primero Entrado Primero Salido-First In First Out). La
función Add() agrega un elemento al final del arreglo, mientras la función Del() lee el primer elemento y comprime
el arreglo en un elemento: (nota: este tipo de manipulación de datos se llama Cola).

Cuando las funciones Add() y Del() se ejecutan en threads distintos, el Arreglo PUBLIC aQueue se accede si-
multáneamente por múltiples threads. Esto conduce a una situación crítica en la función Del() cuando el Arreglo
tiene sólo un elemento. En este caso puede ocurrir un error durante el tiempo de ejecución:

Thread A Thread B

LOCAL xValor
IF Len(aQueue) > 1
El Thread es El Thread ejecuta su función completamente
interrumpido por el
Sistema Operativo LOCAL xValor
IF Len(aQueue) > 1
xValor := aQueue[1]
ADel ( aQueue, 1 )
ASize( aQueue, Len(aQueue)-1 )
ENDIF
RETURN xValor

el Thread retoma su ejecución.

xValor := aQueue[1]

Error de Tiempo de Ejecución:


Mientras tanto, el Arreglo está vacío

El sistema operativo puede interrumpir un thread en cualquier momento para darle acceso a otro thread en la CPU.
Si los threads A y B ejecutan la función Del() al mismo tiempo, es posible que el thread A sea interrumpido in-
mediatamente después de la declaración IF. El thread B puede ejecutar una función hasta que se complete antes
de que el thread A se agenda otra vez para la ejecución del programa. En este caso, un error durante el tiempo de
ejecución en un thread antes que en otro ejecuta la misma función.

La función de ejemplo Del() representa aquellas situaciones en multi- threading que requieren la ejecución de
operaciones en un thread particular antes de que otro thread ejecute las mismas operaciones. Esto puede re-
solverse cuando el thread B se detiene mientras el thread A ejecuta la función Del(). Sólo después que el thread A
ha ejecutado completamente la función puede el thread B comenzar con la ejecución de la misma función. Tal
situación se llama "exclusión mutua" porque un thread excluye todos los demás threads de ejecutar el mismo
código al mismo tiempo.

La exclusión mutua de los threads se alcanza en Xbase++ no en el nivel PROCEDURE/FUNCTION sino con la
ayuda de los métodos SYNC. El atributo SYNC para los métodos garantiza que el código del método sea ejecu-
tado por un solo thread en cualquier momento. Sin embargo, esto está restringido a uno y al mismo objeto. Si un
objeto es visible en múltiples threads y al método se lo llama simultáneamente con ese objeto, la ejecución del
método se serializa entre threads. En contraste, si dos objetos de la misma clase se usan en dos threads y el
mismo método se lo llama con los dos objetos, el código del programa del método se ejecuta paralelamente en
ambos threads. Como resultado, la exclusión mutua sólo es posible si dos threads intentan ejecutar el mismo
método con el mismo objeto. El objeto debe ser una variable de instancia de una clase definida por el usuario que
implementa los métodos SYNC. Un método SYNC se ejecuta completamente en un thread. Todos los demás
threads están automáticamente detenidos cuando intentan ejecutar al mismo método con el mismo objeto.

El ejemplo con el anteriormente mencionado Arreglo PUBLIC aQueue debe programarse como una clase definida
por el usuario con el fin de acceder con seguridad al Arreglo desde múltiples threads:
PUBLIC oQueue := Queue():new()

FOR i:=1 TO 10000 // Este ciclo puede ejecutarse


oQueue:add( "Prueba" ) // simultáneamente en
oQueue:del() // múltiples threads
NEXT

***********
CLASS Queue // Clase para manejar
PROTECTED: // una Cola
VAR aQueue
EXPORTED:
INLINE METHOD init
::aQueue := {} // Inicializar arreglo
RETURN self

SYNC METHOD add, del // Métodos sincronizados


ENDCLASS

METHOD Queue:add( xValor ) // Agregar un elemento


RETURN AAdd( ::aQueue, xValor )

METHOD Queue:del // Remover primer elemento


LOCAL xValor // y comprimir Arreglo

IF Len(::aQueue) > 1
xValor := aQueue[1]
ADel ( ::aQueue, 1 )
ASize( ::aQueue, Len(::aQueue)-1 )
ENDIF
RETURN xValor

En este ejemplo, la clase Queue se usa para manipular un Arreglo dinámico que pueda accederse y cambiarse
desde múltiples threads simultáneamente. Se referencia al Arreglo en la variable de instancia :aQueue y se lo
accede dentro de los métodos SYNC :add() y :del() . El objeto Queue que contiene al Arreglo globalmente visible.
La ejecución del método :del() se la serializa automáticamente entre múltiples threads:

Thread A Thread B
| |
oQueue:del() |
| oQueue:del()
<...>
ADel( ::aQueue, 1 ) el thread es detenido
<...>
RETURN xValor el thread retoma la ejecución
| <...>
| ADel( ::aQueue, 1 )
| <...>
| RETURN xValor
| |

Cuando el thread B quiere ejecutar al método :del() mientras éste método lo ejecuta el thread A, el thread B se
detiene porque quiere ejecutar al método con el mismo objeto Queue. Por tanto los métodos SYNC se utilizan
cuando las múltiples operaciones deben garantizarse para ser ejecutadas en un thread antes que otro thread
ejecute las mismas operaciones. Un método SYNC puede ejecutarse con el mismo objeto sólo en el mismo thread
al mismo tiempo (Nota: si un método de clase se declara con el atributo SYNC, su ejecución se serializa para todos
los objetos de la clase).
Interfaz de usuario y Conceptos de Diálogo
Una larga parte del código del programa para la mayoría de las aplicaciones se asocia con la interfaz del usuario y
permite su acceso a las características de la aplicación. El sistema operativo ofrece dos modos de operar para la
interfaz del usuario, el modo VIO (Video Input Output Mode) y modo GUI (Interfaz Gráfica del Usuario - Graphic
User Interface). El modo VIO es un modo texto basado en el modo operativo que se selecciona cuando está activa
la pantalla completa o cuando el programa de DOS se está ejecutando en una ventana de DOS. El modo GUI es
un modo de operación gráfica. Un ejemplo del modo GUI es el Escritorio. Xbase++ soporta a ambos modos
operativos los cuales hacen posible migrar con facilidad los programas existentes de Xbase escritos para DOS a
un sistema operativo de 32bits.

Para simplificar la migración a programas de DOS Xbase existentes desde el texto basado en modo VIO al modo
GUI, Xbase++ también incluye un modo "híbrido" especial. En este modo, los elementos de programa basados en
caracter pueden mezclarse con elementos de diálogo gráfico. Esto permite al programador migrar programas de
DOS Xbase basados en texto de una forma escalonada a las aplicaciones con interfaz gráfica de usuario com-
pleta.

Este capítulo describe cómo los programas pueden crearse para los diferentes modos operativos e ilustra varios
conceptos para la interfaz de programación de usuarios. Esto incluye aspectos del Ingreso de datos y Salida para
ambos: la programación procedural de las aplicaciones VIO y la Programación Orientada a Objetos de las apli-
caciones GUI, dirigido a eventos.

Aplicaciones en Modo Caracter (modo VIO)


Entradas y salidas sin formato
Entradas y salidas con formato
Teclado y ratón
El sistema Get predeterminado
Modificación del sistema Get
Despliegue de tablas
Aplicaciones en modo gráfico (modo GUI)
Las bases de los Componentes de Xbase++
Ventanas y relaciones
XbpCrt() - La ventana para el modo híbrido
XbpDialog() - La ventana para el modo GUI
Jerarquía de clases de los Componentes de Xbase++
DataRef() - La conexión entre XBP y DBE
Creando aplicaciones GUI
Las Tareas de AppSys()
El sistema de menú de una aplicación
Tareas del Procedimiento Main
DataDialog: Una Clase para integrar bases de datos
DataDialog y pantallas de ingreso de datos
Control del programa en las ventanas de diálogo

Aplicaciones en modo caracter (modo VIO)


Esta sección describe los comandos más importantes, funciones y conceptos de diálogo utilizados en la pro-
gramación de las aplicaciones VIO. Con sólo algunas excepciones todos los elementos relevantes del lenguaje de
Xbase++ son compatibles con Clipper. Los programadores familiarizados con Clipper pueden leer las secciones
"Teclado y ratón" y "El sistema predeterminado Get" de éste capítulo. Note que la funcionalidad de la aplicación
VIO está garantizada en modo híbrido así como en modo GUI.

Entradas y salidas sin formato


La forma más simple por la que se produce la entrada y salida de datos es sin formato. Los datos se ingresan o se
muestran en la posición actual del cursor de pantalla o en el encabezado de impresión. Xbase++ provee un
conjunto de comandos para la entrada y salida sin formato. Estos se listan en la tabla a continuación:

Comandos para entradas y salidas sin formato


Comando Descripción
? | ?? Muestra el resultado de una o más expresiones
ACCEPT Ingresa caracteres en la posición actual del cursor
DISPLAY Muestra el contenido de un archivo de base de datos
INPUT Ingresa una expresión en la posición actual del cursor
LIST Muestra el contenido de un archivo de base de datos
SET ALTERNATE Habilita/Deshabilita la Salida a un archivo
SET COLOR Establece el color de Pantalla
SET CONSOLE Habilita/Deshabilita la Salida a Pantalla
SET PRINTER Habilita/Deshabilita la Salida a Impresora
TEXT...ENDTEXT Muestra una o más líneas de texto
TYPE Muestra el contenido de un archivo
WAIT Ingresa un caracter simple

Estos tres comandos ACCEPT, INPUT y WAIT proveen entradas sin formato. WAIT acepta que se presione una
única tecla mientras que ACCEPT e INPUT permiten que sólo se ingrese un número cualquiera de caracteres. La
entrada vía ACCEPT e INPUT se termina cuando el usuario presiona la tecla Enter. Los caracteres que se in-
gresan usando INPUT se los considera como una expresión y se los compila usando el operador de macro (un
error en la expresión conduce a un error durante el tiempo de ejecución). Los caracteres ingresados usando al
comando ACCEPT permanecen sin modificación y pueden asignárseles a una variable de memoria como cadena
de caracteres.

Los comandos más comúnmente usados para la salida sin formato son los signos de interrogación simple y doble
(? o ??). Estos son equivalentes a las funciones QOut() y QQOut(), respectivamente. Puede producirse la salida de
los resultados de una o más expresiones usando estos comandos. El dispositivo predeterminado de salida es la
pantalla. Los resultados de las expresiones pueden guardarse en un archivo (luego de SET ALTERNATE ON) o
enviados a una impresora (luego de SET PRINTER ON). Si se llama al comando SET CONSOLE OFF antes de
que se produzca la salida, la salida por pantalla se suprime. Después de la supresión de la salida por pantalla, ésta
debe reactivarse usando SET CONSOLE ON después que se haya completado la salida a un archivo y/o a la
impresora.

Los comandos LIST y DISPLAY se usan ambos para la salida de registros desde un archivo de base de datos. El
comando TYPE produce la salida de los contenidos de cualquier archivo de texto. Las opciones TO PRINTER y TO
FILE son válidas con tres de estos comandos, así la salida simultánea a una impresora o a un archivo puede
llevarse a cabo sin llamar a SET PRINTER ON o SET ALTERNATE ON. La salida por pantalla de estos comandos
puede suprimirse llamando primero a SET CONSOLE OFF.

El comando SET COLOR modifica el color del despliegue de la salida por pantalla. Este comando no es realmente
un comando de salida, pero permite que el color de la salida se modifique.

Se encuentran descripciones detalladas de los comandos para las entradas y salidas sin formato, incluyendo los
ejemplo de programa, en la documentación de referencia.

Entradas y salidas con formato


Las entradas y salidas con formato permiten la especificación de la posición exacta en pantalla o en la impresora
(la fila y la columna) de la entrada o de la salida de datos. Xbase++ incluye a los comandos y a las funciones para
las entradas y salidas formateadas. Los comandos se traducen por el preprocesador a la función equivalente lo
que significa que la diferencia entre los comandos y las funciones es una diferencia de sintaxis. La Sintaxis de
Comando a veces permite una lectura mejor del código del programa, puesto que muchos comandos implican
varias llamadas a función y la sintaxis del comando provee palabras claves adicionales. La siguiente tabla lista las
funciones más importantes y los comandos para las entradas y salidas formateadas:

Los comandos y funciones para las entradas y salidas formateadas


Comando/función Descripción
@ Posiciona al cursor en pantalla y borra la línea de pantalla
@...BOX Muestra un cuadro en la pantalla
@...CLEAR Borra un área de pantalla
@...GET Ingresa datos
@...SAY Muestra datos
@...TO Muestra un cuadro en la pantalla
CLEAR Borra la pantalla completa
Col() Devuelve la posición de la columna del cursor en pantalla
DispOut() Muestra el resultado de una expresión, y actualiza la posición del cursor
DispOutAt() Tal como DispOut() pero la posición del cursor no se actualiza
DevOut() Muestra el resultado de una expresión en el dispositivo de salida actual
Row() Devuelve la posición de la fila del cursor en pantalla
MaxCol() Devuelve la máxima cantidad de columnas de la pantalla
MaxRow() Devuelve la máxima cantidad de filas de la pantalla
PCol() Devuelve la posición de la columna del cabezal de impresora
PRow() Devuelve la posición de la fila del cabezal de impresora
SaveScreen() Guarda un área de pantalla
SET DEVICE Especifica el dispositivo actual de salida
SetColor() Establece o devuelve el color de pantalla
SetPos() Cambia la posición del cursor en pantalla
SetPrc() Establece las coordenadas de fila y columna del cabezal de impresora
RestScreen() Repinta un área de pantalla previamente guardada

Algunas de estas funciones y comandos afectan sólo la posición del cursor de pantalla pero se las incluye también
porque las coordenadas del cursor marcan la posición en la que se exhiben los datos. Otras funciones y comandos
manejan a la pantalla misma. En el modo VIO, el origen (0, 0) del sistema de coordenadas es la esquina superior
izquierda de la pantalla o de la ventana. La esquina inferior derecha (MaxRow(), MaxCol()) representa a los valores
más grandes de coordenadas que son visibles. La posición del cursor se establece al especificar los Row() y Col()
a ambos los comandos @ o a la función SetPos(). La pantalla se vacía usando CLEAR generalmente al comienzo
de cada programa antes de desplegar cualquier cosa en cualquier momento. Los comandos @...BOX y @...TO
dibujan cajas en la pantalla. Son válidas únicamente para la pantalla y no están disponibles para la impresora.

Los comandos más importantes para la salida formateada son @...SAY lo que produce la salida del resultado de
alguna expresión. La salida usando @...SAY puede producirse en la pantalla o en la impresora. La salida fo r-
mateada difiere de la salido no formateada en que la salida simultánea en la pantalla y en la impresora es impo-
sible. La selección de un dispositivo de salida se lleva a cabo usando al comando SET DEVICE (TO PRINTER o
TO SCREEN). La salida en la impresora se encuentra en la posición actual del encabezado de impresión que
puede establecerse usando las funciones PRow() y PCol(). La función SetPrc() restablece los valores internos a
las coordenadas de fila y de columna pero no reposiciona el cabezal de la impresora.

El comando @...SAY puede expandirse para incluir los datos de entrada usando la opción GET. Alternativamente,
un campo de entrada puede definirse usando al comando @...GET. Estos comandos se usan para definir uno o
más campos de entrada de datos que se producen dentro del comando READ o a la función ReadModal(). READ
y ReadModal() ambas activan el sistema predeterminado Get de Xbase++ (ver la siguiente sección sobre esto).

Teclado y ratón
Bajo un sistema operativo con la interfaz gráfica de usuario el ratón (no el teclado) es el dispositivo de entrada más
importante para controlar a la aplicación. Debido a esto, los programas que se están ejecutando o los módulos
individuales no se controlan por la lógica de programa sino por "eventos". Estos eventos se producen usualmente
por las acciones del usuario. Los eventos, que provienen desde fuera de la aplicación, están almacenados tem-
porariamente por el sistema operativo en una cola de eventos y son luego secuencialmente procesados por la
aplicación. Algunos ejemplos de eventos serían: el ratón se movió, el botón derecho del ratón fue presionado, el
botón izquierdo del ratón fue presionado dos veces, etc. Cuando se presiona una tecla también se lo considera un
evento, que muestra que los eventos pueden provenir desde diferentes dispositivos.

A cada evento se lo identifica dentro del programa por un código numérico. Cada tecla tiene un único valor nu-
mérico asociado y los diferentes eventos del ratón tienen diversos códigos numéricos. En Xbase++, los eventos se
manipulan con un grupo de funciones, independientemente de su origen. Hay además un grupo de funciones que
aseguran la compatibilidad del lenguaje con Clipper. Estas funciones de compatibilidad pueden responder sólo al
teclado y sólo consideran los códigos del teclado definidos en Clipper. Debería notarse que los valores numéricos
asociados con varias de las claves en Clipper no necesariamente coinciden con los códigos de eventos del sis-
tema operativo o de Xbase++ y que los códigos claves antiguos se ofrecen sólo en el rango de 0 a 255. Estas
funciones de compatibilidad sólo consideran a los códigos clave definidos en Clipper. La distinción entre "fun-
ciones de evento" y "funciones de teclado" se muestra también en la siguiente tabla:

Funciones de evento y funciones de teclado


Función Descripción
Funciones de Evento soportando las entradas de teclado y mouse
AppEvent() Lee un evento y lo remueve de la cola
LastAppEvent() Devuelve el último evento
NextAppEvent() Lee el próximo evento sin removerlo de la cola
PostAppEvent() Pone un evento en la cola
SetAppEvent() Asocia un evento con un bloque de código
SetMouse() Conmuta la disponibilidad de eventos de mouse

Funciones de Compatibilidad soportando sólo entrada de teclado


Inkey() Lee un código de tecla
KEYBOARD Escribe caracteres en el buffer de teclado *)
LastKey() Lee el último código de tecla
NextKey() Lee el próximo código de tecla
SetKey() Asocia código de teclado con un bloque de código

*) KEYBOARD es un comando, no una función.

Una descripción detallada de las funciones de compatibilidad puede encontrarse en la documentación de refe-
rencia. AppEvent() lee eventos desde la cola de eventos y los remueve también de la cola. El valor de retorno de
AppEvent() es el código numérico que identifica únicamente al evento. La función SetMouse(.T.) debe haber sido
previamente convocada para la función AppEvent() con el fin de registrar los eventos del ratón en el modo VIO. En
el siguiente ejemplo de programa, se termina una vez que se presiona el botón derecho del ratón:

#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento := 0

CLEAR
@ 0,0 SAY "Presione el botón derecho del mouse para terminar"
SetMouse(.T.) // registra eventos del mouse

DO WHILE nEvento <> xbeM_RbDown // evento: botón derecho del mouse


nEvento := AppEvent(,,,0) // espera hasta que ocurra un evento

IF nEvento < xbeB_Event // evento: tecla presionada


? "El código de evento para la tecla es:", nEvento
ELSE
? "El código de evento para el mouse es:", nEvento
ENDIF
ENDDO
RETURN

El gran número de eventos posibles hace impráctico la programación directa de eventos usando los códigos
numéricos. En cambio, deberían usarse las constantes definidas en el archivo #include APPEVENT.CH. Estas
constantes comienzan con el prefijo xbe (que representa a xb ase e vent) seguido por una letra mayúscula o por
un guión bajo. La letra mayúscula identifica a la categoría para el evento y el guión bajo separa al prefijo del resto
del nombre del evento descriptivo:

Categorías de eventos
Categoría Prefijo Ejemplo
Ningún evento xbe_ xbe_None
Evento de Teclado xbeK_ xbeK_RETURN
Evento Base xbeB_ xbeB_Event
Evento de Mouse xbeM_ xbeM_LbDown
Evento de Componente Xbase xbeP_ xbeP_Activate

Además del valor de retorno que identifica al evento, la función AppEvent() modifica dos parámetros que se pasan
por referencia. Estos se llaman "parámetros de mensaje" y contienen información adicional sobre el evento. En un
sistema dirigido por evento como el de Xbase++, suele ser insuficiente recibir sólo un único código de evento. Por
ejemplo, es generalmente necesario conocer la posición del puntero del ratón si el evento es "clic del ratón".
Cuando se llama a la función AppEvent(), dos parámetros deben pasarse como referencia para la contención de
información adicional sobre el evento después de que regrese AppEvent(). Si el evento es un clic del ratón, las
coordenadas del puntero del ratón están contenidas en el primer parámetro de mensaje como un arreglo de dos
elementos. El siguiente ejemplo ilustra esto:

#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento := 0, mp1, nFila, nCol

CLEAR
@ 0,0 SAY "Presione el botón derecho del mouse para terminar"
SetMouse(.T.) // registrar evento del mouse
DO WHILE nEvento <> xbeM_RbDown // evento: botón derecho del mouse
nEvento := AppEvent(@mp1,,,0) // espera hasta que ocurra un evento

IF nEvento < xbeB_Event // evento: tecla presionada

? "Código de Evento:", nEvento

ELSEIF nEvento <> xbeM_Motion // eventos de mouse excepto


// 'mouse movido'
nFila := mp1[1] // coordenadas de mouse
nCol := mp1[2]

@ nFila, nCol SAY "El código de evento es:" + Str(nEvento)

ENDIF
ENDDO

RETURN

La variable mp1 se pasa por referencia a la función AppEvent(). Después de cada evento del ratón contienen a las
coordenadas de la fila y de la columna del puntero del ratón en un arreglo de dos elementos. En el ejemplo, los
contenidos del arreglo están asignados a dos variables nFila y nCol . Se produce la salida del código de eventos en
esta posición salvo que se trate de un movimiento del ratón. El evento xbeM_Motion ocurre muy frecuentemente
durante el movimiento del ratón y obstruiría la pantalla cuando se despliega.

En el modo VIO, las coordenadas del ratón constituyen la información más importante, contenida en los paráme-
tros de mensaje. En muchos otros casos los parámetros contienen el valor NIL en modo VIO, pero contienen
también información adicional cuando se utilizan los Componentes de Xbase (por supuesto, los Componentes de
Xbase no están disponibles en el modo VIO) o cuando se crean los eventos definidos por el usuario usando la
función PostAppEvent(). El siguiente ejemplo ilustra las relaciones básicas entre las funciones AppEvent() y
PostAppEvent() que (junto con sus parámetros de mensaje) crean las bases para la programación dirigida por
eventos:

#include "Appevent.ch"

#define xbeU_DibuCaja xbeP_User + 1 // xbeP_User es el valor base


#define xbeU_Salir xbeP_User + 2 // para los eventos de Usuario

PROCEDURE Main
LOCAL nEvento := 0, mp1, mp2

CLEAR
@ 0,0 SAY " DibuCaja | Salir" // Region sensible al mouse
SetMouse(.T.) // registrar mouse

DO WHILE .T. // ciclo infinito de lectura de eventos


nEvento := AppEvent( @mp1, @mp2 ,,0 )

DO CASE
CASE nEvento == xbeU_DibuCaja // Evento de usuario
DibuCaja( mp1, mp2 ) // mp1 = Date(), mp2 = Time()
// desde PostAppEvent()
CASE nEvento == xbeU_Salir // segundo evento de usuario
QUIT

CASE nEvento < xbeB_Event // Se presionó una tecla

@ MaxRow(), 0
?? "El código de tecla es:", nEvento

CASE nEvento == xbeM_LbClick // click con boton izquierdo del mouse

@ MaxRow(), 0
?? "Las Coordenadas Son:", mp1[1], mp1[2]

IF mp1[1]== 0 .AND. mp1[2] <= 21 // en region sensible al mouse


IF mp1[2] <= 15
PostAppEvent( xbeU_DibuCaja, Date(), Time() )
ELSE
PostAppEvent( xbeU_Salir )
ENDIF
ELSE
@ mp1[1], mp1[2] SAY "No hubo selección"
ENDIF

ENDCASE
ENDDO

RETURN

********************************* // define posición y despliegue de


PROCEDURE DibuCaja( dFecha, cHora ) // un recuadro usando clicks del mouse

LOCAL nEvento := 0, mp1, nArriba, nIzq, nAbajo, nDer

SAVE SCREEN
@ 0, 0 SAY "Haga Clic en la esquina superior izquierda del recuadro"

DO WHILE nEvento <> xbeM_LbClick // Esperar por clic botón izquierdo


// del mouse
nEvento := AppEvent( @mp1,,, 0 )
ENDDO

nArriba := mp1[1]
nIzq := mp1[2]

nEvento := 0
@ nArriba, nIzq SAY "Haga Clic en la esquina inferior derecha del recuadro"

DO WHILE nEvento <> xbeM_LbClick // Esperar por clic botón izquierdo


// del mouse
nEvento := AppEvent( @mp1,,, 0 )

IF nEvento == xbeM_LbClick .AND. ;


( mp1[1] <= nArriba .OR. mp1[2] <= nIzq )
Tone(1000,1) // esquina inferior derecha
nEvento := 0 // es inválida
ENDIF
ENDDO

nAbajo := mp1[1]
nDer := mp1[2]

RESTORE SCREEN
@ nArriba, nIzq TO nAbajo, nDer // recuadro de salida

@ nArriba+1,nIzq+1 SAY "Fecha:" // Mostrar valores de


?? dFecha // PostAppEvent()
@ nArriba+2,nIzq+1 SAY " Hora:"
?? cHora

RETURN

En el ejemplo, uno de los dos eventos definidos por el usuario se genera cuando el ratón se clickea en la región
sensible de la primera fila de la pantalla. Las coordenadas del ratón (contenidas en el parámetro de mensaje mp1 )
se usan para distinguir entre el evento xbeU_DibuCaja o el evento xbeU_Salir y se posicionan en la cola mediante
PostAppEvent(). El evento definido por el usuario xbeU_DibuCaja recibe a los valores de retorno de Date() y Time()
como parámetros de mensaje. Cuando se recuperan estos eventos desde la cual mediante AppEvent(), los pa-
rámetros de mensaje se posicionan en las variables mp1 y mp2 . Estos valores se pasan al procedimiento Di-
buCaja() y despliegan en la pantalla con esta función.
El programa es un ejemplo simple que muestra la lógica de la programación dirigida por evento. Los eventos se
identifican por un valor numérico único (usando una constante #define) y contienen información adicional sobre el
evento en los dos parámetros de mensaje. Los valores contenidos en los dos parámetros mp1 y mp2 varían de-
pendiendo del evento. Los valores del parámetro de mensaje pueden variar desde NIL en el caso más simple
hasta datos complejos como arreglos u objetos. PostAppEvent() puede usarse para posicionar cualquier evento en
la cola que pueda recuperarse desde cualquier sitio del programa usando AppEvent().

El sistema Get predeterminado


Xbase++ provee un sistema Get para la incorporación de datos formateados en el modo VIO. El código fuente de
este sistema está contenido en el archivo GETSYS.PRG. La arquitectura abierta del sistema Get ofrece buenas
posibilidades como la validación antes, durante y después de su ingreso. Ofrece también un lugar específico en el
cual un programador puede identificar y procesar eventos que se producen durante el ingreso de los datos sin
tener que alterar los elementos básicos del lenguaje para la definición de la entrada de campos.

Se crea más fácilmente un campo de entrada usando el comando @...SAY...GET. La entrada de datos mismas se
inicia usando el comando READ:

USE Direccion ALIAS Dire

@ 10,10 SAY " Nombre:" GET Dire->NOMBRE // define campos


@ 12,10 SAY "Apellido:" GET Dire->APELLIDO // de ingreso de datos
READ // leer entrada

En estas cuatro líneas se abre un archivo de base de datos, se definen dos campos de entrada de datos, y se lleva
a cabo la entrada vía el comando READ. La entrada termina cuando lo que se ingresa en el segundo campo fi-
naliza usando la tecla Enter o si se presiona la tecla ESC durante la entrada.

La sintaxis de comando constituye la manera más fácil de programar los campos de entrada de datos. El pre-
procesador traduce la sintaxis a código que crea objetos Get y los almacena en el arreglo GetList . Los objetos Get
incluyen los métodos que permiten el despliegue formateado de los valores y la incorporación interactiva de datos.
Los valores de los datos de entrada pueden estar contenidos en las variables de memoria o en las variables de
campo. El acceso al contenido de las variables de los datos de entrada no es directo, pero se da mediante un
bloque de códigos. Este bloque de códigos se llama el bloque de códigos de datos. Este último es utilizado por el
objeto Get para leer el valor de una variable en el buffer del objeto Get y después para escribir el valor modificado
nuevamente en la variable.

cuando el objeto Get tiene el focos de entrada, el usuario puede modificar los contenidos del buffer de edición. Un
objeto Get usa varios métodos para controlar la navegación del cursor y transferir los caracteres al buffer de edi-
ción. Los objetos Get también pueden llevar a cabo la validación de los datos antes y después del ingreso de datos.
Las reglas para la validación de los datos se especifican en bloques de códigos contenidos en las variables de
instancia del objeto Get.

El comando @...GET crea al objeto que ya contenía un bloque de códigos para acceder a la variable especificada.
Cuando los objetos Get se crean directamente usando el método de clase Get():new() , debe especificarse un
bloque de códigos de datos. Los objetos Get se almacenan en un arreglo que se pasa a la función ReadModal(). Si
se emplea el comando @...GET, el arreglo que referencia a los objetos Get estará contenido en la variable GetList .
El comando READ pasa el arreglo de GetList a la función ReadModal(). La función ReadModal() es la rutina de
edición por predeterminación que accede a varios métodos de edición y despliegue de los objetos Get. El sistema
Get predeterminado de Xbase++ incluye a la función ReadModal() junto con otras rutinas de utilidad globalmente
visibles listadas en la tabla a continuación:

Funciones del sistema Get


Función Descripción
Rutinas servicio para READ

ReadModal() Activa ingreso de datos para todos los objetos Get


ReadExit() Define teclas de terminación para ingreso de datos
ReadInsert() Conmuta entre modo inserción y sobreescritura
ReadKill() Termina ingreso de datos para todos los objetos Get en el arreglo
GetList actual

Rutinas servicio para GET

GetEventReader() Lector Get para AppEvent()


GetHandleEvent() Permite procesamiento de eventos por el objeto Get actual
GetPrevalidate() Prevalida antes que un objeto Get reciba el foco
GetPostvalidate() Postvalida antes que un objeto Get pierda el foco
GetDoSetkey() Ejecuta un bloque de código que está vinculado a una tecla o un evento
GetActive() Establece el objeto Get que tiene el foco
GetKillActive() Remueve el foco del objeto Get activo
Getlist() Determina el arreglo GetList actual
GetlistPos() Devuelve la posición del objeto Get en el arreglo GetList
GetEnableEvents() Conmuta entre Inkey() y AppEvent()
GetToMousePos() Posiciona el cursor en un campo de entrada donde está el puntero del
mouse

Funciones de compatibilidad soportando solamente entradas de teclado

GetReader() Lector Get para Inkey()


GetApplykey() Permite que ciertas teclas sean procesadas por el objeto Get actual

Dos de las funciones en el archivo GETSYS.PRG existen con el fin de asegurar la compatibilidad con el sistema
Get de Clippper. En estas funciones la función de compatibilidad Inkey() se utiliza para recuperar las entradas a
partir del teclado. Esto no es compatible con la función AppEvent() que se usa para leer los eventos de Xbase++.
Para facilitar la migración de las aplicaciones existentes de Clipper a Xbase++, las funciones de compatibilidad se
usan por predeterminación en el modo VIO. Esto significa que el sistema Get lee sólo del teclado usando Inkey() y
no procesa evento alguno. En este modo predeterminado el sistema Get es completamente compatible con
Clipper pero el ratón no está disponible.

La configuración predeterminada se activa cuando se llama a SetMouse(.T.) antes de que se produzca el primer
comando READ o la primer llamada a ReadModal(). Los eventos en el sistema Get se procesan luego usando las
funciones GetEventReader() y GetHandleEvent() en vez de las funciones GetReader() y GetApplykey(). En esta
configuración el ratón está disponible. La función SetAppEvent() debe usarse en vez de SetKey() para asociar el
momento en que se presiona una tecla con los bloques de código. La función GetEnableEvents( .T. | .F.) puede
usarse también para alternar entre el modo de compatibilidad y el modo dirigido por evento del sistema Get de
Xbase.

Las funciones de servicio del sistema Get raramente se requieren. El comando @...SAY...GET es suficiente para
permitir que complejas ventanas de entrada de datos se programen con facilidad. Se necesita un conocimiento
detallado de las funciones adicionales del sistema Get cuando el sistema Get predeterminado se modifica para
alcanzar requerimientos únicos.

Modificación del sistema Get


El código fuente del sistema Get de Xbase++ está contenido en el archivo GETSYS.PRG y puede modificarse de
cualquier forma. Los cambios no deberían llevarse a cabo en el archivo mismo sino en la copia de un archivo.
Antes de que se produzcan cambios sustanciales en GETSYS.PRG, deberían explorarse otras manera de modi-
ficar el comportamiento predeterminado para satisfacer los requerimientos individuales. Por ejemplo, la variable de
instancia oGet:reader contiene un bloque de códigos que llama a un lector definido por el usuario de Get para
proveer características especiales. El bloque de códigos del lector de Get debe aceptar un parámetro (el objeto
Get) que debe pasarse como el primer parámetro a la función o al procedimiento que implementa el lector de Get.
La aproximación básica de implementar un lector de Get especial se muestra en el siguiente ejemplo. Este código
usa a la función de compatibilidad Inkey() para leer las entradas a partir del teclado. El propósito del lector de
ejemplo es manejar diéresis del alemán y caracteres especiales ("ß") ingresados como caracter en los campos de
entrada. Estos dos caracteres se traducen en sus dos caracteres equivalentes en el lector definido por el usuario
NoDieresis() :

#include "Get.ch"
**************
PROCEDURE Main
LOCAL cNombre1 := Space(20), cNombre2 := Space(20)
@ 8,10 SAY "Entrada sin diéresis y á"
@ 10,10 SAY " Nombre:" GET cNombre1 SEND reader := {|oGet| NoDieresis(oGet) }
@ 12,10 SAY "Apellido:" GET cNombre2 SEND reader := {|oGet| NoDieresis(oGet) }
READ
RETURN
La definición de estos campos de entrada se produce usando el comando de sintaxis. El bloque de códigos que
asigna al lector de Get definido por el usuario se especifica usando la opción SEND. En él, el bloque de códigos del
lector es asignado a la variable de instancia oGet:reader . Al lector Get mismo se lo programa en el siguiente
procedimiento:

***************************
PROCEDURE NoDieresis( oGet )
LOCAL bBloqueCodigo, cCaracter, nTecla

IF GetPreValidate( oGet ) // Realizar prevalidación


oGet:setFocus() // Dar foco de entrada al Get

bBloqueCodigo := {|o,n1,n2| ; // traducir dentro del bloque de


GetApplykey(o,n1),; // código en dos caracteres
IIf( o:typeOut, NIL, GetApplykey(o,n2) ) }

DO WHILE oGet:exitState == GE_NOEXIT

IF oGet:typeOut // caracteres no editables


oGet:exitState := GE_ENTER // a derecha del cursor
ENDIF

DO WHILE oGet:exitState == GE_NOEXIT


nTecla := Inkey(0) // leer teclado
cCaracter:= Chr(nTecla)

DO CASE // traducir caracteres especiales


CASE cCaracter == "Ä"
Eval( bBloqueCodigo, oGet, Asc("A"), Asc("e") )
CASE cCaracter == "Ö"
Eval( bBloqueCodigo, oGet, Asc("O"), Asc("e") )
CASE cCaracter == "Ü"
Eval( bBloqueCodigo, oGet, Asc("U"), Asc("e") )
CASE cCaracter == "ä"
Eval( bBloqueCodigo, oGet, Asc("a"), Asc("e") )
CASE cCaracter == "ö"
Eval( bBloqueCodigo, oGet, Asc("o"), Asc("e") )
CASE cCaracter == "ü"
Eval( bBloqueCodigo, oGet, Asc("u"), Asc("e") )
CASE cCaracter == "ß"
Eval( bBloqueCodigo, oGet, Asc("s"), Asc("s") )
OTHERWISE
GetApplykey( oGet, nTecla )
ENDCASE

ENDDO

IF ! GetPostValidate( oGet )
oGet:exitState:= GE_NOEXIT // postvalidación
ENDIF // ha fallado
ENDDO
oGet:killFocus() // Devolver el foco de entrada
ENDIF
RETURN

Dentro del lector de Get definido por el usuario NoDieresis() , las teclas se leen usando Inkey(). Lo que se ingresa
mediante el teclado se prueba para ver si incluye uno de los caracteres del alemán Ä, Ö, Ü, ä, ö, ü y ß. Si uno de
estos caracteres está presente se lo traduce a los caracteres equivalentes Ae, Oe, Ue, ae, oe, ue, o ss. Todos los
demás caracteres se pasan junto con el objeto Get a la función GetApplykey() que define el comportamiento
predeterminado para la transferencia de caracteres al buffer de edición de los objetos Get.

Con el fin de modificar este lector Get para usar la función AppEvent() en la lectura de eventos (en vez de sólo
presionar las teclas), deben declararse variables adicionales para los parámetros de mensaje pasados por refe-
rencia a AppEvent(). En vez de GetApplykey(), la función GetHandleEvent() debe utilizarse para la manipulación
por predeterminación de otros eventos.
Despliegue de tablas
Junto con los datos de entrada formateados usando @...SAY...GET con READ o empleando los objetos get con
ReadModal(), el despliegue de una tabla completa es una característica de interfaz importante y frecuentemente
usada. Una tabla permite que el usuario visualice interactivamente grandes volúmenes de datos. Los datos en una
tabla están organizados en filas y columnas. Estos datos pueden pertenecer a un archivo de una base de datos
abierta en un área de trabajo o a partir de un arreglo almacenado en la memoria. La clase TBrowse y la clase
TBColumn se incluyen en Xbase++ para permitir que las tablas se desplieguen. Estas clases se utilizan juntas pero
cada una lleva a cabo un conjunto específico de tareas cuando se despliegan las tablas. El despliegue de tablas es
posible sólo bajo los esfuerzos combinados de los objetos de ambas clases. Un objeto TBrowse lleva a cabo el
despliegue de pantalla y controla las filas y columnas actuales de una tabla. Un objeto TBColumn provee los datos
para una columna de la tabla desplegada por el objeto Tbrowse. Los Objetos TBColumn deben asignarse al objeto
TBrowse y los utiliza el objeto TBrowse cuando un usuario visualiza una tabla.

Las clases TBrowse y TBColumn están asociadas con un conjunto de funciones que existen en Xbase++ sólo para
proveer las funciones de compatibilidad con Clipper. El código fuente para las funciones de compatibilidad que se
listan en la siguiente tabla están contenidas en los archivos DBEDIT.PRG, BROWSYS.PRG y BROWUTIL.PRG.

Funciones para el despliegue de tablas y archivos de las bases de datos


Función Descripción
Funciones de Clase:

TBrowse() Devuelve un objeto de la clase TBrowse


TBColumn() Devuelve un objeto de la clase TBcolumn

Compatibilidad:

Browse() Hojea un archivo de base de datos, incluyendo DELETE y APPEND


DbEdit() Hojea un archivo de base de datos con control via UDF
(UDF - User Defined Function)
TBrowseNew() Crea un objeto TBrowse
TBrowseDb() Crea un objeto TBrowse para un archivo de base de datos
TBColumnNew() Crea un objeto TBColumn

Los objetos TBrowse proveen un mecanismo versátil para el despliegue de los datos en la forma de tabla. Los
objetos TBrowse despliegan los datos tabularmente en una sección definida de la pantalla (la ventana browse).

Las tablas son a veces muy grandes para desplegarse enteramente en la pantalla. Los objetos TBrowse proveen
métodos que permiten que las tablas se visualicen por pantalla. La clase TBrowse está diseñada para que un
objeto de esta clase no sepa nada acerca del origen de los datos que despliega. Para exhibir los datos, un objeto
TBrowse se apoya en más de un objeto de la clase TBColumn con el fin de proveer los datos para las columnas
individuales en la tabla. Un objeto TBrowse despliega los datos provistos en la pantalla. Cada objeto TBColumn
trabaja con sólo una columna de la tabla asociada. El objeto TBrowse maneja su propio cursor de celda y des-
pliega los datos en la fila y columna actual de la tabla (la celda actual) en un color resaltado.

Un usuario puede posicionar el cursor de celda en la tabla usando las teclas del cursor. Un objeto TBrowse incluye
varios métodos que mueven el cursor. Ni bien el usuario trata de mover el cursor de la celda fuera de la ventana
Browse, el objeto TBrowse automáticamente sincroniza los datos visibles en la pantalla con los datos de la tabla
subyacente y desplaza los datos en la ventana browse.

Puesto que el origen de los datos de la tabla no es conocido por el objeto TBrowse, deben especificarse tres
funciones para ejecutar las tres operaciones básicas que pueden llevarse a cabo en una tabla: saltar al comienzo
de la tabla, saltar al final de la tabla y cambiar la fila actual dentro de la tabla. Estas operaciones son comparables
a los comandos de fila GO TOP, GO BOTTOM y SKIP. Estas funciones se proveen a los objetos TBrowse como
bloques de código que se ejecutan automáticamente dentro de los métodos específicos del objeto TBrowse.

La ventana browse en la que el objeto TBrowse despliega a los datos en forma tabular puede dividirse en tres
áreas: títulos de encabezamiento o columnas. líneas de datos que contienen los datos, y pie de página al final de
cada columna. Cada área así como cada columna individual puede delimitarse opcionalmente por una línea se-
paradora.
En contraste con los objetos TBrowse, los objetos TBColumn son objetos muy simples que contienen variables de
instancia pero que no tienen sus propios métodos. Los objetos TBColumn se necesitan para proveer los datos
para las tablas desplegadas usando a los objetos TBrowse y son inútiles sin un objeto TBrowse asociado. Un
objeto TBColumn contiene en su variable de instancia toda la información requerida para el despliegue de una
única columna de una tabla en la ventana browse del objeto TBrowse.
Las variables de instancia de TBColumn más importantes (:block ) contienen un bloque de código de datos que
provee los datos desde el origen de datos para una columna. Este bloque de código podría acceder a una variable
de campo en un área de trabajo, o en una columna en un arreglo. Los objetos TBColumn pueden controlar también
el color de los datos desplegados basados en su valor.

Si se le asignan nuevos valores a las variables de instancia de un objeto TBColumn después que el objeto
TBrowse ha ya exhibido los datos usando al objeto TBColumn, el método oTBrowse:configure() debe ejecutarse
para actualizar las columnas en la ventana browse (ver clase TBrowse).

Aplicaciones en modo gráfico (modo GUI)


Esta sección describe los conceptos que son importantes cuando se programa con los Componentes de Xbase y
se desarrollan aplicaciones GUI. Comienza con un vistazo de los Componentes de Xbase y de cómo los eventos
se procesan mediante bloques de código o métodos callback. Se detallan las clases XbpCrt() y XbpDialog() que
proveen objetos para el manejo de la ventana de aplicación de un programa de Xbase++. Las ventanas XbpCrt
son útiles para la migración de programas existentes de DOS a una interfaz gráfica de usuario y permitir la salida
basada en texto y gráfica simultáneamente. Estas ventanas se usan en aplicaciones híbridas que permiten la
transición de programas DOS existentes a aplicaciones GUI en una serie de pasos. Sólo la salida gráfica puede
producirse en ventanas XbpDialog que se usan entonces sólo en aplicaciones GUI.

Las bases de los Componentes de Xbase++


La programación de interfaces gráficas de usuario para las aplicaciones se lleva a cabo fácilmente usando el
modelo de objeto de Xbase++ y los recursos del sistema disponibles en el nivel de lenguaje de Xbase. A través de
sus "Componentes de Xbase" (XBPs - Xbase Parts), Xbase++ ofrece distintas formas al programador que aún
piensa en los términos procedurales para crear programas con las interfaces gráficas de usuario. Los Compo-
nentes de Xbase proveen elementos gráficos de diálogo, como botones y cuadros de confirmación (checkbox),
que pueden integrarse en aplicaciones basadas en caracter así como en aplicaciones puramente gráficas. Todos
los Componentes de Xbase se basan en los recursos del sistema operativo y caben en el diseño orientado a
evento del sistema operativo. Con el fin de usar los Componentes de Xbase el programa debe enlazarse para el
modo GUI. Todos los Componentes de Xbase usan los mismos mecanismos básicos que se describen en esta
sección.

Qué son los Componentes de Xbase?


Los XBPs son objetos que proveen los mecanismos complejos del sistema operativo en el nivel de lenguaje de
Xbase++. Puede hacerse una distinción entre los Componentes de Xbase diseñados para la interacción de la
pantalla con el usuario y aquellos que únicamente se usan para la salida gráfica o que no pueden visualizarse. El
segundo grupo de los XBPs está descripto en el capítulo El Motor Gráfico de Xbase++ (GRA) y no se discute en
este capítulo. Los XBPs para la interacción con el usuario proveen cada uno un único elemento de diálogo como
componente gráfico. Por ésta razón, los XBPs pueden usarse solamente en un programa que está enlazado para
el modo GUI. En Xbase++, hay Componentes de Xbase para las ventanas, botones, checkboxes, campos de
datos de entrada, etc, así como diálogos standard para aquellas tareas como la selección de fuentes o de ar-
chivos.

El Ciclo de vida de un Componente Xbase


Todos los XBPs están sujetos a lo que se llama el "ciclo de vida", que los distingue de otros objetos como los
objetos TBrowse o los objetos Thread. Todos los objetos incluyendo los objetos creados desde sus clases de
objetos usando al método :new() . Sin embargo, si el objeto es un objeto COMPONENTE DE XBASE aun no es
capaz de hacer nada excepto requerir los recursos del sistema al sistema operativo. Esto se concreta usando el
método :create() . Un XBP es operacional sólo después de la ejecución del método :create() . El siguiente código
del programa es Un ejemplo de este proceso para un botón:

// crear objeto para XBP y especifica las coordenadas


oBoton := XbpPushButton():new( , , {10,20}, {80,30} )

// define las variables para configurar los recursos del sistema: en este
// caso el texto que será mostrado en el botón.
oBoton:caption := " Borrar "

// requerir recursos del sistema


oBoton:create()

Estas líneas muestran el proceso básico para la creación de un COMPONENTE DE XBASE operacional. El objeto
se crea primero usando el método de clase :new() . Luego, se asignan los valores a las variables de instancia
usadas para configurar los recursos del sistema, y finalmente se emplea :create() para solicitar recursos del sis-
tema a partir del sistema operativo (en este caso, un botón). Es importante distinguir entre :new() y :create() .
Ambos métodos deben llamarse para generar un Componente de Xbase operacional. El método :new() genera al
objeto y el método :create() solicita los recursos del sistema. La mayoría de los COMPONENTES DE XBASE usan
las variables de instancia solamente para configurar el recurso del sistema. La asignación de valores a las varia-
bles de instancia usadas para la configuración debe ocurrir antes de que se llame al método :create() .
El recurso del sistema puede reconfigurarse después de que un XBP se crea y ha solicitado el recurso al sistema
a partir del sistema operativo. Si se asignan nuevos valores a las variables de instancia usadas para la configu-
ración del recurso del sistema, el método :configure() debe llamarse para implementar los cambios. A continuación
se da un ejemplo:

// reconfigurar un recurso cargado del sistema.


oBoton:caption := " Recuperar "
oBoton:configure()

El texto (o caption) desplegado en el botón es parte de los recursos de sistema del botón. Con el fin de modificarlo,
el recurso del sistema debe reconfigurarse. El método DE LOS COMPONENTES DE XBASE :configure() se usa
para concretar esto (y es uno de los métodos del "ciclo de vida" de un XPB).
El último método en el "ciclo de vida" de un XPB es :destroy() . Este método libera los recursos del sistema que se
solicitaron desde el sistema operativo mediante el método :create() . Esto vuelve al XPB no operacional pero
permanece existente y puede solicitar recursos del sistema nuevamente. En otras palabras, el método :destroy()
carece de influencia sobre el objeto creado usando :new() , pero libera a los recursos del sistema solicitados
usando :create() . El siguiente diagrama ilustra el "ciclo de vida" de un Componente de Xbase y clarifica cuando se
invoca a cada uno de estos métodos:

El método :destroy() generalmente no es necesario. Sin embargo, puede usarse para liberar recursos del sistema
de memoria intensiva explícitamente (por ejemplo, bitmaps). Los recursos del sistema se liberan implícitamente ni
bien se acaban las referencias al objeto de la Parte de Xbase. En este caso, se manipula una Parte de Xbase como
cualquier otra variable de memoria y se la remueve de la memoria por el colector de basura.

Importante: Mientras se llama al método :create() , un componente de Xbase es incapaz de ejecutar cualquier
otro método (hay algunas raras excepciones que están destacadas explícitamente). Inmediatamente después de
la llamada a :new() es únicamente posible asignar valores a las variables de instancia que se usan para configurar
a los recursos del sistema. Sólo después que el recurso del sistema ha sido recuperado desde el sistema operativo
usando :create() un XPB es completamente funcional. Luego éste puede ejecutar todos los métodos disponibles
excepto por :create() . Este método puede llamarse otra vez solamente después de la llamada al método :destroy()
que libera a los recursos del sistema.

Componentes Xbase y eventos


Los XBPs se insertan sin sobresaltos en la cola de mensajes usada para controlar una aplicación GUI. Los
mensajes identifican a los eventos que han tomado lugar. Las entradas a partir del teclado y los clics del ratón son
ejemplos de los eventos que recibirá un programa. Los eventos como éstos se leen usando la función AppEvent(),
que devuelve un código de evento numérico (ver Teclado y ratón en la sección "Aplicaciones en modo caracter" de
este capítulo). La función AppEvent() juega un rol central en el control de los Componentes de Xbase que proveen
elementos gráficos de diálogo. El siguiente código del programa muestra la relación básica:

#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento, mp1, mp2, oXbp

// crear primer botón


oXbp:= XbpPushButton():new()
oXbp:caption := "A"
oXbp:create( , , {10,20}, {100,40} )
oXbp:activate := {|| QOut( "Botón A" ) }

// crear segundo botón


oXbp := XbpPushButton():new()
oXbp:caption := "B"
oXbp:create( , , {150,20}, {100,40} )
oXbp:activate := {|| QOut( "Botón B" ) }

// Ciclo de lectura de Eventos


nEvento := 0
DO WHILE nEvento <> xbeP_Close
nEvento := AppEvent( @mp1, @mp2, @oXbp )
oXbp:HandleEvent( nEvento, mp1, mp2 )
ENDDO
RETURN

En este ejemplo, se crean dos botones y el control completo del programa cae sobre un único ciclo DO WHILE (el
ciclo de eventos). En este ciclo de eventos, los mismos se leen desde la cola de eventos usando AppEvent(). El
tercer parámetro pasado por referencia a la función se le asigna una referencia al Destino para el evento. El
Destino podría ser la ventana o uno de los botones. El evento se procesa en el método :handleEvent() de este
objeto Destino. Cada botón tiene un bloque de código "activate" que se ejecuta automáticamente dentro del mé-
todo :handleEvent() cuando un botón se clickea con el ratón. Dentro de estos bloques de código se exhibe un
caracter en la pantalla usando QOut(). La siguiente ilustración muestra cómo puede lucir la salida por pantalla si
cada uno de los dos botones fue cliqueado varias veces:

Salida del programa de ejemplo

Un punto importante que se muestra en el programa de ejemplo es que la función AppEvent() en el ciclo de
eventos no sólo lee al evento desde la cola de eventos sino que también determina el Destino para el evento. El
Destino es siempre un Componente de Xbase y cada XPB incluye al método I:handleEvent()!EF. Tres parámetros
se pasan siempre por referencia al método :handleEvent() : el código numérico de evento y dos parámetros de
mensaje. Cuando se procesa al mensaje dentro del método :handleEvent() , se ejecuta el bloque de código al-
macenado en la variable de instancia :activate del botón. Este bloque de códigos contiene la reacción del XPB al
evento específico. Cuando se hace clic con el botón izquierdo del ratón sobre un botón, se genera el evento
xbeP_Activate y el código numérico de eventos (correspondiente a una constante #define) se devuelve a AppE-
vent(). Cuando éste código numérico de eventos se pasa a :handleEvent() , el método ejecuta al bloque de código
contenido en la variable de instancia :activate .

Para cada XBP hay muchas variables de instancia que pueden contener los bloques de código que se ejecutan en
respuesta a los eventos específicos. El evento se lee primero desde la cola de eventos usando AppEvent(). Luego
la reacción ocurre cuando el bloque de código provisto para el evento se ejecuta. Esta secuencia de eventos se
llama una aproximación de "callback". Por ésta razón todas las variables de instancia que contienen los bloques de
código definiendo las reacciones de los eventos se los refiere como "conectores callback". Un conector callback
contiene tanto el valor NIL o un bloque de código que se ejecutará por el método :handleEvent() cuando se pro-
duce el método específico. La siguiente tabla lista a las constantes #define de los eventos predefinidos para la
interacción con el usuario y los conectores callback correspondientes a disposición del usuario:

Eventos predeterminados que se procesados por los Componentes de Xbase


Código de Evento Conector Callback Descripción
Eventos del Mouse
xbeM_LbClick :LbClick Click con Botón Izquierdo
xbeM_LbDblClick :LbDblClick Doble click con Botón Izquierdo
xbeM_LbDown :LbDown Botón Izquierdo presionado
xbeM_LbUp :LbUp Botón Izquierdo liberado
xbeM_MbClick :MbClick Click con botón medio
xbeM_MbDblClick :MbDblClick Doble click con botón medio
xbeM_MbDown :MbDown Botón medio presionado
xbeM_MbUp :MbUp Botón medio liberado
xbeM_Motion :Motion Mouse movido
xbeM_RbClick :RbClick Click Botón Derecho
xbeM_RbDblClick :RbDblClick Doble click Botón Derecho
xbeM_RbDown :RbDown Botón Derecho presionado
xbeM_RbUp :RbUp Botón Derecho liberado

Otros eventos
xbeP_Keyboard :Keyboard Ocurrió una entrada de teclado
xbeP_HelpRequest :HelpRequest Se ha pedido Ayuda
xbeP_SetInputFocus :SetInputFocus Foco de Entrada garantizado
xbeP_KillInputFocus :KillInputFocus Foco de Entrada perdido
xbeP_Move :Move XBP movido
xbeP_Paint :Paint XBP redibujado
xbeP_Quit :Quit Aplicación terminada
xbeP_Resize :Resize Tamaño de XBP cambiado

Los conectores callback en ésta tabla se incluyen en todos los XBPs y permiten la interacción del usuario mediante
el procesamiento de eventos. Muchos XBPs tienen conectores callback adicionales que ocurren sólo con XBPs
específicos. Por ejemplo, los botones tienen el conector callback :activate , que puede contener un bloque de
código para ejecutar en respuesta al evento xbeP_Activate. Este conector callback no está presente en otros
XBPs.

En resumen, el programa de ejemplo antedicho muestra el proceso básico para la programación usando los
procesos básicos para la programación de los Componentes de Xbase. Un XBP se crea y recibe el "conocimiento"
de como debería reaccionar ante los eventos a través de los bloques de código que se le asignan a los conectores
callbacks. El método :handleEvent() se controla en el ciclo de eventos en el que se leen los eventos usando
AppEvent(). Esta función también determina qué Componente de Xbase debe procesar cada evento. Al final, la
aplicación es controlada en su totalidad por el usuario que genera los eventos.

Métodos Callback para procesar eventos


La sección precedente presenta las aproximaciones básicas al procesamiento de eventos con XBPs y describe el
modelo simple en el cual la reacción a un evento de define por un bloque de código asignado a uno de los co-
nectores callback predefinidos. Xbase++ ofrece también un segundo modelo. Si bien éste modelo es más com-
plejo, tiene capacidades más amplias para el procesamiento de eventos. Esta aproximación utiliza métodos de
callback. Junto con cada método de conectores callback, los Componentes de Xbase contienen un método de
callback del mismo nombre que se ejecuta en vez del bloque de código contenido en el conector callback. Así
como el bloque de código del callback, el método de callback correspondiente se ejecuta también por el méto-
do :handleEvent() para cada evento que recibe. Los métodos de callback dentro de los XBPs predefinidos no
ejecutan código alguno. Están disponibles para XBPs definidos por el usuario donde la reacción a un evento se
programa como un método en vez de como un bloque de código. El siguiente programa de ejemplo demuestra
esta aproximación. Su efecto es el mismo que el ejemplo en la sección previa y despliega los mismos resultados
en la pantalla:
#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento, mp1, mp2, oXbp
SetColor("N/W")
CLS

// crear botones definidos por el usuario


MiBoton():new( "A", { 10,20}, {100,40} ):create()
MiBoton():new( "B", {150,20}, {100,40} ):create()

// Ciclo de Eventos
nEvento := 0
DO WHILE nEvento <> xbeP_Close
nEvento := AppEvent( @mp1, @mp2, @oXbp )
oXbp:HandleEvent( nEvento, mp1, mp2 )
ENDDO
RETURN

// boton definido por el usuario


CLASS MiBoton FROM XbpPushbutton
EXPORTED:
METHOD init, activate
ENDCLASS

// inicializa la superclase y self


METHOD MiBoton:init( cCaption, aPos, aSize )
::XbpPushButton:init(,, aPos, aSize )
::caption := cCaption
RETURN self

// método callback para el evento xbeP_Activate


METHOD MiBoton:activate
QOut( "Botón ", ::caption )
RETURN self

Este programa difiere del ejemplo en la sección previa de dos maneras. El procedimiento Main se abrevia y se
instancia la clase MiBoton en vez de la clase XbpPushButton. El método :activate() ha sido redefinido en la clase
MiBoton definida por el usuario para producir una salida usando QOut(). En el ejemplo previo, la salida fue pro-
gramada dentro del bloque de código del callback. El rol de estos últimos en el ejemplo previo es reemplazado por
el método callback :activate() en este ejemplo. El método :activate() se ejecuta entonces cuando se clickea el
botón izquierdo del ratón. Sobre cualquiera de los botones. Los métodos callback se usan para procesar los
eventos en los Componentes de Xbase definidos por el usuario que se heredan a partir de XPB existentes. Ge-
neralmente reemplazan al código en bloques de código del callback y requieren que la clase nueva se derive
desde un Componente de Xbase existente. El método callback de la nueva clase contiene el código del programa
que se ejecuta cuando se produce un evento específico. Cuando se define un método callback, un bloque de
código no debería asignarse en general al conector callback del mismo nombre, puesto que sería ejecutado
después del método callback.

Variables de Instancia y Métodos existentes en todos los Componentes Xbase


Todos los Componentes de Xbase, inclusive aquellos que no procesan eventos o tienen una representación visual,
incluyen la variable de instancia :cargo . Esta variable de instancia no es empleada por cualquier Componente de
Xbase pero permite que los datos definidos por el usuario estén contenidos en el XBP sin requerir que la clase
nueva sea creada.

Los XBPS también incluyen al método :status() , que devuelve la condición actual de un XPB en su ciclo de vida.
Este método devuelve un valor numérico que corresponde a la constante #define a partir de un archivo XBP.CH.
Los valores posibles de retorno se listan en la siguiente tabla:

Constantes para el valor de retorno de :status()


Constante Descripción
XBP_STAT_INIT Componente Xbase inicializado
XBP_STAT_CREATE Petición exitosa de recursos de Sistema
XBP_STAT_FAILURE No pudieron proveerse los recursos de Sistema
El método :status() es principalmente útil para propósitos de debugging. Puede llamarse después del méto-
do :create() para determinar si los recursos del sistema fueron provistos para un Componente de Xbase.

Ventanas y relaciones
Toda la salida en una aplicación GUI se produce en una ventana en la pantalla. El significado general del término
"ventana" es un área rectangular de la pantalla que contiene un despliegue gráfico. La ventana de aplicación vi-
sible en la pantalla consiste de un marco y un número de ventanas adicionales. Todos los Componentes de Xbase
que tienen una representación visible son ventanas en el sentido de ésta definición más amplia.

El programa mismo se ejecuta en una ventana de aplicación que limita al área que usa una aplicación en la
pantalla. La ventana de aplicación contiene elementos de diálogo (Componentes de Xbase) que también repre-
sentan ventanas. Cada aplicación GUI está constituida de varias ventanas. Existe una relación jerárquica entre
éstas ventanas y se la describe usando los términos "padre" e "hijo". Una ventana padre provee el área de des-
pliegue para las ventanas hijas y las ventanas hijas están contenidas en la ventana padre.

La relación padre-hijo entre ventanas representa una relación física y es la relación más importante en la pro-
gramación usando los Componentes de Xbase. Volviendo al ejemplo de la sección previa, los botones son hijos de
la ventana de aplicación y están contenidos en ella. Vista de otra manera, la ventana de aplicación es el padre de
los dos botones y contiene los Componentes de Xbase. Esta relación es muy significativa en el procesamiento de
los eventos del teclado. Cuando un hijo COMPONENTE DE XBASE recibe una tecla que no puede procesar o no
entiende, el evento se envía al padre para su procesamiento. Una modificación del ejemplo en la sección previa
ilustra tal relación:

#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento, mp1, mp2, oXbp
SetColor("N/W")
CLS
oXbp := SetAppWindow() // Obtener ventana de la aplicación

// mostrar el caracter correspondiente a la tecla presionada


oXbp:KeyBoard := {|mp1| QQOut( Chr(mp1) ) }

// crear botones definidos por el usuario


MiBoton():new( "A", { 10,20}, {100,40} ):create()
MiBoton():new( "B", {150,20}, {100,40} ):create()

// Ciclo de lectura de Eventos


nEvento := 0
DO WHILE nEvento <> xbeP_Close
nEvento := AppEvent( @mp1, @mp2, @oXbp )
oXbp:HandleEvent( nEvento, mp1, mp2 )
ENDDO
RETURN
En este ejemplo se determina la ventana de aplicación
usando la función SetAppWindow(). A la ventana de
aplicación se le asigna al bloque de código del callback
para el evento xbeP_Keyboard. Cualquier tecla que no
pueda ser procesada por los botones es exhibida como
caracteres en la ventana. El despliegue en la pantalla
podría aparecer así:

Despliegue del programa de ejemplo

El botón "B" tiene foco de entrada. Un botón procesa la


barra espaciadora por sí solo y es activado por esta
tecla. En el ejemplo la cadena "Entrada vía teclado" se
ingresó y sólo los espacios en blanco fueron recono-
cidos por el botón. El bloque de código del callback
asignado a la variable de instancia :keyBoard en la
ventana de aplicación usa QQOut() para desplegar el
caracter del teclado que se presionó.
La Lista de Hijos
Las relaciones entre ventanas o Componentes de Xbase respectivamente, son manejadas por la clase
XbpPartHandler. Es la clase raíz para todos los XBPs que tienen una representación visual. Cuando un XBP
ejecuta al método :create() , el objeto se añade automáticamente a un arreglo interno de su ventana padre: una
lista de hijos. La lista de hijos referencia a todos los XBPs contenidos en una ventana (Nota: Se devuelve un
arreglo de lista de hijos mediante el método :childList() ). Por tanto, en un programa es suficiente asignar la re-
ferencia a una ventana de aplicación a una variable con el fin de poder acceder a todos los COMPONENTES DE
XBASE contenidos. Estos se agregan al arreglo de lista de hijos en el mismo orden en que ejecutan al méto-
do :create() . Si se conoce el orden de creación un XBP puede recuperarse usando un índice numérico. Por
ejemplo:

oBoton := oXbp:childList()[2]
? oBoton:caption // resultado: B

En el programa de ejemplo, el botón "B" se crea en segundo término. Se lo referencia en el segundo elemento del
arreglo de lista de hijos de la ventana de aplicación.

Superponiendo XBPs
Por un lado, el orden de la creación de los XBPs determina su posición en la ventana padre de la lista de hijos. El
XBP que ejecuta su método :create() se despliega primero, también. El que se crea al último se despliega último.
Por tanto, aparece al frente de los otros COMPONENTES DE XBASE. Este aspecto se vuelve importante cuando
los COMPONENTES DE XBASE tienen las mismas posiciones en una ventana o se superponen entre sí. Este es
el caso de cuando se agrupan los XBPs visualmente con un marco que los rodea. Por ejemplo, si una única línea
de campos de entrada (XbpSLE) ha de agruparse con un marco estático (XbpStatic), el marco ha de crearse
primero y luego los campos de entrada han de posicionarse dentro del marco.

Información adicional sobre las relaciones entre las ventanas puede encontrarse en la documentación de refe-
rencia para esta clase. En este punto deberán describirse primero los dos Componentes de Xbase que deben
usarse como el primer o padre mayor en la jerarquía padre-hijo de una aplicación GUI. Estas proveen a la ventana
de aplicación. La ventana de aplicación maneja el área en pantalla en la cual se produce la salida completa de una
aplicación PM y en la cual todos los otros Componentes de Xbase se despliegan (una excepción la constituye las
aplicaciones MDI que pueden tener varias ventanas de aplicación). La ventana de aplicación para una aplicación
GUI se crea en la función AppSys() cuando se inicia el programa de Xbase++. El código fuente para AppSys() está
contenido en el archivo ..\SOURCE\SYS\APPSYS.PRG. Una ventana de la clase XbpCrt se genera por prede-
terminación y permite las salidas basadas en texto y gráfico. Sin embargo, la función AppSys() puede modificarse
para que se provea una ventana de aplicación como un objeto de la clase XbpDialog. En este caso, sólo es posible
la salida gráfica.

XbpCrt() - La ventana para el modo híbrido


La ventana de aplicación utilizada por una aplicación GUI es una ventana XbpCrt. Este tipo de ventana es híbrida
entre el modo de texto (modo VIO) y el modo de gráficos. Fue creada para Xbase++ con el fin de permitir una
migración sin sobresaltos desde las aplicaciones de DOS (programas de Clipper) a un sistema operativo de 32 bit
con interfaz gráfica de usuario. Dentro de una ventana XbpCrt, los datos puede desplegarse usando funciones
orientadas a texto puro como DispOut() o QOut() así como las salidas gráficas puras usando funciones del motor
de GRA. Una ventana XbpCrt combina las capacidades del modo de texto de una aplicación VIO con las capa-
cidades a modo de gráfico de una aplicación GUI. Esta forma combinada se la denomina "modo híbrido", y permite
la migración paso a paso de aplicaciones de modo de texto de DOS a aplicaciones GUI de 32bits.

Una ventana XbpCrt permite salidas orientadas a gráfico y texto. Además, los Componentes de Xbase pueden
desplegarse en ventanas XbpCrt así los programas desarrollados originalmente bajo DOS pueden migrarse paso
a paso a una aplicación GUI. Las adaptaciones especiales para el procesamiento de eventos no se requieren
puesto que una ventana XbpCrt lleva a cabo estas tareas independientemente. Por ejemplo, considere un pro-
grama simple creado usando una aproximación procedural que implementa la entrada de datos usando una serie
de objetos Get. El registro actual se modifica durante el ingreso vía las teclas de función F6 y F7:
#include "Inkey.ch"

PROCEDURE Main

SetColor( "N/W,W+/N" )
CLS
@ MaxRow()-1, 2 SAY "<F6>-Próximo <F7>-Previo"

SetKey( K_F6, {|| DbSkip( 1), RefrescaListaGet() } )


SetKey( K_F7, {|| DbSkip(-1), RefrescaListaGet() } )
USE CLIENTE EXCLUSIVE

DO WHILE LastKey() <> K_ESC


@ 3,1 SAY " Nombre:" GET CLIENTE->NOMBRE
@ 5,1 SAY "Apellido:" GET CLIENTE->APELLIDO
READ
ENDDO
RETURN

PROCEDURE RefrescaListaGet
AEval( GetList, {|oGet| oGet:reset() , ;
oGet:display() } )
RETURN
Se implementa la lógica del programa para cambiar los registros vía los bloques de código que están asociados
con las teclas de función usando SetKey(). Las teclas de función que están enlazadas a bloques de código son
ideales candidatos para el reemplazo con botones en una aplicación de modo híbrido. Esto ayudará a la aplicación
modo de texto un aspecto más GUI. Los botones se agregan con facilidad a la ventana XbpCrt. Los dos botones
necesitan crearse en el procedimiento Main y reemplazan a la función SetKey():

#include "Inkey.ch"

PROCEDURE Main
LOCAL oXbp
SetColor( "N/W,W+/N" )
CLS

SetMouse(.T.)
oXbp:= XbpPushButton():new()
oXbp:caption := "Próximo"
oXbp:activate:= {|| DbSkip( 1), RefrescaListaGet() }
oXbp:create( ,, {10,10}, {90,30})

oXbp:= XbpPushButton():new()
oXbp:caption := "Previo"
oXbp:activate:= {|| DbSkip(-1), RefrescaListaGet() }
oXbp:create( ,, {110,10}, {90,30})

USE CLIENTE EXCLUSIVE

DO WHILE LastKey() <> K_ESC


@ 3,1 SAY " Nombre:" GET CLIENTE->NOMBRE
@ 5,1 SAY "Apellido:" GET CLIENTE->APELLIDO
READ
ENDDO
RETURN

El resultado de este cambio en el programa de ejemplo se muestra en la próxima ilustración:

Migración desde el modo de texto a una aplicación híbrida


Hay un par de puntos importantes en estos dos ejemplos. Primero, solamente el control del movimiento de la base
de datos se manipula por los objetos de la clase XbpPushButton(). La lógica actual de la pantalla de entrada de
datos no se modifica y el programa parece ser aún procedural. Es ahora, de alguna manera, más dirigido por
eventos, pero la ventana XpbCrt únicamente asume la tarea de manipular los eventos. La llamada a la función
SetMouse(.T.) al comienzo del segundo ejemplo provoca que los campos de entrada de Get reaccionen a los
eventos del ratón. Esto significa que dentro del sistema Get los eventos se recuperan usando AppEvent() en vez
de la función de compatibilidad Inkey(). Este proceso ofrece una manera muy fácil de migrar aplicaciones exis-
tentes de DOS a un entorno GUI paso a paso. Por ejemplo, todos los comandos @...PROMPT / MENU TO para el
control del programa se migran con facilidad a un sistema de menú con un menú principal apareciendo como barra
del menú arriba de la ventana XbpCrt.

Este programa modificado muestra otra característica de una ventana XbpCrt: es posible emplear las coordenadas
de fila y de columna a modo de texto con un origen en la esquina superior de la ventana así como las coordenadas
gráficas xy con origen en la parte inferior izquierda (los botones están ubicados en la ventana basada en las co-
ordenadas xy). Dentro de una ventana XbpCrt, las funciones de diálogo como AChoice() o MemoEdit() pueden
usarse y lo que se despliega puede hacerlo después de la salida gráfica como los Componentes de Xbase, bit-
maps o un gráfico de barras. La ventana XbpCrt soporta también el sistema de colores del modo de texto junto con
el modo de colores del modo gráficos lo que significa que los colores pueden establecerse usando SetColor() o
GraSetColor().

Puesto que una ventana XbpCrt permite también la salida orientada a texto: su tamaño máximo se limita a los
valores de función de MaxRow() y MaxCol(). El tamaño de la ventana predeterminado es 25 líneas de texto y 80
columnas correspondientes al tamaño de la pantalla de texto. Sobre la plataforma OS/2, el tamaño de la pantalla
puede reducirse usando el ratón, pero no puede agrandarse más que MaxRow() y MaxCol(). Esto no es posible en
las plataformas de Windows. Sin embargo, el tamaño de una ventana en modo de texto puede establecerse
cuando se crea una ventana XbpCrt en cualquier número de filas y columnas deseadas.

En síntesis, todos los comandos y funciones en el alcance del lenguaje de Clipper y la funcionalidad adicional de
Xbase++ y los Componentes de Xbase puede usarse en una ventana XbpCrt. Se crea una ventana XbpCrt para el
propósito exclusivo de permitir una manera fácil de migrar aplicaciones existentes de DOS al entorno de GUI. Esto
tiene la ventaja de permitir la migración sin sobresaltos de aplicaciones existentes a modo de texto a GUI. La
desventaja de esta aproximación es el requerimiento de memoria de una ventana XbpCrt opuesta a la ventana de
diálogo que no permite salida basada en texto. Se paga el requerimiento aumentado de memoria de una ventana
XbpCrt inevitablemente en forma de una pérdida de performance. Por esta razón se recomienda que el nuevo
desarrollo de aplicaciones bajo Xbase++ no use a la ventana XbpCrt. Esto se lleva a cabo al modificar la función
AppSys() en APPSYS.PRG para crear un objeto de la clase XbpDialog() como la ventana de aplicación.

XbpDialog() - La ventana para el modo GUI


La clase XbpDialog() provee una ventana de aplicación para el modo puro gráfico. La salida basada en texto no es
posible en estas ventanas de diálogo. Esto significa que todos los comandos de salida y las funciones que llevan a
cabo salida en pantalla basada en texto bajo Clipper no puede continuar usándose. La salida en pantalla se
produce en una ventana de diálogo usando sólo los Componentes de Xbase y las funciones del motor de GRA.
Los objetos XbpDialog se utilizan para programar aplicaciones puras de GUI que se optimizan para el sistema
operativo y emplean la mayoría de sus capacidades.

Antes de continuar con la explicación del objeto XbpDialog, vale la pena clarificar qué funciones y comandos no
están más disponibles. Los comandos y funciones listados en la siguiente tabla no pueden usarse si la ventana
actual es un objeto XbpDialog:

Comandos y funciones que no pueden usarse en las ventanas de diálogo


Comandos Funciones
? | ?? SET CURSOR AChoice() QOut() | QQOut()
@...BOX SET DELIMITERS Alert() Read...()
@...CLEAR SET INTENSITY Browse() RestScreen()
@...GET SET MESSAGE Col() Row()
@...PROMPT SET SCOREBOARD ColorSelect() SaveScreen()
@...SAY SET WRAP DbEdit() Scroll()
@...TO TEXT DevOut() SetBlink()
ACCEPT TYPE DevOutPict() SetColor()
CLEAR ALL WAIT DevPos() SetCursor()
CLEAR GETS DispBegin() SetMode()
CLEAR SCREEN DispCount() SetMouse()
DISPLAY DispEnd() SetPos()
INPUT DispOut() TBApplyKey()
LIST DispOutAt() TBColumn()
MENU TO Get() TBColumnNew()
READ Get...() TBHandleEvent()
RESTORE SCREEN GetNew() TBrowse()
SAVE SCREEN MaxCol() TBrowseDb()
SET COLOR MaxRow() TBrowseNew()
SET CONSOLE MemoEdit() TBtoMousePos()

Todos los comandos y funciones que afectan al cursor no se encuentran disponibles puesto que el cursor como es
empleado en el modo de texto no existe en el modo gráfico. Similarmente, los comandos y funciones que mani-
pulan la ventana en modo de texto (como por ejemplo SaveScreen(), DispBegin(), Scroll() y RESTORE SCREEN)
no puede usarse en una ventana de diálogo que opera en el modo gráfico. Los Componentes de Xbase del motor
de GRA se utilizan en lugar de la salida de estas funciones y comandos basados en texto.

Junto con estos comandos y funciones hay otros dos pequeños grupos de funciones. El primero representa las
funciones de compatibilidad que no deberían seguir usándose. El segundo grupo de funciones sólo operan si la
impresora ha sido establecida como el dispositivo de salida usando el comando SET DEVICE TO PRINTER. La
siguiente tabla da un vistazo:

Comandos y funciones que no deberían usarse en las ventanas de diálogo


Comando/Función Solamente usable con SET DEVICE TO PRINTER
SET KEY DevOut()
Inkey() DevOutPict()
SetKey() DevPos()
LastKey() PRow()
NextKey() PCol()
IsPrinter()

Para que se provea a la ventana de aplicación como una ventana de diálogo de gráficos, es necesario la creación
de un objeto XbpDialog en la función AppSys(). Esta función se ejecuta normalmente antes de la llamada del
procedimiento MAIN. El código fuente predeterminado para AppSys() aparece en el archivo APPSYS.PRG pero
puede incluirse en otro archivo si se lo enlaza al archivo ejecutable. Las siguientes líneas se toman a partir del
programa de ejemplo SDIDEMO.PRG (el ejemplo se reduce) y dan un ejemplo de las modificaciones concretadas
a AppSys():

********************************************************************
* Ejemplo para AppSys() en una aplicación SDI
* La función se ejecuta previo a Main()
********************************************************************
PROCEDURE AppSys
LOCAL oDlg, oXbp, aPos[2], aSize, nAltura:=400, nAnchura := 615
// Obtener tamaño de la ventana del escritorio (desktop)
// para centrar la ventana de la aplicación
aSize := SetAppWindow():currentSize()
aPos[1] := Int( (aSize[1]-nAnchura ) / 2 )
aPos[2] := Int( (aSize[2]-nAltura) / 2 )

// Crear ventana de aplicación


oDlg := XbpDialog():new()
oDlg:title := "Toys & Fun Inc. [Xbase++ - SDI Demo]"
oDlg:border:= XBPDLG_THINBORDER
oDlg:create( ,, aPos, {nAnchura, nAltura},, .F. )

// Establecer color de fondo para el área de dibujo


oDlg:drawingArea:SetColorBG( GRA_CLR_PALEGRAY )

// Seleccionar font
oDlg:drawingArea:SetFontCompoundName( "8.Helv.normal" )

// Mostrar ventana de la aplicación y transferirle el foco.


oDlg:show()
SetAppWindow( oDlg )
SetAppFocus ( oDlg )
RETURN
En este proceso, se devuelve una referencia a la ventana del escritorio mediante la función SetAppWindow() y se
la emplea para determinar su tamaño. Esto permite el centrado de la ventana de aplicación de Xbase++ en la
pantalla cuando se la abre, independientemente de la resolución de la misma. La ventana de diálogo creada en el
ejemplo tiene un tamaño fijo porque el tipo de recuadro XBPDLG_THINBORDER no permite al usuario modificar el
tamaño de la ventana. Esta es la variación más simple porque cambiar el tamaño de la ventana de aplicación
también involucra la reubicación o redimensionamiento de los elementos de diálogo (XBPs) contenidos en la
ventana.

Después que la ventana de diálogo ha sido creada se recomienda establecer el color de fondo para el área de
dibujo. Esto también establece el color de fondo para el texto desplegado como etiquetas para los XBPs como por
ejemplo XbpStatic, XbpRadioButton o XbpCheckBox. El método :setColorBG() devuelve el color usado para
desplegar todos los XBPs con etiquetas.

El área de dibujo de XbpDialog


El color de fondo se establece usando el método :setColorBG() . A éste método no lo ejecuta el objeto XbpDialog
en el ejemplo, pero sí un objeto contenido en la variable de instancia :drawingArea . Esto es porque los objetos
XbpDialog contienen objetos adicionales de la clase XbpIWindow. Los objetos de esta clase proveen ventanas
implícitas que no tienen una barra de título o marco, pero que manipulan áreas de pantalla rectangulares. Un
objeto XbpDialog consiste básicamente de un marco que limita el área de la pantalla que está disponible para la
aplicación. La barra del título de la ventana de diálogo como aparece en pantalla es una ventana tal como lo es el
área de dibujo dentro del diálogo. Ambas ventanas son ventanas implícitas del objeto XbpDialog. Son objetos de la
clase XbpIWindow y están contenidos en las variables de instancia :titleBarArea y :drawingArea . El área de dibujo
del objeto XbpDialog juega un rol importante cuando se programa con las ventanas de diálogo.

Importante: Cada elemento de diálogo (XBP) que se despliega en una ventana de diálogo debe tener
a :drawingArea como el padre en vez del objeto XbpDialog mismo. De otra manera, el borde de tamaño ajustable
de la ventana de diálogo se vuelve el padre y el XBP será visible solamente en este marco de ventana de 2-8 pixel
de ancho.

Por ejemplo, si se exhibe un botón en una ventana de diálogo debe ser un hijo del área de dibujo (:drawingArea )
del objeto XbpDialog. Esta es una diferencia entre cómo trabaja una ventana de diálogo y una ventana XbpCrt que
puede actuar como padre por sí misma. Un objeto XbpDialog es el padre de sólo las dos ventanas implícitas que
manejan el área de dibujo y la barra del título. Ambas ventanas son accesibles mediante las variables de instancia
del objeto XbpDialog. Un ejemplo del despliegue del botón en una ventana de diálogo se muestra en el siguiente
código de programa:

#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento, mp1, mp2, oXbp

// Crear boton
oXbp:= XbpPushButton():new( SetAppWindow():drawingArea )
oXbp:caption := "Cancelar"
oXbp:create( , , {10,20}, {100,40} )
oXbp:activate := {|| PostAppEvent( xbeP_Close) }

// Ciclo de lectura de Eventos


nEvento := 0
DO WHILE nEvento <> xbeP_Close
nEvento := AppEvent( @mp1, @mp2, @oXbp )
oXbp:HandleEvent( nEvento, mp1, mp2 )
ENDDO
RETURN

Aquí el padre para el botón se especifica en la llamada a un método. La función SetAppWindow() regresa al objeto
de diálogo XbpDialog creado en AppSys() y se pasa entonces al área de dibujo como referencia al método del
botón :new() . Si el área de dibujo no se especifica como el padre para los Componentes de Xbase, conduce
inevitablemente a un error en el despliegue o no se produce despliegue alguno dentro de la ventana de diálogo. Es
importante recordar que con los objetos XbpDialog el área de dibujo (oXbpDialog:drawingArea ) debe establecerse
como el padre, pero no el objeto de XbpDialog mismo.
Jerarquía de clases de los Componentes de Xbase++
Esta sección da un panorama de la jerarquía de clase de los Componentes de Xbase. La relación entre los dife-
rentes Componentes de Xbase se ilustra debajo así como cuales XBPs utilizan las capacidades de otra clase. Las
clases marcadas con "[A]" son abstractas y no pueden instanciarse por sí solas en el nivel de lenguaje de Xbase++.
Todos los Componentes de Xbase que tienen una representación visible se derivan de la clase XbpPartHandler.
La clase XbpPartHandler implementa la relación padre-hijo y define al método :handleEvent() .
DataRef() - La conexión entre XBP y DBE
En la jerarquía de clase de los Componentes de Xbase, nueve clases se derivan no sólo desde XbpPartHandler o
XbpWindow sino también desde la clase DataRef. La clase DataRef provee a los objetos que referencian datos.
Provee datos desde una variable de memoria o una variable de campo para su uso en un XBP. Todas los Com-
ponentes de Xbase que pueden modificar los datos tienen también DataRef como superclase. La clase DataRef
provee servicios para acceder a los datos. En el caso más simple, estos datos son un valor que existe en el buffer
de edición de un Componente de Xbase y se modifica allí. Los siguientes Componentes de Xbase usan un buffer
de edición para almacenar datos:
Componentes de Xbase derivados a partir de DataRef (XBPs con buffer de edición)
XBP Tipo de datos en el buffer de edición
XbpSLE Cadena de caracteres sin formato (Línea Simple)
XbpMLE Cadena de caracteres con formato (Múltiples Líneas)
Xbp3State Valor Numérico (0, 1, 2)
XbpCheckBox Valor Lógico
XbpRadioButton Valor Lógico
XbpSpinButton Valor Numérico (entre límites Min y Max)
XbpListBox Arreglo (Posición Numérica de las entradas seleccionadas)
XbpComboBox Arreglo y Cadena de caracteres (caso especial)
XbpScrollBar Valor Numérico (-2^15 a +2^15)

El acceso a los datos almacenados en el buffer de edición de un Componente de Xbase ocurre en el méto-
do :getData() que se implementa en la clase DataRef. :getData() devuelve el valor en el buffer de edición. En el
siguiente ejemplo se crea un campo de entrada y se despliegan luego los contenidos actuales del buffer de edición
cada vez que se presiona una tecla:
#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento, mp1, mp2, oXbp

CLS // Esta es una ventana XbpCrt

// Crear campo de entrada


oXbp:= XbpSLE():new()
oXbp:create( , , {50,50}, {100,30} )
oXbp:keyBoard := {|mp1, mp2, obj|;
IIf( mp1== xbeK_ESC, ; // Se presionó la tecla Esc
PostAppEvent( xbeP_Close), ; // terminar programa
QOut( obj:getData() ) ) } // mostrar buffer

// Ciclo de lectura de Eventos


nEvento := 0
DO WHILE nEvento <> xbeP_Close
nEvento := AppEvent( @mp1, @mp2, @oXbp )
oXbp:HandleEvent( nEvento, mp1, mp2 )
ENDDO
RETURN

En el ejemplo los contenidos del buffer de edición del objeto XbpSLE se leen en el bloque de código asignado al
conector callback :keyBoard . Este bloque de código es evaluado después de cada presión de tecla y recibe al
objeto XbpSLE como el tercer parámetro obj .

El ejemplo ilustra que el manejo y la modificación de los datos en un programa apenas requiere Componentes de
Xbase que incluyen un buffer de edición. El valor a editarse se almacena en éste buffer de edición. Generalmente
no es necesaria ninguna otra variable de memoria que referencia al valor.

La clase DataRef provee la variable de instancia :dataLink para enlazar un Componente de Xbase a un campo de
base de datos con el fin de desplegar o modificar variables de campo. Puede asignársele a esta variable de ins-
tancia un bloque de código que define cómo acceder a un campo de base de datos. Este bloque de código provee
la conexión entre un Componente de Xbase, una variable de campo y el DatabaseEngine. El siguiente ejemplo
emplea un objeto XbpSLE para acceder a un campo de base de datos:
#include "Appevent.ch"

PROCEDURE Main
LOCAL nEvento, mp1, mp2, oXbp, oSLE
FIELD APELLIDO

// abrir base de datos (para acceso exclusivo)


USE CLIENTE EXCLUSIVE

// crear campo de entrada


oSLE := XbpSLE():new()
oSLE:create( ,, {50,100}, {100,30} )

// campo de referencia a base de datos


oSLE:dataLink := {|x| IIf( x==NIL, APELLIDO, APELLIDO:=x ) }

// copiar campo de base de datos en un SLE


oSLE:setData()

// Botón para cancelar programa


oXbp := XbpPushButton():new()
oXbp:caption := "Cancela"
oXbp:create( , , {50,50}, {80,30} )
oXbp:activate := {|| PostAppEvent( xbeP_Close ) }

// Botón para ir al próximo registro


oXbp := XbpPushButton():new()
oXbp:caption := "Próximo"
oXbp:create( ,, {150,50}, {80,30} )
oXbp:activate := {|| oSLE:getData(), ; // graba datos
DbSkip( 1 ) , ; // salta un registro
oSLE:setData() } // lee nuevos datos

// Botón para ir al registro previo


oXbp := XbpPushButton():new()
oXbp:caption := "Previo"
oXbp:create( ,, {250,50}, {80,30} )
oXbp:activate := {|| oSLE:getData(), ; // graba datos
DbSkip( -1 ) , ; // retrocede un registro
oSLE:setData() } // lee nuevos datos

// Ciclo de lectura de Eventos


nEvento := 0
DO WHILE nEvento <> xbeP_Close
nEvento := AppEvent( @mp1, @mp2, @oXbp )
oXbp:HandleEvent( nEvento, mp1, mp2 )
ENDDO
RETURN

El ejemplo es un poco más complejo porque crea tres tipos de elementos de diálogo: un objeto XbpSLE que puede
editar un campo de base de datos, un botón para cancelar al programa y dos botones para llevar a cabo nave-
gación en la base de datos. Uno de los elementos más importantes en este ejemplo es el bloque de código de
asignado a la variable de instancia :dataLink . Este bloque de código define el acceso al campo APELLIDO de la
base de datos. Este bloque de código se ejecuta dentro de los métodos :setData() y :getData() que se imple-
mentan en la clase DataRef (la clase XbpSLE hereda también a partir de DataRef!). El método :setData() evalúa al
bloque de código sin pasarle el parámetro y asigna un valor de retorno del bloque de código al buffer de edición del
objeto XbpSLE. En otras palabras, el método :setData() lee el valor del campo APELLIDO de la base de datos y lo
copia al buffer de edición del campo de entrada. El método :getData() lleva a cabo la función inversa: lee el valor
desde el buffer de edición del objeto XbpSLE y pasa este valor al bloque de código en la variable de instan-
cia :dataLink . Se produce una asignación a la variable de campo dentro de este bloque de código. :getData() lee el
buffer de edición de un Componente de Xbase y lo escribe en el campo de la base de datos vía :dataLink .

En el ejemplo, se programan dos botones para proveer la navegación en la base de datos. En cualquier momento
dado pueden usarse para seleccionar el registro próximo o previo. Esto ocurre en los bloques de código asignados
a los conectores callback :activate de los botones. En ambos bloques de código se llama al método :getData()
antes que el puntero de registros se mueva y se llama a :setData() después de DbSkip(). Una de las características
especiales de una aplicación GUI orientada a eventos es que el usuario controla la ejecución del programa. En el
programa mismo no puede predecirse cuando un botón particular será presionado o cuando se llevará a cabo la
entrada de datos en el campo de entrada. Debido a esta incerteza, el valor actual en el buffer de edición debe
escribirse nuevamente a una base de datos antes que se mueva el puntero de registros. Y por el contrario, los
contenidos de un campo de la base de datos deben copiarse al buffer de edición inmediatamente después de que
se haya movido el puntero.

En síntesis, todos los Componentes de Xbase usan la funcionalidad de la clase DataRef por lo que pueden editar
los datos (todos aquellos que tienen un buffer de edición). Estos XBPs se derivan desde XbpWindow y las clases
DataRef. DataRef provee el mecanismo para la conexión entre XBP y las variables (tanto variables de campo o de
memoria). Para crear esta conexión, la clase DataRef usa el bloque de código :dataLink y los métodos :setData()
y :getData() . La clase DataRef también provee los métodos que llevan a cabo la validación de los datos (el
método :validate() ) o evitan cambios realizados al buffer de edición (el método :undo() ).

Creando aplicaciones GUI


Esta sección muestra cómo diseñar y programar aplicaciones GUI. Sirve tanto como guía para los programadores
novatos a una interfaz gráfica de usuario y como fuente de soluciones a algunos de los problemas que pueden
surgir cuando se programan aplicaciones GUI. La mayoría de los programas de ejemplo discutidos en esta sección
se proveen también en la instalación de Xbase++. Se incluyen sugerencias para la organización de aplicaciones
GUI y se discuten las respuestas a las preguntas como "¨Cómo se programa esto?" y "¨Dónde se implementa
esto?".

Las Tareas de AppSys()


La tarea principal de la función AppSys() es crear la ventana de aplicación. Puesto que AppSys() es un INIT
PROCEDURE implícito, se lo llama siempre antes que al procedimiento Main (Principal). El objeto de la ventana de
aplicación creado en AppSys() depende del tipo de aplicación. Podría ser una ventana XbpCrt o una ventana
XbpDialog. Para asegurarse la compatibilidad más amplia posible la rutina predeterminada AppSys() incluida en
Xbase++ crea una ventana XbpCrt. Cuando se desarrollan nuevas aplicaciones GUI usando Xbase++, AppSys()
debería generalmente modificarse para crear en cambio una ventana XbpDialog. Las tareas adicionales que
deben llevarse a cabo sólo una vez cuando se inicia la aplicación pueden incluirse en este procedimiento. Esto a
veces incluye crear el sistema de menú, la provisión de una rutina de ayuda y la inicialización de variables amplias
de sistema u otros recursos necesarios. Estas tareas pueden llevarse a cabo antes que la ventana de aplicación
sea visible, lo que permite que las partes esenciales de la aplicación estén disponibles cuando se llama al pro-
cedimiento Main.

La primer decisión en la implementación de AppSys() es si usar ventanas XbpCrt o XbpDialog. En el caso de una
aplicación GUI debe considerarse también el tipo de aplicación. El concepto "tipo de aplicación" designa al tipo de
interfaz de usuario que proveerá la aplicación. El caso más simple es una aplicación SDI (Interfaz de Documento
Unico - Single Document Interface) en la cual la aplicación consiste de una única ventana. La alternativa es una
aplicación MDI (Interfaz de Documento Múltiple - Multiple Document Interface). Una aplicación MDI se ejecuta en
ventanas múltiples y AppSys() simplemente crea la ventana principal permitiendo que las ventanas adicionales se
generen más tarde en el programa. El tamaño de una ventana de aplicación generalmente depende del tipo de
aplicación. El tamaño de ventana de una aplicación SDI puede ser más pequeña que aquella de una aplicación
MDI, puesto que ninguna ventana de aplicación adicional se necesita en una aplicación SDI. Además, puede
arreglarse el tamaño de la ventana de una aplicación SDI sin permitir que el usuario modifique el tamaño. El ta-
maño de una ventana de una aplicación MDI debe ser modificable por el usuario.

El siguiente ejemplo es un procedimiento AppSys() desde el archivo SDIDEMO.PRG que presenta un ejemplo
completo de una aplicación SDI. Las tareas varias que lleva a cabo AppSys() se demuestran en el siguiente
procedimiento:

PROCEDURE AppSys
LOCAL oDlg, oXbp, aPos[2], aSize, nAltura:=400, nAnchura := 615
// Obtener tamaño de la ventana del escritorio (desktop)
// para centrar la ventana de la aplicación
aSize := SetAppWindow():currentSize()
aPos[1] := Int( (aSize[1]-nAnchura ) / 2 )
aPos[2] := Int( (aSize[2]-nAltura) / 2 )

// Crear ventana de aplicación


oDlg := XbpDialog():new()
oDlg:title := "Toys & Fun Inc. [Xbase++ - SDI Demo]"
oDlg:border:= XBPDLG_THINBORDER
oDlg:create( ,, aPos, {nAnchura, nAltura},, .F. )

// Establecer color de fondo para el área de dibujo


oDlg:drawingArea:SetColorBG( GRA_CLR_PALEGRAY )

// Seleccionar font
oDlg:drawingArea:SetFontCompoundName( "8.Helv.normal" )

// Mostrar ventana de la aplicación y transferirle el foco.


oDlg:show()
SetAppWindow( oDlg )
SetAppFocus ( oDlg )

// Crear sistema de menú (UDF)


MenuCreate( oDlg:menuBar() )

// Proveer ayuda en línea via UDF


oXbp := XbpHelpLabel():new():create()
oXbp:helpObject := ;
HelpObject( "SDIDEMO.HLP", "Ayuda para demo de SDI" )
oDlg:helpLink := oXbp

// Mostrar ventana de aplicación y asignarle el foco


oDlg:show()
SetAppWindow( oDlg )
SetAppFocus ( oDlg )

RETURN

En este ejemplo se crea una ventana de diálogo con de tamaño 615 x 400 pixeles. Este tamaño permite que se
despliegue completamente inclusive en una pantalla de baja resolución. La ventana se provee para una aplicación
SDI y tienen un tamaño fijo (XBPDLG_THINBORDER). La primer llamada a SetAppWindow() provee una refe-
rencia al escritorio en el cual se despliega la ventana de aplicación. El método :currentSize() de este objeto provee
el tamaño de la ventana escritorio que corresponde a la resolución actual de la pantalla. Esta información se usa
para posicionar la ventana de aplicación cuando se llama a una aplicación de Xbase++. En el ejemplo, la ventana
de aplicación se despliega centrada en la pantalla.

Una vez que se establece el color de fondo para el área de dibujo de la ventana de diálogo, el sistema del menú se
genera en la función MenuCreate(). Esta función definida por el usuario (UDF) recibe el valor de retorno del mé-
todo :menuBar() como un argumento. El método :menuBar() crea un objeto XbpMenuBar y lo instala en la ventana
de aplicación. El sistema de menú debe construirse después en el UDF. Se recomienda esto porque la construc-
ción del sistema del menú puede llevarse a cabo antes que una ventana de aplicación sea visible. La mecánica de
la construcción de una sistema de menú se describe en la siguiente sección.

En este ejemplo AppSys(), el mecanismo para la ayuda en línea se implementa luego de la creación del sistema
del menú se crea en MenuCreate(). Esto incluye la generación de un objeto XbpHelpLabel que se le asigna a la
variable de instancia :helpLink . El objeto etiqueta de Ayuda referencia información de ayuda y activa la ventana de
la ayuda en línea. La ventana de ayuda en línea es manipulada por un objeto XbpHelp que ha de proveerse al
objeto XbpHelpLabel. Esto se logra al asignar un objeto XbpHelp a la variable de instancia :helpObject del objeto
XbpHelpLabel. El objeto XbpHelp maneja ventanas de ayuda en línea y debería existir una sola vez dentro de una
aplicación de Xbase++. Por esta razón, se la crea en la función definida por el usuario HelpObject() que se muestra
debajo:
********************************************************************
* Rutina para recuperar al objeto Ayuda. Maneja la ayuda en línea
********************************************************************
FUNCTION HelpObject( cArchivoAyuda, cTitulo )
STATIC soHelp
IF soHelp == NIL
soHelp := XbpHelp():new()
soHelp:resGeneralHelp := IPFID_HELP_GENERAL
soHelp:resKeysHelp := IPFID_HELP_KEYS
soHelp:create( SetAppWindow(), cArchivoAyuda, cTitulo )
ENDIF
RETURN soHelp
Un objeto XbpHelp se crea y almacena en una variable STATIC la primera vez que se llama a ésta función. El
objeto XbpHelp manipula la ventana de ayuda en línea de una aplicación de Xbase++ y es posible recuperar una
referencia a esto llamando a la función HelpObject() en cualquier tiempo en el programa. Esto permite que cual-
quier número de objetos XbpHelpLabel se creen, los cuales activan siempre el mismo objeto XbpHelp (o la misma
ayuda en línea).

La función HelpObject() necesita recibir al nombre del archivo para el archivo HLP y el título de la ventana para la
ayuda en línea. De lo contrario esta función es genérica. Usa también dos constantes #define que hacen refe-
rencia las dos ventanas de ayuda disponibles en cada aplicación. Estas constantes sólo pueden ser definidas por
el usuario y deben emplearse en el código fuente de la ayuda en línea como IDs numéricos con el fin de hacer
referencia a la ventana de ayuda específica.

El sistema de menú de una aplicación


El sistema de menú de una aplicación GUI juega un rol central para el control del programa. Este menú debe
crearse sólo una vez, generalmente dentro de la función AppSys() antes del primer despliegue de una ventana de
aplicación. Cuando se crea el sistema de menú dentro de AppSys() o antes del despliegue de la ventana de
aplicación, el usuario no ve la construcción del sistema de menú. El menú en la ventana de aplicación está com-
pleto cuando la ventana se despliega por primera vez.

El sistema de menú consiste de un objeto XbpMenuBar que manipula la barra de menú horizontal en la ventana de
aplicación y varios objetos XbpMenu que se insertan en la barra de menú como submenús. Hay varias maneras de
implementar el control del programa usando los menús. La forma más simple se muestra en el archivo
SDIMENU.PRG (que presenta una aplicación de ejemplo). Los pasos más importantes se muestran en el siguiente
código:

/* Llamada en AppSys() */

MenuCreate( oDlg:menuBar() )

********************************************************************
* Crear sistema de menú en la barra de menú del diálogo
********************************************************************
PROCEDURE MenuCreate( oMenuBar )
LOCAL oMenu

// Primer sub-menu

oMenu := SubMenuNew( oMenuBar, "~Archivo" )

oMenu:addItem( { "Opciones", } )
oMenu:addItem( MENUITEM_SEPARATOR )
oMenu:addItem( { "~Salir" , NIL } )

oMenu:activateItem := {|nItem,mp2,obj| MenuSelect(obj, 100+nItem) }

oMenuBar:addItem( {oMenu, NIL} )

// Segundo sub-menu -> datos de CLIENTE


//
oMenu := SubMenuNew( oMenuBar, "C~liente" )
oMenu:setName( CUST_MENU )

oMenu:addItem( { "~Nuevo" , NIL } )


oMenu:addItem( { "~Buscar" , NIL } )
oMenu:addItem( { "~Cambiar" , NIL } )
oMenu:addItem( { "~Eliminar", NIL , 0, XBPMENUBAR_MIA_DISABLED } )
oMenu:addItem( { "~Imprimir", NIL , 0, XBPMENUBAR_MIA_DISABLED } )

oMenu:itemSelected := {|nItem,mp2,obj| MenuSelect(obj, 200+nItem) }

oMenuBar:addItem( {oMenu, NIL} )

/* Y así en adelante... */
Los objetos XbpMenu se crean para contener los ítems del menú. Estos submenúes se crean en la función defi-
nida por el usuario SubMenuNew() (que se muestra debajo), y los ítems del menú están adjuntos a los submenúes
usando el método :addItem() . Un ítem del menú es un arreglo que contiene entre dos y cuatro elementos. En el
caso más simple el primer elemento es una cadena de caracteres que se desplegará como el texto del ítem de
menú y el segundo elemento es NIL. Cualquier caracter en la cadena de caracteres puede identificarse como una
clave de acceso rápido al posicionar un tilde (~) al frente del mismo. El segundo elemento es el bloque de código
que se ejecuta cuando el usuario selecciona el ítem del menú. En este ejemplo, en vez de definir bloques de có-
digo individuales para cada ítem del menú, se asigna un bloque de código al conector callback para el submenú
entero. La posición numérica del ítem seleccionado y el objeto del menú mismo se pasan a la rutina de selección
MenuSelect(). En esta rutina una estructura simple DO CASE...ENDCASE provee ramificaciones al módulo del
programa apropiado.

Al segundo menú en el ejemplo se asigna a un ID numérico (constante #define CUST_MENU) en la llamada al


método :setName() . Esto permite a un objeto específico XbpMenu ser hallado más tarde, puesto que el valor se
encuentra en la lista de hijos del objeto XbpMenuBar que a su vez está guardado en la lista de hijos de la ventana
de aplicación. La expresión SetAppWindow():childFromName( CUST_MENU ) proveerá una referencia a este
objeto XbpMenu. Este puede usarse para evitar la disponibilidad de los ítems del menú individuales tempora-
riamente (o disponibles también) si esto se desea en una situación específica del programa.

La inserción de submenúes en el menú principal se lleva a cabo usando el método :addItem() ejecutado por el
objeto XbpMenuBar. El título de la barra del menú sirve como texto para el ítem del menú. En el programa de
ejemplo este texto se establece para un nuevo submenú de la siguiente manera:

********************************************************************
* Crear sub-menu en un menú
********************************************************************
FUNCTION SubMenuNew( oMenu, cTitulo )
LOCAL oSubMenu := XbpMenu():new( oMenu )
oSubMenu:title := cTitulo
RETURN oSubMenu:create()

En esta función el menú principal (o el menú inmediatamente arriba) se provee como el padre para el submenú. La
asignación del título debe ocurrir antes de la llamada al método :create() para el correcto posicionamiento:

El menú de Ayuda predeterminado

Cada aplicación debería tener un ítem del menú de "Ayuda" que generalmente incluye el mismo conjunto de ítems
de menú. En el programa de ejemplo, este menú de ayuda se crea a través de un procedimiento separado que
crea los ítems predeterminados del menú. El control del programa se implementa por bloques de código que se
pasan al menú en el método :addItem() . En este caso el bloque de código del conector callback:itemSelected no
se usa.

********************************************************************
* Crear un menú de ayuda standard
********************************************************************
PROCEDURE HelpMenu( oMenuBar )
LOCAL oMenu := SubMenuNew( oMenuBar, "Ay~uda" )

oMenu:addItem( { "~Indice de Ayuda",{|| HelpObject():showHelpIndex() } } )


oMenu:addItem( { "Ayuda ~General",{|| HelpObject():showGeneralHelp() } } )
oMenu:addItem( { "~Usando Ayuda",{|| HelpObject():showHelp(IPFID_HELP_HELP) } } )
oMenu:addItem( { "Ayudas ~Clave",{|| HelpObject():showKeysHelp() } } )

oMenu:addItem( MENUITEM_SEPARATOR )

oMenu:addItem( { "Información de ~Producto",{|| MsgBox("demo Xbase++ SDI") } } )


oMenuBar:addItem( {oMenu, NIL} )
RETURN

El objeto XbpHelp maneja la ayuda en línea que se almacena como una variable estática en la función definida por
el usuario HelpObject(). Esto significa que está siempre disponible cuando se llama a la función HelpObject(). La
información de ayuda predeterminada puede llamarse desde el menú de ayuda al ejecutar los métodos del objeto
XbpHelp provistos para estos propósitos. No existe un método especial para el ítem "Usando Ayuda". Aquí se
especifica al objeto XbpHelp una constante #define que designa el ID numérico para la ventana de ayuda apro-
piada en la ayuda en línea. El mismo ID debe también usarse en el código fuente IPF.
Un menú dinámico para manejar ventanas

Además del menú de ayuda que está disponible en las aplicaciones SDI y MDI, las aplicaciones MDI tienen un
segundo menú predeterminado de ayuda que se emplea para traer diferentes ventanas hijas de la aplicación MDI
al frente. El texto en el título de cada ventana abierta aparece como un ítem del menú y la selección del menú
asigna el foco a la ventana hija correspondiente. Esto requiere una aproximación dinámica al menú, porque el
número de los ítems del menú corresponde al número de ventanas abiertas. Un menú dinámico de ventana se
implementa para este propósito en el archivo MDIMENU.PRG (que es parte del código fuente para la aplicación de
ejemplo MDIDEMO). Es un buen ejemplo de derivar nuevas clases desde una Componente de Xbase. Para
concretar esto, es necesaria una manera de determinar fácilmente el menú principal de una ventana de aplicación
(la ventana padre). La función AppMenu() se incluye en MDIDEMO.PRG para este propósito y devuelve el menú
principal de la aplicación. Hay también sólo un menú de ventana por aplicación y entonces puede guardarse en
una variable STATIC. La función WinMenu() lleva a cabo esta tarea como se muestra en el siguiente código:

********************************************************************
* Crear un menú para manejar ventanas abiertas
********************************************************************
FUNCTION WinMenu()
STATIC soMenu

IF soMenu == NIL
soMenu := WindowMenu():new():create( AppMenu() )
ENDIF
RETURN soMenu

El menú de ventana es una instancia de la clase WindowMenu y recibe el valor de retorno de AppMenu() como su
padre. Esto significa que se despliega como un submenú del menú principal de la aplicación MDI. La clase definida
por el usuario WindowMenu se deriva desde XbpMenu:

********************************************************************
* Clase Menú para manejo de ventanas abiertas
********************************************************************
CLASS WindowMenu FROM XbpMenu
EXPORTED:
CLASS VAR windowStack
CLASS METHOD initClass
METHOD init, addItem, delItem, setItem
ENDCLASS

********************************************************************
// Pila para ventanas de diálogo abiertas como variable de clase
//
CLASS METHOD WindowMenu:initClass
::windowStack := {}
RETURN self

La variable de clase :windowStack se declara para referenciar a las ventanas abiertas. El método de cla-
se :initClass() , cuya única tarea es inicializar la variable de clase con un arreglo vacío también se incluye. Los
cuatro métodos de la clase XbpMenu están sobrecargados. El método :new() termina. El método :init() de una
clase XbpMenu debe llamarse incluso para inicializar las variables de miembro implementadas allí:

********************************************************************
// Seleccionar una ventana vía código de bloque de callback
//
METHOD WindowMenu:init( oParent, aPresParam, lVisible )
::xbpMenu:init( oParent, aPresParam, lVisible )
::title := "~Ventana"
::itemSelected := ;
{|nItem,mp2,obj| SetAppFocus( obj:windowStack[nItem] ) }
RETURN self

Después de la inicialización de la superclase, el título del menú le es asignado en :init() . Se le asigna un bloque de
código a la pila de llamadas :itemSelected . Este bloque de código establece el foco a la ventana cuyo título de
ventana se selecciona desde el menú. La posición numérica del ítem del menú seleccionado se pasa al bloque de
código como el parámetro nItem y obj contiene una referencia al objeto del menú mismo. Dentro de este bloque de
código se accede a la variable de clase :windowStack . :windowStack contiene referencia a todas las ventanas
hijas de la aplicación MDI. La ventana seleccionada se pasa a la función SetAppFocus() que la establece como la
ventana en primer plano.

Los tres últimos métodos de la clase de la ventana del menú permite la inserción, modificación o borrado de la
clase del menú de ventana. Estos métodos tienen los mismos nombres que los métodos de la clase XbpMenu pero
el parámetro pasado a los métodos es diferente. en vez de un arreglo con entre dos o cuatro elementos el pa-
rámetro pasado es un objeto XbpDialog o XbpCrt que ha de recibir foco si el ítem del menú se selecciona.

*************************************************************************
// Usar título de la ventana de diálogo como texto para el ítem de menú
//
METHOD WindowMenu:addItem( oDlg )
LOCAL cItem := oDlg:getTitle()

AAdd( ::windowStack, oDlg )

::xbpMenu:addItem( {cItem, NIL} )


IF ::numItems() == 1
::setParent():insItem( ::setParent():numItems(), {self, NIL} )
ENDIF
RETURN self

Se pasa una ventana abierta al método :addItem() . Dentro de este método la ventana se agrega a la variable de
clase :windowStack . El título de ventana se agrega como un ítem del menú al pasarlo al método :addItem() de la
clase XbpMenu. Una característica especial de la ventana del menú es que se despliega en el menú principal
solamente cuando al menos una ventana hija se encuentra abierta. De lo contrario, el ítem del menú de la "Ven-
tana" no aparece en el menú principal. El menú de ventana se inserta como un ítem del menú en su padre (el menú
principal) después de la primera vez en que se ejecuta el método :addItem() .

***************************************************************
// Transferir el título cambiado de la ventana al ítem de menú
//
METHOD WindowMenu:setItem( oDlg )
LOCAL aItem, i := AScan( ::windowStack, oDlg )

IF i == 0
::addItem( oDlg )
ELSE
aItem := ::xbpMenu:getItem(i)
aItem[1] := oDlg:getTitle()
::xbpMenu:setItem( i, aItem )
ENDIF

RETURN self

***************************************************************
// Borrar la ventana de diálogo de la pila de la pila de
// ventanas y del menú.
//
METHOD WindowMenu:delItem( oDlg )
LOCAL i := AScan( ::windowStack, oDlg )
LOCAL nPos := ::setParent():numItems()-1 // el menú de ventanas está
// siempre cerca del último item
IF i > 0
::xbpMenu:delItem( i )
ADel( ::windowStack, i )
Asize( ::windowStack, Len(::windowStack)-1)
IF ::numItems() == 0
::setParent():delItem( nPos )
ENDIF
ENDIF
RETURN self

El método :setItem() se usa cuando el título de la ventana de una ventana de diálogo abierta cambia. Este cambio
debe también hacerse en el ítem del menú de la ventana de menú dinámica. El método :delItem() se llama cuando
de cierra la ventana de diálogo. Este método remueve el título de la ventana de diálogo a partir de la ventana del
menú. Si ninguna ventana hija permanece abierta, el menú de ventana se remueve a sí mismo del menú principal
(el padre) y el ítem del menú "Ventana" no continúa siendo visible.

Tareas del procedimiento Main


Después que la ventana de aplicación, incluyendo al sistema de menú, ha sido creada en AppSys(), continúa la
ejecución del programa en el procedimiento Main (asumiendo que no hay otro INIT PROCEDURE). Al comienzo
del procedimiento Main deberían chequearse todas las condiciones requeridas para una ejecución de la aplicación
GUI libre de error. Por ejemplo, esto debería incluir evaluar la existencia de todos los archivos requeridos creando
archivos índice que no están disponibles y la inicialización de las variables necesarias a través de la aplicación
(variables PÚBLICAS). La recuperación de las variables de configuración usando el comando RESTORE FROM
debería ocurrir generalmente dentro del procedimiento Main antes que el programa entre en el ciclo de eventos. El
ciclo de eventos lleva a cabo la tarea central del procedimiento Main. En este ciclo, los eventos se recuperan y se
envían al Destinatario. El siguiente código del programa es del archivo MDIDEMO.PRG y muestra parte de lo que
tiene que incluirse en el procedimiento principal o en las funciones que llama el procedimiento Main. (Nota: El
ejemplo no tiene la intención de cubrir todos los aspectos que deberían incluirse en un procedimiento Main).

#include "Gra.ch"
#include "Xbp.ch"
#include "AppEvent.ch"
#include "Mdidemo.ch"

********************************************************************
* Procedimiento Principal y ciclo de eventos
********************************************************************
PROCEDURE Main
LOCAL nEvento, mp1, mp2, oXbp
FIELD NRCLIE, APELLIDO, NOMBRE, PARTENRO, PARTEDESC

// Verifica archivos Indice y los crea si no existen


IF ! ExistenLosArchivos( { "CLIEA.NTX", "CLIEB.NTX", ;
"PARTA.NTX", "PARTB.NTX" } )
USE CLIENTE EXCLUSIVE
INDEX ON NrClie TO CLIEA
INDEX ON Upper(APELLIDO+NOMBRE) TO CLIEB

USE Partes EXCLUSIVE


INDEX ON Upper(PARTENRO) TO PartA
INDEX ON Upper(PARTEDESC) TO PartB

CLOSE DATABASE
ENDIF

SET DELETED ON

// Ciclo Infinito. El programa termina con AppQuit()


DO WHILE .T.
nEvento := AppEvent( @mp1, @mp2, @oXbp )
oXbp:handleEvent( nEvento, mp1, mp2 )
ENDDO
RETURN

********************************************************************
* Verifica si todos los archivos del arreglo 'aFicheros' existen
********************************************************************
FUNCTION ExistenLosArchivos( aFicheros )
LOCAL lExiste := .T., i:=0, imax := Len(aFicheros)

DO WHILE ++i <= imax .AND. lExiste


lExiste := File( aFicheros[i] )
ENDDO
RETURN lExiste

En este ejemplo, el procedimiento Main simplemente evalúa si todos los archivos de índice existen y recrean a los
archivos índice si cualquiera de estos no se encuentra. La existencia de los archivos se prueba en la función
ExistenLosArchivos(). Cuando ésta está completa. El procedimiento Main entra en un ciclo infinito que lee los
eventos de la cola usando AppEvent() y los envía al Destinatario llamando al método del Destinata-
rio :handleEvent() .

Mirando esta implementación, la pregunta inevitable es: ¨Dónde y cómo se termina el programa?. El ciclo infinito
en el procedimiento Main no puede terminar basándose en su condición DO WHILE .T.. Una rutina separada se
usa para terminar el programa. El código para esta rutina se muestra debajo:

********************************************************************
* Rutina para terminar el programa
********************************************************************
PROCEDURE AppQuit()
LOCAL nButton

nBoton := ConfirmBox( , ;
"Realmente desea salir ?", ;
"Finaliza Aplicación", ;
XBPMB_YESNO , ;
XBPMB_QUESTION+XBPMB_APPMODAL+XBPMB_MOVEABLE )

IF nBoton == XBPMB_RET_YES
COMMIT
CLOSE ALL
QUIT
ENDIF

RETURN

En la rutina de terminación AppQuit(), se recibe confirmación que el programa debería terminar por determinación
del usuario vía la función ConfirmBox(). Si la aplicación ha de terminarse, todos los buffers de datos se reescriben
en los archivos y todas las bases de datos se cierran usando CLOSE ALL. El comando QUIT luego le da fin al
programa. Si el usuario no confirma que el programa debería terminar, el ciclo infinito en el procedimiento Main
continúa.

Se recomienda generalmente que el código fuente para una aplicación GUI se divida en tres secciones: inicio del
programa, ejecución del programa y fin del programa. El inicio del programa está contenido en AppSys() así como
el código del programa ejecutado dentro del procedimiento Main previo al ciclo de eventos. El ciclo de eventos
mismo es la ejecución del programa. A veces dentro de este ciclo de el código del programa que se generó en
MenuCreate() durante el inicio del programa se produce en el procedimiento definido por el usuario AppQuit(),
donde la verificación por el usuario puede requerirse y cualquier dato puede guardarse.

Hay sólo dos sitios en un programa en los cuales se llama al procedimiento AppQuit(). AppQuit() se lo llama ge-
neralmente a partir de un ítem del menú y desde un bloque de código callback o desde un método de callback. Las
siguientes dos líneas ilustran esto:

oMenu:addItem( {"~Salir", {|| AppQuit() } } )


oDialog:close := {|| AppQuit() }

En la primera línea, AppQuit() se ejecuta después de la selección de un ítem del menú por lo que deberá ob-
viamente haber un menú conteniendo un ítem del menú que termine la aplicación. La segunda línea define un
bloque de código de callback para que la ventana de diálogo ejecute después que el ícono del menú de sistema de
la ventana de diálogo se haga doble clic o el ítem del menú de "Cierre" se seleccione en el menú del sistema de la
ventana. Generalmente, la rutina para la terminación de una aplicación GUI debería estar disponible en el menú de
la aplicación así como en respuesta al evento xbeP_Close.

DataDialog: Una clase para integrar bases de datos


Un aspecto importante en la programación de aplicaciones GUI es la conexión entre los elementos de la ventana
de diálogo y el Motor de Base de Datos. El enlace entre un único elemento de diálogo y un único campo de base de
datos se crea vía el bloque de código de datos contenido en la variable de instancia :dataLink de la clase DataRef
que manipula los datos. Este mecanismo se describe en la sección DataRef() - La conexión entre XBP y DBE. Una
ventana generalmente contiene varios elementos de diálogo que se enlazan a diferentes campos de bases de
datos. Pueden producirse situaciones especiales que han de considerarse cuando se programa aplicaciones GUI.
El programador debería recordar también que esa aplicación es dirigida por eventos completamente. Ni bien hay
un sistema de menú en la ventana, no se asegura completamente un orden definido exacto en la ejecución del
programa puesto que el usuario tiene control de la aplicación más que el programador.

Las dos aplicaciones de ejemplo SDIDEMO y MDIDEMO están provistas como ejemplo para aplicaciones GUI
bajo Xbase++. Las dificultades que surgen al acceder bases de datos se toman en cuenta de diferentes maneras
en estos dos programas. En SDIDEMO, se implementa una aproximación procedural y se usa un estilo orientado a
objeto en MDIDEMO. Ambos estos programas de ejemplo resuelven el problema de la no-modalidad de los
campos de entrada resultando de la naturaleza dirigida por evento de una aplicación GUI. Los problemas de
no-modalidad se describen mediante las preguntas: "¨Dónde y cuándo se valida el ingreso de datos? Y "¨Cuándo
se escriben los datos en la base de datos?". Puesto que los campos de entrada de datos se activan con un clic del
ratón la prevalidación (validación antes del ingreso de los datos) no es posible (después de un clic del ratón un
campo de entrada tiene el foco de entrada). Esta condición requiere algunas consideraciones por parte de los
programadores que han desarrollado previamente sólo bajo DOS sin un ratón. La validación de los datos en una
aplicación GUI puede producirse en el marco de la postvalidación (validación después del ingreso de datos). El
método :validate() en la clase DataRef sirve para este propósito. Si la postvalidación falla, debería llamarse al
método :undo() del campo de entrada (Componente de Xbase). En una aplicación dirigida por evento, esta es la
única forma de asegurar que ningún dato inválido se escribe en la base de datos.

No obstante, la tarea mayor en la programación de aplicaciones GUI no consiste generalmente en la validación de


datos, sino en la transferencia de los datos de entrada a la base de datos. En los programas de ejemplo SDIDEMO
y MDIDEMO la filosofía usada es que los datos necesitan escribirse en la base de datos cuando el puntero de
registros se modifica. Todos los Componentes de Xbase tienen su propio buffer de edición para mantener los
datos modificados. El valor a escribir en los campos de las bases de datos se almacena en este buffer de edición
perteneciente a cada Componente de Xbase. Para todos los campos de la base de datos que pueden modificarse
dentro de la ventana de diálogo, debe existir un Componente de Xbase para almacenar el valor en su buffer de
edición. El siguiente fragmento de código ilustra esto:

oXbp := XbpSLE():new( oDlg:drawingArea,, {95,135}, {180,22} )


oXbp:bufferLength := 20
oXbp:dataLink := {|x| IIf( x==NIL, APELLIDO, APELLIDO := x ) }
oXbp:create():setData()

En este código, se crea un campo de entrada para la edición de los datos en el campo APELLIDO de la base de
datos. Llamando al método :setdata() en conexión con :create() se copian los datos desde el campo de la base de
datos al buffer de edición de un objeto XbpSLE. Dentro de una ventana de diálogo pueden existir cualquier número
de campos de entrada para acceder los campos de la base de datos. El buffer de edición de todos los campos de
entrada en la ventana de diálogo pueden modificarse en cualquier momento (un clic del ratón en un campo de
entrada es suficiente para comenzar con la edición). Por esta razón debería determinarse cuando deberían co-
piarse los cambios de los datos en un campo de entrada nuevamente al archivo. Hay dos aproximaciones: Las
modificaciones a los campos de entrada de datos individuales se escriben en el archivo ni bien se produce el
cambio o todos los cambios de todos los campos de entrada de datos en una ventana se escriben en el archivo ni
bien se llama a una rutina "Guardar" explícitamente o el puntero de registros se reposiciona.

La segunda aproximación se prefiere en las aplicaciones GUI que se designan para el acceso simultáneo en un
entorno de red. Esta aproximación permite que diversos campos de entrada de datos sean modificados en una
ventana de diálogo sin la copia de cada cambio individual a la base de datos. En la operación concurrente o en
entorno de red guardar cada cambio a la base de datos requerirá un bloqueo más largo y la liberación del registro
actual. Una aplicación GUI de performance optimizada sólo bloquea un registro cuando puede escribir varios
campos en la base de datos o cuando cambia el puntero de registros.

Los problemas de validación de guardado de datos en la base de datos está presente en cada aplicación. El si-
guiente código muestra varios aspectos de este problema y se basa en la aplicación de ejemplo MDIDEMO. En
este ejemplo de aplicación la clase DataDialog se usa para proveer ventanas de diálogo para el acceso al Motor de
Base de Datos. Un objeto DataDialog coordina un Motor de Base de Datos con una ventana de diálogo. El código
fuente para esta clase está contenido en el archivo DATADLG.PRG. Un ejemplo de una pantalla de entrada ba-
sado en DataDialog, se muestra en la siguiente ilustración:
Pantalla de Ingreso de datos de cliente

La clase DataDialog se deriva a partir de XbpDialog. Añade siete nuevas variables de instancia y once métodos
adicionales para la transferencia de datos a partir de una base de datos al diálogo y viceversa. Tres de estas va-
riables de instancia son para uso interno solamente y se declaran como PROTECTED:. Los cuatro méto-
dos :init() , :create() , :configure() y :destroy() llevan a cabo pasos en el "ciclo de vida" de un objeto DataDialog:

#include "Gra.ch"
#include "Xbp.ch"
#include "Dmlb.ch"
#include "Common.ch"
#include "Appevent.ch"

********************************************************************
* Declaración de Clase
********************************************************************
CLASS DataDialog FROM XbpDialog
PROTECTED:
VAR appendMode // Es un nuevo registro?
VAR editControls // Lista de XBPs para edición de datos
VAR appendControls // Lista de XBPs sólo habilitados durante APPEND

EXPORTED:
VAR area READONLY // Area de trabajo actual
VAR newTitle // Bloque de código para cambiar título de ventana
VAR contextMenu // Menú de contexto para diálogo de datos
VAR windowMenu // Menú de ventanas dinámico en ventana de aplicación

METHOD init // métodos sobrecargados


METHOD create
METHOD configure
METHOD destroy
METHOD addEditControl // registrar XBP para edición
METHOD addAppendControl // registrar XBP para agregado
METHOD notify // procesar mensajes de DBO
METHOD readData // leer datos desde DBF
METHOD validateAll // validar todos los datos almacenados en XBPs
METHOD writeData // escribir datos desde XBPs hacia DBF
METHOD isIndexUnique // verificar unicidad del valor de índice
ENDCLASS

La variable de instancia protegida :appendMode contiene el valor lógico .T. (verdadero) sólo cuando el registro de
datos fantasmas (número de registro LastRec()+1) es el actual. Las otras dos variables de instancia protegi-
das :editControls y :appendControls son arreglos que contienen listas de los Componentes de Xbase que pueden
modificar los datos. Con el fin de crear un diálogo de datos, los XBPs editables que se requieren así como los
Componentes de Xbase que no pueden editar los datos sino que despliegan texto estático o cajas (objetos
XbpStatic). La variable de instancia :editControls contiene una lista de referencia a aquellos XBPs en la lista de
hijos (todos los XBPs que se despliegan en la ventana de diálogo están contenidos en esta lista) que puede edi-
tarse.

La tarea de la variable de instancia :appendControls es similar y contiene una lista de los XBPs que se habilitan
cuando se añade un nuevo registro. En todos los demás casos, estos XBPs están deshabilitados. Ellos sólo
despliegan datos y no permiten que los datos en ellos sean editados. Esto es útil para editar los campos de las
bases de datos que están contenidos en la clave primaria de la base de datos la cual no debería modificarse una
vez que se ingresan en la base de datos. :editControls y :appendControls se inicializan ambos con arreglos vacíos.
Esto se lleva a cabo en el método :init() después de llamar al método :init() de la clase XbpDialog como se muestra
debajo:

******************************************* *************************
* Inicializar data dialog
********************************************************************
METHOD DataDialog:init( oParent, oOwner , ;
aPos , aSize , ;
aPParam, lVisible )

DEFAULT lVisible TO .F.

::xbpDialog:init( oParent, oOwner, ;


aPos , aSize , ;
aPParam, lVisible )

::area := 0
::border := XBPDLG_THINBORDER
::maxButton := .F.
::editControls := {}
::appendControls := {}
::appendMode := .F.
::newTitle := {|obj| obj:getTitle() }

RETURN self

Todas las variables de instancia se establecen en valores con los tipos de datos válidos en el método :init() . Sólo
las variables de instancia:border y :maxButton cambian los valores predeterminados asignados en la clase
XbpDialog. La ventana de un objeto DataDialog se fija en tamaño y no puede agrandarse. El método tiene la
misma lista de parámetros que los métodos :new() y :init() en la clase XbpDialog. Esto le permite recibir parámetros
y simplemente pasarlos a la superclase. El DataDialog es diferente en el sentido que se crea oculto por prede-
terminación. Esto se recomienda cuando se desplegarán muchos XBPs en la ventana después que la ventana
haya sido generada. La construcción de la pantalla con el método :show() es más rápida si se despliega todo de
una vez después que todos los XBPs han sido añadidos a la ventana de diálogo.

Las variables de instancia :newTitle deben contener un bloque de código que se pasa al objeto DataDialog. Por
esta razón un bloque de código se define en el método :init() , pero debe redefinirse más tarde. Este bloque de
código cambia el título de la ventana mientras la ventana de diálogo es visible. El bloque de código predeterminado
se asigna a las variables de instancia en el método :init() para asegurar que la variable de instancia tiene el tipo de
dato correcto.

El próximo método en el "ciclo de vida" de un objeto DataDialog es :create() . Una base de datos debe abrirse en el
área de trabajo actual previo a este método que se está llamando. Un objeto DataDialog continúa usando el área
de trabajo actual cuando se ejecuta el método :create():

********************************************************************
* Cargar recursos del sistema
* Registrar a DataDialog en el área de trabajo actual
********************************************************************
METHOD DataDialog:create( oParent, oOwner , ;
aPos , aSize , ;
aPParam, lVisible )

::xbpDialog:create( oParent, oOwner , ;


aPos , aSize , ;
aPParam, lVisible )
::drawingArea:setColorBG( GRA_CLR_PALEGRAY )

::appendMode := Eof()
::area := Select()

::close := {|mp1,mp2,obj| obj:destroy() }


::setDisplayFocus := {|mp1,mp2,obj| ;
DbSelectArea( obj:area ) }
DbRegisterClient( self )
RETURN self

La tarea más importante de :create() es el requerimiento de recursos de sistema para la ventana de diálogo. Esto
ocurre cuando se llama al método del mismo nombre en la superclase y los parámetros se pasan a él simplemente.
El color de fondo para el área de dibujo (:drawingArea ) de la ventana de diálogo se establece luego. La llamada
a :setColorBG() define a su vez el color de fondo para todos los XBPs desplegados más tarde en la ventana de
diálogo. Esto afecta todos los XBPs que tienen texto para exhibir. Esto simplifica la programación porque el color
de fondo de los XBPs individuales con contenidos no tienen que establecerse separadamente. Generalmente
cuando han de emplearse los colores del sistema definidos en la configuración, no es posible llamar
a :setColorBG() .

Las siguientes líneas son importantes porque enlazan al objeto DataDialog y al área de trabajo. Primero, se de-
termina si el puntero está actualmente en Eof(). Luego Select() determina el número del área actual de trabajo. Dos
bloques de código se asignan a los conectores callback :close y :setDisplayFocus . El método :destroy() (descripto
debajo) se llama después del evento xbeP_Close. Ni bien el objeto DataDialog recibe foco, se ejecuta el bloque de
código en :setDisplayFocus . En este bloque de código, el área de trabajo manejada por el objeto DataDialog se
selecciona como el área de trabajo actual usando DbSelectArea(). Esto significa que si el ratón se clickea en una
ventana de DataDialog, el área de trabajo correcta se selecciona correctamente.

La llamada a DbRegisterClient() es crítica para la lógica del programa. Esto registra el objeto DataDialog en el área
de trabajo por lo que es automáticamente notificado cuando se modifica algo en la misma. Esto incluye notificación
de los cambios en la posición del puntero de registros. Cuando el puntero de registros se modifica, los nuevos
datos deben desplegarse mediante los XBPs que se listan en la variable de instancia :editControls . Esto se hace
usando el método :notify() que se describe después de los métodos restantes discutidos en el "ciclo de vida". El
método :configure() se provee para manipular cambios en el área de trabajo manejados por el objeto DataDialog.
A saber:

********************************************************************
* Cargar recursos del sistema
* Registrar a DataDialog en el área de trabajo actual si es preciso.
********************************************************************
METHOD DataDialog:configure( oParent, oOwner , ;
aPos , aSize , ;
aPParam, lVisible )
LOCAL lRegistrar := (::area <> Select())

::xbpDialog:configure( oParent, oOwner , ;


aPos , aSize , ;
aPParam, lVisible )
IF lRegistrar
(::area)->( DbDeRegisterClient( self ) )
ENDIF

::area := Select()
::appendMode := Eof()

IF lRegistrar
DbRegisterClient( self )
ENDIF

RETURN self

Un objeto DataDialog siempre manipula el área de trabajo actual. Debido a esto, el método :configure() compara
las variables de instancia :area para Select() para determinar si el área actual ha sido modificada. Si así fue, el
objeto de desregistra en el área de trabajo más antigua. Además, los recursos del sistema para la ventana de
diálogo se reconfiguran en la llamada al método :configure() de la superclase.
El método final del ciclo de vida de DataDialog es :destroy() . Este método cierra la base de datos que usa el objeto
DataDialog y libera los recursos del sistema. Las variables de instancia declaradas en la clase DataDialog se
restablecen en los valores asignados en el método :init() :

********************************************************************
* Liberar recursos de sistema y desregistrar datadialog del área de
* trabajo.
********************************************************************
METHOD DataDialog:destroy()

::writeData()
::hide()

(::área)->( DbCloseArea() )

IF ! Empty( ::windowMenu )
::windowMenu:delItem( self ) // borrar item de menú en menú
// de ventanas.
::windowMenu := NIL
ENDIF

IF ! Empty( ::contextMenu )
::contextMenu:cargo := NIL // Borrar referencia de DataDialog
::contextMenu := NIL // y menú de contexto
ENDIF

::xbpDialog:destroy() // Liberar recursos de sistema


::Area := 0 // y poner las variables de instancia
::appendMode := .F. // a valores correspondientes a
::editControls := {} // estado :init() .
::appendControls := {}
::newTitle := {|obj| obj:getTitle() }

RETURN self

El método :writeData() se llama en :destroy() con el fin de escribir todas las modificaciones de los datos en la base
de datos antes que se cierre usando DbCloseArea(). Después que la base de datos se cierra, el objeto DataDialog
se desregistra de manera implícita del área de trabajo y no es necesaria una llamada al área de trabajo. Si en la
variable de instancia :windowMenu está contenido un objeto del menú, el objeto DataDialog se remueve de la lista
de los ítems en este menú (la clase WindowMenu se describe en la sección previa). La variable de instan-
cia :contextMenu puede contener un menú de contexto que se activa al hacer clic en el botón derecho del ratón.
Este mecanismo se describe en la sección posterior. Es esencial que la referencia al objeto DataDialog en la va-
riable de instancia :cargo del menú de contexto sea borrada porque se espera que el método :destroy() elimine
todas las referencias al objeto DataDialog. Y si un objeto de diálogo permanece referenciado en cualquier parte ya
sea en una variable, un arreglo, o una variable de instancia, no se removerá de la memoria por el colector de re-
siduos. Esto concluye la discusión de los métodos que llevan a cabo tareas en el "ciclo de vida" del objeto Data-
Dialog.

Uno de los métodos más importantes de la clase DataDialog es el método :notify() . Se lo llama cuando algo se
modifica en el área de trabajo asociado con el objeto. Una versión abreviada de este método que resalta sus
elementos esenciales se muestra debajo:
********************************************************************
* Método Notify:
* - Escribe datos a los campos previo a mover el puntero de registros.
* - Lee datos desde los campos luego de mover el puntero de registros.
********************************************************************
METHOD DataDialog:notify( nEvento, mp1, mp2 )

IF nEvento <> xbeDBO_Notify // no hay mensaje de notificación


RETURN self // ** volver **
ENDIF

DO CASE
CASE mp1 == DBO_MOVE_PROLOG // el puntero de registros
::writeData() // está por ser movido
CASE mp1 == DBO_MOVE_DONE .OR. ; // el salto fue hecho
mp1 == DBO_GOBOTTOM .OR. ;
mp1 == DBO_GOTOP
::readData()

ENDCASE
RETURN self

Llamando a la función DbRegisterClient() en el método :create() del objeto DataDialog registra al objeto en el área
de trabajo que usa. Ni bien algo cambia en esta área de trabajo, se llama al método :notify() . Para el movimiento
del puntero de registros es método es siempre el doble. La primera vez que el objeto DataDialog recibe el valor
representado por la constante DBO_MOVE_PROLOG (definida en el archivo DMLB.CH) como el parámetro mp1 .
Esto es una señal que significa "Cuidado que la posición del puntero de registros está por cambiar". Cuando el
objeto DataDialog recibe este mensaje ejecuta el método :writeData() que escribe los datos del registro actual en la
base de datos. En la segunda llamada a :notify() , el objeto recibe el valor de la constante DBO_MOVE_DONE.
Este mensaje le dice al objeto "Ok, el puntero ha sido modificado". En respuesta a este mensaje, el objeto ejecuta
al método :readData() que copia los campos del nuevo registro en los buffer de edición de los XBPs que están en
la variable de instancia :editControls de los diálogos de datos. Esto permite que se editen los datos en el nuevo
registro.

El método :notify() provee la lógica importante del programa para el objeto DataDialog. En este método el objeto
DataDialog reacciona a los mensajes enviados por el área de trabajo que usa. Se llama a este método sólo
después que se registra al objeto en el área de trabajo usando DbRegisterClient(). O, más precisamente, se lo
llama sólo cuando el objeto se registra en el objeto de la base de datos (DBO) que maneja el área de trabajo (un
DBO se crea automáticamente cuando se abre una base de datos). Basado en el evento pasado, el evento :notify()
determina si un registro debería leerse en los XBPs o si los datos en el XBPs deberían escribirse en la base de
datos. El objeto DataDialog no manipula directamente a los datos pero si a los XBPs contenidos en el arre-
glo :editControls . El agregado de los XBPs a este arreglo se lleva a cabo usando el método :addEditControl() .

********************************************************************
* Agregar un control de edición a la lista interna
********************************************************************
METHOD DataDialog:addEditControl( oXbp )
IF AScan( ::editControls, oXbp ) == 0
AAdd( ::editControls, oXbp )
ENDIF
RETURN self

********************************************************************
* Agregar un control de añadido a la lista interna
********************************************************************
METHOD DataDialog:addAppendControl( oXbp )
IF AScan( ::appendControls, oXbp ) == 0
AAdd( ::appendControls, oXbp )
ENDIF
RETURN self

Los dos métodos :addEditControl() y :addAppendControl() son prácticamente iguales. Uno agrega un Compo-
nente de Xbase al arreglo almacenado en la variable de instancia :editControls y el otro agrega un Componente de
Xbase a :appendControls . Cuando un objeto DataDialog ejecuta el método :readData() o :writeData() , procesa
secuencialmente los elementos en el arreglo :editControls y envía a cada elemento (cada Componente de Xbase)
el mensaje para leer o escribir sus datos. Se incluye más abajo un fragmento de código para ilustrar cómo los
Componentes de Xbase pueden agregarse a la ventana de un objeto DataDialog y a la variable de instan-
cia :editControls si es apropiado. La variable oDlg referencia a un objeto DataDialog.

oXbp := XbpStatic():new( oDlg:drawingArea,, {5,135}, {80,22} )


oXbp:caption := "Apellido:" // almacena texto estático
oXbp:options := XBPSTATIC_TEXT_RIGHT // sólo en la lista hija
oXbp:create( )
oXbp := XbpSLE():new( oDlg:drawingArea,, {95,135}, {180,22} )
oXbp:bufferLength := 20 // campo de entrada enlazado a
oXbp:tabStop := .T. // la base de datos
oXbp:dataLink := {|x| IIf( x==NIL, APELLIDO, APELLIDO := x ) }
oXbp:create():setData()
oDlg:addEditControl( oXbp ) // agrega nuevos XBP a :editControls
Los Componentes de Xbase aparecen en el área de dibujo de una ventana de diálogo, así oDlg:drawingArea debe
especificarse como el padre. El fragmento de código crea un objeto XbpStatic para desplegar el texto "Apellido:" y
un objeto XbpSLE para acceder y editar al campo de la base de datos llamado APELLIDO. Pasando el objeto
XbpSLE al método :addEditControl() agrega este Componente de Xbase al arreglo :editControls . En la lista de
hijos del objeto DataDialog hay ahora dos XBPs pero el arreglo :editControls contiene sólo al COMPONENTE DE
XBASE o los datos que pueden editarse. Los métodos :readData() , :validateAll() y :writeData() asumen que todos
los Componentes de Xbase que pueden editar los datos se incluyen en el arreglo :editControls . El código del
programa para :readData() se muestra debajo:

*********************************************************************
* Leer registro actual y transferir datos a los controles de edición
*********************************************************************
METHOD DataDialog:readData()
LOCAL i, imax := Len( ::editControls )

FOR i:=1 TO imax // Transferir datos desde el


::editControls[i]:setData() // archivo a los XBPs
NEXT

Eval( ::newTitle, self ) // Poner nuevo título de ventana

IF Eof() // habilita/deshabilita XBPs


IF ! ::appendMode // activos sólo durante
imax := Len( ::appendControls ) // APPEND
FOR i:=1 TO imax //
::appendControls[i]:enable() // Alcanzó Eof(), entonces
NEXT // habilitar XBPs
ENDIF
::appendMode := .T.
ELSEIF ::appendMode // El puntero de registros fue
imax := Len( ::appendControls ) // movido desde Eof() a
FOR i:=1 TO imax // un registro existente.
::appendControls[i]:disable() // Deshabilitar XBPs de
NEXT // sólo agregado.
::appendMode := .F.
ENDIF

RETURN

El método :setData() en el primer ciclo FOR...NEXT causa que todos los XBPs referenciados en la variable de
instancia :editControls relean sus buffers de edición al copiar el valor de retorno del bloque de código de datos
contenido en :dataLink a su buffer de edición. El código restante habilita y Deshabilita a los XBPs en la lis-
ta :appendControls . Además de la lectura de los datos en los campos de datos de las bases de datos, este método
es el sitio apropiado para habilitar o deshabilitar aquellos Componentes de Xbase que deberían editarse cuando
está siendo añadido un nuevo registro.

La contraparte de :readData() es el método :writeData() . En este método los datos en el buffer de edición de cada
Componente de Xbase listada en :editControls se reescribe en la base de datos. Este método involucra código de
programa relativamente extenso, porque lleva a cabo el bloqueo de registros e identifica si un registro nuevo
debería agregarse.

********************************************************************
* Escribe datos de los controles de edición al archivo
********************************************************************
METHOD DataDialog:writeData()
LOCAL i, imax
LOCAL lBloqueado := .F. , ; // ¨Está bloqueado el registro?
lAgregar := .F. , ; // ¨Es un nuevo registro?
aCambiados := {} , ; // XBPs conteniendo datos cambiados
nViejaArea := Select() // Area de trabajo actual

dbSelectArea( ::área )

IF Eof() // Agregar un nuevo registro


IF ::validateAll() // Validar datos primero
APPEND BLANK
lAgregar := .T.
aCambiados := ::editControls // Probar todos los cambios posibles
lBloqueado := ! NetErr() // Bloqueo Implícito
ELSE
MsgBox("Datos No Válidos") // No escribir datos No Válidos.
DbSelectArea( nViejaArea ) // al nuevo registro
RETURN .F. // *** VOLVER ***
ENDIF
ELSE
imax := Len( ::editControls ) // Encontrar todos los XBPs
FOR i:=1 TO imax // conteniendo datos cambiados
IF ::editControls[i]:changed
AAdd( aCambiados, ::editControls[i] )
ENDIF
NEXT

IF Empty( aCambiados ) // Nada ha cambiado, por lo tanto


DbSelectArea( nViejaArea ) // no es necesario bloquear registro
RETURN .T. // *** VOLVER ***
ENDIF

lBloqueado := DbRLock( Recno() ) // bloquear registro actual


ENDIF

IF ! lBloqueado
MsgBox( "El registro está bloqueado" )
DbSelectArea( nViejaArea ) // falló el bloqueo de registro
RETURN .F. // *** VOLVER ***
ENDIF

imax := Len( aCambiados ) // Acceso a escritura es necesario


FOR i:=1 TO imax // sólo para los datos cambiados
IF ! lAgregar
IF ! aCambiados[i]:validate()
aCambiados[i]:undo() // Datos No Válidos !
LOOP // deshacer cambios y validar
ENDIF // al próximo XBP
ENDIF
aCambiados[i]:getData() // Tomar datos de XBP y
NEXT // escribirlos al archivo

DbCommit() // Volcar a disco los buffers


DbRUnlock( Recno() ) // liberar registro bloqueado

IF ::appendMode // Deshabilitar XBPs de agregado.


imax := Len( ::appendControls ) // luego de APPEND
FOR i:=1 TO imax
::appendControls[i]:disable()
NEXT
::appendMode := .F.

IF ! Empty( ::contextMenu )
::contextMenu:disableBottom()
::contextMenu:enableEof()
ENDIF
ENDIF
DbSelectArea( nViejaArea )
RETURN .T.

El agregado de un nuevo registro requiere una lógica especial en el método :writeData() del objeto DataDialog. Un
registro especial vacío (el registro fantasma) está automáticamente disponible cuando se posiciona el puntero de
registros en Eof(). Si el puntero se posiciona en Eof(), el método :readData() ha copiado valores "vacíos" desde los
campos de la base de datos a los buffers de edición XBP listados en la variable de instancia :editControls . Debido
a esto, todos los XBPs contienen tipos de datos válidos. Pero no hay garantía que los datos válidos están conte-
nidos también en los buffers de edición de cada XBP. Esto significa que la validación de los datos debe llevarse a
cabo antes que el registro sea agregado. Puesto que no hay un registro aún, todos los datos que van a guardarse
se encuentran sólo en el buffer de edición de los Componentes de Xbase correspondientes. Se llama al méto-
do :validateAll() antes de la añadidura de un registro nuevo, el cual recibirá datos a partir de los buffers de edición
de los Componentes de Xbase.

La validación de los datos es especialmente importante cuando se añade un registro nuevo porque previamente
no existen datos válidos que permitan que los cambios a los buffers de edición sean vaciados. Para los registros
que están siendo editados, el método :undo() permite modificaciones a los valores en los buffers de edición a
vaciar. Pero esta aproximación asume que hay un valor de campo original que es válido. Esto es verdad sólo si el
registro que está siendo editado existió previo a la edición. Cuando se agrega un registro, los valores originales
son valores "vacíos" que probablemente no son válidos.

En :writeData() , esta situación se maneja llamando al método :validateAll() antes que un nuevo registro se
agregue al archivo usando APPEND BLANK. Si la validación de los datos falla en un sólo campo, se despliega un
mensaje que contiene el texto "Datos No Válidos" y no se agrega un nuevo registro. Los datos no válidos per-
manecen en los buffers de edición de los Componentes de Xbase correspondientes (:editControls ) y el usuario
puede corregir. Cuando se edita un registro existente, la validación de los datos se produce individualmente para
cada Componente de Xbase. Si el método :validate() de un XBP devuelve el valor .F. (falso) (indicando datos no
válidos), se ejecuta el método :undo() del XBP, que copia de los valores originales los datos válidos nuevamente al
buffer de edición.

Si se modificó cualquier componente de Xbase listado en :editControls , se bloquea el registro usando DbRLock().
Después de la validación, los datos de los buffers de edición se escriben en las bases de datos llamando al mé-
todo :getData() . La función DbCommit() asegura que los datos en los buffers de archivo se escriban en la base
datos. Finalmente se libera el bloqueo de registros.

El método :writeData() maneja los problemas de la validación de datos y de la añadidura de registros como ocurren
en un entorno dirigido por eventos. Este proceso es controlado por el ratón o por el usuario quien causa los clics
del mouse. También piense que si bien se invoca :writeData() desde solamente un lugar en el método :notify() , es
imposible visualizar cuándo éste método será ejecutado. Mientras que es claro que será llamado cuando el
puntero de registros se mueve, no es posible predecir cuál registro será el actual cuando el método :writeData()
sea llamado. El caso especial ocurre cuando el puntero es posicionado en el registro fantasma. En este caso la
validación de datos no puede ser revertida usando el método :undo() porque existen datos previamente no vali-
dados. Por esta razón, todos los datos deben ser validados antes que sea agregado un nuevo registro. El método
que verifica que todos los datos sean válidos se llama :validateAll() y se muestra más abajo:

********************************************************************
* Valida datos de todos los controles de edición
* Esto es necesario previo a agregar
* un nuevo registro a la Base de Datos
********************************************************************
METHOD DataDialog:validateAll()
LOCAL i := 0, imax := Len( ::editControls )
LOCAL lValido := .T.

DO WHILE ++i <= imax .AND. lValido


lValido := ::editControls[i]:validate()
ENDDO

RETURN lValido

El método consiste solamente en un ciclo DO WHILE que es terminado tan pronto como el método :validate() envíe
una señal de datos no válidos. El método :validate() se ejecuta para de todos los Componente de Xbase listados
en :editControls . Este método siempre retorna el valor .T. (verdadero) a menos que haya un bloque de código
contenido en la variable de instancia :validate . Si hay un bloque de código en esta variable de instancia, será
ejecutado y recibirá al Componente de Xbase como primer parámetro. Este bloque de código hace la validación de
datos y devuelve .T. (verdadero) si los datos son válidos.

Se necesita Una validación de datos especial para la clave primaria en una base de datos. La clave primaria es el
valor en la base de datos que unívocamente identifica cada registro. Hay siempre un índice para la clave primaria.
El método :isIndexUnique() (que se muestra debajo) prueba si un valor ya existe como clave primaria en un archivo
índice de la base de datos. Este método demuestra un aspecto extremadamente importante para el uso de los
objetos DataDialog (más precisamente: para el uso de la función DbRegisterClient()):
********************************************************************
* Verificar cuándo un valor de índice *no* existe en un índice.
********************************************************************
METHOD DataDialog:isIndexUnique( nOrden, xValorEnIndice )
LOCAL nIndiceAnterior := OrdNumber()
LOCAL nRegistro := Recno()
LOCAL lEsUnico := .F.

DbDeRegisterClient( self ) // Suprimir notificación del DBO


// a self durante DbSeek() !!!
OrdSetFocus( nOrden )
lEsUnico := .NOT. DbSeek( xValorEnIndice )
OrdSetFocus( nIndiceAnterior )
DbGoTo( nRegistro )
DbRegisterClient( self )
RETURN lEsUnico

La funcionalidad del método :isIndexUnique() es muy limitada. Todo lo que hace es buscar un valor en el índice
especificado y retornar el valor .T. (verdadero) si el valor no se encontró. Un punto importante que se muestra aquí
es que el objeto DataDialog que ejecuta al método debe desregistrarse del área de trabajo. Este fue inicialmente
registrado en el área de trabajo por el método :create() , causando la llamada del método :notify() cada vez que el
puntero de registros cambia. En este caso, se trata de un método del objeto DataDialog cambiando el puntero
cuando se llama a DbSeek(). Si el objeto DataDialog no ha sido desregistrado, resultaría una recursión implícita ya
que cada cambio al puntero vía DbSeek() llama al método :notify() . Por esta razón, se usa DbDeRegisterClient()
para desregistrar al objeto DataDialog previo a la llamada a DbSeek(). Se lo registra otra vez en el área de trabajo
usando DbRegisterClient() después DBSeek().

En síntesis, la clase DataDialog resuelve muchos problemas que deben considerarse cuando se programan
aplicaciones GUI que trabajan con bases de datos. Los movimientos del puntero de registros se identifican con
facilidad en el método :notify() que se llama automáticamente cuando se registra al objeto DataDialog en el área de
trabajo actual usando la función DbRegisterClient(). Antes que se mueva el puntero de registros, un objeto Da-
taDialog copia los datos modificados en :editControls nuevamente a la base de datos. Una vez que se cambió el
puntero de registros, un objeto DataDialog despliega los datos actuales. La validación de los datos se produce
antes de la escritura de los datos en la base de datos ya sea mediante la añadidura de un nuevo registro o la
modificación de datos existentes que se sobreescriben. Si datos nuevos se están guardando o los datos existentes
se están modificando lo determina el objeto DataDialog.

DataDialog y pantallas de ingreso de datos


Los objetos de la clase DataDialog descriptos en la sección previa son apropiados para la programación de
pantallas de ingreso de datos en las aplicaciones GUI. Cada pantalla de ingreso de datos es una ventana inde-
pendiente que se despliega como hija de la ventana de aplicación. En cada ventana hija (pantalla de ingreso) los
Componentes de Xbase se agregan para editar los campos de la base de datos. Debido a que son ventanas
separadas, se recomienda que cada pantalla de ingreso sea programada en una rutina separada. Las tareas de
esta rutina incluyen la apertura de todas las bases de datos requeridas para la pantalla de ingreso, creando la
ventana hija (DataDialog), y agregando los Componentes de Xbase necesarios para editar los campos de la base
de datos a la pantalla de ingreso. En la aplicación de ejemplo MDIDEMO, se programan dos pantallas de ingreso,
una para los datos de los clientes y una para datos de componentes. El proceso de crear la pantalla de ingreso de
datos es la misma en ambos casos. Las secciones del código del programa desde el archivo MDICUST.PRG se
discuten debajo para ilustrar varios aspectos significativos cuando se programan las pantallas de ingreso de datos:

********************************************************************
* Diálogo de Cliente
********************************************************************
PROCEDURE Customer( nRegistro )
LOCAL oXbp, oStatic, drawingArea, oDlg
FIELD NRCLIE, SR_SRA, APELLIDO, NOMBRE, CALLE, CODPOST
FIELD CIUDAD, ESTADO, TELEFONO, FAX, BLOQUEADO, TOTALVENTA

IF ! OpenCustomer( nRegistro ) // Abrir base de datos CLIENTE


RETURN
ENDIF

oDlg := DataDialog():new( RootWindow():drawingArea ,, ;


{100,100}, {605,315},, .F. )
oDlg:title := "Cliente Nro: "+ LTrim( NrClie )
oDlg:icon := ICON_CUSTOMER
oDlg:create()
/* ... */

El procedimiento Customer() crea una ventana hija nueva en la aplicación MDI donde los datos del cliente pueden
editarse. Las variables LOCAL se declaran primero para referenciar los Componentes de Xbase creados y todos
los campos de las bases de datos se identifican para el compilador como variables de campo. Antes de la creación
de la ventana hija (DataDialog), las bases de datos requeridas deben estar abiertas. Esto ocurre en la función
OpenCustomer() que retorna el valor .F. (falso) sólo si la base de datos del cliente no puede abrirse. La apertura de
la base de datos puede fallar porque otra estación de trabajo tiene al archivo exclusivamente abierto o si el archivo
simplemente no se encuentra. (Nota: la evaluación de la existencia de archivos debería haberse concretado al
inicio en el Procedimiento Main).

Cuando lo(s) archivo(s) requerido(s) pueden abrirse, se crea la ventana de diálogo. Esto se lleva a cabo usando el
método :new() de la clase DataDialog que genera una nueva variable de instancia de la clase DataDialog. El padre
del nuevo objeto es el área de dibujo (:drawingArea ) de la ventana de aplicación creada en AppSys() y devuelta
por la función definida por el usuario RootWindow(). Ni bien se crea la ventana hija, el ID de Recurso para un ícono
debe ingresarse en la variable de instancia :icon . Este ícono es desplegado dentro de la ventana de aplicación
cuando la ventana hija se minimiza. En este ejemplo, se usa la constante #define ICON_CUSTOMER. Se declara
a un ícono en un archivo de recursos y se lo debe enlazar a un archivo ejecutable usando el compilador de re-
cursos. Si ningún ícono ID es especificado para una ventana hija, los contenidos de la ventana en el rango desde
el punto {0,0} hasta el punto {32,32} se usan como el ícono cuando la ventana es minimizada. Esto significa que
todo lo visible en la esquina inferior izquierda de la ventana hija hasta el punto {32,32} aparece como el símbolo
para la ventana hija minimizada.

En el programa de ejemplo MDICUST.PRG, sólo el archivo de la base de datos CLIENTE.DBF necesita abrirse. Es
importante para reabrir la base de datos del cliente cada vez que se llama al procedimiento Customer(). Esto se
muestra en el código del programa de la función OpenCustomer ():

********************************************************************
* Abrir base de datos de Clientes
********************************************************************
FUNCTION OpenCustomer( nRegistro )
LOCAL nViejaArea := Select(), lHecho := .F.

USE CLIENTE NEW


IF ! NetErr()
SET INDEX TO CLIEA, CLIEB
IF nRegistro <> NIL
DbGoto( nRegistro )
ENDIF
lHecho := .T.
ELSE
DbSelectArea( nViejaArea )
MsgBox( "No pudo abrirse la Base de Datos" )
ENDIF

RETURN lHecho

Cada instancia de la clase DataDialog (cada pantalla de ingreso de datos) maneja su propia área de trabajo. Si se
ejecuta el procedimiento 10 veces, se crean 10 pantallas de ingreso de datos para los datos de los clientes y el
archivo CLIENTE.DBF se abre 10 veces. Esta regla es estándar para las aplicaciones dirigidas a eventos GUI:
Cada diálogo abre su propia base de datos . Esto requiere algunas consideraciones para los programadores
que provienen de la programación procedural de DOS, puesto que la misma aproximación no es apropiada bajo
DOS debido al límite máximo de 255 archivos abiertos. Este límite no existe bajo un sistema operativo de 32bit. El
acceso a un único archivo de base de datos a partir de varios diálogos no requiere la implementación de los
mecanismos de protección en la aplicación de Xbase++. Los mecanismos para el bloqueo de registros o archivos
es suficiente bajo Xbase++ así cuando el programa permite el acceso simultáneo en un entorno de red, también
manejará al archivo que está siendo abierto múltiples veces dentro de una única aplicación.

Cada llamada a OpenCustomer() abre la base de datos del cliente en un área de trabajo nueva y el método Da-
taDialog:create() registra la ventana de diálogo en esta área nueva de trabajo. Desde este punto en adelante, el
objeto DataDialog es notificado sobre cada cambio en el área de trabajo (vía el método :notify() ) y puede asig-
nársele los componentes de Xbase apropiados (vía :editControls ) para que puedan manipularse automáticamente
por los métodos DataDialog. (Nota para los programadores Clipper: Se permite la expresión USE Cliente NEW en
Xbase sin especificación de un nombre de alias. Si se abre una base de datos varias veces, Xbase++ provee un
nombre de alias único formado a partir del nombre del archivo y el número del área de trabajo actual).

Cuando la base de datos está abierta y se crea DataDialog (la ventana hija), los procesos más importantes para la
programación de una pantalla de ingreso de datos están casi completos. Los Componentes de Xbase deben
añadirse aún a la ventana de diálogo. Esta incluye ambos componentes de Xbase que contribuyen a la organi-
zación visual de la pantalla de ingreso datos (bordes y textos) y los componentes de Xbase que permiten el acceso
a los campos de las bases de datos vía :dataLink . Este segundo grupo se compone principalmente de objetos de
las clases XbpSLE, XbpCheckBox y XbpMLE. Los objetos XbpSLE proveen campos de ingreso de datos de línea
única. Los objetos XbpCheckBox manipulan los valores lógicos y los objetos XbpMLE proveen campos de ingreso
de datos de líneas múltiples que permiten la edición de campos de memo. Los objetos de las clases XbpSLE,
XbpCheckBox y XbpMLE son suficientes para programar las secciones de las pantallas de ingreso de datos en las
que se editan los campos de las bases de datos.

Las cajas que se despliegan a través de los objetos XbpStatic se usan para proveer una organización visual de las
pantallas de ingreso de datos. Los campos de entrada no están sólo separados visualmente cuando aparecen en
una caja, sino que los campos pueden programarse también en la lógica del programa. La siguiente sección de
programa muestra otro ejemplo de código definiendo una parte de una pantalla de ingreso de datos. Este ejemplo
es una continuación del procedimiento Customer():

// obtener el área de dibujo del diálogo


drawingArea := oDlg:drawingArea

oStatic := XbpStatic():new( drawingArea ,, {12,227}, {579,58} )


oStatic:type := XBPSTATIC_TYPE_GROUPBOX
oStatic:create()

En el ejemplo anterior, el área de dibujo de la ventana de diálogo se recupera y pasa como el padre para los
elementos de diálogo para desplegarse en la ventana. El primer elemento de diálogo es un objeto XbpStatic
responsable de desplegar un recuadro de grupo. Esta caja despliega texto (el contenido) en la esquina superior de
la izquierda. Se emplea un recuadro de grupo para agrupar los campos de ingreso de datos y actúa como el padre
para todos los Componentes de Xbase que se exhiben dentro del recuadro de grupo. En otras palabras: el padre
para un recuadro de grupo es el :drawingArea y el padre para los Componentes de Xbase desplegados dentro del
recuadro de grupo es el objeto XbpStatic que representa una caja. Por esta razón el objeto XbpStatic se referencia
en la variable oStatic y se usa como el padre en el ejemplo de la creación de los campos de ingreso de datos que
se muestra debajo:

oXbp := XbpSLE():new( oStatic,, {95,135}, {180,22} )


oXbp:bufferLength := 20
oXbp:dataLink := {|x| IIf( x==NIL, Trim(APELLIDO), APELLIDO := x ) }
oXbp:create():setData()
oDlg:addEditControl( oXbp ) // registrar Xbp como Control de Edición

En el ejemplo anterior, se crea un campo de ingreso de datos para la exhibición dentro del recuadro de grupo (el
padre del campo de ingreso de datos es oStatic). El objeto XbpSLE accede al campo NOMBRE de la base de
datos y la longitud del buffer de edición se limita a la longitud de un campo de base de datos. El campo APELLIDO
tienen 20 caracteres en el ejemplo. Se maneja una compatibilidad general entre los campos de las bases de datos
y los objetos XbpSLE en el bloque de código :dataLink . Cuando se leen los datos del campo de la base de datos,
los espacios en blanco están incluidos. Si se copian los datos directamente del campo APELLIDO al buffer de
edición del objeto XbpSLE, se incluyen siempre 20 caracteres en el buffer de edición inclusive para un apellido
como "Smith" que tiene sólo cinco caracteres de largo. Los espacios en blanco almacenados en el campo de la
base de datos se copian al buffer de edición del objeto XbpSLE. El resultado es que el buffer de edición del objeto
de los componentes de Xbase ya está lleno y los caracteres pueden agregarse sólo al buffer de edición en el modo
"Sobreescribir". Un objeto XbpSLE considera a los espacios en blanco como caracteres completamente válidos y
para prevenir estos problemas, los espacios en blanco al final del nombre (espacios precedentes) se remueven
explícitamente usando Trim() cuando los datos se leen desde el campo de la base de datos APELLIDO dentro
de :dataLink .

Un objeto XbpSLE puede editar valores en su buffer de edición que son de tipo "carácter". El número máximo de
caracteres es 32KB. Los valores de tipo numérico o fecha deben convertirse en una cadena de caracteres cuando
se copian en el buffer de edición de un objeto XbpSLE y se convierten de nuevo al tipo correcto antes de escribirse
en el campo de la base de datos. Esto debe llevarse a cabo en el bloque de código contenido en :dataLink . Abajo
se muestran ejemplos para los bloques de código que llevan a cabo las conversiones de tipo:
oXbp:dataLink := {|x| IIf(x==NIL, Transform( FIELD->NUMERICO, "@N"), ;
FIELD->NUMERICO := Val(x) ) }
oXbp:dataLink := {|x| IIf(x==NIL, DtoC( FIELD->FECHA ), ;
FIELD->FECHA := CtoD(x) ) }

Cuando los campos de las bases de datos se leen en el buffer de edición de un objeto XbpSLE se deben suprimir
los espacios en blanco y convertir los valores numéricos y de fecha a cadenas de caracteres. Cuando los datos
modificados se guardan en los campos de base de datos, los valores de los campos fecha y numérico deben
reconvertirse al tipo de dato correcto. Esta tarea se leva a cabo por el bloque de código asignado a la variable de
instancia :dataLink .

Otra tarea del bloque de código de datos contenidos en :dataLink se produce cuando se requiere más de un ar-
chivo para las pantallas de ingreso de datos (el DataDialog). En este caso, los campos de diversas bases de datos
se editan en una pantalla de ingreso de datos única y el bloque de códigos de datos ha de seleccionar el área de
trabajo correcta para la variable de campo.

Control del programa en las ventanas de diálogo


Las discusiones previas de los conceptos de aplicación GUI han focalizado en la organización básica de un pro-
grama GUI. Los incidentes claves discutidos fueron el inicio, ejecución, y terminación del programa. Estos co-
rresponden a AppSys() con un sistema de menú, el Procedimiento Main con el ciclo de eventos y AppQuit(),
respectivamente. La clase DataDialog fue discutida como un mecanismo para el enlace de ventanas de diálogo
con Motor de Base de Datos. Esta clase ofrece soluciones a los problemas que pueden ocurrir durante el acceso
simultáneo en un entorno de red o cuando una base de datos se abre múltiples veces en una aplicación única. En
la sección previa, se ilustró la incorporación de los Componentes de Xbase en una ventana de diálogo. La pre-
gunta final que queda para la programación de aplicaciones GUI es: ¨Cómo se controla el programa dentro de una
ventana de diálogo individual?

Debe hacerse una distinción entre el control de una ventana y el control de una aplicación. Se controla la completa
ejecución de la aplicación mediante el menú de ejecución instalado en la ventana de aplicación. En una aplicación
SDI, el control de una aplicación es básicamente el mismo que el control de la ventana de diálogo, puesto que la
aplicación consiste de sólo una ventana de diálogo. En la aplicación de ejemplo SDIDEMO, el control de la apli-
cación a través del sistema de menú incluye la selección de pantallas de ingreso de datos para los datos del cliente
o para datos de partes. El control dentro de las ventanas se produce usando botones que permiten el movimiento
del puntero de registros dentro del archivo del cliente o el archivo de partes, causan que se guarden los datos
actuales o que termine el ingreso de datos.

En la aplicación de ejemplo MDIDEMO, el control de la aplicación se limita a la apertura de las pantallas de ingreso
de datos del cliente o de partes. Ni bien se abre una ventana hija, la aplicación y el menú de aplicación no tienen
más control sobre la ventana recientemente abierta. El control del programa dentro de la ventana hija se lleva a
cabo en una aplicación MDI por un menú de contexto que es un elemento de control del programa esencial. Se
activa un menú de contexto generalmente al hacer clic con el botón derecho del ratón. Se despliega en la pantalla
como un menú instantáneo (PopUp). Los ítems del menú proveen una selección de las acciones que son apro-
piadas para ejecutar dentro de la ventana o en relación al elemento de diálogo donde se produjo un clic en el botón
derecho del ratón.

El menú de contexto en la aplicación de ejemplo MDIDEMO incluye al control del programa de la navegación de
bases de datos (DbSkip(), DbGoBottom(), DbGoTop()) y operaciones de bases de datos elementales como
"Buscar", "Borrar" y "Nuevo registro". La programación de un menú de contexto requiere la definición de un objeto
XbpMenu y es de otra manera similar a la programación de los objetos del menú de aplicación. Como ejemplo, el
código del programa para crear el menú de contexto para la base de datos del cliente usada en MDIDEMO se
muestra debajo:

********************************************************************
* crear menú de contexto para el diálogo de Cliente
********************************************************************
STATIC FUNCTION ContextMenu()
STATIC soMenu
IF soMenu == NIL
soMenu := DataDialogMenu():new()
soMenu:title := "Menú de contexto Cliente"
soMenu:create()

soMenu:addItem( { "~Nuevo", {|mp1,mp2,obj| DbGoTo( LastRec()+1 ) } } )


soMenu:addItem( { "~Buscar" , {|mp1,mp2,obj| SeekCustomer( obj:cargo ) } } )
soMenu:addItem( { "~Eliminar" , {|mp1,mp2,obj| DeleteCustomer( obj:cargo ) } } )
soMenu:addItem( { "~Guardar" , {|mp1,mp2,obj| obj:cargo:writeData() } } )

soMenu:addItem( MENUITEM_SEPARATOR )

soMenu:addItem( { "~Primero" , {|mp1,mp2,obj| DbGoTop() } } )


soMenu:addItem( { "~Ultimo" , {|mp1,mp2,obj| DbGoBottom() } } )

soMenu:addItem( MENUITEM_SEPARATOR )

soMenu:addItem( { "~Anterior" , {|mp1,mp2,obj| DbSkip(-1) } } )


soMenu:addItem( { "~Siguiente" , {|mp1,mp2,obj| DbSkip(1) } } )

// Items de menú están deshabilitados luego de Bof() o GoTop()


soMenu:disableTop := { 6, 9 }

// Items de menú están deshabilitados luego de GoBottom()


soMenu:disableBottom := { 7, 10 }

// Items de menú están deshabilitados luego de Eof()


soMenu:disableEof := { 1, 2, 3 }

ENDIF

RETURN soMenu

Se define un bloque de código para cada ítem del menú en el menú de contexto. Muchos de los bloques de código
se ejecutan cuando el usuario selecciona al ítem del menú. Muchos de los bloques de código controlan la nave-
gación de la base de datos usando funciones como DbSkip(), DbGoTop(), y DbGoBottom(). El objeto DataDialog
se notifica automáticamente acerca de estas operaciones (se llama a su método :notify() ) puesto que se registra
en el área de trabajo. El menú de contexto mismo puede activarse solamente en el objeto DataDialog (ventana hija)
en la que se ha focalizado. El menú se activa con un clic en el botón derecho del ratón que ha de producirse dentro
del ventana DataDialog. La ventana DataDialog activa su menú de contexto mediante el siguiente bloque de có-
digo callback (ver el archivo MDICUST.PRG, función Customer()):

drawingArea:RbDown := {|mp1,mp2,obj| ;
ContextMenu():cargo := obj:setParent(), ;
ContextMenu():popup( obj, mp1 ) }

El :drawingArea es el área de dibujo de la ventana DataDialog. La función ContextMenu() se muestra debajo. Esta
función devuelve los contenidos de la variable STATIC soMenu , que es el menú de contexto. El parámetro del
bloque de código obj contiene una referencia al Componente de Xbase que está procesando al evento
xbeM_RbDown (se presiona al botón derecho del ratón). En este caso, esta es el área de dibujo del DataDialog
(:drawingArea ) y la expresión obj:setParent() retorna al objeto DataDialog que se le asigna a la variable de ins-
tancia :cargo del menú de contexto. Esto también ocurre antes de que el menú de contexto se despliegue usando
al método :popUp() . Las coordenadas actuales del ratón (relativas a obj ) están contenidas en mp1 . Esto permite
la exhibición del valor de retorno de ContextMenu() (el menú de contexto) en la posición del puntero del ratón.

Cuando se selecciona un ítem del menú en el menú de contexto, el objeto DataDialog donde se activa al menú de
contexto está siempre contenido en la variable de instancia :cargo . Este objeto DataDialog tiene el foco (de lo
contrario no reaccionaría a un clic del botón derecho del ratón). El objeto DataDialog con el foco fue seleccionado
previamente vía el bloque de código callback :setDisplayFocus que establece el área de trabajo apropiada como el
área de trabajo actual. La navegación en la base de datos puede producirse en el menú de contexto al llamar
simplemente a DbSkip() o DbGobottom() sin la especificación del área de trabajo donde se producirá el movi-
miento. Se selecciona el área de trabajo mediante el objeto DataDialog cuando recibe el foco. El menú de contexto
puede activarse en un objeto DataDialog que tiene el foco porque sólo el objeto DataDialog con foco reacciona al
evento xbeM_RbDown y el menú de contexto se activa sólo en el bloque de código callback :RbDown .

Esta discusión resalta el control del programa vía un menú de contexto como se lo usa en la aplicación de ejemplo
MDIDEMO (puede ser más fácil seguir los pasos a través del código en el debugger). En conclusión, un menú de
contexto puede ser un elemento de control importante en una aplicación GUI. Generalmente, un menú de contexto
no es específico a un área de trabajo pero llama a la funcionalidad que ha de operar independientemente del área
de trabajo. En breve: un menú de contexto controla un Componente de Xbase.
El motor gráfico de Xbase++ - Xbase++ Graphics Engine (GRA)
Este capítulo describe el motor gráfico de Xbase++ - Xbase++ Graphics Engine (motor GRA) que provee al pro-
gramador un entorno conveniente para la salida gráfica. El motor GRA provee acceso funcional al sistema de sa-
lida gráfica. Incluye un total de 32 funciones que pueden usarse para crear de manera sencilla gráficos de negocio
actualizados, como gráficos de barras y tortas. El motor GRA puede desplegar bitmaps y metaarchivos y modificar
la apariencia de elementos de diálogo de los Componentes de Xbase.

Conceptos básicos de la salida gráfica


Las primitivas gráficas
Atributos para las primitivas gráficas
Los segmentos gráficos
Los caminos gráficos
Las transformaciones gráficas y operaciones sobre retícula

Conceptos básicos de la salida gráfica


El empleo del motor de GRA requiere que un programa de Xbase++ sea enlazado para el modo GUI. La salida
gráfica no puede concretarse en el modo VIO. Todas las funciones gráficas comienzan con el prefijo "Gra". A
veces requieren coordenadas gráficas especificadas generalmente en "unidades pixel". Un pixel es un punto en la
pantalla. El tamaño predeterminado de la ventana en la cual se ejecuta una aplicación de Xbase++ es 640x400
pixels. El origen del sistema de coordenadas gráficas (el punto 0,0) se encuentra en la esquina izquierda de abajo,
en la ventana.

En modo de texto (modo VIO), las coordenadas del cursor de la pantalla están determinadas usando las funciones
Row() y Col(). El origen del sistema de coordenadas en modo de texto se ubica en la esquina izquierda de arriba,
en la ventana y el tamaño predeterminado de la ventana es 25 filas y 80 columnas. Cuando se programa usando
funciones gráficas, debe usarse un sistema de coordenadas gráficas diferente. Tiene un origen diferente que el
sistema de coordenadas del modo de texto. No se usan las coordenadas de las filas y de las columnas, sino las
coordenadas gráficas de X e Y. Las coordenadas XY están en relación geométrica al eje X (horizontal) y al eje Y
(vertical).

La salida gráfica no involucra un cursor de pantalla como aquel empleado en modo de texto para identificar la
posición actual en pantalla. En cambio, el modo gráfico incorpora un "lápiz". La posición del lápiz en modo gráfico
es similar a la posición del cursor en modo de texto. La posición del lápiz se define como un punto en el sistema de
coordenadas y la posición del cursor se define en términos de la posición de su fila y columna. En la programación
gráfica un punto se representa siempre por un arreglo que contiene dos elementos que representan las coorde-
nadas X e Y para un punto. El siguiente código muestra una comparación de las posiciones del cursor y del lápiz:

SetPos( 10, 20 ) // posición cursor en modo texto


? Row() // resulta: 10
? Col() // resulta: 20

GraPos( , {20,10} ) // posición del lápiz en modo Gráfico


? GraPos() // resulta: { 20, 10}

Nota: El origen del sistema de coordenadas para la salida gráfica es izquierda debajo mientras que para el modo
de texto es izquierda arriba. La unidad para el sistema de coordenadas es "pixel". Un pixel (o punto) se especifica
usando un arreglo de dos elementos.

La salida gráfica se produce generalmente en la ventana actual. Cuando no puede visualizarse la salida gráfica en
la ventana (por ejemplo, puede haber sido enviada a la impresora), debe tenerse en cuenta el mecanismo de sa-
lida para gráficos. La salida gráfica ocurre sólo en lo que se llama un "Espacio de Presentación" (Nota: en Xbase++,
se proveen los espacios de presentación por la clase XbpPresSpace()). Un Espacio de Presentación contiene la
parte de la definición para la salida gráfica que es independiente del dispositivo de salida. Un gráfico puede lucir
diferente dependiendo del dispositivo de salida usado para el despliegue. Por ejemplo, el despliegue de un gráfico
en la pantalla ocurre en una ventana. La unidad para el sistema de coordenadas es el "pixel". Cuando el gráfico se
envía para salir por la impresora puede seleccionarse la unidad "0.1 cm" en vez del pixel. La salida del sistema
gráfico en la impresora se diferenciara luego considerablemente de lo que sale en pantalla.

En la discusión acerca de salidas gráficas, debe distinguirse entre la salida independiente del dispositivo y la salida
del dependiente de dispositivo. Un Espacio de Presentación define cualquier cosa requerida para el dispositivo
independiente de despliegue de gráfico. Esto incluye, por ejemplo:
- Origen y unidad del sistema de coordenadas
- Colores para el despliegue
- Tipo de línea de las líneas
- Rellenar áreas con patrones
- Tipo de Font y tamaño de los caracteres
- Tamaño del área de despliegue

El Espacio de Presentación puede pensarse como un área de dibujo abstracto para salidas gráficas (puede
pensarse también como similar a un pedazo de papel en blanco sobre el que puede dibujarse). Define todos los
atributos para la salida gráfica que son independientes del dispositivo de salida. Un dispositivo de salida es, por
ejemplo, una ventana (la pantalla) o la impresora. Un dispositivo de salida físico se conoce como "contexto del
dispositivo". El contexto del dispositivo contiene todos los dispositivos dependientes de atributos. Para desplegar
salidas gráficas, deben combinarse un Espacio de Presentación (independiente del dispositivo) con un dispositivo
de contexto (dispositivo dependiente). Ningún dibujo en el Espacio de Presentación es visible. Sólo enlazando el
Espacio de Presentación con el dispositivo de contexto puede hacerse visible la salida gráfica del Espacio de
Presentación en el dispositivo físico de salida.

Mientras se produzca una salida gráfica en una ventana XbpCrt solamente, no es necesario lidiar con el Espacio
de Presentación porque esa ventana tiene un Espacio de Presentación asociado con él y permite que este dis-
ponible el dispositivo de contexto. En la descripción del motor GRA y de sus funciones no se consideran otras
ventanas o dispositivos de salida. Estos son descriptos en los capítulos Espacios de Presentación para Salidas
gráficas y Dispositivos de Salida Gráfica.

En todas las funciones gráficas el primer parámetro específica el espacio de presentación para dibujo. La ventana
XbpCrt actual es el espacio de presentación predeterminado por lo que no es necesario especificar un dispositivo
de contexto o un Espacio de Presentación cuando se dibuja en la ventana actual. Si se crea una ventana en el
tamaño predeterminado queda disponible un área de dibujo de 640x350 pixeles. La posición del lápiz está en el
punto {0,0} una vez que la ventana fue creada. La salida del dibujo siempre comienza en la posición actual del lápiz.
Puesto que establece la posición del lápiz la función GraPos() es una función importante, fundamental del motor
GRA. Otra función básica es GraError() que determina si la salida gráfica fue exitosa o ha ocurrido un error.

Funciones básicas del motor GRA

Función Descripción
GraPos() Devuelve o establece la posición del lápiz
GraError() Devuelve un código numérico de error para la última Salida Gráfica

El motor GRA depende del hardware y de los manejadores de dispositivo instalados de la misma forma que de-
pende el sistema operativo. Si la salida gráfica no se produce como lo esperado, GraError() chequea si la opera-
ción gráfica está respaldada o no por el hardware o por el manejador de dispositivos.

Las Primitivas gráficas


Las "Primitivas Gráficas" se requieren para producir gráficos. Son funciones que dibujan figuras gráficas ele-
mentales, como líneas, círculos y rectángulos. Siempre dibujan en un Espacio de Presentación que se despliega
por si mismo en un dispositivo de contexto que permite la visualización del resultado de las primitivas gráficas en
un dispositivo físico de salida.
Las primitivas gráficas son los pilares básicos de la construcción para la salida gráfica. El motor GRA usa las seis
primitivas gráficas que se listan en la siguiente tabla:

Primitivas gráficas
Función Descripción
GraMarker() Dibuja el marcador
GraLine() Dibuja una línea
GraBox() Dibuja un cuadrado o rectángulo
GraStringAt() Dibuja una cadena de caracteres
GraSpline() Dibuja una curva
GraArc() Dibuja un círculo, arco o elipse

GraQueryTextBox() Devuelve las coordenadas de una cadena de caracteres

Las funciones en esta tabla dibujan seis figuras gráficas elementales: marcadores, líneas, rectángulos, círculos,
curvas y cadenas de caracteres. La función GraQueryTextBox() no es una primitiva gráfica y no despliega nada.
Esta función determina la coordenadas del área ocupada por una cadena de caracteres cuando ésta última es
desplegada. Esto es significativo para el posicionamiento correcto de las cadenas de caracteres en el despliegue
especialmente con fonts proporcionales. La siguiente ilustración muestra el resultado de varias primitivas gráficas.

Salida de primitivas gráficas

Esta ilustración muestra una pocas posibilidades provistas por el motor GRA a través de sus primitivas gráficas. La
ilustración se crea usando Xbase++ y contiene cuatro ejemplos. En el cuadrante superior de la izquierda se
muestran las líneas, curvas y marcadores (las funciones GraMarker(), GraLine() y GraSpline()). El cuadrante
superior de la derecha se dibuja usando GraStringAt(), GraQueryTextBox() y GraBox(). En la mitad de abajo de la
ilustración se muestran dos gráficos de negocios. El gráfico de barras se dibuja fundamentalmente utilizando
GraBox() y GraLine() mientras que el gráfico de torta emplea la función GraArc().

Atributos para las primitivas gráficas


Los atributos se establecen antes de dibujar con el fin de modificar la salida de las primitivas gráficas. Estos
atributos son válidos hasta que se los establece nuevamente. Por ejemplo, pueden definirse colores, tipos de línea
y relleno de áreas con patrones. El número de atributos es distinto para las diferentes primitivas gráficas. Los
atributos se definen en la forma de un arreglo de atributos cuyo tamaño y elementos se determina usando cons-
tantes definidas en el archivo GRA.CH. Además, los valores que se ingresan en los elementos del arreglo de
atributos se representan usando constantes #define. El procedimiento básico para cambiar atributos de las pri-
mitivas gráficas se muestra en el siguiente código:

aAttribute := Array( GRA_AL_COUNT ) // crea un atributo vacío arreglo para líneas

aAttribute[ GRA_AL_TYPE ] := GRA_LINETYPE_SHORTDASH


aAttribute[ GRA_AL_COLOR ] := GRA_CLR_RED // seleccionar tipo de línea
// "guion corto" y el color
GraSetAttrLine( , aAttribute ) // rojo establece atributos de líneas

Este código ilustra la convención de nombres utilizada por el motor GRA para las constantes #define. Todas las
constantes comienzan con el prefijo GRA_. Generalmente se incluye otro prefijo para identificar el grupo de
constantes GRA_ y se incluye un sufijo al final que define únicamente a la constante. Los atributos pueden esta-
blecerse para marcadores, líneas, áreas y cadenas de caracteres. Por tanto, se crean cuatro posibles arreglos de
atributos cuando se emplean las cuatro constantes:

aAttrMarker := Array( GRA_AM_COUNT ) // arreglo de atributos para marcadores


aAttrLine := Array( GRA_AL_COUNT ) // arreglo de atributos para líneas
aAttrArea := Array( GRA_AA_COUNT ) // arreglo de atributos para áreas
aAttrString := Array( GRA_AS_COUNT ) // arreglo de atributos para cadenas de caracteres

Las letras AM, AL, AA y AS son abreviaturas de "atributos de marcador", "atributos de línea", "atributos de área" y
"atributos de cadena". Cada elemento se identifica por una constante #define que tiene como prefijo un arreglo de
atributos como GRA_AM_, GRA_AL_, GRA_AA_ o GRA_AS_. Se establece un atributo asignando un valor al
elemento apropiado en el arreglo de atributos. Se establece un atributo al asignar un valor al elemento apropiado
en el arreglo de atributos. Para cada atributo hay sólo un numero limitado de valores válidos que se usan nue-
vamente en la forma de constantes #define.

aAttribute[ GRA_AL_TYPE ] := GRA_LINETYPE_SHORTDASH

En esta línea de códigos el atributo "tipo de línea" recibe un valor para un guión corto. Los valores válidos para
tipos de líneas (mas precisamente para el atributo GRA_AL_TYPE) son constantes que tienen como prefijo a
GRA_LINETYPE_. Para cada atributo hay un grupo de constantes #define como estas que pueden asignarse
como el valor para el atributo.

Se definen los atributos para un Espacio de Presentación y por tanto el arreglo de atributos deben pasarse al
Espacio de Presentación luego que el atributo se inserta en el arreglo. Sólo después se establecen realmente los
atributos y se los utiliza para primitivas gráfica mientras se dibuja. Las funciones usadas para asignar los arreglos
de atributos al espacio de presentación se listan en la siguiente tabla.

Funciones para los atributos de primitivas gráficas


Función Descripción
GraSetAttrMar Establece atributos para marcadores
GraSetAttrLin Establece atributos para líneas
GraSetAttrAre Establece atributos para áreas
GraSetAttrStr Establece atributos para cadenas de caracteres
GraSetColor() Establece colores para todas las primitivas gráficas
GraSetFont() Establece fuentes para salidas gráficas de los caracteres

Las funciones GraSetAttr..() pasan un arreglo de atributos a un espacio de presentación (Nota: estas funciones se
codifican en el archivo GRASYS.PRG. Llaman a los métodos correspondientes de un objeto de XbpPresSpace).
Los atributos establecidos para los marcadores se utilizan por las primitivas gráficas GraMarker(). Los atributos
para las líneas se utilizan por GraLine() y GraSpline(). También se usan por GraArc() y GraBox() cuando se dibujan
un círculo o un rectángulo con un borde (el borde es una línea). Si se rellenan un círculo o un rectángulo con un
patrón, se utilizan los atributos para áreas. Estos últimos se definen por GraSetAttrArea(). Las primitivas gráficas
GraArc() y GraBox() usan atributos de líneas así como atributos de áreas. Los atributos para cadenas de carac-
teres los usan exclusivamente GraStringAt(). Las siguientes tablas dan un vistazo de los atributos que pueden
establecerse para marcadores, líneas, áreas y cadenas de caracteres.

Constantes #define para el arreglo de atributos para los marcadores - GraSetAttrMarker()


Elemento de arreglo #define | valor Descripción
GRA_AM_COLOR GRA_CLR_* Color del frente
GRA_AM_BACKCOLOR GRA_CLR_* Color de fondo
GRA_AM_MIXMODE GRA_FGMIX_* Atributo de mezcla para el color del frente
GRA_AM_BGMIXMODE GRA_BGMIX_* Atributo de mezcla para el color de fondo
GRA_AM_SYMBOL GRA_MARKSYM_* Símbolo del marcador
GRA_AM_BOX {nTamañoX, nTamañoY} Tamaño del símbolo del marcador

Constantes #define para el arreglo de atributos las líneas - GraSetAttrLine()


Elemento de arreglo #define | grupo Descripción
GRA_AL_COLOR GRA_CLR_* Color del frente
GRA_AL_MIXMODE GRA_FGMIX_* Atributo de mezcla para el color del frente
GRA_AL_WIDTH GRA_LINEWIDTH_* Ancho de línea
GRA_AL_TYPE GRA_LINETYPE_* Tipo de línea

Constantes #define para el arreglo de atributos para áreas - GraSetAttrArea()


Elemento de arreglo #define | valor Descripción
GRA_AA_COLOR GRA_CLR_* Color del frente
GRA_AA_BACKCOLOR GRA_CLR_* Color de fondo
GRA_AA_MIXMODE GRA_FGMIX_* Atributo de mezcla para el color del frente
GRA_AA_BGMIXMODE GRA_BGMIX_* Atributo de mezcla para el color de fondo
GRA_AA_SYMBOL GRA_SYM_* Llena patrones para las áreas

Constantes #define para el arreglo de atributos para las cadenas de caracteres


Elemento de arreglo #define | valor Descripción
GRA_AS_COLOR GRA_CLR_* Color del frente
GRA_AS_BACKCOLOR GRA_CLR_* Color del fondo
GRA_AS_MIXMODE GRA_FGMIX_* Atributo de mezcla para el color del frente
GRA_AS_BGMIXMODE GRA_BGMIX_* Atributo de mezcla para el color de fondo
GRA_AS_BOX {nTamañoX, nTamañoY} Tamaño de cada caracter
GRA_AS_ANGLE {nX,nY} ángulo de salida de las cadenas de caracteres
GRA_AS_SHEAR {nX,nY} Inclinación de caracteres (itálicas)
GRA_AS_DIRECTION GRA_CHDIRN_* Dirección de escritura
GRA_AS_HORIZALIGN GRA_HALIGN_* Alineamiento horizontal
GRA_AS_VERTALIGN GRA_VALIGN_* Alineamiento vertical
GRA_AS_EXTRA nExtra Distancia entre caracteres en
cadenas de caracteres
GRA_AS_BREAK_EXTRA nBreakExtra Distancia entre palabras

Colores para Primitivas Gráficas


La función GraSetColor() define los colores de fondo y del frente para las primitivas gráficas del motor GRA. Los
colores definidos usando la función SetColor() son válidas sólo para el despliegue en modo de texto y en el modo
híbrido. Las funciones GraSetAttr..() pueden establecer colores individualmente para los marcadores, líneas,
áreas y cadenas de caracteres que se usan luego en vez de los colores definidos globalmente. Con la excepción
de las líneas, las primitivas gráficas tienen un color de fondo y frente. Las líneas tienen sólo un color de frente. El
color de fondo no es generalmente visible. Se utiliza cuando se despliegan los caracteres, que están siempre
rodeados por un rectángulo. El rectángulo que rodea cada caracter se dibuja en el color de fondo y el mismo
caracter (la letra) se despliega en el color de fondo. El atributo mezcla para el color de fondo debe definirse para
que se vuelva visible el color de fondo. Los atributos mezcla del color deben definirse para el color de frente y para
el color de fondo. Determinan como los colores de las primitivas gráficas se mezclan entre si cuando la salida de
una primitiva gráfica cubre un dibujo existente.

Mezcla de colores de fondo y del frente

En ésta ilustración los círculos se dibujan primero. El atributo mezcla para el color de fondo cambia desde
GRA_FGMIX_OVERPAINT (el valor predeterminado que causa que la nueva primitiva cubra la antigua) a
GRA_FGMIX_OR previo al despliegue del cuadrado más inferior. Cuando se establece este atributo de mezcla, el
color del frente del cuadrado se mezcla con el color de frente del dibujo existente. Cuando se establece
GRA_FGMIX_OR la mezcla se produce usando OR binario entre los valores de color de los colores existentes y
los colores del nuevo gráfico que se está dibujando. El área donde se combinan los círculos y el cuadrado es vi-
sible en el color de mezcla y ésta sección aparece de alguna manera más clara que el resto del círculo. El color del
resto del cuadrado en la ilustración es el resultado de la mezcla de colores entre gris oscuro (cuadrado) y un gris
pálido (color de ventana).

En la porción derecha de la ilustración las cadenas de caracteres se dibujan sobre un círculo. Dentro del círculo, el
color de fondo para los caracteres es visible. Causa que el patrón de llenado del círculo que aparezca más claro.
En este caso, el color de mezcla es el resultado de los valores GRA_BGMIX_OR para el atributo de mezcla del
color de fondo de GraStringAt(). Por predeterminación los atributos de mezcla se establecen para que el color de
fondo pinte sobre todo lo que ya ha sido desplegado (GRA_FGMIX_OVERPAINT) quedando desafectado el color
de fondo (GRA_BGMIX_LEAVEALONE). El atributo de mezcla para el color del frente es siempre significativo
cuando las primitivas gráficas se superponen entre ellas y la sección compartida debe permanecer visible. Cuando
las cadenas de caracteres se despliegan debe tenerse en cuenta al atributo de mezcla para el color de fondo,
puesto que se exhiben ambos el color del frente (letra) y el color de fondo (el rectángulo que rodea una letra).

Un valor especial para el atributo del color mixto para los colores del frente es GRA_FGMIX_XOR. El resultado del
despliegue de las primitivas gráficas cuando se establece este valor es que las primitivas gráficas se borran de la
pantalla si se dibujan en la misma localización una segunda vez. Además, el color de mezcla resultante de una
primitiva gráfica que pinta sobre un dibujo existente depende del color del dibujo existente y del color de la primitiva
gráfica. Las siguientes dos tablas listan las constantes que pueden usarse para los atributos mixtos del color del
frente y del fondo:

Atributos mixtos para los colores del frente


Constante Descripción
GRA_FGMIX_OVERPAINT Pinta sobre los colores existentes
GRA_FGMIX_LEAVEALONE Usa colores existentes
GRA_FGMIX_OR Mezcla colores nuevos y existentes usando operación
OR de los bits de color
GRA_FGMIX_XOR Mezcla colores nuevos y existentes usando
XOR de los bits de color
GRA_FGMIX_AND Mezcla los colores nuevos y existentes usando
AND de los bits de color
GRA_FGMIX_NOTMERGESRC Invierte la mezcla de colores de GRA_FGMIX_OR
GRA_FGMIX_NOTMASKSRC Invierte la mezcla de colores de GRA_FGMIX_AND
GRA_FGMIX_NOTXORSRC Invierte la mezcla de colores de GRA_FGMIX_XOR
GRA_FGMIX_INVERT Invierte el color existente
GRA_FGMIX_NOTCOPYSRC Invierte un color nuevo
GRA_FGMIX_SUBTRACT Invierte un color nuevo y lo mezcla con el existente
color usando rotación de bits AND los bits de color
GRA_FGMIX_MERGENOTSRC Invierte nuevos colores y los mezcla con los existentes
color usando operación OR en los bits de color
GRA_FGMIX_MASKSRCNOT Invierte los colores existentes y lo mezcla con el nuevo
color usando AND en los bits de color
GRA_FGMIX_MERGESRCNOT Invierte el color existente y lo mezcla con el nuevo
color usando OR en los bits de colores
GRA_FGMIX_ZERO El color resultante es negro (todos los bits de color son
establecidos en 0)
GRA_FGMIX_ONE El color resultante es blanco (todos los bits de color son
establecidos en 1)

Atributos de mezcla para colores de fondo


Constante Descripción
GRA_BGMIX_OVERPAINT Pinta sobre los colores existentes
GRA_BGMIX_LEAVEALONE Usa los colores existentes
GRA_BGMIX_OR Mezcla nuevos colores y el color existente usando
OR en los bits de color
GRA_BGMIX_XOR Mezcla colores nuevos y existentes usando
XOR en los bits de color

Establecer Caracteres Tipográficos


La función GraSetFont(), junto con GraSetAttrString(), afecta la exhibición de los caracteres y de las cadenas de
caracteres. GraSetFont() pasa a un Espacio de Presentación un objeto XbpFont definiendo el font para el des-
pliegue gráfico de caracteres. Por predeterminación, se emplea el font "Sistema VIO". Para cambiar el font debe
crearse un objeto XbpFont y pasárselo al Espacio de Presentación. Puede definirse el tamaño cuando se crea un
objeto XbpFont (el tamaño está en puntos). Cuando se crea un objeto XbpFont usando XbpFont():new(), un font no
está disponible aun, pero se la carga solamente en la memoria por el método :create() (refiérase a la sección Las
bases de los Componentes de Xbase++ y la discusión sobre sus ciclos de vida). Además, un objeto XbpFont
puede cargar solamente los fonts disponibles como sistemas de fuentes. Debe hacerse una distinción entre
fuentes que son dispositivo dependientes (por ejemplo, los fonts están a disposición de la impresora) y aquellos
que son dispositivo independiente.

Se recomienda que los fonts sean cargados usando XbpFont():new() y luego modificados usando GraSetAttrS-
tring(). Los atributos para las cadenas de caracteres permiten la creación de diversas exhibiciones de fuentes.
Esto comienza con el tamaño de caracteres individuales e incluye el ángulo en que se despliega un caracter re-
lativo al eje horizontal.
Debido a diferencias internas entre XbpFont() y GraSetAttrString(), XbpFont() debería usarse sólo para cargar un
font y GraSetAttrString() debería usarse para definir los atributos para desplegar los caracteres. Debería notarse
que muchos atributos para desplegar los caracteres son válidos sólo para los fonts vectoriales. Un Font vectorial
se dibuja como la línea externa de las letras utilizando las primitivas gráficas y luego se llena la línea externa. Una
letra en un font de bitmap consiste de una imagen reticulada. Luego de esto, los caracteres de un font de bitmap no
puede convertirse automáticamente a itálicas. Con el fin de desplegar los caracteres de un font bitmap en itálicas,
se requiere por separado un font bitmap en itálicas.

Los segmentos gráficos


Un segmento de gráfico contiene una o más primitivas gráficas y posibilita que el resultado de varias primitivas
gráficas sean re- dibujadas sin tener que convocar nuevamente a las primitivas gráficas. Los segmentos de gr á-
ficos pueden tener también un ID numérico y pueden encontrarse basándose en un punto específico en el sistema
de coordenadas. Por ejemplo, esto puede permitir que una aplicación determine en que segmento de gráfico se
produjo un clic del mouse. La definición de un segmento de gráfico se inicia usando la función GraSegOpen() que
devuelve el ID numérico para el nuevo segmento. Todas las llamadas subsecuentes a las primitivas gráficas se
registran en el segmento hasta que la definición del segmento de gráfico se complete usando la función Gra-
SegClose().

Luego de la definición del segmento gráfico, todas las primitivas gráficas contenidas en el segmento son
re-dibujadas cuando se llama a la función GraSegDraw(). Las funciones usadas para programar los segmentos de
gráfico se listan en la siguiente tabla:

Funciones para los segmentos de gráfico


Función Descripción
GraSegOpen() Inicia la definición del segmento de gráfico
GraSegClose() Termina la definición del segmento de gráfico
GraSegPriority() Establece un orden de dibujo del segmento gráfico
GraSegDestroy() Libera el segmento de gráfico
GraSegDraw() Dibuja el segmento del gráfico
GraSegDrawMode() Especifica el modo de dibujo para los segmentos de gráfico
GraSegFind() Localiza el segmento de gráfico basado en la posición
GraSegPickResolution() Establece la resolución usada en la búsqueda de segmentos

Un segmento de gráfico puede visualizarse como una primitiva compleja consistente de muchas primitivas gráficas
que se ejecutan entre GraSegOpen() y GraSegClose(). Dibujar, o hacer visible un segmento de gráfico, depende
del modo de dibujo definido en el espacio de presentación cuando se abre el segmento con GraSegOpen(). Hay
tres modos diferentes que se establecen usando la función GraSegDrawMode():

Modos de dibujo para segmentos de gráfico


Constante Descripción
GRA_DM_DRAW Las primitivas entre GraSegOpen() y
GraSegClose() se dibujan pero no se almacenan
GRA_DM_RETAIN Las primitivas entre GraSegOpen() y
GraSegClose() se almacenan pero no se dibujan
GRA_DM_DRAWANDRETAIN Las primitivas entre GraSegOpen() y
GraSegClose() se dibujan y almacenan

El modo GRA_DM_DRAW dibuja pero no almacena las primitivas definidas en el segmento. El segmento no puede
re-dibujarse usando GraSegDraw(). Este modo es útil solamente cuando debe almacenarse un dibujo en un
Metaarchivo (ver la subsección El Metaarchivo - XbpMetaFile() en la sección "Los dispositivos de salida gráfica").
El modo de dibujo GRA_DM_RETAIN almacena todas las primitivas gráficas en un segmento, pero no las des-
pliega. Esto permite que dibujos complejos se definan de manera invisible por el usuario, paso por paso, y luego se
hagan visibles todas al mismo tiempo usando GraSegDraw(). En el tercer modo de dibujo, la salida se produce
cuando el segmento está siendo definido y todas las primitivas gráficas son visibles cuando se cierra el segmento
usando GraSegClose(). En este modo, la función GraSegDraw() solo tiene que llamarse si el segmento necesita
ser re-dibujado.

Si se definen segmentos gráficos para una ventana, sólo las primitivas gráficas pueden almacenarse en el seg-
mento y no los atributos para las primitivas gráficas. Cuando se dibuja un segmento y no los atributos para puntos,
líneas, áreas y cadenas de caracteres que se establecen cuando se llama a GraSegDraw() este se uti liza en la
exhibición (Nota: cuando se almacenan los segmentos gráficos en un Metaarchivo, también se almacenan los
atributos).
Los segmentos de gráfico definidos se arreglan de acuerdo a la prioridad que aumenta en el orden en que fueron
definidos los segmentos. El primer segmento definido tiene la más baja prioridad y el ultimo segmento definido
tiene la prioridad más alta. La prioridad determina el orden en que se dibujaron los segmentos de gráfico. Si se
llama a la función GraSegDraw() sin los parámetros, todos los segmentos de gráfico se re- dibujan. El segmento
con la prioridad más baja se dibuja primero y el segmento con la prioridad más alta se dibuja ultimo. Esto es sig-
nificativo cuando los segmentos de gráfico se superponen durante el dibujo. Los segmentos con la prioridad más
alta pintan sobre los segmentos con la prioridad más alta. El resultado consiste en que los segmentos con la
prioridad más alta aparecen "al frente" y que los segmentos con la prioridad más baja aparecen "detrás" de los
segmentos con alta prioridad. La prioridad puede pensarse como el eje z en un sistema tridimensional de coor-
denadas. Mientras más alta es la prioridad, aparecen más hacia el frente los segmentos de gráfico si se super-
ponen con otros segmentos. La función GraSegPriority() sube o disminuye la prioridad de un segmento individual
en relación con un segundo segmento.

Una característica única de los segmentos de gráfico es el hecho que pueden "encontrarse" basándose en su
posición. Esto se lleva a cabo usando la función GraSegFind() a la que se le pasan las coordenadas para un punto
como ser un parámetro y que devuelve un arreglo conteniendo los IDs numéricos de todos los segmentos de
gráfico que abarcan a este punto. Los segmentos de gráfico pueden usarse entonces para interacción con el
usuario. Después de un clic con el mouse, por ejemplo, las coordenadas del puntero del mouse pueden pasarse a
la función GraSegFind() con el fin de identificar todos los segmentos de gráfico que incluyen el punto donde se hizo
un clic con el mouse. La función GraSegFind() no considera las coordenadas exactas del puntero del mouse (el
punto en la punta del puntero del mouse), pero trabaja con un rectángulo virtual que se mueve junto con el puntero
del mouse. Es posible encontrar un segmento de gráfico donde el puntero del mouse esta cerca del segmento pero
permanece apenas afuera. En este caso el rectángulo virtual alrededor del puntero del mouse se superpone con el
borde del segmento. El tamaño del rectángulo virtual puede establecerse luego usando la función GraSegPic-
kResolution() y determina la precisión o resolución usada por la resolución de la función GraSegFind() cuando se
buscan segmentos de gráfico.

Los Caminos Gráficos


Un camino gráfico es muy similar a un segmento de gráfico. Se lo inicia usando la función GraPathBegin() y se lo
termina usando GraPathEnd(). Las primitivas gráficas que se llaman entre estas dos funciones describen el ca-
mino gráfico. Este ultimo se define de manera similar a la definición de un segmento de gráfico pero tiene un
propósito completamente diferente: define los bordes de un área.
Luego de la definición de un camino gráfico el área descripta por el puede hacerse visible por cualquiera de las tres
operaciones de camino. Se da un vistazo de las operaciones de camino en la siguiente tabla:

Funciones para los caminos de gráfico


Función Descripción
GraPathBegin() Inicia la definición de un camino gráfico
GraPathEnd() Termina la definición de un camino gráfico
GraPathFill() Rellena un camino gráfico
GraPathOutline() Bordea externamente un camino gráfico
GraPathClip() Especifica al camino gráfico como un camino recortado

Las primitivas gráficas que se llaman entre GraPathBegin() y GraPathEnd() definen el borde externo de un área
que forma el camino gráfico. Las primitivas no se dibujan y permanencen escondidas. El área definida puede
llenarse luego con un color o patrón usando GraPathFill(). Se utilizan los atributos para áreas establecidas con
GraSetAttrArea(). El borde del área definida puede dibujarse usando GraPathOutline() que hace visible a las
primitivas. En este caso se emplea el atributo de líneas establecido con GraSetAttrLine().

Después la ejecución de una de las dos operaciones (GraPathFill() o GraPathOutline()), el camino gráfico se
descarta y no queda disponible nuevamente. Cuando debe bordearse externamente un área así como rellenada,
un camino gráfico puede definirse dentro de un segmento de gráfico. El camino puede luego redefinirse usando
GraSegDraw(). A saber:

nSegmentoID := GraSegOpen() // abre el segmento

GraPathBegin() // inicia al camino


/* ejecuta Primitivas Gráficas */
GraPathEnd() // finaliza el camino

GraSegClose() // cierra el segmento

GraPathFill() // rellena un área


GraSegDraw( , nSegmentoID ) // redefine el camino

GraPathOutline() // bordea externamente el camino

Al contrario de GraPathOutline(), la operación del camino GraPathFill() requiere que el área descripta por el c a-
mino se cierre. Si este no es el caso, la ventana entera es rellenada por GraPathFill().

Se lleva a cabo una operación especial usando GraPathClip(). Cualquier cosa que se dibuje en la ventana esta
limitado al área definida por el camino gráfico. La salida fuera de esta ventana no es posible mientras se establece
el camino de recorte. Esto incluye todas las funciones gráficas así como las funciones disponibles en modo híbrido,
como QOut() y Alert(). El camino de recorte se activa usando GraPathClip(, .T.) y debe desactivarse usando
GraPathClip(, .F.).

Los caminos gráficos

Esta ilustración muestra algunas maneras en las que pueden usarse los caminos gráficos. Sobre la izquierda se
define un camino de recorte por dos círculos concéntricos (usando la función GraArc()). Dentro del camino de
recorte, se dibuja varias veces una cadena de caracteres usando GraStringAt(). El "aeroplano de papel" consiste
de sólo siete puntos. Los puntos se conectan entre si usando GraLine() y los caminos gráficos se definen y bor-
dean externamente y rellenan con varios patrones. Tres patrones de llenado se utilizan para ilustrar los caminos
gráficos separados, (las dos "alas" son un camino gráfico). El texto "Alaska" se dibuja entre GraPathBegin() y
GraPathEnd() y se rellena después con un patrón usando GraPathFill(). Esto demuestra que un camino gráfico por
si sólo puede consistir de varias áreas incluidas individualmente. Cada letra forma un área cerrada separada.

Las transformaciones gráficas y operaciones sobre retícula


El motor GRA posibilita que los dibujos creados con las primitivas gráficas sean transformados. "Transformado"
significa que un dibujo puede girarse, invertirse, agrandarse o reducirse en tamaño. Hay aquí una distinción im-
portante entre imágenes vectoriales y reticuladas. Una imagen vectorial es un dibujo definido sólo por puntos en el
sistema de coordenadas y como estos puntos se conectan entre sí por otras líneas. Las líneas pueden definir el
borde de un área y el área puede rellenarse con un color o con un patrón. A la imagen reticulada se la llama ge-
neralmente "bitmap". Un punto en un bitmap se lo llama "pixel".

Hay tres funciones en el motor GRA para transformar imágenes vectoriales y una única función para llevar a cabo
operaciones reticuladas. Estas cuatro funciones se listan en la siguiente tabla:

Funciones para transformaciones y operaciones reticuladas


Función Descripción
GraRotate() Calcula la matriz de transformación de rotación
GraScale() Calcula la matriz de transformación de escala
GraTranslate() Calcula la matriz de transformación de la translación
GraBitBlt() Desarrolla operaciones con imágenes reticuladas (bitmaps)
Transformación desde imágenes de vectores
Las seis primitivas gráficas del motor GRA se emplean para crear imágenes vectoriales (excepto que cuando se
establece un Font de bitmap, las cadenas de caracteres se dibujan como imágenes reticuladas). Una imagen
vectorial puede transformarse de cualquier forma. Cabe destacar la limitación practica consistente en que las
transformaciones de las primitivas gráficas no es posible, sino solamente la transformación de los segmentos
gráficos (estos contienen primitivas gráficas). Por tanto, cuando han de transformarse los dibujos estos deben
definirse dentro de los segmentos de gráfico.

Las tres posibles transformaciones gráficas de una imagen vectorial involucran las operaciones de "rotación" (girar
una imagen", "escala" (agrandar o reducir una imagen) y "translación" (mover, copiar o invertir una imagen). Cada
una de estas operaciones requiere el cálculo de una matriz de transformación. Los cálculos para las tres posibles
transformaciones se hace usando las funciones GraRotate(), GraScale() y GraTranslate(). Las tres funciones sólo
calculan una matriz de transformación para una de estas tres posibles transformaciones gráficas, no despliegan
nada. La matriz de transformación gráfica misma es un arreglo bidimensional con tres filas y tres columnas. El
arreglo debe crearse antes de la transformación usando GraInitMatrix(). GraInitMatrix() es una pseudo función
definida en GRA.CH. Se traduce a un arreglo bidimensional que debe usarse para calcular una matriz de trans-
formación.

Para hacer visible la transformación, el segmento de gráfico debe re- dibujarse. Esto ocurre usando la función
GraSegDraw() y pasando a la matriz de transformación conteniendo el resultado de los cálculos para la trans-
formación deseada a ésta función. Aquí hay un ejemplo:

#include "Gra.ch"

PROCEDURE Main
LOCAL nSegmento, i, aMatriz := GraInitMatrix()

SetColor("N/W") // pintar ventana con gris pálido


CLS

nSegmento := GraSegOpen() // definir segmento


GraBox( NIL, {200,150}, {300,230}, GRA_OUTLINE )
GraSegClose() // crear un recuadro en un segmento

GraSegDraw( NIL, nSegmento ) // desplegar recuadro

FOR i:=1 TO 12 // dibujar recuadro 12 veces rotando


// cada uno 30 grados a la izquierda
GraRotate ( NIL, aMatriz, -30, {200,150}, GRA_TRANSFORM_ADD )
GraSegDraw( NIL, nSegmento, aMatriz )
NEXT
RETURN

En este ejemplo, se crea un segmento de gráfico que contiene sólo una primitiva de gráfica (GraBox()). Dentro del
ciclo FOR..NEXT, el segmento se dibuja 12 veces, y cada vez que se calcula una rotación de -30 grados basadas
en un punto de giro en la esquina inferior izquierda (el punto {200,150}). El ángulo negativo de -30 grados causa
una rotación hacia la izquierda (un ángulo positivo causaría una rotación hacia la derecha). El ejemplo muestra
que es esencial calcular primero la matriz de transformación. Para calcular la matriz de transformación es nece-
saria una matriz creada por GraInitMatrix(). El resultado del cálculo de GraRotate() es visible sólo luego de llamar
a GraSegDraw(). La matriz de transformación debe pasarse a la función GraSegDraw().

Una transformación gráfica requiere un total de cuatro pasos. Primero, la matriz de transformación debe crearse
usando GraInitMatrix(). El segundo paso consiste en definir el segmento de gráfico que contiene el dibujo a
transformar. Tercero, la transformación se calcula usando GraRotate(), GraScale() o GraTranslate() para crear la
matriz de transformación. Finalmente, la transformación gráfica se despliega usando GraSegDraw(). La matriz de
transformación que se cálculo previamente usando GraRotate(), GraScale() o GraTranslate() debe pasarse a la
función GraSegDraw().

Nota
Las dos constantes #define GRA_TRANSFORM_ADD y GRA_TRANSFORM_REPLACE afectan el cálculo de
transformaciones repetidas. Si se especifica GRA_TRANSFORM_ADD, la matriz de transformación queda inal-
terada luego de emplearla para exhibir el segmento. GRA_TRANSFORM_REPLACE (predeterminada) produce
que se restablezca la matriz de transformación al valor inicial de GraInitMatrix() cuando está completo el des-
pliegue usando la matriz de cálculo. Usando GRA_TRANSFORM_ADD, diversas transformaciones pueden s u-
marse efectivamente. GRA_TRANSFORM_REPLACE comienza cada transformación gráfica con la matriz inicial
predeterminada por GraInitMatrix().
Importante: Estas transformaciones son válidas sólo para imágenes vectoriales. No pueden aplicarse a imágenes
reticuladas (bitmaps).

Transformación de bitmaps - Operaciones reticuladas


Las imágenes reticuladas (bitmaps) pueden transformarse de manera similar a las imágenes vectoriales con la
limitación que las imágenes en la forma de bitmap no pueden girarse (la rotación no es posible con bitmaps). Los
bitmaps pueden copiarse, agrandarse o reducirse en su totalidad o por porciones. La función GraBitBlt() maneja
estas tareas. No necesita calcularse ninguna matriz de transformación para la transformación de imágenes re-
ticuladas. Las coordenadas origen y destino se pasan a la función GraBitBlt() en forma de arreglo. GraBitBlt()
procesa las coordenadas para el área del bitmap que se copiará y las coordenadas para el área en la cual se
copiará. Las coordenadas fuente corresponden al Espacio de Presentación en el cual la imagen reticulada está
desplegada y las coordenadas blanco corresponden al Espacio de Presentación en el que esta copiado. Ambos
espacios de presentación pueden asociarse con el mismo dispositivo de contexto. En este caso, una imagen re-
ticulada del área de donde se copiaron las coordenadas fuente al área de las coordenadas blanco. Cuando las
coordenadas fuente y blanco describen áreas de diferentes tamaños, se produce la escala automática de la
imagen reticulada. La siguiente ilustración muestra el resultado de varias operaciones reticuladas usando Gra-
BitBlt(). La imagen inicial se exhibe del lado superior izquierdo:

Operaciones reticuladas con GraBitBlt()

Para crear esta ilustración, se despliega un bitmap inicialmente en el lado superior izquierdo de la imagen y luego
se llama a la función GraBitBlt() cuatro veces usando diferentes coordenadas fuente y blanco. La imagen reticu-
lada se copia, agranda, y reduce entera y por secciones.

Una característica especial de la función GraBitBlt() es que las áreas fuente y blanco de la operación reticulada
pueden ocurrir en dos espacios de presentación diferentes. La salida de la imagen reticulada depende del dispo-
sitivo de contexto enlazado al Espacio de Presentación cuando ocurre el despliegue. Por ejemplo, es posible co-
piar imágenes reticuladas desde un Espacio de Presentación asociado con una ventana a un espacio de pre-
sentación asociado con un dispositivo de contexto como la impresora. De esta manera, las imágenes reticuladas
visibles en una ventana pueden visualizarse cuando se imprimen.

Espacios de Presentación para Salidas gráficas


Un Espacio de Presentación es el elemento central para la salida gráfica. Xbase++ usa la clase XbpPresSpace()
para proveer un mecanismo simple para acceder a espacios de presentación en el nivel del lenguaje de Xbase++.
Los elementos esenciales de un Espacio de Presentación han sido descriptos anteriormente en la sección Con-
ceptos básicos de la salida gráfica Como se discutió, un Espacio de Presentación pone a disposición un área de
dibujo abstracto que contiene toda la información de los dispositivos independientes sobre un dibujo. La salida de
un dibujo en el dispositivo de salida gráfica depende del dispositivo de contexto asociado con el Espacio de
Presentación. Un dispositivo de contexto maneja a un dispositivo de salida y se lo accede en Xbase++ usando
clases de instancia, como XbpPrinter() o XbpFileDev(). La interacción entre el Espacio de Presentación y varios
dispositivos de contexto se describen en la próxima sección. Este capitulo describe diferentes espacios de pre-
sentación. Comienza con una descripción de las características comunes a todos los espacios de presentación.

Sistema de coordenadas y área de visualización


El Espacio de Presentación inteligente - XbpCrt:presSpace()
El Espacio de Presentación de alta velocidad - Xbp:lockPS()
El Espacio de Presentación completo - XbpPresSpace()

Sistema de coordenadas y Area de Visualización


Esta sección cubre el aspecto más importante de un espacio de presentación; el sistema de coordenadas. El
Espacio de Presentación determina la unidad de medida del sistema de coordenadas usado para la salida gráfica.
Las unidades pueden ser pixel, centímetro, pulgada o una medida arbitraria.

El Espacio de Presentación determina el origen del sistema de coordenadas (el punto {0,0}) junto con la unidad de
medida para la salida. Toda la salida gráfica esta en relación con el sistema de coordenadas del dispositivo de
salida. Cuando se visualizan los elementos gráficos dos sistemas de coordenadas son considerados: el sistema
de coordenadas del Espacio de Presentación y el del dispositivo de contexto.

El sistema de coordenadas del Espacio de Presentación esta predeterminado por el tamaño de la página. La
página determina el espacio disponible donde se puede producir la salida gráfica. Ya que toda la salida gráfica
ocurre en el Espacio de Presentación lo que sale usa las coordenadas de página del Espacio de Presentación.
El Espacio de Presentación muestra la pagina en el dispositivo de contexto. Usa lo que se conoce como "viewport".
Toda la pagina de un Espacio de Presentación se despliega completamente en el Area de Visualización. El Area
de Visualización está referido a las coordenadas del dispositivo de salida, significando que el tamaño del Area de
Visualización determina que es visible en el dispositivo de salida. El tamaño de la pagina determina que puede
exhibirse en el Espacio de Presentación.

El tamaño de la página de un Espacio de Presentación así como el tamaño del Area de Visualización pueden
establecerse para un Espacio de Presentación. El tamaño de la página se determina usando el méto-
do :setPageSize() y el tamaño del Area de Visualización se establece usando el método :setViewPort() . La página
determina el despliegue en el Espacio de Presentación y el Area de Visualización determina el despliegue en el
dispositivo de salida (en el dispositivo de contexto). La siguiente ilustración muestra el efecto de Areas de Visua-
lización de diferente tamaño con dos páginas de igual tamaño y dispositivos de salida de igual tamaño en dos
ventanas:

Viewport A es más pequeño que la ventana (viewport B > ventana)

La ilustración muestra dos ventanas de igual tamaño con un área de dibujo de aproximadamente 260 * 200 pixeles
(excluyendo la barra del título) que presentan la misma imagen. La única diferencia entre las ventanas es el ta-
maño del Area de Visualización de sus respectivos espacios de presentación. El área gris en la ilustración re-
presenta al Area de Visualización. En la ventana izquierda el Area de Visualización es más pequeño que la ven-
tana. En la ventana derecha, la imagen se sale del borde de la ventana porque el Area de Visualización es más
grande que la ventana. Cualquier cosa que esté afuera del borde de la ventana de la ventana derecha no será
visible en la pantalla. El siguiente código del programa muestra como puede ser influenciado el Area de Visuali-
zación:
oPS1 := oWindowA:presSpace()
oPS1:setViewPort( {20, 20, 240, 180} ) // Area de Visualiz. < ventana

DisplayGraphic( oPS1 ) // dibuja imagen

oPS2 := oWindowB:presSpace()
oPS2:setViewPort( {-20, -20, 280, 240} ) // Area de Visualiz. > ventana

DisplayGraphic( oPS2 )

En ambas ventanas, la imagen se dibuja con las mismas coordenadas. Sin embargo, en la ventana izquierda el
Area de Visualización del Espacio de Presentación es más pequeño que el dispositivo de salida (la ventana). De tal
manera, la imagen se reduce. En la ventana derecha, el Area de Visualización es más grande que el dispositivo de
salida. Lo que se despliega en la ventana se agranda y partes de la imagen se cortan. El área gris corresponde al
Area de Visualización. Este código del programa denota que las coordenadas del Area de Visualización están
especificadas como coordenadas de la ventana. Por tanto, el Area de Visualización en la ventana izquierda c o-
mienza con el punto {20,20}, que está comprendido dentro de la ventana porque empieza en el punto {-20,-20}.

Un Espacio de Presentación transfiere la salida gráfica a través del Area de Visualización en el dispositivo de
contexto. El Area de Visualización de un espacio de presentación está siempre relacionado al sistema de coor-
denadas del dispositivo de salida. En el ejemplo, el dispositivo de contexto es 260*200 pixeles de grande y se
definen dos Areas de Visualización de diferente tamaño. De esta manera, las imágenes pueden escalarse de
cualquier forma en el despliegue porque el Area de Visualización determina las coordenadas en el dispositivo de
salida donde se produce la salida gráfica. Si el Area de Visualización no tiene el mismo tamaño que el dispositivo
de salida, se lleva a cabo una transformación gráfica automáticamente durante la salida de una imagen al dispo-
sitivo de contexto.

Junto con el Area de Visualización, el tamaño del área de dibujo abstracta que representa al Espacio de Pre-
sentación puede establecerse para un espacio de presentación. Esto se produce usando el método :setPageSize()
para definir el tamaño de la "página" del Espacio de Presentación. El tamaño de la página afecta las dimensiones
del sistema de coordenadas en un Espacio de Presentación.

La página de un Espacio de Presentación en la ventana derecha

La ilustración nuevamente muestra dos ventanas del mismo tamaño con un área de dibujo de 260*200 pixeles, y
en ambas ventanas se despliega la misma imagen. Aquí el tamaño de las páginas es diferente:
oPS1 := oWindowA:presSpace()
oPS1:setViewPort( {0, 0, 260, 200} ) // Area de Visualiz. = ventana
oPS1:setPageSize( {260, 200} ) // tamaño de página = ventana

DisplayGraphic( oPS1 ) // dibujar imagen

oPS2 := oWindowB:presSpace()
oPS2:setViewPort( {0, 0, 260, 200} ) // Area de Visualiz. = ventana
oPS2:setPageSize( {520, 400} ) // tamaño de página > ventana

DisplayGraphic( oPS2 )

En ambos casos, el Area de Visualización tiene el mismo tamaño que la ventana. Esto significa que todo lo que se
encuentra dibujado en el espacio de presentación también aparece en la ventana. Pero el tamaño de la página
para el Espacio de Presentación en la ventana derecha es cuatro veces más grande que en la ventana izquierda.
En la ventana izquierda el sistema de coordenadas del Espacio de Presentación extiende desde 0 a 200 en di-
rección del eje y (vertical), significando que la página es 260*200 pixeles de grande. En la ventana derecha el eje
x del espacio de presentación se extiende desde 0 a 520 y el eje y desde 0 a 400. Ya que la imagen en ambos
casos se despliega en las mismas coordenadas en el Espacio de Presentación es cuatro veces más pequeña en el
espacio de presentación de la ventana derecha que en la ventana izquierda.

El método :setPageSize() define el tamaño de la página o las dimensiones del sistema de coordenadas en el
Espacio de Presentación. El método :setViewPort() , sin embargo, especifica las coordenadas de dispositivo del
dispositivo de contexto donde se produce la salida. Ambos métodos causan una transformación gráfica cuando el
tamaño de la página del Espacio de Presentación no coincide con el tamaño del dispositivo de salida o cuando el
Area de Visualización no coincide con las dimensiones del sistema de coordenadas del dispositivo de contexto.

Los dos métodos :setPageSize() y :setViewPort() ofrecen dos formas de escalar un gráfico. Ambos métodos
pueden usarse para que se produzca una salida en una ventana. El método :setViewPort() puede usarse sola-
mente para la salida en pantalla y está limitado a la salida gráfica que contiene imágenes vectoriales. Ningún
sistema de escalado automático se lleva a cabo en el despliegue de imágenes reticuladas (bitmaps) u operaciones
reticuladas (la función GraBitBlt() ). El despliegue de bitmaps o de fuentes de bitmap ocurre siempre en las co-
ordenadas del dispositivo de contexto. Los caracteres que se exhiben con el fuente del bitmap no pueden esca-
larse y los bitmaps deben escalarse explícitamente usando GraBitBlt().

El Espacio de Presentación inteligente - XbpCrt:presSpace()


Una ventana XbpCrt se crea en el procedimiento AppSys() como la ventana de aplicación predeterminada. Provee
al modo híbrido de Xbase++ donde se pueden combinar salidas de pantalla orientadas a texto y gráficas. Esta es
la forma más fácil de desarrollar programas para GUI.

Como característica especial la ventana XbpCrt tiene ya un espacio de presentación y sirve como dispositivo de
contexto para él. Una ventana XbpCrt forma una unidad con su Espacio de Presentación y por tanto permite el uso
más fácil posible del GraphicsEngine. El método :presSpace() de una ventana XbpCrt devuelve su espacio de
presentación. Este método existe sólo en la clase XbpCrt. Mientras la ventana de aplicación es una ventana
XbpCrt, las funciones Gra..() pueden llamarse sin elegir un Espacio de Presentación como primer parámetro
puesto que predetermina a SetAppWindow():presSpace() .

Además de proveer al Espacio de Presentación predeterminado, una ventana XbpCrt tiene inteligencia interna que
encapsula complejidades del GUI con las que el programador se topa generalmente. El espacio de presentación
de un XbpCrt 'recuerda' lo que se está desplegando actualmente en la ventana. Retiene la salida gráfica. Esta
inteligencia se vuelve obvia cuando una ventana XbpCrt se cubre temporalmente por otra ventana y luego se re-
gresa al frente. En este caso, la parte cubierta de una ventana de XbpCrt debe refrescarse o pintarse una vez más,
respectivamente. Para repintarse, una ventana recibe al evento xbeP_Paint del sistema operativo. Es normal-
mente una tarea del programador implementar un código para un repintado de la ventana. Este no es el caso con
las ventanas de XbpCrt. Procesa mensajes xbeP_Paint por las suyas y causa que su Espacio de Presentación
despliegue nuevamente partes perdidas de los contenidos de la ventana.

Debido a su inteligencia interna, el Espacio de Presentación de una ventana XbpCrt garantiza a toda la salida vi-
sible que se despliegue nuevamente cuando la ventana XbpCrt se cubre temporalmente y trae al frente una vez
más. Asume el siguiente código de línea:

GraLine( , {0,0}, {640,400} )

Esta llamada a la función GraLine() dibuja una línea, diagonal desde el lado inferior izquierdo hasta la esquina
superior de la derecha. Permanece visible inclusive si se les hace clic a las otras ventanas al frente temporaria-
mente. La línea se despliega nuevamente cuando una ventana XbpCrt regana foco y no debe implementarse
ningún código para procesar el evento xbeP_Paint.

El Espacio de Presentación de alta velocidad - Xbp:lockPS()


Para salidas gráficas comunes con las funciones Gra..(), se utiliza un Espacio de Presentación denominado Micro
(Micro PS). El sistema operativo mismo tiene un buffer de Micro PSs y provee a las aplicaciones sobre demanda.
Un Micro PS se optimiza para salida gráfica de alta velocidad y se la reutiliza por el sistema operativo. cuando una
ventana usa un Micro PS y se produce la salida gráfica, se regresa al Micro PS al sistema operativo donde se
recolecta en un buffer para requerimientos posteriores. Por tanto, no se produce una ubicación que consume
mucho tiempo y la liberación de la memoria con un Micro PS.

Un Micro PS puede usarse por todos los Componentes de Xbase subclasificados a partir de la clase XbpWindow.
Se solicita y devuelve por el método :lockPS() . Un Componente de Xbase tiene acceso exclusivo a este Micro PS
hasta que lo libera con :unlockPS() y lo devuelve al sistema operativo. De lo contrario, con el nivel de lenguaje de
Xbase++, un Micro PS se representa por un objeto. Tiene los mismos métodos que la clase XbpPresSpace, ex-
cepto por los métodos :create() , :configure() y :destroy() . Debido a detalles técnicos del sistema operativo, un
Micro PS puede crearse solamente por :lockPS() y debe liberarse con :unlockPS() (nota: la clase XbpCrt no se
subclasifica desde XbpWindow. Por tanto, un Micro PS no puede usarse por una ventana XbpCrt). El uso general
de un Micro PS sigue con este patrón:

oPS := oXbp:lockPS() // requiere Micro PS


Gra???( oPS, ... ) // Salida Gráfica
oXbp:unlockPS( oPS ) // libera Micro PS

Se despliega un dibujo en un Micro PS de la misma manera que en un Espacio de Presentación de una ventana
XbpCrt. Sin embargo, debido a esta optimización de la performance no están reconocidos segmentos gráficos así
como caminos gráficos. La salida gráfica se limita a funciones Gra..() simples. Las funciones complejas GraSe-
gOpen() y GraPathBegin() además de las funciones correspondientes para manejar segmentos y caminos gráficos
no pueden usarse con un Micro PS. Además, un Micro PS no realiza un buffer de pantalla. Esto requiere de la
reacción de un Componente de Xbase con el evento xbeP_Paint. Cuando se usa un Micro PS, debe implemen-
tarse el código para la salida gráfica a desplegarse luego de eventos de re- pintado. Este código puede progra-
marse tanto en una función llamada por el bloque de códigos de la pila de llamadas de :paint , o debe programarse
en el método de pila de llamadas de :paint() de una clase derivada. Un ejemplo de uso de un Micro PS se en-
cuentra en ...\SAMPLES\XPARTS\MICROPS.PRG.

El Espacio de Presentación completo - XbpPresSpace()


Las instancias de la clase XbpPresSpace son espacios de presentación completos (PS). Pueden usarse por todas
las funciones del motor de GRA. Esto incluye salida gráfica compleja que consiste de segmentos gráficos y/o
caminos gráficos. Si un segmento gráfico debe desplegarse en Partes de Xbase subclasificadas desee la ventana
XbpWindow, un objeto de XbpPresSpace debe conectarse al dispositivo de ventana de este Componente de
Xbase. Si se despliega un dibujo en una ventana de XbpDialog, por ejemplo, el dispositivo de salida es
XbpDialog:drawingArea y el PS debe conectarse a su dispositivo de ventana:

oDialog := XbpDialog():new() // Crear una ventana XbpDialog


oDialog:create( ,, {0,0}, {600,400} )

oPS := XbpPresSpace():new() // Crear un PS


oDevice := oDialog:drawingArea:winDevice() // Obtener el dispositivo
// de contexto
oPS:create( oDevice ) // Vincula el dispositivo
// de contexto al PS
GraLine( oPS, {0,0}, {600,400} ) // Desplegar dibujo

El ejemplo demuestra los pasos diferentes para usar un PS completo. El dispositivo de contexto (el dispositivo de
salida) lo devuelve el método :winDevice() , y es enlazado al PS cuando se lo pasa al método :create() . Luego es
posible crear un dibujo usando las funciones Gra..() y la salida aparece en la ventana de XbpDialog. Ya que el PS
no hace un buffer de la salida de pantalla, el código que crea un dibujo debe ejecutarse repetidamente luego de los
eventos de xbeP_Paint.

Dispositivos de salida gráfica


Con el fin de hacer visible la salida gráfica en un espacio de presentación de una aplicación GUI, el Espacio de
Presentación debe enlazarse a un dispositivo de contexto. Este último contiene toda la información dependiente
del dispositivo requerida para la salida de un dibujo en un dispositivo de salida. En la mayoría de los casos el
despliegue de gráficos ocurre en una ventana en la pantalla. Una ventana representa un dispositivo de contexto. El
motor GRA y sus funciones no necesitan considerar tanto un Espacio de Presentación o un dispositivo de contexto
mientras la salida se produce en la pantalla en una ventana de XbpCrt. Pero la salida de un gráfico puede ocurrir
también en un archivo o la impresora. En este caso, un dispositivo de contexto debe asociarse a un Espacio de
Presentación. La salida a una impresora es extremadamente dependiente del dispositivo. Requiere la instalación
correcta de una unidad de impresión en el entorno de trabajo. Se usa una impresora para la salida en Xbase++ por
un objeto de la clase XbpPrinter(). Objetos de XbpPrinter preparan a un dispositivo de contexto para la salida
gráfica a una impresora. Si la salida debe salir al archivo, debe tomarse una decisión acerca de si el archivo
contendrá un Metaarchivo o una imagen reticulada (bitmap). Ambos tipos de archivos contienen datos gráficos y
hay clases de objetos para manejar el formato de archivos para ambos formatos de archivo. Estas clases son:
XbpMetafile() and XbpBitmap(), respectivamente.
La impresora - XbpPrinter()
El Metaarchivo - XbpMetaFile()
La imagen reticulada - XbpBitmap()
Usando ventanas como dispositivos de salida
La salida a la ventana y la impresora - WYSIWYG

La Impresora - XbpPrinter()
Los objetos de la clase XbpPrinter se usan para salida impresa. Esto requiere la instalación de la unidad de im-
presión apropiada para una impresora.

Conceptos Básicos para la impresión de gráficos


La clase XbpPrinter crea una conexión con las unidades de impresión, o con los objetos de impresión, respecti-
vamente, instalados en el sistema. Si un objeto de impresión no es configurado correctamente, la salida gráfica
correcta a la impresora desde Xbase++ no está garantizada. Los objetos de impresión se identifican en Xbase++
por su nombre, el cual se despliega debajo de un icono de impresión. Un objeto de XbpPrinter controla un objeto
de impresión y representa el dispositivo de contexto para un Espacio de Presentación en el que la información
gráfica se despliega. Para preparar una impresión de imagen por tanto el contexto del dispositivo impresora (ob-
jeto XbpPrinter)se asocia con un espacio de presentación. La siguiente función definida por el usuario es la
adecuada para ello:

FUNCTION PrinterPS( cNombreObjetoImpresora )


LOCAL oPS, oDC := XbpPrinter():new()
oDC:create( cNombreObjetoImpresora )
oPS := XbpPresSpace():new()
oPS:create( oDC, oDC:paperSize(), GRA_PU_LOMETRIC )
RETURN oPS

La función definida por el usuario PrinterPS() crea un espacio de presentación asociado con el contexto del dis-
positivo impresora. Recibe como parámetro el nombre de uno de los objetos de impresión instalados en el sistema
como una cadena de caracteres y crea un objeto XbpPrinter que mantiene la conexión al objeto de impresión. En
la función, se crea un nuevo Espacio de Presentación y al objeto de impresión se lo provee como dispositivo de
contexto. El valor de retorno del método :paperSize() se utiliza para el tamaño de página del espacio de presen-
tación. Esto asegura que el Espacio de Presentación y el dispositivo de contexto tengan el mismo tamaño de
sistemas de coordenadas. Puesto que el tamaño del papel siempre se devuelve por el método :paperSize() en las
unidades de 1/0mo de milímetros, la constante GRA_PU_LOMETRIC se especifica en la llamada a oPS:create()
para establecer las mismas unidades en el Espacio de Presentación.

La salida de los gráficos en la impresora puede llevarse a cabo usando la función PrinterPS() como se muestra en
el siguiente ejemplo:

oPS := PrinterPS() // Espacio de Presentación con


// objeto impresora predeterminado
oPS:device():startDoc() // iniciar salida por impresora
// (abrir spooler)
GraBox( oPS, {0,0}, {1000,1000} ) // dibujar recuadro

oPS:device():endDoc() // fin de salida por impresora (cerrar spooler)

Estas cuatro líneas de código muestran el procedimiento gráfico para la impresión de gráficos. Un Espacio de
Presentación asociado con un objeto XbpPrinter (contexto del dispositivo impresora) deben estar disponibles. En
el ejemplo, el método :device() devuelve al objeto XbpPrinter y comienza la salida de la impresión usando la
llamada al :startDoc() . Generalmente, los trabajos de impresión son enviados a la cola de impresión y :startDoc()
abre el spooler. Toda la salida gráfica que se produce en el Espacio de Presentación se envía al spooler. El final
del trabajo de impresión se señala por el método :endDoc() que cierra al spooler.

La función PrinterPS() muestra cómo se crea un Espacio de Presentación para salidas en la impresora, y también
muestra la dificultad básica de la salida gráfica: el sistema de coordenadas de los dispositivos de salida "pantalla"
e "impresora" difieren en cosas como las unidades del sistema de coordenadas de los dos dispositivos. El si-
guiente código del programa ilustra esto:

GraBox( oPS, {0,0}, {1000,1000} )

Esta llamada a GraBox() dibuja un cuadrado con una longitud de eje de 1000 unidades. Cómo y dónde se des-
pliega el cuadrado depende del dispositivo de contexto asociado con el Espacio de Presentación oPS y en las
unidades para el sistema de coordenadas. La unidad del dispositivo para la pantalla es "pixel" y el cuadrado será
parcialmente visible sólo en la pantalla excepto cuando hay una extremadamente alta resolución de pantalla. Si la
impresora es el dispositivo de contexto, la unidad es 1/0mo de milímetros y el cuadrado será desplegado con una
longitud de eje de 10cm que se acomoda a los formatos de papel más comunes.

Imprimir texto como Gráfico


El problema de los diferentes sistemas de coordenadas y las unidades se vuelven especialmente obvias cuando
se produce la salida de los caracteres de texto. La función GraStringAt() dibuja una cadena de caracteres en un
Espacio de Presentación. El Font establecido en el Espacio de Presentación es significativo. Hay fuentes que
pueden mostrarse en pantalla únicamente, otros que sólo pueden mostrarse a través de la impresora y otros que
pueden mostrarse en ambos dispositivos. Cuando sale el texto por la impresora, los fonts que son sólo para
despliegue en la pantalla obviamente no pueden usarse. El siguiente programa de ejemplo ilustra este problema:

#include "Gra.ch"
#include "Xbp.ch"

PROCEDURE Main
LOCAL oWindowPS, oImpresoraPS, oFont, aListaDeFonts
LOCAL i, imax, nY, nPointSize, cTexto, aSize

// Espacio de Presentación para ventana e impresora


oWindowPS := SetAppWindow():presSpace()
oImpresoraPS := PrinterPS()

// crear objeto font


oFont := XbpFont():new( oWindowPS )

// leer la lista de fonts disponibles


aListaDeFonts := oFont:list()

// Imprime los fonts en la impresora


oImpresoraPS:device():startDoc()

imax := Len( aListaDeFonts )


nY := oImpresoraPS:device():paperSize()[2] - 100
nPointSize := 1

FOR i:=1 TO imax

// imprime sólo los fonts de vectores universalmente válidos


IF aListaDeFonts[i]:generic .AND. aListaDeFonts[i]:vector

oFont := aListaDeFonts[i]
oFont:nominalPointSize := ++nPointSize

// crea texto para imprimir


cTexto := Str( nPointSize, 2 ) + "." + ;
oFont:compoundName

// establece font para la impresora


GraSetFont( oImpresoraPS, oFont )

// calcula tamaño y nueva posición de la cadena


aSize := GraQueryTextBox( oImpresoraPS, cTexto )
nY := nY - aSize[1,2]

// imprime una cadena y libera el font


GraStringAt( oImpresoraPS, {20,nY}, cTexto )
oFont:destroy()

ENDIF
NEXT

oImpresoraPS:device():endDoc()
RETURN
Este programa de ejemplo imprime el tamaño y el nombre de todos los fonts vectoriales en la impresora. Primero,
se determina el espacio de presentación de la ventana de aplicación. La función PrinterPS() crea un Espacio de
Presentación que se enlaza con un dispositivo de contexto de impresión. Un objeto XbpFont se genera luego para
manejar los fonts. La llamada oFont:list() devuelve un arreglo conteniendo los objetos Font para todos los fonts
que pueden desplegarse en una ventana. La salida de impresión comienza luego. El texto a ser impreso se crea en
el ciclo FOR..NEXT. Sólo se imprime cuando las variables de instancia :generic y :vector de un objeto Font en el
arreglo aListaDeFonts contiene .T. (verdadero). Este es el prerrequisito para los fonts que pueden desplegarse en
una ventana y puede también mostrarse a escala en la impresora. Dentro del ciclo FOR..NEXT, los objetos de
fuente que satisfacen el prerrequisito se pasan usando GraSetFont() para el Espacio de Presentación oImpre-
soraPS que se asocia con el contexto del dispositivo impresora. En este punto en el programa de ejemplo, el
Espacio de Presentación conoce que debe imprimirse y los datos métricos para el tamaño de las letras. Puesto
que el tamaño del punto para un Font se incrementa de a uno después que cada uno pasa a través del ciclo, la
posición y de la cadena de caracteres debe ajustarse continuamente a la altura de las letras. La altura de la cadena
de caracteres cTexto se determina usando la función GraQueryTextBox().

El programa de ejemplo trata todos los problemas que deben considerarse en la programación de la salida gráfica
del texto en una impresora. El texto se muestra en modo gráfico usando la función GraStringAt(). Con el fin de que
los caracteres sean impresos en el papel la salida debe ocurrir en un Espacio de Presentación enlazado con el
contexto del dispositivo impresora (un objeto XbpPrinter). el Font usada para la salida de la impresión se establece
usando un objeto XbpFont. los fonts disponibles se determinan usando el método :fontList() de un objeto fuente.
En el ejemplo, todas los fonts que pueden desplegarse en un Espacio de Presentación para una ventana se
prueban puesto que el espacio de presentación de la ventana de aplicación se provee al objeto XbpFont
(XbpFont():new(oWindowPS) ). Sólo los fonts de vector independientes de dispositivo (:generic y :vector son .T.)
pueden usarse para impresión. Cuando :generic y :vector son .T., esto indica que el Font puede escalarse co-
rrectamente durante la impresión. La posición exacta donde se imprime una cadena de caracteres cTexto en el
programa de ejemplo debe calcularse después de cualquier paso a través del ciclo usando la función GraQue-
rytextBox(), ya que el tamaño del punto de el Font está bajo cambio continuo.

Se puede resumir el uso de los objetos XbpPrinter para mostrar texto o gráficos de la siguiente manera:

- El objeto de impresión debe instalarse y configurarse correctamente.


- La salida debe ocurrir en un Espacio de Presentación que tiene un objeto XbpPrinter como su dispositivo de
contexto.
- El sistema de coordenadas del objeto XbpPrinter se basa en la unidad de 1/10 de milímetro.
- El Font para la salida de texto se establece usando un objeto XbpFont.
- El tamaño de una cadena de caracteres se determina por GraQueryTextBox().

Estos cinco puntos deben siempre considerarse usando objetos XbpPrinter o enviando la salida gráfica a una
impresora.

El Metaarchivo - XbpMetaFile()
Los Metaarchivos juegan un rol central en guardar e intercambiar datos gráficos y dibujos. Los Metaarchivos
guardan imágenes de la misma forma en la que se dibujan en el Espacio de Presentación y permiten que se re-
despliegue el dibujo como es necesario en la misma o en otra aplicación. Las imágenes pueden guardarse e im-
primirse en el formato de un Metaarchivo, intercambiarse entre dos estaciones de trabajo en este formato y
guardarse temporariamente en el Clipboard mientras se transfieren de una aplicación a la otra.

La secuencia de primitivas gráficas usadas para crear un dibujo se guardan en un Metaarchivo. Esto incluye las
coordenadas de las primitivas gráficas y sus atributos. un Metaarchivo generalmente contiene una imagen vecto-
rial porque guarda una secuencia de primitivas gráficas que pueden ser "recreadas" nuevamente luego que se
carga el Metaarchivo en un Espacio de Presentación. Los Metaarchivos se utilizan también para intercambiar
dibujos entre dos estaciones de trabajo. Mientras el Metaarchivo contenga un dibujo creado usando sólo primitivas
gráficas, puede mostrarse un dibujo en diferentes pantallas e impresoras. Las imágenes reticuladas pueden
guardarse también en un Metaarchivo (ver la función GraBitBlt() ), y en este caso no se garantiza el correcto re-
despliegue de un Metaarchivo en diferentes dispositivos de salida porque es dispositivo dependiente.

Xbase++ permite que se carguen, desplieguen, guarden y creen Metaarchivos. Para cargar y desplegar se usan
objetos de la clase XbpMetaFile(). Ellos manejan Metaarchivos que están ya guardados en el disco. Las si-
guientes líneas de código muestran cómo los Metaarchivos se exhiben en la ventana:

oMF := XbpMetaFile():new():create() // crear objeto Metaarchivo


oMF:load( "METAFILE.MET" ) // cargar Metaarchivo
oPS := SetAppWindow():presSpace() // obtener Espacio de Presentación
// desde la ventana
oMF:draw( oPS ) // redibujar el contenido del
// metaarchivo en el Espacio de Presentación

Este procedimiento se designa para guardar los Metaarchivos existentes en una aplicación de Xbase++. Pero
también es posible guardar las salidas gráficas creadas por una aplicación de Xbase++ en un Metaarchivo. Para
llevar a cabo esto, debe crearse un objeto XbpFileDev. Este representa al dispositivo de contexto donde la salida
gráfica de las funciones Gra...() se registran. Por ejemplo:

PROCEDURE Main
LOCAL oPS, oDC, oMF

SetColor( "N/W" ) // colorear la ventana con gris


CLS // pálido

oDC := XbpFileDev():New():Create() // crear dispositivo de contexto


// crear Espacio de Presentación
oPS := XbpPresSpace():New():Create( oDC ) // y combinarlo con DC

GraBox( oPS, {10,10}, {400,100} ) // Salida Gráfica


GraStringAt( oPS, {20,50}, "la Imagen será guardada en un metaarchivo")
oPS:configure() // desvincular dispositivo de contexto

oMF := oDC:metaFile() // crear objeto MetaArchivo


oDC:destroy() // liberar contexto del dispositivo

IF File( "Test.met" ) // asegurarse que no existe


FErase( "Test.met" ) // el archivo TEST.MET
ENDIF

oMF:save( "Test.met" ) // guardar la imagen en un archivo


WAIT "El MetaArchivo fue creado, presione una tecla..."

oMF:= XbpMetaFile():new():create() // nuevo objeto MetaArchivo


oMF:load( "Test.met" ) // cargar archivo

oPS:= SetAppWindow():presSpace() // obtener PS de la ventana


oMF:draw( oPS ) // salida en este PS

WAIT // esperar una tecla


RETURN

En este ejemplo, un objeto XbpFileDev se crea como un dispositivo de contexto y se asocia con un Espacio de
Presentación. La salida gráfica se lleva a cabo en el Espacio de Presentación. Cuando se completa la salida, el
dispositivo de contexto se separa del Espacio de Presentación usando una llamada al método :configure() del
Espacio de Presentación. El objeto XbpFileDev puede ahora crear un objeto XbpMetaFile conteniendo la salida
gráfica de GraBox() y GraStringAt(). El método :save() guarda esta información gráfica en un nuevo Metaarchivo.

La imagen reticulada - XbpBitmap()


Una imagen reticulada (bitmap) contiene información gráfica en forma de pixeles. Se guardan todos los pixeles en
un bitmap que se requieren para desplegar la imagen. La clase XbpBitmap provee la habilidad para crear y des-
plegar bitmaps. Las imágenes reticuladas guardadas en un archivo se conectan a un archivo ejecutable como un
recurso y pueden identificarse, cargarse y desplegarse usando un objeto XbpBitmap basado en el recurso ID
numérico. Usando objetos XbpBitmap de esta forma comienza con la declaración de un archivo bitmap (archivo
BMP) como un recurso dentro de un archivo RC. Por ejemplo:

/* Tipo de recurso ID de Recurso Nombre de Archivo */


ICON 1 = "\XPP\RESOURCE\XPPPMT.ICO"
POINTER 2 = "\XPP\RESOURCE\XPPPOINT.PTR"

BITMAP 2000 = "\XPP\BITMAP\PHOTO.BMP"

Si este archivo de recurso se compila por el compilador de recursos y enlaza al archivo ejecutable, un objeto
XbpBitmap puede cargar y desplegar el recurso 2000. Esto se realiza en el ejemplo a continuación:
PROCEDURE Main
LOCAL oBMP, oPS

SetColor( "N/W" )
CLS

// obtener Espacio de Presentación de la ventana XbpCrt


oPS := SetAppWindow():presSpace()

// crear XbpBitmap para este PS


oBMP:= XbpBitmap():new():create( oPS )

// cargar bitmap
oBMP:load( NIL, 2000 )

// desplegar bitmap en el PS
oBMP:draw( oPS, {50,100} )

WAIT
RETURN

Antes de la creación de un objeto XbpBitmap, el Espacio de Presentación y su dispositivo de contexto deben existir
para usarse para la salida de la imagen reticulada. En el ejemplo, la salida ocurre en el espacio de presentación de
una ventana XbpCrt y esto representa al dispositivo de salida. El Espacio de Presentación debe especificarse en la
llamada al método :create() que prepara al objeto XbpBitmap por el despliegue en la pantalla. La imagen reticulada
desde el archivo PHOTO.BMP con el recurso ID 2000 se carga luego usando el método :load() y despliega en el
Espacio de Presentación de la ventana usando :draw() .

Se utiliza un objeto XbpBitmap inclusive cuando una imagen reticulada debe crearse temporariamente en la
memoria principal. Se proveen los métodos :presSpace() y :make() para esta situación. Un objeto XbpBitmap se
asocia con un Espacio de Presentación usando :presSpace() . La salida gráfica puede producirse luego direc-
tamente en la imagen reticulada, y no es visible en la pantalla. Después de que un objeto XbpBitmap ha recibido su
propio Espacio de Presentación usando el método :presSpace() , el tamaño de la imagen reticulada deben es-
pecificarse usando el método :make() . La memoria de la información de pantalla (pixeles) se ubica luego. La salida
gráfica puede producirse entonces en el Espacio de Presentación de un bitmap.

El área más importante para este uso de los objetos XbpBitmap se encuentra en buffering del modo de salida
Gráfica por pantalla. El despliegue entero en una ventana o en una sección pueden guardarse como una imagen
reticulada de un objeto XbpBitmap y redesplegada tiempo después. La información gráfica del Espacio de Pre-
sentación de una ventana se copia al Espacio de Presentación de un objeto XbpBitmap usando la función Gra-
BitBlt(). Esto se muestra en el siguiente ejemplo en el código para la función definida por el usuario GraSaveS-
creen():

PROCEDURE Main
LOCAL oPS, oBitmap

// obtener Espacio de Presentación de la ventana XbpCrt


oPS := SetAppWindow():presSpace()

// dibujar recuadro
GraBox( oPS ,{10,10}, {100,100} )

// guardar recuadro
oBitmap := GraSaveScreen( oPS, {10,10}, {91,91} )

// limpiar la ventana
CLS
WAIT "El recuadro fue guardado"

GraRestScreen( oPS, {10,10}, oBitmap )


WAIT "el recuadro es redesplegado"
RETURN

FUNCTION GraSaveScreen( oSourcePS, aPos, aSize )


LOCAL oBitmap := XbpBitmap():new():create( oSourcePS )
LOCAL oTargetPS := XbpPresSpace():new():create()
LOCAL aRectangOrigen[4], aRectangDestino

aRectangOrigen[1] := aRectangOrigen[3] := aPos[1]


aRectangOrigen[2] := aRectangOrigen[4] := aPos[2]
aRectangOrigen[3] += aSize[1]
aRectangOrigen[4] += aSize[2]

aRectangDestino := {0, 0, aSize[1], aSize[2]}

oBitmap:presSpace( oTargetPS )
oBitmap:make( aSize[1], aSize[2] )

GraBitBlt( oTargetPS, oSourcePS, aRectangDestino, aRectangOrigen )


RETURN oBitmap

La función GraSaveScreen() recibe al Espacio de Presentación de una ventana en el parámetro oSourcePS . Éste
contiene la información gráfica a ser guardada para copiarla al Espacio de Presentación oTargetPS . Se crea un
objeto XbpBitmap como un dispositivo de salida que es útil para la salida en la pantalla (oSourcePS se pasa
a :create() ). Para que el objeto XbpBitmap pueda guardar la salida gráfica necesita su propio Espacio de Pre-
sentación que se pasa al objeto usando el método :presSpace() . El Espacio de Presentación creado reciente-
mente se referencia por oTargetPS . El tamaño de la imagen reticulada se especifica en :make() . El tamaño co-
rresponde al parámetro aSize que define las dimensiones de la sección de la pantalla a guardarse en la dirección
x y en la dirección y. Finalmente, la función GraBitBlt() copia los pixeles desde el espacio de presentación de la
ventana al Espacio de Presentación del objeto XbpBitmap.

La función definida por el usuario GraRestScreen() se utiliza para redesplegar la sección guardada de la pantalla.
La imagen reticulada manejada por un objeto XbpBitmap se redespliega en el Espacio de Presentación de una
ventana. Llamando al método :draw() es suficiente para llevar a cabo esta tarea:

FUNCTION GraRestScreen( oTargetPS, aPos, oBitmap )


oBitmap:draw( oTargetPS, aPos )
RETURN NIL

Un objeto XbpBitmap necesita un Espacio de Presentación donde salga una imagen reticulada manejada por el
objeto. Cuando se añade un bitmap como un recurso a un archivo ejecutable, llamando al método :load() es su-
ficiente para permitir el despliegue de una imagen reticulada. este método requiere de manera implícita un Espacio
de Presentación para el objeto XbpBitmap. Si, sin embargo, una imagen reticulada necesita crearse (durante el
buffering de pantalla, por ejemplo), el objeto XbpBitmap debe tener su propio Espacio de Presentación que es
provisto al objeto por el método :presSpace() . Este método debe ejecutarse antes de la llamada a :make() . :make()
define las dimensiones para una imagen reticulada y la memoria requerida para sostener todos los pixeles de la
imagen reticulada. La memoria requerida puede determinarse sólo cuando se provee un Espacio de Presentación
asociado a un dispositivo de contexto.

Usando ventanas como dispositivos de salida


En la discusión de ventanas que sirven como dispositivos de salida es necesario distinguir entre una ventana de
aplicación (cómo la proveen las clases XbpCrt y XbpDialog) de todos los demás Componentes de Xbase que
tienen una representación visual. Éstos, por supuesto, son ventanas también. La manera más fácil de concretar la
salida gráfica es usando una ventana de XbpCrt. Sirve como dispositivo de contexto para su Espacio de Pre-
sentación especializado y lleva a cabo salida de pantalla a través de bufferes. Por tanto, las funciones Gra..()
pueden usarse para dibujar en una ventana XbpCrt sin la necesidad de reaccionar al evento xbeP_Paint. Este
evento creado por el sistema operativo cuando una ventana necesita repintarse (parcialmente) a sí misma.

Ya que una ventana XbpDialog no hace un buffer de la salida de pantalla, debe reaccionar al mensaje xbeP_Paint
si las funciones Gra..() se usan para dibujar en la pantalla. Lo mismo es cierto para todos los demás Componentes
de Xbase. A parte de la salida de pantalla, una ventana XbpDialog no difiere de un botón XbpPushButton o de un
objeto XbpStatic. La única diferencia es que una ventana XbpDialog es un objeto compuesto y el dibujo debe
realizarse en el área de dibujo debajo de la barra del título de la ventana (XbpDialog:drawingArea ). En un ele-
mento XbpStatic, por ejemplo, la salida de pantalla aparece en el objeto mismo.

Con el fin de permitir que un Componente de Xbase reaccione al evento xbeP_Paint, debe reasignarse un bloque
de código a la variable de instancia :paint . Este bloque de código debe llamar a una rutina donde se implementa
la salida gráfica. Como alternativa, una clase definida por el usuario puede derivarse de un Componente de Xbase.
En este caso, el método :paint() debe sobrecargarse y el código para la salida gráfica debe implementarse en este
método. Cualquiera de las dos posibilidades requiere de Espacio de Presentación asociado con un Componente
de Xbase cuando se llaman a las funciones Gra..(). Esto puede ser un Micro Espacio de Presentación devuelto por
el método :lockPS() o por un objeto XpbPresSpace enlazado al dispositivo de contexto del Componente de Xbase.
Es el valor de retorno del método :winDevice() .

Los diferentes aspectos de la salida gráfica en las ventanas, o Componentes de Xbase, se ilustran en el programa
de ejemplo CHARTS.PRG que se localiza en el directorio ..\SOURCE\SAMPLES\GRAPHIC. Este programa dibuja
una tabla de simple entrada - gráfico simple de líneas conectando un conjunto de puntos con líneas usando
GraLine():

// los valores x e y para un gráfico de líneas en el rango 0-300


aValores := ;
{ { 30, 184}, ;
{ 60, 84}, ;
{ 90, 144}, ;
{120, 254}, ;
{150, 170}, ;
{180, 235}, ;
{210, 289}, ;
{240, 190}, ;
{270, 152}, ;
{300, 36} }

LineChart( , aValores )

PROCEDURE LineChart( oPS, aPuntos )


GraPos( oPS, {0,0} )
AEval( aPuntos, {|aPos| GraLine( oPS, , aPos )} )

RETURN

Este código ya trabajaría de haberse estado usando una ventana XbpCrt para despliegue. El procedimiento Li-
neChart() simplemente recibe un arreglo de valores especificando las coordenadas xy para conectar los puntos
con líneas. Sin embargo, el programa de ejemplo CHART.PRG utiliza un objeto XbpDialog y otro XbpStatic para el
despliegue.

La salida del programa de ejemplo CHART.PRG

El programa dibuja la tabla de simple entrada en la ventana XbpDialog y luego en tamaño reducido en el objeto
XbpStatic (marco-frame). El código del programa que despliega el XbpDialog se da debajo. Consiste de la crea-
ción de la ventana, la definición de bloque de códigos :paint , el despliegue de la ventana y el ciclo de eventos:
// Crear ventana oculta
oDlg := XbpDialog():new(,,{10,10},{400,400},,.F.)
oDlg:title := "Gráfico de Líneas"
oDlg:create()

// Define :bloque de código de pintado


oDlg:drawingArea:paint := ;
{|mp1,mp2,obj| mp1 := obj:lockPS(), ;
LineChart( mp1, aValores ), ;
obj:unLockPS( mp1 ) }

// Desplegar ventana
oDlg:show()

// Procesar eventos
DO WHILE nEvent <> xbeP_Close
nEvent := AppEvent( @mp1, @mp2, @oXbp )
oXbp:handleEvent( nEvent, mp1, mp2 )
ENDDO

El bloque de códigos :paint debe asignarse al área de dibujo del objeto XbpDialog (oDlg:drawingArea ) porque
exhibe el dibujo. Los bloques de código llaman al procedimiento LineChart() que dibuja la tabla. La evaluación del
bloque de código, sin embargo, tiene lugar en el método :handleEvent() cuando el evento xbeP_Paint es devuelto
a partir de AppEvent(). Los pasos siguientes conducen al despliegue de la tabla de entradas - Gráfico de Líneas.

Llamando oDlg:show() crea un evento xbeP_Paint en la cola de eventos

AppEvent() devuelve xbeP_Paint y oXbp contiene oDlg:drawingArea


oXbp:handleEvent( xbeP_Paint, mp1, mp2 )
evalúa el bloque de código :paint
Eval( oXbp:paint, mp1, mp2, oXbp ) despliega el Gráfico de Líneas

El resultado final del evento xbeP_Paint es que oDlg:drawingArea se pasa al bloque de códigos :paint como tercer
parámetro. Dentro del bloque de códigos se requiere un Micro PS usando :lockPS() que pasa junto con aValores al
procedimiento LineChart(). Llamar a una rutina de dibujo de esta manera vía el bloque de código :paint también
garantiza que se ejecuta el procedimiento nuevamente cuando la ventana se cubre temporariamente por otra y
recupera foco. En este caso, el sistema operativo crea un nuevo evento de xbeP_Paint y evalúa una vez más al
bloque de código :paint .

El despliegue de la tabla de entrada en el objeto XbpStatic sigue la misma lógica: se llama a LineChart() desde el
bloque de código :paint . Pero en vez de un Micro PS, se usa un objeto XbpPresSpace que se enlaza a un dis-
positivo de contexto de ventana del objeto XbpStatic:

// Crear recuadro estático dentro de oDlg:drawingArea


oXbp := XbpStatic():new( oDlg:drawingArea,,{270,250}, {80,80} )
oXbp:type := XBPSTATIC_TYPE_RAISEDBOX
oXbp:create()

// Vincula Espacio de Presentación al dispositivo de ventana


oPS := XbpPresSpace():new()
oPS:create( oXbp:winDevice(), {310,310} )

// Establece el Area de Visualización al tamaño del recuadro


oPS:setViewPort( {0,0,80,80} )

oXbp:paint := {|| LineChart( oPS, aValores ) }

La tabla de entrada se dibuja dentro del objeto XbpStatic con las mismas coordenadas exactas que con el objeto
XbpDialog. La razón por qué es completamente visible se da por el tamaño de página y el Area de Visualización
del Espacio de Presentación. La página es lo suficientemente grande como para desplegar todos los puntos
(tamaño 310*310 pixel vs. rango de 0-300). La página se escala al Area de Visualización que es tan grande como
el marco estático (80*80 pixel). Por tanto, la tabla se reduce en tamaño pero es completamente visible.
La salida a la ventana y la impresora - WYSIWYG
Un Espacio de Presentación contiene toda la información del dispositivo independiente de un dibujo. Puede en-
lazarse a diferentes dispositivos de salida y por ende un dibujo puede desplegarse en una ventana o en una im-
presora. Dependiendo del dispositivo de contexto al que un espacio de presentación se enlaza, un dibujo puede
desplegarse en una ventana o imprimirse. Con el fin de cumplir con la regla WYSIWYG (Lo Que Ve Es Lo Que
Obtiene), debe desplegarse un dibujo en la ventana en la escala correcta. Las proporciones de un dibujo des-
plegado en una ventana deben ser las mismas que en el papel luego de la impresión.

El despliegue de lo impreso en una ventana en la escala correcta involucra tres diferentes niveles de abstracción:
un espacio de presentación y dispositivos de contexto para la ventana y la impresora. Además, los dispositivos de
contexto para los dos dispositivos de salida tienen diferentes unidades de medida para su sistema de coordenadas.
Una ventana despliega un dibujo basado en la medida pixel mientras la impresora usa unidades de 1/10mo de
milímetro.

Vista preliminar de lo impreso en una ventana

Esta ilustración se basa en el programa de ejemplo PREVIEW.PRG. Demuestra varios aspectos para salidas
correctamente a escala en diferentes dispositivos. El programa lista los registros de una base de datos en una
ventana y puede imprimirlos. Los contenidos de los campos de la base de datos se dibujan con la función GraS-
tringAt().

Sobre el lado izquierdo de la ilustración, se muestra la ventana de aplicación que se crea por el programa de
ejemplo. Los registros de la base de datos se exhiben en esta ventana por una instancia de la clase definida por el
usuario XbpPreview, implementada en el programa de ejemplo. Su tamaño es de 270 x 380 pixel. Sobre el lado
derecho del objeto XbpPreview, hay botones que agrandan (zoom ++), reducen (zoom --) o imprimen lo que se
exhibe. Si el despliegue se agranda puede ser desplazado por medio de dos barras de desplazamiento. El objeto
XbpPreview provee al dispositivo de contexto para la salida en pantalla (dispositivo de ventana).

Se muestra un pedazo de papel en el lado derecho de la ilustración. Representa lo impreso (el dispositivo im-
presora) y tiene formato A4. Esto significa que su tamaño es 2100 x 2970 unidades, siendo una unidad 1/10mo de
milímetro.

Entre el dispositivo de ventana y el dispositivo impresora, se despliega un Espacio de Presentación. Dependiendo


del dispositivo de contexto al que este Espacio de Presentación está enlazado, la salida aparece en la ventana o
en el papel. Los diferentes aspectos que resultan del despliegue WYSIWYG se demuestran en PREVIEW.PRG.

Escalado
Una escala automática del dibujo y la transformación de los sistemas de coordenadas de ambos dispositivos de
salida los realiza el espacio de presentación. Este define las unidades y el tamaño del sistema de coordenadas.
Para lo impreso el sistema de coordenadas debe dimensionarse de acuerdo al tamaño del papel. Puede deter-
minarse con el método :paperSize() de un objeto XbpPrinter. Este método devuelve un arreglo conteniendo el
tamaño de una hoja en unidades de 1/10mo de milímetro. Para algunas impresoras especialmente las impresoras
láser el tamaño debe ajustarse puesto que hay márgenes sobre los que no pueden imprimirse.

Las coordenadas de la impresora se usan en el Espacio de Presentación. Esto significa que el tamaño de la página
del Espacio de Presentación es idéntico al área imprimible en un pedazo de papel. Con el fin de desplegar la
página completa de un Espacio de Presentación en la ventana, el Area de Visualización debe establecerse al
tamaño de la ventana. En este caso se usan las coordenadas de la ventana (pixel). Si el tamaño de la página y el
Area de Visualización se dimensionan apropiadamente, toda la salida gráfica dibujada en las coordenadas de la
impresora aparece en la escala correcta en una ventana.

Además del Area de Visualización y del tamaño de la página, es necesario seleccionar una vector fuente en el
Espacio de Presentación. Esto se requiere para la correcta escala del texto ya que las fuentes de bitmap no
pueden escalarse. Por tanto, un Espacio de Presentación debe prepararse en varios pasos para conseguir una
vista preliminar WYSIWYG de lo impreso. Estos pasos se discuten en el siguiente código (nota: se asume que la
variable oPrinter referencia a un objeto XbpPrinter y oWindow referencia a un Componente de Xbase):

// Determina tamaño de página


aSize := oPrinter:paperSize()

// Deducir los márgenes que no pueden ser sobreimpresos


aSize := { aSize[5]-aSize[3], aSize[6]-aSize[4] }

// Vincula Espacio de Presentación con el dispositivo de contexto de


// ventana pero usa las coordenadas y unidades de impresora
oPresSpace := XbpPresSpace():new()
oPresSpace:create( oWindow:winDevice(), ;
aSize , ;
GRA_PU_LOMETRIC )

// Establece el Area de Visualización al tamaño de la ventana.


aSize := oWindow:currentSize()
oPresSpace:setViewPort( {0, 0, aSize[1], aSize[2] } )

// Selecciona el fuente del vector para el Espacio de Presentación


oFont := XbpFont():new( oPresSpace )
oFont:familyName := "Helvetica"
oFont:nominalPointSize := 12
oFont:create()
oPresSpace:setFont( oFont )

Cuando se prepara un Espacio de Presentación de esta manera, las funciones Gra..() pueden usarse para pro-
ducir la salida gráfica. El despliegue en la ventana corresponde a lo impreso y aparece en la escala correcta. No
obstante, la salida por pantalla es extremadamente reducida en tamaño comparada con la salida impresa.

Ampliación
Si deben chequearse los detalles de una vista preliminar de lo que se va a imprimir, debe agrandarse el despliegue
en la ventana (ampliada). Esto se logra al agrandar el Area de Visualización del Espacio de Presentación. En este
caso, sólo una parte del Area de Visualización es visible en la ventana. Asuma el siguiente código:

// Obtener el tamaño actual del Area de Visualización (ej.: { 0, 0, 270, 380 } )


aViewPort := oPresSpace:setViewPort()
// Doble altura y ancho del Area de Visualización
// Su tamaño es ahora de 540 x 760 pixeles
aViewPort[3] *= 2
aViewPort[4] *= 2

// Establece la nueva Area de Visualización en el Espacio de Presentación.


oPresSpace:setViewPort( aViewPort )

Se asume que la ventana es 270 pixeles de ancho y 380 pixeles de alto. Si el Area de Visualización se agranda al
doble de ancho y alto (540 x 760 pixeles), la ventana despliega sólo el cuarto inferior de la izquierda del Area de
Visualización. Ya que el tamaño de la ventana permanece inalterado, se exhibe sólo un cuarto del dibujo en vez del
dibujo entero. Esta parte del dibujo aparece agrandada cuatro veces.
Desplazamiento
Mientras el Area de Visualización es más grande que la ventana, sólo un Componente de la ventana es visible y el
resto es recortado. Para ver las diferentes partes del dibujo, éste debe ser desplazado dentro de la ventana. El
desplazamiento se realiza cambiando el origen del Area de Visualización relativo al origen de la ventana. El origen
del Area de Visualización se establece en coordenadas negativas:

// Obtener tamaño actual del Area de Visualización (ej. { 0, 0, 540, 760 } )


aViewPort := oPresSpace:setViewPort()
aViewPort[1] -= 270
aViewPort[2] -= 380
aViewPort[3] -= 270
aViewPort[4] -= 380

oPresSpace:setViewPort( aViewPort )

En este ejemplo el Area de Visualización de tamaño 540 x 760 pixel se establece en las coordenadas
{-270,-380,270,380}. Esto es relativo al punto {0,0} en la ventana. El origen del Area de Visualización se desplazó
para abajo del lado izquierdo mientras su tamaño permaneció inalterado. Como resultado, la parte superior de la
derecha del dibujo se torna visible en la ventana.

Si el origen del Area de Visualización se establece en coordenadas negativas, todas las partes de un dibujo
agrandado pueden visualizarse en una ventana. En el programa ejemplo de PREVIEW.PRG, esto se lleva a cabo
usando dos barras de desplazamiento así el dibujo puede desplazarse con clics del mouse.

Imprimiendo
Cuando se crea un Espacio de Presentación usando un coordenadas de impresora un dibujo visible en pantalla
puede imprimirse fácilmente al intercambiar el dispositivo de contexto del Espacio de Presentación. Para imprimir,
se usa un objeto XbpPrinter como dispositivo de contexto y el dibujo debe redesplegarse para su aparición en el
papel. Los siguientes pasos son necesarios para imprimir un dibujo:

// Guardar el Area de Visualización actual


aViewPort := oPresSpace:setViewPort()

// Vincula Espacio de Presentación con dispositivo de impresora


oPresSpace:configure( oPrinter )

// Abrir el spooler
oPrinter:startDoc()

// Redesplegar el dibujo
Gra..( oPresSpace )

// Cierra el spooler
oPrinter:endDoc()

// Vincula Espacio de Presentación al dispositivo de ventana


oPresSpace:configure( oWindow:winDevice() )

// Restablecer Area de Visualización para la ventana.


oPresSpace:setViewPort( aViewPort )

El dispositivo de contexto se intercambia al pasar el objeto correspondiente al método :configure() del Espacio de
Presentación. Esto transfiere la salida gráfica desde la ventana a la impresora. Puesto que el Area de Visualiza-
ción se pierde cuando se cambian los dispositivos de contexto, debe guardarse y hacerse reset.

Conceptos sobre el manejo de errores


Además del código designado para resolver el problema, otra área importante del desarrollo del programa son las
condiciones de código para manejar el error. En programas complejos los errores o condiciones de excepción se
producen puesto que ningún programador puede probar cada combinación posible en una aplicación extensa, y
ninguno puede visualizar cada una de las muchas condiciones de excepción posibles. Condiciones potenciales de
error deben considerarse cuando se desarrolla una aplicación. Pueden usarse varias estrategias para rectificar los
potenciales errores dentro de un programa. No hay una estrategia universal. Este capitulo ilustra varias aproxi-
maciones a la planificación de condiciones de error dentro de un programa durante el desarrollo de una aplicación.
Manejo ofensivo y defensivo del error
Uso de objetos de error
Manejo predeterminado de errores- ERRORSYS.PRG y XPPERROR.LOG

Manejo ofensivo y defensivo del error


Hay dos "filosofías" respecto a la planificación del manejo de condiciones de error durante el desarrollo de un
programa: una asume que todo esta mal y la otra que todo esta bien. Estas estrategias tienen consecuencias
significativas sobre como están escritos los procedimientos, funciones definidas por el usuario, y métodos, espe-
cialmente aquellos que pueden argumentarse. Pasar argumentos a las rutinas es un área critica para el manejo de
error porque conduce frecuentemente a errores, en particular cuando los argumentos individuales deben tener un
tipo de dato especifico o deben comprenderse dentro de un rango especifico de valores. Un ejemplo simple lo
constituye una función definida por el usuario que lleva a cabo una simple operación de división.

FUNCTION Divide(nDividendo, nDivisor)


RETURN nDividendo / nDivisor

Esta función presume que ambos parámetros son siempre del tipo numérico y que el parámetro nDivisor jamás es
igual a cero. Si el programador puede garantizar estas condiciones la función es aceptable como se la muestra
aquí. Sin embargo, uno debe asumir que normalmente pueden pasarse argumentos incorrectos que podrían
conducir a un error del programa. Un caso como este puede evitarse si se prueban los parámetros.

FUNCTION Divide(nDividendo, nDivisor)


LOCAL nResultado := 0
IF Valtype(nDividendo) + Valtype(nDivisor)=="NN" .AND. ;
nDivisor <> 0
nResultado := nDividendo / nDivisor
ENDIF
RETURN nResultado

En este ejemplo, la función cubre todas las posibles condiciones de error. Prueba los dos parámetros corrobo-
rando que sean del tipo de dato numérico y evita la división por cero. De esta manera, la función Divide() sigue la
estrategia defensiva del manejo de error porque la función evita todas las potenciales fuentes de error. Si se pasa
un parámetro incorrecto la función simplemente substituye un resultado numérico (el valor cero).

La estrategia de evitar la fuente de todos los posibles errores garantiza programas estables, pero puede conducir
a una reducción significativa en la performance del programa durante el tiempo de ejecución. En una programa-
ción cuidadosamente programada los errores no ocurren a menudo. No se requieren generalmente pruebas fre-
cuentes de errores potenciales; de hecho, estas pruebas consumen bastante tiempo.

Por tanto, la estrategia defensiva del manejo de errores tienen un efecto negativo en la performance de un pro-
grama durante el tiempo de ejecución. Esto es porque se ejecutan pruebas frecuentemente que son raras veces
necesarias y que son superfluas algunas veces. Es mas ventajoso usar una estrategia de manejo de error ofensiva
que evita pruebas de rutinas superfluas y que asumen que no ocurrirá error alguno. Esta filosofía es básica en un
sistema operativo de 32 bit y debería también seguirse cuando se programa usando Xbase++.

La estructura de control BEGIN SEQUENCE...ENDSEQUENCE, en conexión con la función ErrorBlock() y un


bloque de códigos para el manejo de errores, constituyen la base para el manejo ofensivo de errores. La función de
ejemplo Divide() aparece así siguiendo una estrategia ofensiva:

FUNCTION Divide(nDividendo, nDivisor)


LOCAL nResultado, bError

bError := ErrorBlock( {|e| Break(e)} ) // instala nuevo bloque de


// código manejador de errores
BEGIN SEQUENCE
nResultado := nDividendo / nDivisor // código normal de programa
RECOVER
nResultado := 0 // código de manejo de errores
ENDSEQUENCE

ErrorBlock( bError ) // reinstalar antiguo bloque de


RETURN nResultado // código manejador de errores
En este ejemplo, no se lleva a cabo ninguna prueba en los parámetros que se pasan. En cambio, se asume que
estos últimos siempre tienen los valores correctos y los tipos de datos. La función contiene dos tipos de códigos del
programa empotrados en la estructura BEGIN SEQUENCE ...ENDSEQUENCE. Caben destacar el código del
programa que procesa la condición normal, libre de error, y el código adicional del programa que se ejecuta si se
produce algún error. Cuando se produce un error durante el tiempo de ejecución se ejecuta el código del programa
luego de la declaración RECOVER. Sino se saltea esta parte de la función. Para que se ejecute el código
RECOVER, un bloque de códigos para el manejo local de errores se instala usando la función ErrorBlock(). Este
bloque de códigos llama a la función Break(), interrumpe la ejecución normal del programa y causa que continúe el
flujo del programa luego de la declaración RECOVER.

Si bien la función Divide() no es realmente necesaria demuestra las dos "filosofías" básicas respecto al manejo de
errores producidos durante el tiempo de ejecución. El tratamiento ofensivo de errores es generalmente ventajoso
ya que provee una performance mejorada durante el tiempo de ejecución. Para el programador, el manejo ofen-
sivo de errores significa que, desde el principio del desarrollo del proceso, el código del programa se organ iza en
dos partes. Estas partes están empotradas en la estructura BEGINSEQUENCE...ENDSEQUENCE. Una parte
contienen la lógica del programa normal. La otra parte captura las condiciones del error y retorna al programa a
una condición estable asegurando una ejecución continua, ininterrumpida. El manejo ofensivo del error protege al
código del programa principal que mejora la calidad y manutención del programa. Es posible atrapar a todos los
errores con el manejo ofensivo del error, lo que no es posible usando la estrategia defensiva salvo que todas las
condiciones posibles se prueben explícitamente. Debería notarse que no hay una regla universal para usar en la
determinación de que condiciones del programa se definen como errores y cuando debe llevarse a cabo un ma-
nejo simple del error. Las condiciones de error simples como la división por cero son la excepción en vez de la
regla. Cuando en duda, una decisión subjetiva debe hacerse respecto a que constituye un error y que forma de
manejo del error será empleada. En muchas condiciones del error tiene sentido no intentar la recuperación del
error, pero sencillamente abortar el programa. En la mayoría de las condiciones de error la terminación del pro-
grama es la reacción predeterminada del sistema de manejo de errores de Xbase++ (ver el archivo
ERRORSYS.PRG).

Uso de objetos de error


Los objetos que contienen información sobre errores durante el tiempo de ejecución juegan un rol central en el
sistema de manejo de error en Xbase++. Los objetos de error son simplemente objetos conteniendo variables de
instancia pero no métodos. Un objeto error se crea automáticamente cuando se produce un error y la información
sobre un error esta contenida en la variable de instancia del objeto. Luego de la creación de un objeto error se lo
pasa al bloque de códigos de error que se instalo usando la función ErrorBlock(). El bloque de códigos de error
puede llamar a cualquier función y procedimiento y pasarle a ellos el objeto del error recibido por el bloque de
códigos. Usando la función ErrorBlock(), las funciones definidas por el usuario que usan información de un objeto
error pueden llamarse para llevar a cabo el manejo de errores cuando ocurren errores durante el tiempo de eje-
cución.

Con el fin de utilizar información en un objeto error, debe definirse un parámetro en el bloque de códigos de error.
En caso de un error, el parámetro recibe un objeto error que puede pasarse a cualquier función. El caso mas
simple es pasar el objeto error a la función Break() que continua con el flujo del programa luego de la declaración
RECOVER. Esto se implementa en el siguiente ejemplo de programa. Este programa prueba la disponibilidad
operativa de las unidades. La prueba ocurre en la función definida por el usuario DiscoEstaListo() llamada desde
Main() . En el procedimiento Main() , la letra de una unidad puede ingresarse y a esta unidad se la prueba para
corroborar su disponibilidad.

**************
PROCEDURE Main // probar Discos para
LOCAL cDisco := "C" , nListo // ver si están listos.

CLS // borrar pantalla


DO WHILE LastKey() <> 27 // terminar con tecla ESC

@ 0,0 SAY "Disco a probar:" GET cDisco // ingresar Disco


READ
nListo := DiscoEstaListo( cDisco ) // probar Disco

@ 10,10 // posiciona el cursor y


// borra la pantalla
IF nListo == 0 // displiega el mensaje
@ 10,10 SAY "Disco listo"
ELSEIF nListo == -1
@ 10,10 SAY "El Disco no está listo"
ELSE
@ 10,10 SAY "Disco no encontrado o no válido"
ENDIF
ENDDO

RETURN

#define DRIVE_NOT_READY 21 // Código de error del SO

*******************************
FUNCTION DiscoEstaListo( cDisco ) // Está listo el Disco ?
LOCAL nReturn := 0
LOCAL cVjoDisco := CurDrive() // recordar Disco actual
LOCAL bError := ErrorBlock( {|e| Break(e) } )
LOCAL oError

BEGIN SEQUENCE

CurDrive( cDisco ) // cambiar Disco


CurDir( cDisco ) // leer el directorio actual

RECOVER USING oError // ha ocurrido un error

IF oError:osCode == DRIVE_NOT_READY
nReturn := -1 // El Disco no está listo
ELSE
nReturn := -2 // Disco no disponible
ENDIF // o no válido

ENDSEQUENCE

ErrorBlock( bError ) // restablecer bloque de código


CurDrive( cVjoDisco ) // de error y Disco

RETURN nReturn

La función definida por el usuario DiscoEstaListo() incluye algunas técnicas importantes para el manejo de error.
Comienza asignando el bloque de códigos de error {|e| Break(e) } que se ejecuta si se produce un error. El bloque
de códigos pasa un objeto error a la función Break(). Por ejemplo, se produce un error cuando se ingresa uno de
los caracteres "&/(123" en Main() o cuando se llama a DiscoEstaListo("K") y no existe la unidad "K".

La función Break() continua al flujo del programa después de la declaración RECOVER. En el ejemplo de pro-
grama, la declaración RECOVER incluye la opción USING definiendo la variable oError . Esta ultima recibe el
parámetro que se le pasa a Break(), en este caso es el objeto error. La variable oError referencia un objeto error
solo luego que ocurre un error. Sino contiene el valor NIL y no se ejecuta el código del programa entre RECOVER
y ENDSEQUENCE.

Si ocurre un error, un objeto error se crea automáticamente y se pasa a un bloque de códigos de error. Este objeto
error contiene información sobre el error en los valores de sus variables de instancia. Dentro del bloque de códigos,
se pasa el objeto a la función Break() que se ramifica a RECOVER. El objeto error se le asigna finalmente a la
variable oError especificada en RECOVER USING. Luego que se produce un error, las variables de instancia del
objeto error pueden accederse luego por el código del programa entre RECOVER y ENDSEQUENCE. En este
ejemplo, solo la variable de instancia :osCode es inspeccionada porque contiene el código de error para los errores
que pueden darse durante la prueba para determinar la disposición operativa de la unidad (osCode = código del
Sistema Operativo - Operating System Code). Se prueba la unidad usando el sistema operativo que genera un
error cuando una unidad no esta lista o no existe. En el sistema de manejo de error de Xbase++, los errores ge-
nerados por el sistema operativo son capturados.

La función DiscoEstaListo() asigna los diversos valores de retorno en el código del programa de manejo de errores.
Este código del programa no se ejecuta cuando la unidad a ser probada esta lista. En este caso, el código esencial
del programa contienen solo dos líneas. La función DiscoEstaListo() es un buen ejemplo de la estrategia del
manejo ofensivo de error, demuestra como pueden capturarse errores, y muestra como puede utilizarse infor-
mación sobre errores durante el tiempo de ejecución.
El ítem mas esencial para un manejo exitoso del error es el bloque de códigos de error, que recibe un objeto error.
El objeto error contiene información sobre el error que ocurrió. El código del programa para el manejo del error
aparece entre RECOVER y ENDSEQUENCE. La declaración RECOVER ocurre entre BEGIN SEQUENCE y
ENDSEQUENCE y puede recibir opcionalmente un valor como parámetro. La opción RECOVER USING define
una variable a ser asignada el valor del argumento pasado a Break().

Manejo predeterminado de errores - ERRORSYS.PRG y XPPERROR.LOG


El manejo predeterminado del error de Xbase++ se implementa en el archivo ERRORSYS.PRG. Contiene la
función ErrorSys() que instala el error predeterminado del bloque de códigos al inicio del programa. Cuando ocurre
un error durante el tiempo de ejecución, se despliega información en pantalla sobre el error. Dependiendo de la
condición del error el usuario puede decidir si ignorar el error, reintentar la ultima operación o terminar el programa.
En caso de la terminación del programa es posible crear un registro de error opcionalmente que se escribe en el
archivo XPPERROR.LOG. El registro de error contiene datos importantes sobre la condición del error y provee al
que lo desarrolla con información valiosa. Puede analizarse en detalle la situación una vez que ocurrió el error lo
que permite la identificación directa de un error de programación o ayuda a localizar el error usando el debugger de
Xbase++. La información que puede listarse en el registro de error es muy numerosa para describir. Por tanto, se
discute a continuación un ejemplo de un registro de error. Explica como interpretar la información contenida en el
registro de archivos. El ejemplo se origina desde el programa de ejemplo MDIDEMO que es parte de la instalación
de Xbase++.

------------------------------------------------------------
ERROR LOG

Xbase++ version : Xbase++ (R) Version 1.70.267


Operating system : Windows NT 4. 0 Build 01381
------------------------------------------------------------
oError:args :
-> VALTYPE: L VALUE: .T.
-> VALTYPE: U VALUE: NIL
-> VALTYPE: C VALUE: Cliente
-> VALTYPE: U VALUE: NIL
-> VALTYPE: U VALUE: NIL
-> VALTYPE: L VALUE: .F.
oError:canDefault : .T.
oError:canRetry : .T.
oError:canSubstitute: .F.
oError:cargo : NIL
oError:description : Operating system error
oError:filename :
oError:genCode : 40
oError:operation : DbUseArea
oError:osCode : 2
oError:severity : 2
oError:subCode : 4
oError:subSystem : BASE
oError:thread : 1
oError:tries : 1
------------------------------------------------------------
CALLSTACK:
------------------------------------------------------------
Called from STANDARDEH(155)
Called from (B)ERRORSYS(24)
Called from ABRECLIENTE(230)
Called from CLIENTE(27)
Called from (B)MENUCREATE(28)
Called from MAIN(46)

El registro de error comienza con la información de versión sobre Xbase++ y del sistema operativo. A saber, dos
secciones principales: el contenido del objeto de error y de la pila de llamadas. La pila de llamadas lista la se-
cuencia de llamadas a funciones, procedimientos o métodos que condujeron a la condición de error. El nombre de
la función es seguido por el numero de línea del archivo PRG donde se llama a la función. En el ejemplo, el registro
de error se inicia en la línea 155 de la función StandardEH() . Esta función se llama desde un bloque de códigos
que se programa en la línea 24 de ErrorSys()!EF. Ambas funciones son parte del archivo ERRORSYS.PRG e
implementan el manejo del error predeterminado de Xbase++. No contribuyen a un error durante el tiempo de
ejecución pero son parte de la pila de llamadas cuando se crea el registro de error. El error durante el tiempo de
ejecución ocurrió en la línea 230 de la función AbreCliente() que se programa en el archivo MDICUST.PRG. La pila
de llamadas identifica claramente la línea del programa que provoca un error durante el tiempo de ejecución. No
puede desplegar el nombre del archivo PRG, solo el numero de línea y el nombre de la función.

Si la información contenida en el objeto error no es suficiente para identificar claramente un error de programación,
la situación del error puede analizarse fácilmente con el debugger de Xbase++. Para ello, la aplicación debe ini-
ciarse desde el debugger y debe establecerse un punto de interrupción en la línea 230 del archivo MDICUST.PRG.
En este ejemplo, sin embargo, el objeto error provee suficiente información para identificar al error: el archivo
CLIENTE.DBF no podría encontrarse. El siguiente código ha provocado el error durante el tiempo de ejecución:

USE Cliente NEW

Esta línea de código se traduce por el preprocesador y el código actual ejecutado durante el tiempo de ejecución
se ve así:

DbUseArea( .T., NIL, NIL, "Cliente", .F. )

En su variable de instancia :operation , el objeto error contiene una cadena con el nombre de la operación fallida o
de la función, respectivamente, y la variable de instancia :args contiene todos los argumentos o parámetros for-
males pasados a la función. El registro de error lista los tipos de datos y los valores de los argumentos (VALTYPE
y VALUE). La variable de instancia :description da una corta descripción de la operación fallida. En el ejemplo, el
error se provoca por el sistema operativo, y no por la aplicación de Xbase++. Esto se indica también por la variable
de instancia :osCode que contiene el valor 2 en este caso. Si esta variable de instancia contiene un numero no
igual a cero, luego esto es un código de error del sistema operativo.

Otra información importante se guarda en la variable de instancia :genCode . Es el código de error genérico de
Xbase++ que corresponde a la constante #define encontradas en el archivo ERRORSYS.CH. El numero 40
mostrado en el ejemplo iguala a la constante XPP_ERR_DOS e indica el error a provocarse por el Sistema
Operativo del Disco - Disk Operating System.

Sumario
El registro de error registra los contenidos de un objeto error junto con la pila de llamadas. La pila de llamadas
indica DONDE ocurrió un error durante el tiempo de ejecución mientras que el objeto error provee información de
POR QUE se provoco. En muchos casos los contenidos de las variables de instan-
cia :arg, :description, :operation, :osCode y :genCode son suficientes para identificar claramente y resolver un error
durante el tiempo de ejecución (por favor, referirse a la documentación de referencia para una descripción de otras
variables de instancia del objeto error).

Errores Fatales - XPPFATAL.LOG


Si se produce un error fatal que no es posible manejar por el sistema predeterminado de manejo de error, el núcleo
de Xbase++ durante el tiempo de ejecución, crea un archivo XPPFATAL.LOG en el directorio actual. Este archivo
contiene información acerca de que paso y donde ocurrió el error fatal, lo cual debe enviarse a Alaska Software
para análisis "forense".
Note que el archivo XPPFATAL.LOG también se crea cuando se termina la aplicación presionando Alt+C. En este
caso, no envíe XPPFATAL.LOG a Alaska Software.

Glosario de términos utilizados en este manual.


Buffer Almacén intermedio
CheckBox Cuadros de confirmación (checkbox)
Click Anglicismo. Acto de presionar el botón izquierdo del ratón (ver Mouse) para desarrollar una acción
en un programa, dependiendo del contexto. El equivalente en español es Clic.
Comunicación Interprocesos Comunicación que se establece entre dos procesos que están ejecután-
dose simultáneamente.
DBE DataBase Engine. Motor de Base de Datos.
DDL Data Definition Language Lenguaje de Definición de Datos.
DLL Dynamic Link Library Biblioteca de enlace dinámico.
DMLB Database Management Language Broker. Agente de Manipulación del Lenguaje de Base de Datos.
El DMLB encamina los pedidos desde el programa a un componente específico de una DBE.
Doble Click Acto de hacer Click dos veces en un lapso breve. Hacer doble -click en un Componente produce la
ejecución del código vinculado al evento :LbDblClick. El equivalente en español es Doble Clic.
FDU Función Definida por el Usuario
Hard Disk Disco Rígido.
MDI MDI (Interfaz de Documentos Múltiples - Multiple Document Interface)
MLE Multiple Line Entry field. Componente utilizado para ingresar datos de tipo caracter en varias líneas.
Su funcionalidad es similar al del editor de campos memo de Clipper.
Mouse Ver Ratón.
PPO PreProcessed Output - significa Salida del PreProcesador.
Proceso Carga y ejecución del código necesario para realizar una tarea completamente.
Ratón Dispositivo de entrada que permite las siguientes acciones:

Tareas que pueden realizarse comúnmente con un ratón


Tarea Cómo lograrlo
Seleccionar un elemento Hacer clic en él

Abrir/Ejecutar un elemento Hacer doble clic en él

Seleccionar un conjunto de Presionar y mantener presionada la tecla


valores u objetos SHIFT desplazar el ratón al primer ele-
mento, hacer clic en él, desplazar el
puntero del ratón al último elemento, y
hacer clic en él. Liberar la tecla SHIFT.

Seleccionar múltiples elementos Presionar y mantener presionada la tecla


CONTROL desplazar el ratón al primer
elemento, hacer clic en él, desplazar el
puntero del ratón al siguiente elemento a
seleccionar, y hacer clic en él. Repetir
con todos los elementos del conjunto
deseado. Liberar la tecla CONTROL.

SDI SDI (Interfaz de Documento Unico - Single Document Interface)


SLE Single Line Entry field. Componente utilizado para ingresar datos de tipo caracter en una sola línea.
Sincrono Que tiene relación con el tiempo. Implica la participación simultánea de dos o más procesos. Ej:
Una conversación telefónica o una videoconferencia son sincrónicas. Un Fax es asíncrono. No
requiere de la asistencia en línea del destinatario.
Thread Hebra de un hilo de ejecución. Considérese al programa en ejecución como a un hilo. Cada una
de las hebras del hilo forma al hilo mismo. Cada programa de aplicación se encuentra embutido en
un proceso (Hilo). Un proceso contiene al menos un "thread" (Hebra) en la que se ejecuta el código
del programa. El programa se ejecuta en un thread, no en un proceso. Múltiples threads pueden
encontrarse activos en un proceso. Esto es posible gracias a la capacidad multi-threading del
sistema operativo quien permite a diversos componentes de la misma aplicación ejecutarse al
mismo tiempo. Los Threads son herramientas que permiten la ejecución de rutinas en paralelo.
Estas rutinas pueden ser controladas por los métodos del objeto Thread(). El sincronismo de la
ejecución de éstas rutinas es posible usando Signal().
UDF UDF - User Defined Function Función Definida por el Usuario
XBP Componente de Xbase (XBP - Xbase Part)

También podría gustarte