Anda di halaman 1dari 478

intro.

dwp Page 1 Friday, December 1, 1995 7:01 PM

Guía del Programador


Visual dBASE® para Windows
Introduc ción
Copyright

La Guía del programador contiene información útil para escribir aplicaciones de dBASE.
Está concebida para ayudar a trabajar en Visual dBASE a los usuarios que tienen poca o
nula experiencia de programación. Si ya tiene experiencia de programación, este
manual le puede servir como una introducción acelerada al lenguaje dBASE.
La Guía del programador y la Referencia del lenguaje son libros complementarios; juntos,
describen el lenguaje dBASE y proporcionan información para escribir programas de
aplicación en dBASE.
• La Referencia del lenguaje contiene información detallada sobre la sintaxis y
funcionalidad de cada elemento del lenguaje, cuyo uso se demuestra de forma
individual con ejemplos de código.
• La Guía del programador contiene introducciones conceptuales, descripciones de tareas
y ejemplos que abarcan a grupos de elementos del lenguaje. Utilice este manual para
crear aplicaciones de Visual dBASE con interfaces de usuario controlados por sucesos
mediante un diseño orientado a objetos.
La Tabla Intro.1 proporciona ejemplos de dónde buscar diferentes tipos de información
referente al lenguaje dBASE.
Tabla Intro.1 Lo que necesita y dónde encontrarlo
Ejemplo de lo que necesita Dónde encontrarlo
Un ejemplo del uso de la función TAGNO() Referencia del lenguaje
Un ejemplo que muestre cómo se complementan las funciones Guía del programador
de índice
Una lista de las propiedades de la clase Form Referencia del lenguaje
Una descripción de cómo funciona un programa controlado por Guía del programador
sucesos
La longitud máxima de un nombre de variable de memoria Referencia del lenguaje
Una explicación de los diferentes ámbitos de las variables de Guía del programador
memoria

Introducción 1
intro.dwp Page 2 Friday, December 1, 1995 7:01 PM

Organización de este manual


Este manual está dividido en las partes que se indican a continuación:
• La Parte I, “Conceptos básicos de programación,” describe los conceptos básicos de
programación en dBASE: creación de subrutinas, uso de las variables de memoria,
trabajo con tipos de datos, comprobación y depuración de programas y preproceso.
• La Parte II, “Objetos y clases,” es una introducción a los objetos, clases y al diseño
orientado a objetos.
• La Parte III, “Fichas,” describe la programación controlada por sucesos, la ejecución
de controladores de sucesos, los temas relacionados con fichas y la modificación de
código generado por el Diseñador de fichas.
• La Parte IV, “Tablas,” presenta el trabajo con tablas, registros y campos, la
ordenación de registros, la programación para un entorno compartido, el uso de
transacciones y el uso de tablas que no son dBASE.
• La Parte V, “Entorno Windows,” trata las aplicaciones Windows, la impresión, el
uso de las DLL y del API de Windows y el intercambio de datos con DDE.
• También se incluyen dos apéndices con información sobre la mejora de las
aplicaciones dBASE III PLUS y dBASE IV para Visual dBASE y uno sobre el uso de
los juegos de caracteres y controladores de idioma.

Uso del ratón


La Tabla Intro.2 describe las técnicas de trabajo con el ratón.
Tabla Intro.2 Técnicas del ratón
Técnica Acción Se utiliza para
Señalar Situar el puntero del ratón en Normalmente es el preludio de otra acción, como hacer
un objeto específico. clic o arrastrar. El puntero del ratón puede tener varias
formas distintas.
Hacer clic Pulsar y soltar el botón Activar el botón o ventana que hay bajo el puntero o
izquierdo del ratón una vez. seleccionar un icono.
Hacer doble Pulsar y soltar el botón Seleccionar un elemento de una lista o ejecutar un icono
clic izquierdo del ratón dos veces (tabla, ficha, etc.).
de forma rápida.
Arrastrar 1. Pulsar y mantener Mover un objeto o cambiar su tamaño. Esta técnica
pulsado el botón izquierdo también se utiliza para arrastrar y soltar.
del ratón.
2. Mover el ratón.
3. Soltar el botón del ratón.
Hacer clic Pulsar y soltar el botón Mostrar el menú de propiedades del objeto situado bajo el
con el botón derecho del ratón una vez. puntero.
derecho

Nota Para usuarios zurdos, Windows permite invertir la función de los botones del ratón.
Si lo hace, lea “derecho” donde dice “izquierdo” (y viceversa) en la Tabla Intro.2.

2 Guía del programador


intro.dwp Page 3 Friday, December 1, 1995 7:01 PM

Uso del teclado


Los nombres de las teclas se indican en el manual mediante un tipo de letra especial. Por
ejemplo, “pulse Alt” significa que pulse la tecla Alt. Intro hace referencia a la tecla Intro, que
en algunos teclados también aparece como Return o con un símbolo de retorno.
Siempre que es posible, el texto utiliza los nombres estándar que aparecen rotulados en
las teclas del teclado.
En algunos casos, es necesario pulsar dos teclas al mismo tiempo. El texto lo indica
uniendo los dos nombres de tecla con un signo más (+). Así, “pulse Ctrl+F2” significa que
debe mantener pulsada la tecla Ctrl mientras pulsa la tecla F2.
El texto de este manual también utiliza un tipo de letra especial para indicar lo que debe
introducir en el teclado, como en este ejemplo:
Introduzca SET EDITOR TO "c:\windows\notepad.exe" en la ventana de comandos.
Nota Las comillas que se incluyen en la cadena (en este caso, encerrando la especificación de
vía de acceso) deben teclearse con el texto.

Referencias a dBASE para DOS


Como servicio a los usuarios de las versiones de DOS de dBASE que van a utilizar
Visual dBASE, este libro porporciona información sobre los archivos de actualización y
código de las versiones de DOS de dBASE a Visual dBASE. Se hace referencia a las
versiones de DOS de dBASE como "dBASE para DOS". A menos que se indique lo
contrario, "dBASE para DOS" hace referencia principalmente a dBASE IV y dBASE 5.0
para DOS. También puede hacer referencia a dBASE III PLUS, pero no necesariamente.

Introduction 3
intro.dwp Page 4 Friday, December 1, 1995 7:01 PM

4 Guía del programador


feature.dwp Page 5 Wednesday, August 30, 1995 1:51 PM

Capítulo

Introducción a la programación
Capítulo 1
1
en dBASE
Bienvenido a Visual dBASE, el lenguaje de desarrollo de aplicaciones más productivo
para el entorno Windows. El lenguaje dBASE se ha mejorado significativamente en
Visual dBASE. Este capítulo presenta las novedades más importantes del lenguaje y le
indica dónde se describe cada una en este manual.
Para ayuda sobre la ejecución de aplicaciones de dBASE DOS, consulte el Apéndice A.
Para ayuda sobre la mejora de estas aplicaciones con las características de Windows,
consulte el Apéndice B.

Extensiones orientadas a objetos


Visual dBASE incorpora un lenguaje de programación orientado a objetos que permite
que los programadores creen aplicaciones sofisticadas y escalables utilizando objetos y
clases. El Capítulo 9 presenta la programación orientada a objetos y el Capítulo 12
describe cómo diseñar aplicaciones que utilicen las técnicas orientadas a objetos.

Objetos y clases
Los objetos son un medio de encapsular conjuntos de variables, algunos de los cuales
contienen datos, mientras que otros hacen referencia a código. Las clases son un
mecanismo para crear grupos reutilizables de objetos. Con Visual dBASE, es posible:
• Crear objetos a partir de clases estándar o de clases declaradas por el usuario.
• Añadir propiedades personalizadas a un objeto con una sentencia de asignación
simple.

Capítulo 1, Introducción a la programación en dBASE 5


feature.dwp Page 6 Wednesday, August 30, 1995 1:51 PM

• Declarar clases personalizadas.


• Declarar clases que se basan en otras clases y heredan sus propiedades y métodos.
• Escribir código altamente reutilizable mediante la creación de jerarquías de clases.
El Capítulo 10 describe cómo crear y trabajar con los objetos. El Capítulo 11 describe
cómo crear y trabajar con las clases.

Ámbito de las variables de memoria


Visual dBASE permite los ámbitos de variables de memoria clásicos en dBASE: público
y privado, así como local y estático. La existencia de los ámbitos local y estático reduce
los conflictos entre los nombres de las variables y facilita el encapsulado de variables
dentro de procedimientos y objetos. El Capítulo 5 describe y compara los diferentes
ámbitos de las variables de memoria.

Bloques de código y punteros de función


Como en otros lenguajes, en Visual dBASE es posible agrupar comandos en
procedimientos, funciones o archivos de programa. Visual dBASE también proporciona
otras dos formas de agrupar y ejecutar comandos:
• El bloque de código es un tipo de datos para almacenar pequeños grupos sin nombre de
comandos o expresiones. Mediante los bloques de código, es posible trabajar con
código de dBASE como si se tratara de datos. Los bloques de código pueden
asignarse a variables de memoria o propiedades de objetos y pasarse como
parámetros.
• El puntero de función es otro tipo de datos nuevo para almacenar referencias a
procedimientos, funciones o bloques de código. Una variable del tipo puntero de
función contiene una referencia a código que puede asignar a una propiedad, pasarse
como un parámetro o llamarse directamente.
Los punteros de función y los bloques de código son especialmente importantes para la
programación orientada a objetos. Todas las propiedades de suceso, como OnClick u
OnOpen, de los objetos estándar aceptan los tipos de datos de puntero de función o
bloque de código. Para más información, consulte el Capítulo 1 de la Referencia del
lenguaje y el Capítulo 4 de este manual.

6 Guía del programador


feature.dwp Page 7 Wednesday, August 30, 1995 1:51 PM

Fichas
En Visual dBASE, las aplicaciones se construyen creando las fichas que constituyen el
interface de usuario.

Ventanas de ficha
Mediante las clases estándar, es posible crear cualquiera de las ventanas de
visualización estándar para pantallas de introducción de datos, cuadros de diálogo y
cuadros de mensaje. Definiendo unas pocas propiedades, es posible hacer que las fichas
puedan maximizarse, minimizarse, moverse, cambiar de tamaño y tener otras
características. Ábralas con READMODAL( ) para que tengan un control exclusivo del
escritorio; ábralas con OPEN( ) para activar un interface de usuario totalmente
controlado por sucesos. El Capítulo 13 compara los enfoques que pueden plantearse al
crear aplicaciones con las fichas.

Objetos del interface de usuario


Visual dBASE proporciona un completo conjunto de objetos de interface gráfico, o
controles, para su colocación en las fichas, que permiten crear botones (de comando),
botones de radio, casillas de selección, cuadros de lista y combinados, editores y otros.
Incluso puede crear controles personalizados propios mediante la modificación del
comportamiento de los controles estándar de dBASE. El Capítulo 15 describe cómo
crear y utilizar controles personalizados.

Controladores de sucesos
Las ventanas de ficha, y los objetos que contienen, reconocen y pueden responder de
forma automática a sucesos, que son acciones iniciadas por el usuario o el sistema. Es
posible definir cómo responde la ficha a los sucesos mediante la definición de
controladores de sucesos, es decir, procedimientos que se ejecutan cuando tiene lugar un
suceso. Para una introducción sobre cómo funcionan los programas controlados por
sucesos, consulte el Capítulo 2. Para información sobre cómo escribir controladores de
sucesos, consulte el Capítulo 14.

Utilidades de dos vías


El Diseñador de fichas, el Diseñador de menús y el Diseñador de menús de ventana son
utilidades versátiles de generación de código que le permiten diseñar visualmente
fichas y menús. Estos Diseñadores producen archivos de programa dBASE que pueden
modificarse con un editor de texto y volver a cargarse en los Diseñadores. Esta
característica le permite desarrollar programas a su manera, situando los objetos de
forma visual, refinando el código y volviendo al Diseñador según sea necesario.
El Capítulo 16 describe cómo trabajar con el código generado.

Capítulo 1, Introducción a la programación en dBASE 7


feature.dwp Page 8 Wednesday, August 30, 1995 1:51 PM

Sesiones
Un nuevo comando, CREATE SESSION, le permite encapsular tablas con fichas
mediante la creación de un nuevo conjunto de áreas de trabajo (con punteros de registro
distintos) dedicadas exclusivamente a la ficha, como si hubiera iniciado otra copia de
dBASE. En un entorno controlado por sucesos, donde el usuario puede cambiar entre
tareas y abrir y cerrar tablas libremente, una sesión es similar a otro usuario en un
sistema de varios usuarios. Para más información sobre la programación para un
entorno compartido, consulte el Capítulo 21.

Entorno Windows
El entorno Windows proporciona muchas características (DDE, OLE) y recursos
compartidos (controladores de impresora y fuentes) que puede utilizar para mejorar sus
aplicaciones.

Controladores de impresora Windows


El entorno Windows incorpora controladores de impresora que pueden compartir todas
las aplicaciones Windows. De esta forma, no es necesario configurar los controladores
de impresora para cada nueva aplicación que instale.
Visual dBASE permite el uso de los controladores de impresora Windows y aprovecha
las funciones de impresión de Windows, como el Administrador de impresión, al
tiempo que mantiene el soporte de la mayoría de los comandos de impresión y variables
de memoria del sistema que se utilizan en dBASE DOS.
La función CHOOSEPRINTER( ) muestra un cuadro de diálogo de configuración de
impresión que suelen utilizar las aplicaciones Windows. En este cuadro de diálogo, es
posible cambiar de impresora, conmutar entre modo apaisado y vertical y seleccionar
bandejas de papel. Cuando es apropiado, las selecciones realizadas en este cuadro de
diálogo actualizan automáticamente los valores de las variables de memoria, como
_pdriver y _porientation. Para más detalles, consulte el Capítulo 25.

Fuentes de Windows
El entorno Windows permite el uso de fuentes comunes que pueden compartir todas las
aplicaciones Windows.
Visual dBASE permite el uso de las fuentes de Windows. Es posible mostrar el texto en
pantalla o en la impresora con cualquier fuente instalada, y personalizar el aspecto del
texto de pantalla con propiedades de fuentes, como FontBold, FontItalic o
FontUnderline. El Capítulo 8 de Referencia del lenguaje describe las propiedades de las
fuentes.

8 Guía del programador


feature.dwp Page 9 Wednesday, August 30, 1995 1:51 PM

Llamadas a DLL y API de Windows


Las bibliotecas de enlace dinámico (DLL) son archivos que contienen código de
programa compilado que las aplicaciones Windows cargan y ejecutan en tiempo de
ejecución. El código de programa puede contener subrutinas y recursos, como mapas de
bits e iconos. Las DLL permiten que el código dBASE llame funciones que no son de
dBASE.
El API (interface de programación de aplicaciones) de Windows es una biblioteca
incorporada en el entorno Windows que contiene cientos de funciones C útiles
almacenadas en archivos DLL. El soporte de DLL permite que los programadores de
dBASE accedan al API de Windows.
Las funciones DLL son fácilmente accesibles, bien interactivamente, bien en un
programa. Basta crear un prototipo de cualquier función con el comando EXTERN y
entonces llamarla como si fuera una parte normal del lenguaje dBASE. Para más
detalles, consulte el Capítulo 26.

Multimedia: gráficos y sonido


Es posible crear, así como reproducir y mostrar, formatos conocidos de multimedia en
Windows para sonidos e imágenes gráficas, como .WAV, .BMP, .TIF y .PCX. Utilice
RESTORE IMAGE para mostrar una imagen gráfica y PLAY SOUND para reproducir
los sonidos de un archivo .WAV. Incluso puede almacenar archivos de sonido y gráficos
en un campo binario de una tabla o mostrarlos en fichas.
Consulte la Referencia del lenguaje para más información sobre los comandos
RESTORE IMAGE y PLAY SOUND y el Capítulo 18 de este manual para más detalles
sobre el almacenamiento de datos binarios en las tablas.

DDE y OLE
El Intercambio dinámico de datos (DDE) es una característica de Windows que permite
que dos aplicaciones compartan e intercambien datos e instrucciones. Visual dBASE
permite el uso de DDE como cliente y como servidor. Para más información sobre DDE,
consulte el Capítulo 27.
Otra función de intercambio de datos, Vinculación e incrustación de objetos (OLE), le
permite utilizar aplicaciones externas directamente desde una tabla o ficha de dBASE.
Incluso es posible controlar otra aplicación desde Visual dBASE mediante la
automatización OLE. Para más información sobre OLE, consulte la Guía del usuario.

Capítulo 1, Introducción a la programación en dBASE 9


feature.dwp Page 10 Wednesday, August 30, 1995 1:51 PM

Tablas
Las tablas son el medio principal de guardar datos en Visual dBASE. En Visual dBASE,
un solo archivo de datos se denomina tabla, y se conoce como base de datos a un
conjunto de tablas y otros archivos asociados.
Visual dBASE añade varias características nuevas para trabajar con las tablas en los
programas.

El Database Engine de Borland (BDE)


Visual dBASE ofrece un acceso directo a diversos tipos de tabla, además del formato
.DBF de dBASE. Es posible abrir, crear, modificar y consultar tablas de Paradox y SQL
empleando los mismos comandos que operan en las tablas estándar de dBASE. Incluso
es posible establecer relaciones entre archivos de tipos diferentes. Mediante el Database
Engine de Borland, Visual dBASE maximiza el acceso a diferentes tipos de tabla sin
añadir otra capa de lenguaje que manipule los datos. Para más información sobre el uso
de tablas de Paradox y SQL, consulte el Capítulo 23.

Soporte SQL
Visual dBASE permite el uso de SQL para trabajar con tablas locales, así como tablas de
bases de datos SQL. Tanto si trabaja con tablas dBASE o Paradox locales, como con
datos SQL en un servidor de base de datos, tiene la opción de utilizar sintaxis de dBASE
o de SQL.
Visual dBASE permite la creación de vistas actualizables de datos de servidor SQL
mediante SQL incrustado. Para más información, consulte el Capítulo 23.

Fichas escalables
Visual dBASE permite transparentemente el uso de bases de datos de servidores de
dBASE, Paradox y SQL. Las fichas y aplicaciones pueden adaptarse fácilmente para
trabajar con datos locales o de servidor, normalmente con sólo cambiar la base de datos
a que señalan.

10 Guía del programador


feature.dwp Page 11 Wednesday, August 30, 1995 1:51 PM

Integridad referencial
Nuevas opciones del comando SET RELATION aplican una estricta integridad
referencial entre tablas .DBF relacionadas. La opción CONSTRAIN limita el acceso de la
tabla secundaria a los registros que coinciden con la principal. Así, no es necesario un
filtro adicional en la tabla secundaria. La opción INTEGRITY CASCADE borra
automáticamente los registros secundarios cuando se borra el registro principal
correspondiente, mientras que INTEGRITY RESTRICTED impide el borrado de los
registros secundarios si el principal aún existe. Visual dBASE también permite el uso de
las funciones de integridad referencial de las tablas Paradox. Además, cuando se trabaja
con una tabla de servidor de base de datos, Visual dBASE permite el uso de diccionarios
de datos y reglas de integridad referencial de las bases de datos de servidor. Para más
información, consulte el Capítulo 19.

Seguridad
Visual dBASE permite el cifrado de tablas de dBASE y la seguridad con contraseñas a
nivel de aplicación, tabla y campo. Visual dBASE también permite el uso de la seguridad
con contraseñas en las tablas de Paradox y de la seguridad en servidores de base de
datos. Para más información sobre la definición de seguridad, consulte la
Guía del usuario.

Proceso de transacciones controladas por sucesos


Visual dBASE proporciona un proceso de transacciones controlado por sucesos, donde
las operaciones de recuperación y aceptación de transacciones se producen de forma
independiente del bloque de código en que comenzaron las transacciones. Tres
funciones, BEGINTRANS( ), COMMIT( ) y ROLLBACK( ), especifican los puntos de
transacción. Visual dBASE también permite el uso de niveles de aislamiento cuando se
trabaja con datos en servidores de base de datos. Para más información, consulte el
Capítulo 22.

Tipos de campo especiales: binario y OLE


Visual dBASE proporciona tipos de campo para almacenar datos binarios y OLE en las
tablas .DBF. Suplementan las capacidades ya proporcionadas por los campos memo.
Además de permitirle añadir estos campos en las tablas, existen comandos que
almacenan y recuperan datos de estos campos, como COPY BINARY, REPLACE
BINARY, REPLACE OLE, RESTORE IMAGE y PLAY SOUND. El Capítulo 18
proporciona ejemplos.

Capítulo 1, Introducción a la programación en dBASE 11


feature.dwp Page 12 Wednesday, August 30, 1995 1:51 PM

Indicadores
Las tablas de Paradox y SQL no tienen números de registro fijos como las de dBASE. Los
indicadores proporcionan toda la funcionalidad del número de registro de dBASE a las
tablas de Paradox, SQL y dBASE. La función BOOKMARK( ) establece un puntero a un
registro que puede utilizarse como si se tratara de un número de registro. Los
indicadores tienen la ventaja de que sus valores siempre se basan en el orden actual de
los registros de una tabla, por lo que la comparación de los valores de BOOKMARK( )
con >, = ó < indicará la posición relativa de dos registros en el orden del índice actual.
Para más información, consulte el Capítulo 23.

Expresiones de campos memo


Los campos memo que contienen texto pueden utilizarse donde esté permitida una
expresión de caracteres, excepto con el comando INDEX (no es posible indexar por un
campo memo). Así, es más fácil manipular y formatear campos memo empleando
cualquiera de las funciones de gestión de cadenas, como UPPER( ), TRANSFORM( ) o
STUFF( ). El Capítulo 18 proporciona ejemplos.

Programación general
Las siguientes son algunas de las nuevas características de programación general
añadidas en dBASE.

Preprocesador
Visual dBASE tiene un preprocesador incorporado similar al utilizado en los
compiladores de lenguaje C. Cuando se compila un programa, el preprocesador explora
el código en busca de directivas de preprocesador y las evalúa, generando un archivo
intermedio temporal que compila el compilador. Mediante el uso de directivas de
preprocesador en el código, es posible insertar archivos de programa, definir constantes,
expandir expresiones y realizar una compilación condicional. El preprocesador concede
al programador un mayor control sobre el proceso de compilación. Para más
información, consulte el Capítulo 7.

Depurador
El Depurador incorpora un completo conjunto de utilidades de depuración. Los puntos
de evaluación, los puntos de ruptura, el código fuente y la pila de llamada pueden verse
todos al mismo tiempo o de forma individual. Un potente inspector le permite ver las
variables, campos y objetos y cambiar sus valores durante la ejecución. Para más
detalles sobre el uso del Depurador, consulte el Capítulo 8.

12 Guía del programador


feature.dwp Page 13 Wednesday, August 30, 1995 1:51 PM

Matrices versátiles
Visual dBASE añade nuevas y versátiles características a las matrices. Es posible:
• Declarar matrices con cualquier número de dimensiones.
• Añadir, borrar, ordenar y buscar elementos de una matriz con nuevas funciones
como AINS( ), ADEL( ), ASORT( ) y ASCAN( ), y muchas otras.
• Trabajar con matrices como objetos accediendo a sus propiedades y métodos.
• Crear matrices dispersas con un número variable de elementos no contiguos.
• Crear matrices asociativas que utilizan etiquetas de texto, en lugar de números, para los
índices de los elementos.
Para más información sobre las nuevas funciones de matrices, consulte el Capítulo 5.
El Capítulo 10 describe cómo trabajar con matrices como objetos, incluyendo la creación
de matrices dispersas.

Transmisión mejorada de parámetros


La transmisión de parámetros a procedimientos y funciones se ha mejorado
significativamente. Es posible:
• Pasar hasta 255 parámetros a un procedimiento.
• Pasar objetos y matrices completas.
• Pasar más o menos parámetros que los declarados para el procedimiento o función.
• Pasar parámetros por su valor o por referencia.
Para más información, consulte el Capítulo 4.

Análisis de cobertura
El análisis de cobertura es una característica potente que le informa exactamente de qué
secciones de código se activan cuando se ejecuta un programa. Esta información es
esencial en la mejora de protocolos de pruebas y para garantizar que se han
comprobado todas las partes de una aplicación. El Analizador de cobertura de
Visual dBASE es una de las pocas utilidades de análisis no intrusivas de su especie para
lenguajes de desarrollo de aplicaciones. Para más detalles, consulte el Capítulo 3.

Capítulo 1, Introducción a la programación en dBASE 13


feature.dwp Page 14 Wednesday, August 30, 1995 1:51 PM

Generación de datos aleatorios para comprobación


El nuevo comando GENERATE supone una forma sencilla de llenar una tabla con datos
aleatorios para comprobación. Abra la tabla y ejecute GENERATE con un argumento
numérico que indique cuántos registros se desea crear. Por ejemplo, GENERATE 100 llenará
todos los campos (excepto los memo, binarios y OLE) con datos aleatorios del tipo
adecuado para los 100 registros siguientes. Para más información, consulte el Capítulo 3.

Amplias capacidades
Las amplias capacidades de Visual dBASE —el número de campos de una tabla, el
número de áreas de trabajo activas, la longitud de los nombres de variables de
memoria— se han diseñado para que nunca sean un impedimento en el desarrollo de
aplicaciones. Para ver una lista de capacidades, consulte el Apéndice B de la
Referencia del lenguaje.

14 Guía del programador


p_prog.dwp Page 15 Wednesday, August 30, 1995 1:58 PM

Parte

I
Conceptos básicos de programación
Parte I

Esta parte presenta los programas controlados por sucesos y trata los temas generales de
programación en dBASE.
Si nunca antes ha programado en dBASE:
1 Lea el Capítulo 2 para una introducción básica.
2 Lea los apartados relacionados con la creación y compilación de programas en el
Capítulo 3.
3 Lea los Capítulos 4, 5 y 6 para aprender los principios fundamentales sobre la
escritura de código de dBASE.
Esta sección contiene los siguientes capítulos:
• Capítulo 2, “Principios de la programación en dBASE”
• Capítulo 3, “Creación, compilación y comprobación de programas”
• Capítulo 4, “Uso de los procedimientos y bloques de código”
• Capítulo 5, “Uso de las variables de memoria”
• Capítulo 6, “Uso de los tipos de datos”
• Capítulo 7, “Uso de las directivas de preprocesador”
• Capítulo 8, “Depuración de programas”

Parte I, Conceptos básicos de programación 15


p_prog.dwp Page 16 Wednesday, August 30, 1995 1:58 PM

16 Guía del programador


getstrt.dwp Page 17 Wednesday, August 30, 1995 1:59 PM

Capítulo

Principios de la programación
Capítulo 2
2
en dBASE
Este capítulo presenta los programas controlados por sucesos, describe cómo funcionan
y ofrece una introducción al proceso de crear programas controlados por sucesos con
Visual dBASE.
Para ayuda sobre la ejecución de aplicaciones de dBASE DOS, consulte el Apéndice A.

Codificación manual frente a programación visual


Mediante un editor de texto, es posible escribir programas desde cero tecleando cada
comando, línea tras línea. Así es como la mayoría de programadores dBASE solían
escribir programas: la forma difícil. Con Visual dBASE, se usan utilidades de diseño que
generan el código del programa. La parte más laboriosa de la programación de
interfaces tradicionales —adivinar cómo aparecerán los campos y menús después de
situarlos con coordenadas— ya está desfasada. El programador pone los objetos en una
ficha exactamente donde los desea, y Visual dBASE calcula las coordenadas. Es la
programación visual.
Pero la programación visual no se reduce a la disposición de las fichas. Los objetos que
se ponen en las fichas tienen una capacidad incorporada de responder a las acciones del
usuario. Un botón reconoce automáticamente un clic del ratón. Una ficha “sabe” cuándo
el usuario la mueve o cambia su tamaño. El programador no necesita calcular lo que
hace el usuario y cómo sucede. Visual dBASE lo gestiona. El programador sólo indica a
los objetos cómo responder a esos sucesos mediante la asignación de procedimientos
que se ejecutan cuando se producen los sucesos.

Capítulo 2, Principios de la programación en dBASE 17


getstrt.dwp Page 18 Wednesday, August 30, 1995 1:59 PM

Para mostrar los datos con otro estilo tipográfico u otro tamaño, seleccione el objeto y
defina sus propiedades de fuente. Para mostrar una imagen gráfica en una ficha,
arrastre un objeto de imagen desde la Paleta hasta la ficha y vincúlelo a un archivo de
mapa de bits o a un campo memo.
El Capítulo 14 describe paso a paso cómo escribir controladores de sucesos y vincularlos
a sucesos con el Diseñador de fichas. Para una introducción general al Diseñador de
fichas y sus utilidades, consulte el Capítulo 8 de la Guía del usuario.
El resultado de programar visualmente son aplicaciones fáciles de crear y fáciles de
usar. Y son fáciles de usar porque están controladas por sucesos.

Razones para el uso de programas controlados por sucesos


Los tres tipos principales de interfaces de usuario son:
• Línea de comandos, donde el usuario teclea comandos junto a un indicativo o mensaje.
El sistema operativo MS-DOS, el punto indicativo de dBASE (en versiones anteriores)
y la ventana de comandos (en Visual dBASE) son ejemplos de interface de línea de
comandos.
• Controlado por menús, en que el usuario selecciona en una jerarquía de opciones de
menú. La mayoría de aplicaciones escritas en versiones anteriores de dBASE
proporcionan interfaces controlados por menús.
• Controlado por sucesos, donde el usuario interactúa con objetos visibles, como fichas
que contienen botones y cuadros de lista, en cualquier secuencia. El interface de
usuario de Visual dBASE está controlado por sucesos, y es posible crear aplicaciones
controladas por sucesos empleando el lenguaje dBASE.
Mediante las técnicas tradicionales de programación, es posible construir interfaces de
usuario controlados por menús. En estas aplicaciones, el programa dicta la secuencia de
sucesos. Si el usuario selecciona Introducir pedido en un menú, el programa suele
avanzar por una serie de pantallas que solicitan información al usuario: introduzca el
nombre del cliente, introduzca la fecha y número de pedido, introduzca los artículos,
introduzca los gastos de envío.
Estas técnicas controladas por menús no son muy adecuadas para la programación en
entornos controlados por sucesos como Windows. En una aplicación controlada por
sucesos, el usuario controla el flujo del programa. El usuario puede hacer clic en un
botón, activar una ventana o seleccionar una opción de menú en cualquier momento, y
el programa debe responder a estos sucesos en la secuencia en que se produzcan.

18 Guía del programador


getstrt.dwp Page 19 Wednesday, August 30, 1995 1:59 PM

En una aplicación controlada por sucesos que esté bien diseñada:


• El usuario puede concentrarse en la tarea, no en la aplicación. El usuario no tiene que
aprender una compleja jerarquía de opciones de menú. Por contra, cuando elige
introducir un pedido, el usuario ve una ficha de pedido similar al formulario de
papel con que está familiarizado.
• El usuario no necesita volver a aprender cómo realizar tareas. Puesto que el interface de
una aplicación se crea con objetos ya conocidos, como botones y cuadros de lista, las
operaciones comunes —abrir un archivo, desplazarse en una ficha, salir de la
aplicación— son más parecidas entre las aplicaciones.
Aún más importante, los interfaces controlados por sucesos reflejan la forma en que
trabajan las personas en el mundo real. Cuando un administrativo o una secretaria
escribe un pedido, toma unos formularios y los rellena. Cuando recibe cheques por los
pedidos, toma las facturas y las marca como pagadas. Este flujo natural de trabajo sigue
un patrón objeto-acción. Es decir, el administrativo selecciona un objeto (formulario de
pedido, factura) y realiza alguna acción con él (rellena el pedido, anota el cheque).
De igual forma, en una aplicación controlada por sucesos, el usuario selecciona objetos
(fichas, campos de entrada, botones) y realiza acciones con ellos.
La Figura 2.1 ilustra el patrón de trabajo objeto-acción en el mundo real y en
aplicaciones controladas por sucesos.
Figura 2.1 Patrón de trabajo objeto-acción
En el mundo real En una aplicación controlada por sucesos

Objeto Acción Objeto Acción

Factura
Factura Archivo Edición Ayuda
Buscar. . .
Cliente Cliente
Buscar cliente Elegir
Artº Cantidad Precio Artº Cantidad Precio Edición|Buscar

TOTAL TOTAL
PAGADO
Marcar como Pagado Hacer clic en el
pagado botón “Pagado”

Capítulo 2, Principios de la programación en dBASE 19


getstrt.dwp Page 20 Wednesday, August 30, 1995 1:59 PM

Funcionamiento de los programas controlados por sucesos


En una aplicación de Visual dBASE, la ficha es el principal componente del interface de
usuario. Las fichas contienen controles, como campos de entrada y botones, para la
interacción con el usuario. Los controles reconocen los sucesos, como un clic del ratón o
la pulsación de una tecla, en respuesta a los cuales, se ejecutan controladores de sucesos,
que es código de programa escrito por el programador.
Se anexa código a las propiedades de control de sucesos de los controles, como OnClick
u OnLeftMouseDown (la mayoría comienzan con On), que corresponden a sucesos
específicos. Por ejemplo, cuando un usuario hace clic en un botón, se ejecuta el
controlador de sucesos OnClick.
La especificación de controladores de sucesos para fichas es similar a utilizar los
comandos ON de dBASE DOS, como ON KEY LABEL u ON ERROR. Al igual que un
controlador de sucesos, el comando ON designa que se ejecute algún código de
programa cuando se produce un suceso, como la pulsación de una tecla o un error en
tiempo de ejecución. Sin embargo, los sucesos gestionados por los comandos ON son
limitados y no están asociados a los objetos del interface de usuario.
En una aplicación controlada por sucesos típica:
1 La aplicación muestra una ficha inicial de forma automática.
2 La ficha, o un control de la ficha, recibe un suceso, como un clic del ratón o una
pulsación.
3 Se ejecuta un controlador de sucesos asociado al suceso del paso 2.
4 La aplicación espera al suceso siguiente.
La Figura 2.2 muestra una ficha de ejemplo, “¡Hola a todos!”. Después de mostrar la
ficha, Visual dBASE espera que se produzca un suceso. El usuario puede mover,
redimensionar, minimizar o maximizar la ficha. Cuando el usuario hace clic en el botón,
Visual dBASE ejecuta el suceso OnClick.
Figura 2.2 Controladores de suceso de ejemplo para la ficha “¡Hola a todos!”

El controlador de sucesos
OnClick del botón se
ejecuta cuando el usuario
hace clic en él.

20 Guía del programador


getstrt.dwp Page 21 Wednesday, August 30, 1995 1:59 PM

El código siguiente, un archivo .WFM generado por el Diseñador de fichas, crea la ficha
que se muestra en la Figura 2.2. Este código sigue la estructura general de todas las
fichas generadas por el Diseñador de fichas. De momento, no intente comprender todas
las líneas. Observe solamente la estructura general para tener una idea de cómo se crean
las fichas, se definen las propiedades y se asignan controladores a los sucesos.

LOCAL f
f = NEW Hello()
f.Open()
CLASS Hello OF Form
This.Text = "My first dBASE form"
This.Width = 50
This.Top = 4
This.Left = 54
This.Height = 15
DEFINE Text Text1 OF This;
PROPERTY;
ColorNormal "N/W",;
FontSize 23,;
Height 3,;
Left 11,;
Text "Hello world!",;
Top 3,;
Width 33
DEFINE PushButton Button1 OF This;
PROPERTY;
Height 2,;
Left 19,;
OnClick Class::Button1_OnClick,;
StatusMessage "Click button to exit",;
Text "Goodbye",;
Top 9,;
Width 13

PROCEDURE Button1_OnClick
DO WHILE (Form.Height > 0) .AND. (Form.Width > 0)
Form.Text1.Text = "Goodbye"
Form.Height = Form.Height - 1
Form.Width = Form.Width - 1
Form.Top = Form.Top + .5
Form.Left = Form.Left + .5
ENDDO
Form.Close()
RETURN
ENDCLASS

Capítulo 2, Principios de la programación en dBASE 21


getstrt.dwp Page 22 Wednesday, August 30, 1995 1:59 PM

Desarrollo de programas controlados por sucesos


Todo lo que necesita para desarrollar programas controlados por sucesos es el
Diseñador de fichas. Mediante él y sus utilidades, puede crear menús, fichas de
introducción de datos, cuadros de diálogo —todos los componentes visibles de una
aplicación—. Use entonces el editor de procedimientos (utilidad del Diseñador de fichas
para introducir y editar código de programa) para ensamblar los componentes
escribiendo procedimientos que se ejecuten cuando se produzcan los sucesos.
Sin embargo, los proyectos que son más complejos precisan planificación y un buen
diseño. Es ahí donde resulta de utilidad la orientación a objetos. Mediante sus técnicas,
puede agrupar información relacionada en objetos propios, crear “clases” de objetos
relacionados y crear nuevos objetos por medio de sencillos cambios en los existentes.
El Capítulo 12 proporciona una introducción a las técnicas orientadas a objetos para el
diseño de aplicaciones.

22 Guía del programador


comptst.dwp Page 23 Wednesday, August 30, 1995 2:00 PM

Capítulo

Creación, compilación y
Capítulo 3
3
comprobación de programas
Visual dBASE permite tanto la programación procedural mediante un editor de texto
como la programación visual interactiva mediante el Diseñador de fichas y el Diseñador
de consultas. Es posible combinar los dos sistemas de programación en el mismo
programa e integrar código generado por el Diseñador de fichas en las estructuras de
programación tradicionales de dBASE para crear aplicaciones con interfaces de usuario
al estilo de Windows.
Este capítulo trata la creación de archivos de programa, las convenciones estilísticas de
programación, la compilación con comparación automática de la fecha de los archivos,
la compilación con análisis de cobertura y la comprobación de programas.

Creación de un archivo de programa


Un programa de dBASE es un archivo de texto que contiene sentencias de dBASE, a las
que se denominan código de programa, código fuente o simplemente código.
Puede utilizar el editor de textos para escribir programas. Para crear un programa
nuevo, teclee CREATE COMMAND en la ventana de comandos o seleccione el icono
Programas en el Selector y haga doble clic en el icono de programa Sin título.
Para abrir un archivo de programa existente, haga doble clic en su icono en el Selector o
teclee MODIFY COMMAND seguido por el nombre de archivo. Por ejemplo:
MODIFY COMMAND CONTACTO.PRG
abre el programa Contacto para su edición.

Capítulo 3, Creación, compilación y comprobación de programas 23


comptst.dwp Page 24 Wednesday, August 30, 1995 2:00 PM

Uso de convenciones estilísticas


Para armonizar el aspecto de los archivos de texto que contienen código de programa,
utilice normas de sangrado, nombres y mayúsculas para facilitar la lectura del código.
Utilice comentarios para explicar lo que sucede, en especial en programas grandes,
donde puede no ser obvio. Esto facilita la comprensión y mantenimiento de los
programas, en particular cuando el mantenimiento de un programa puede ser realizado
por otras personas.
Probablemente desarrolle un estilo propio de escribir código de programa. Sin embargo,
las siguientes son algunas convenciones habituales que debería observar:
• Comente el código. Además de los comandos de dBASE, un archivo de programa
puede contener otro texto que se inserta para describir lo que hace el código. dBASE
reconoce tres marcas de comentario diferentes:
• Dos ampersands (&&)
• Asterisco (*), sólo como primer carácter de una línea
• La palabra NOTE en mayúsculas o minúsculas y sólo al principio de una línea
USE CIUDADES EXCLUSIVE && Abre la tabla Ciudades
INDEX ON Nombre TAG Nombre
* Crea un índice
BROWSE
note Edita la tabla
• Incluya una introducción comentada al programa. Si lo desea, puede incluir un párrafo o
dos de texto comentado al principio de un archivo de programa para describirlo
brevemente. La información útil de una introducción de programa consiste en:
• Fecha de la revisión, nombre del programador y propósito del programa
• Si se ejecuta bajo DOS, WINDOWS u otro sistema operativo
• La sintaxis de línea de comandos y, si los hay, los argumentos opcionales
• Sangre el código. El sangrado de las líneas de código le da una indicación visual de la
jerarquía de las sentencias del programa; así es posible ver los bucles anidados.
El sangrado permite comprobar con facilidad si las estructuras de programación
tienen pares de sentencias inicial y final.
IF Nombre = "Lola"
? "¡Hola Lola!"
ELSE
IF Nombre = "Juan"
? "¡Hola Juan!"
ENDIF
ENDIF

24 Guía del programador


comptst.dwp Page 25 Wednesday, August 30, 1995 2:00 PM

• Divida las sentencias largas. El editor de textos de dBASE permite 1024 bytes en una
línea de código; no obstante, no es una longitud adecuada para su visualización en
pantalla o su impresión. Para dividir las sentencias de código largas, emplee el
carácter de continuación de dBASE, el signo de punto y coma (;). Cuando se pone un
punto y coma al final de una línea de programa seguido por un retorno de carro o un
carácter de avance de línea, dBASE continúa en la línea siguiente antes de interpretar
la sentencia del programa.
• Utilice los nombres y las mayúsculas de forma coherente en los elementos del lenguaje.
dBASE no distingue entre mayúsculas y minúsculas, por lo que puede utilizarlas
para mejorar la legibilidad de las variables de memoria y los nombres de campo. Esto
es optativo; si tiene experiencia programando en C y está acostumbrado a distinguir
entre mayúsculas y minúsculas, quizá no le sea conveniente.
Comience las variables de memoria con los mismos caracteres, ya que podrá buscar
sus nombres con caracteres comodín para encontrarlas con mayor facilidad y de
forma secuencial.
• Utilice notación que identifique el ámbito y tipo de las variables de memoria. Hay cuatro
ámbitos diferentes para las variables de memoria en Visual dBASE: local, estático,
público y privado. Pueden combinarse con los tipos de datos de carácter, numérico,
fecha y lógico para crear prefijos de dos letras al objeto de lograr nombres de
variables de memoria significativos, por ejemplo:
ll_pagado && variable lógica local
ne_Balance && variable numérica estática
cl_archtempo && variable carácter local
Nota dBASE utiliza un carácter de subrayado inicial en los nombres de sus variables
predefinidas, a las que se denomina variables de memoria del sistema. Para evitar la
duplicación, no utilice el subrayado inicial en los nombres de sus variables.

Capítulo 3, Creación, compilación y comprobación de programas 25


comptst.dwp Page 26 Wednesday, August 30, 1995 2:00 PM

Compilación de programas
Después de terminar de escribir un programa, necesita compilar el archivo de texto en
un archivo de código objeto. En este capítulo, compilación y ejecución hacen referencia a
programas que funcionan en el entorno interpretado de dBASE. Estos archivos objeto
compilados contienen pseudocódigo (también llamado pcódigo o tokens). En general,
estos programas precisan que se ejecute dBASE y son diferentes de los programas que
están compilados al nivel del sistema operativo y pueden ejecutarse como aplicaciones
independientes en ordenadores que pueden no tiener dBASE. Si requiere la capacidad
de distribuir programas a usuarios que quizá no dispongan de Visual dBASE, los
archivos de código objeto de dBASE pueden vincularse en un ejecutable Windows
mediante el compilador opcional de Visual dBASE.
Dependiendo del tipo de código que esté escribiendo o generando con las utilidades de
diseño, dBASE emplea diferentes extensiones de archivo por defecto. Dado que estas
extensiones son significativas para el compilador e implican relaciones entre código
fuente y código compilado, no debería cambiarlas.
No utilice ninguna extensión que termine con la letra o para los nombres de los archivos
fuente de sus programas. Si lo hace, cuando compile el programa, el archivo objeto lo
sustituirá. Los archivos objeto compilados están en formato binario y no pueden
visualizarse ni modificarse.
Por defecto, dBASE crea archivos objeto compilados en el mismo directorio en que se
encuentren los archivos de código fuente. Las extensiones por defecto del código fuente
y del código compilado se muestran en la Tabla 3.1.
Tabla 3.1 Extensiones por defecto de los archivos de programa fuente y objeto
Extensión de archivo Extensión de
Descripción de archivo de código fuente archivo compilado
Archivo de programa .PRG .PRO
Archivo de ficha generada .WFM .WFO
Archivo de consulta .QBE .QBO
generada
Archivo de menú generado .MNU .MNO
Archivo de menú de .POP .POO
ventana generado
Archifo de ficha .CFM .CFO
personalizada
Archivo de control .CC .CO
personalizado
Archivo de ficha de .FMT .FMO
dBASE IV
Archivo de informe de .FRG .FRO
dBASE IV
Archivo de etiqueta de .LBG .LBO
dBASE IV

Los tipos de archivo de dBASE DOS no pueden crearse en Visual dBASE, pero sí se
permite su uso al objeto de lograr la compatibilidad con versiones anteriores.

26 Guía del programador


comptst.dwp Page 27 Wednesday, August 30, 1995 2:00 PM

Hay varios comandos de dBASE que compilan archivos de programa. COMPILE crea
programas a partir de uno o más archivos sin ejecutar el código objeto resultante. Los
comandos siguientes compilan un programa como parte de su acción:
• DO compila un programa y lo ejecuta si la compilación se produce sin errores.
• SET PROCEDURE TO <archivo> busca un archivo de programa ya compilado. Si no
encuentra un archivo .PRO, compila un archivo .PRG.
COMPILE tiene varias ventajas sobre la compilación de archivos con DO o SET
PROCEDURE.
• COMPILE no ejecuta ni abre los archivos especificados.
• COMPILE acepta caracteres comodín en los nombres de archivo y puede compilar
archivos relacionados o no relacionados.
• COMPILE tiene una opción AUTO que compila los archivos de programa llamados
por los programas que compila explícitamente.
• COMPILE puede crear un archivo de respuesta para el compilador opcional si éste
está instalado. Para más información sobre esta opción, consulte la sección que trata
de la creación de ejecutables.
Cuando se compila un programa, dBASE detecta todos los errores de sintaxis existentes
en el archivo fuente y muestra un mensaje de error en el cuadro de diálogo Error, donde
se muestran cuatro botones que puede seleccionar como respuesta al mensaje de error
durante la compilación:
• El botón Cancelar cancela la compilación. Equivale a pulsar Esc.
• El botón Ignorar cancela la compilación del programa que contiene el error de
sintaxis pero continúa compilando los demás archivos que cumplan el filtro de
caracteres comodín, si ha utilizado uno.
• El botón Solucionar abre el código fuente en una ventana de edición y sitúa el punto
de inserción en la línea de código en que se produjo el error de sintaxis.
• El botón Ayuda proporciona ayuda contextual.

Capítulo 3, Creación, compilación y comprobación de programas 27


comptst.dwp Page 28 Wednesday, August 30, 1995 2:00 PM

Comparación de versiones de los archivos fuente y objeto


El parámetro de entorno SET DEVELOPMENT determina si dBASE compara la hora y
fecha de modificación del archivo fuente y del archivo objeto. Cuando SET
DEVELOPMENT es ON, dBASE compara siempre la hora y fecha de modificación de
los archivos de programa, ficha o procedimiento con las de sus archivos objeto
correspondientes. Si modifica un archivo de programa y no lo compila inmediatamente,
dBASE lo hace de forma automática cuando intente ejecutar el archivo modificado.
El valor por defecto de SET DEVELOPMENT es ON. Para cambiarlo:
1 Seleccione Escritorio en el menú Propiedades. Se abre el cuadro de diálogo
Propiedades del escritorio.
2 Seleccione la solapa Programación.
3 Haga clic en la casilla de verificación Compilación automática para deseleccionarla.
O bien, para definir DEVELOPMENT como OFF en la ventana de comandos,
introduzca:
SET DEVELOPMENT OFF
Cuando SET DEVELOPMENT es OFF, dBASE no compara las marcas de hora y fecha y
ejecuta los programas, fichas o consultas que haya compilados actualmente. Si no existe
ningún archivo objeto, dBASE compila el archivo fuente, sea cual sea el valor de
SET DEVELOPMENT.
Durante el desarrollo de programas, utilice SET DEVELOPMENT ON para asegurarse
de que está ejecutando la última versión editada. Cuando no esté desarrollando
programas, utilice SET DEVELOPMENT OFF para acelerar su ejecución.

Creación de ejecutables
Si dispone del compilador opcional de Visual dBASE, puede vincular los archivos de
código objeto que constituyen una aplicación, así como otros tipos de archivos que
requiera el programa, en un ejecutable Windows (.EXE). Si realiza apliaciones que van a
utilizar otras personas, el compilador de Visual dBASE ofrece varias ventajas:
• El usuario no necesita una copia de Visual dBASE para ejecutar la aplicación.
• El programador tiene derechos de distribución gratuitos (sin tener que pagar
derechos de autor) e ilimitados para la aplicación.
• Para ejecutar la aplicación, es necesario distribuir muchos menos archivos.
• Hay menos posibilidades de que un usuario corrompa accidentalmente el entorno
del programa, porque la ventana de comandos y los menús de Visual dBASE no están
disponibles.
• Los programas tienen un aspecto más profesional porque los usuarios no tienen
necesidad de comenzar un sistema aparte en tiempo de ejecución para utilizar la
aplicación.
Consulte la documentación que acompaña al compilador de Visual dBASE para obtener
información específica sobre la generación de ejecutables.

28 Guía del programador


comptst.dwp Page 29 Wednesday, August 30, 1995 2:00 PM

Comprobación de programas
La comprobación de un programa implica ejercitar todos sus módulos y forzar sus
límites para localizar errores y debilidades en el programa. Mediante la comprobación,
puede asegurarse de disponer de la gestión de errores adecuada para los casos en que el
programa reciba datos inadecuados —por ejemplo, que muestre un mensaje de error
cuando el usuario introduzca valores numéricos en un campo de texto—.
Para comprobar una aplicación de Visual dBASE, necesita comprobar datos en las tablas
que utiliza la aplicación y también debe saber qué partes de la misma se ejecutan en
realidad al ejecutarla. Existen dos comandos que proporcionan esas capacidades:
GENERATE y SET COVERAGE.
La depuración es otra forma de comprobar la presencia de errores y puntos débiles en
un programa. El Depurador de Visual dBASE dispone de potentes utilidades de
depuración para seguir la ejecución del programa. Para una descripción del Depurador,
consulte el Capítulo 8.

Generación de registros aleatorios


Cuando necesite una tabla grande para comprobar los límites de un programa, puede
utilizar el comando GENERATE, que rellena tablas con caracteres y números aleatorios.
Si una tabla ya contiene registros, GENERATE los deja intactos y añade el número de
registros especificado.
Nota No es posible generar datos en los campos memo, binario u OLE.
Para generar registros aleatorios en cualquier momento desde el Selector o una ventana
de catálogo:
1 Abra la tabla en que desea añadir registros aleatorios.
2 Elija Tabla|Utilidades de tabla|Generar e introduzca el número de registros que
desea añadir en el cuadro de diálogo Generar registros.
Para generar registros aleatorios desde la ventana de comandos:
USE <nombre tabla> && <nombre tabla> es la tabla en la que queremos
&& generar registros
GENERATE <expN> && <expN> es el número de registros a añadir

Capítulo 3, Creación, compilación y comprobación de programas 29


comptst.dwp Page 30 Wednesday, August 30, 1995 2:00 PM

Uso del análisis de cobertura


El comando SET COVERAGE proporciona análisis de cobertura para los programas que
se compilan cuando está definido como ON. Los resultados del análisis de cobertura
permiten comprobar si se ejecutan todos los módulos del programa. Si hay partes del
programa que no se ejecutan, compruébelas específicamente para asegurarse de que
funcionan. Por ejemplo, si una subrutina no se activa porque la condición que la llama
nunca se produce, cambie los datos de entrada para que se produzca, y compruebe
entonces si la subrutina funciona como esperaba.
Cuando SET COVERAGE es ON y se compila y ejecuta un programa, Visual dBASE
analiza el programa en tiempo de ejecución y crea un archivo binario, llamado archivo de
cobertura, donde guarda el resultado del análisis. El archivo de cobertura usa el mismo
nombre que el programa analizado y la extensión .COV.
Cuando SET COVERAGE es ON, los programas que compile producen archivos .PRO
diferentes que cuando está desactivada la cobertura. Estos archivos .PRO contienen
información para generar archivos de cobertura, sea cual sea el valor de SET
COVERAGE durante la ejecución. La única forma de impedir la generación de los
archivos de cobertura es volver a compilar los programas con SET COVERAGE OFF y
generar archivos .PRO sin información de cobertura.

Inicio del análisis de cobertura


Para iniciar el análisis de cobertura, defina SET COVERAGE como ON y compile
(COMPILE) o ejecute (DO) el programa. La activación de la cobertura puede hacerse de
una de las formas siguientes:
• Ejecute SET COVERAGE ON en la ventana de comandos.
• Elija Propiedades|Escritorio o ejecute SET en la ventana de comandos; a
continuación, en la solapa Programación del cuadro de diálogo Propiedades del
escritorio, marque la casilla Cobertura.
• Ejecute #pragma COVERAGE(ON) en un archivo de programa.
• Ejecute SET COVERAGE ON en un archivo de programa.
Después de cualquiera de estos pasos, compile y ejecute el programa para crear un
archivo de cobertura para él. Lo importante es tener activada la cobertura durante la
compilación. Puede desactivarla durante la fase de ejecución y, aún así, obtendrá
archivos de cobertura.
Si incluye SET COVERAGE ON en un archivo de programa, sin activar la cobertura
antes de empezar a compilar, dBASE no crea un archivo de cobertura, pero sí compila
las subrutinas que llame el programa estando activada la cobertura.

30 Guía del programador


comptst.dwp Page 31 Wednesday, August 30, 1995 2:00 PM

Por ejemplo, la ejecución de un programa como COMPILAR.PRG con la siguiente


secuencia de comandos activa automáticamente el análisis de cobertura, compila el
programa principal (PPAL.PRG) y los archivos de programa de subrutinas (SUB1.PRG
y SUB2.PRG), y genera archivos de cobertura (PPAL.COV, SUB1.COV y SUB2.COV)
para esos tres archivos de programa:
* Compilar.prg
#include "version.h" && Crea e inicializa a 1 la variable Depurar
#ifdef Depurar
SET COVERAGE ON
#endif
Compile Ppal
Compile Sub1
Compile Sub2
En un programa con muchos procedimientos, cada módulo tiene su archivo de análisis
de cobertura.
La única forma de activar la cobertura desde dentro de un programa para él mismo es
con la directiva de preprocesador #pragma COVERAGE(ON). Un programa en que se
activa la cobertura con #pragma no debería contener la directiva #pragma
COVERAGE(OFF) como parte de su procedimiento de salida. Si se incluyen estas dos
directivas en el mismo programa, sólo funciona la segunda.
Si utiliza #pragma COVERAGE(ON) en un archivo de programa, no tiene que ejecutar
SET COVERAGE ON antes de compilarlo. Visual dBASE actualiza los archivos de
cobertura existentes cada vez que modifique el programa y lo vuelva a compilar.
Antes de que Visual dBASE puede crear un archivo de cobertura para un programa,
debe compilar éste con SET COVERAGE ON. Si compila un programa mientras que
SET COVERAGE es OFF, y a continuación define SET COVERAGE como ON y ejecuta
el programa, Visual dBASE muestra un mensaje de advertencia y no produce ningún
archivo de cobertura.

Finalización del análisis de cobertura


Para terminar el análisis de cobertura, defina SET COVERAGE como OFF y vuelva a
compilar el programa mientras que la cobertura está desactivada.
Para definir SET COVERAGE como OFF, utilice cualquiera de estos métodos:
• Ejecute SET COVERAGE OFF en la ventana de comandos.
• Deseleccione la casilla Cobertura en el cuadro de diálogo Propiedades del escritorio.
Compile de nuevo el programa mientras la cobertura está desactivada para terminar el
análisis de cobertura.
Edite los programas que contienen la directiva de preprocesador #pragma
COVERAGE(ON) y cámbiela para que quede como sigue:
#pragma COVERAGE (OFF)
Esta sentencia garantiza que la siguiente ocasión en que compile este programa, el
análisis de cobertura esté desactivado.

Capítulo 3, Creación, compilación y comprobación de programas 31


comptst.dwp Page 32 Wednesday, August 30, 1995 2:00 PM

Creación frente a actualización de los archivos de cobertura


La información de un archivo de cobertura se acumula cada vez que ejecute el programa
con la cobertura activada. Si compila los programas con SET COVERAGE ON y no
cambia los archivos de programa que está analizando, dBASE actualiza los archivos de
cobertura que crea para esos programas, sea cual sea el valor de SET COVERAGE al
ejecutar los programas durante la comprobación.
Cuando modifica un archivo de programa, lo compila de nuevo y lo ejecuta
(con SET COVERAGE como ON), Visual dBASE crea un nuevo archivo de cobertura
para ese programa.
Cuando utiliza #pragma COVERAGE(ON) para iniciar una sesión de cobertura,
Visual dBASE actualiza un archivo de cobertura existente. Cuando realiza cambios en el
programa que contiene #pragma COVERAGE(ON), lo compila de nuevo y lo ejecuta,
dBASE crea un nuevo archivo de cobertura.

Bloques lógicos
Los archivos de cobertura son archivos binarios que contienen información acumulativa
sobre cuántas veces accede y sale Visual dBASE (y por tanto ejecuta por completo) de
cada bloque lógico de un programa. Los bloques lógicos no incluyen líneas comentadas ni
líneas de comandos de control de flujo, como IF y ENDIF. Por el contrario, sí incluye las
líneas de comandos situadas dentro de esas líneas de comandos de flujo.
Si un programa no contiene comandos de control de flujo (IF...ENDIF, DO
WHILE...ENDDO, FOR...NEXT, SCAN...ENDSCAN, DO CASE...ENDCASE,
DO...UNTIL), el programa tiene un único bloque lógico que consiste en todas las líneas
de comandos, excluidas las de comentarios.
El archivo de cobertura identifica los bloques lógicos por su número o números
correspondientes de línea de programa. Por ejemplo, los comentarios del programa
siguiente indican qué líneas identificaría el análisis de cobertura como bloques lógicos.
(Esto no es lo que se genera en un archivo de cobertura.)
* ACTUALI.PRG
SET TALK OFF && Línea 2, Bloque 1 (Líneas 2-3)
USE Cliente INDEX Vendedor
SCAN
DO CASE
CASE Vendedor = "S-12"
SELECT 2 && Línea 7, Bloque 2 (Líneas 7-8)
USE S12
CASE Vendedor = "L-5"
SELECT 2 && Línea 10, Bloque 3 (Líneas 10-11)
USE L5
CASE Vendedor = "J-25"
SELECT 2 && Línea 13, Bloque 4 (Líneas 13-14)
USE J25
ENDCASE
DO Cambios && Línea 16, Bloque 5 (Líneas 16-17)
SELECT 1
ENDSCAN
CLOSE ALL && Línea 19, Bloque 6 (Líneas 19-20)
SET TALK ON

32 Guía del programador


comptst.dwp Page 33 Wednesday, August 30, 1995 2:00 PM

Visualización de la información del archivo de cobertura


Utilice DISPLAY COVERAGE para ver la información, pantalla a pantalla, del panel de
resultados de la ventana de comandos.
Si la información que muestra DISPLAY COVERAGE o LIST COVERAGE supera el
tamaño de la memoria intermedia de visualización, no podrá volver al principio de la
información una vez que ha pasado por el panel de resultados de la ventana de
comandos. Para volver a mostrarla, ejecute de nuevo DISPLAY COVERAGE o LIST
COVERAGE.
Utilice LIST COVERAGE para enviar la información a un archivo en disco o a la
impresora. Consulte las opciones TO FILE y TO PRINTER de los comandos DISPLAY o
LIST en la Referencia del lenguaje.
Figura 3.1 Salida de DISPLAY COVERAGE en el panel de resultados de la ventana de comandos

Capítulo 3, Creación, compilación y comprobación de programas 33


comptst.dwp Page 34 Wednesday, August 30, 1995 2:00 PM

34 Guía del programador


proc&cb.dwp Page 35 Wednesday, August 30, 1995 2:02 PM

Capítulo

Uso de los procedimientos y bloques


Capítulo 4
4
de código
La programación modular implica la organización de las tareas de programación en
unidades de programa pequeñas y autónomas, denominadas módulos, que son fáciles
de mantener, depurar y reutilizar. Estos módulos pueden ser procedimientos
(o funciones) y bloques de código, y pueden interactuar unos con otros o con el
programa principal para realizar tareas complejas.
Como en otros lenguajes de programación, puede declarar los módulos de programa
como procedimientos o como funciones. Además, Visual dBASE también permite los
bloques de código, que son unidades de código pequeñas y sin nombre que se asignan a
variables o se incrustan directamente en otras sentencias.
Dado que Visual dBASE no distingue entre procedimientos y funciones, el término
procedimiento se utiliza en este capítulo para hacer referencia a las rutinas declaradas con
PROCEDURE o con FUNCTION.
Este capítulo presenta los procedimientos y los bloques de código, explica las
diferencias entre ellos y muestra cómo escribirlos. También describe las diversas formas
de llamar procedimientos y cómo utilizar los bloques de código en los programas.

Capítulo 4, Uso de los procedimientos y bloques de código 35


proc&cb.dwp Page 36 Wednesday, August 30, 1995 2:02 PM

Acerca de los procedimientos y los bloques de código


Visual dBASE permite el uso de procedimientos, funciones, bloques de código de
sentencia y bloques de código de expresión.
• Un procedimiento es un breve programa que contiene una o más sentencias de
programación y realiza una tarea. Puede devolver un valor o no.
• Una función es un breve programa similar a un procedimiento, pero debe incluir la
devolución de un valor.
• Los bloques de código de sentencias son uno o más comandos dBASE que pueden
devolver un valor.
• Los bloques de código de expresiones son expresiones dBASE individuales que siempre
devuelven el valor con que se evalúan.
Los bloques de código pueden ocultar al resto del código las rutinas empleadas para
realizar una tarea concreta. Los procedimientos o bloques de código pueden asignarse a
variables de memoria o a la propiedad de un objeto.
La Tabla 4.1 resume los usos de cada uno.
Tabla 4.1 Resumen de los usos de los procedimientos, funciones y bloques de código
Uso habitual Ejemplo
Procedimientos Crear un grupo de sentencias reutilizable Un controlador de sucesos OnClick para un
y funciones y con nombre que realizan una tarea y botón que calcula un valor; un controlador
pueden o no devolver un valor de sucesos Valid para un campo de entrada
Bloques de Crear una subrutina que consta de uno o Un controlador de sucesos OnClick que
código de varios comandos que pueden asignarse a pasa al registro siguiente
sentencias una variable de memoria o a la
propiedad de un objeto
Bloques de Crear una subrutina que consta de una Un controlador de sucesos Valid o When
código de expresión que devuelve un valor
expresiones evaluado dinámicamente durante la
ejecución

Procedimientos frente a funciones


En dBASE IV, hay diferencias significativas entre los procedimientos y las funciones:
sólo las funciones pueden devolver valores, y sólo los procedimientos pueden llamarse
con el comando DO. Estas condiciones no se aplican en Visual dBASE.
Las funciones precisan que se incluya una expresión en la sentencia RETURN, pero los
procedimientos también permiten una expresión así. Tanto los procedimientos como las
funciones pueden llamarse con un comando DO, como en DO MiProc, o con el
operador de llamada (paréntesis), como en MiProc().
Cuando se utiliza el Diseñador de fichas para escribir un controlador de sucesos,
Visual dBASE declara un procedimiento, no una función, aunque el controlador de
sucesos devuelva un valor.
Si está habituado a declarar procedimientos y funciones para fines diferentes, puede
continuar haciéndolo. Dado que los procedimientos y funciones son intercambiables en
Visual dBASE, en los ejemplos de código de este capítulo sólo se utilizan los primeros.

36 Guía del programador


proc&cb.dwp Page 37 Wednesday, August 30, 1995 2:02 PM

Declaración de procedimientos
Para declarar un procedimiento en un programa, es necesario codificarlo, compilarlo
para comprobar que se ejecuta sin errores y, entonces, copiarlo al final del archivo de
programa. O bien, puede crear un archivo de procedimiento aparte que contenga
muchas subrutinas que utilice con frecuencia.
La primera línea de un procedimiento es PROCEDURE <nombre procedimiento>.
Escriba, a continuación, las sentencias que realizan la tarea. La última línea del
procedimiento es RETURN.
Para la descripción completa del comando RETURN, consulte la Referencia del lenguaje.
Sitúe los procedimientos en el archivo de programa en que desea llamar el
procedimiento.
El ejemplo siguiente declara un procedimiento que avanza por una tabla de registro en
registro. En lugar de repetir estas sentencias en el programa cada vez que desea
desplazarse por una tabla, puede incluir la sentencia DO SaltaRegs o anexar el
procedimiento al suceso OnClick de un botón llamado Registro siguiente.
PROCEDURE SaltaRegs
SKIP && Salta al siguiente registro si es posible
IF EOF() && Si es el final del archivo
GO TOP && Ir al primer registro de la tabla
ENDIF
RETURN

Declaración de parámetros
Los parámetros cambian el comportamiento de un procedimiento haciendo que éste
funcione con muchos valores diferentes. Los parámetros permiten modificar el valor
devuelto cambiando los valores que se transmiten al procedimiento para su proceso. La
transmisión de parámetros suele utilizarse para enviar valores a un procedimiento y
devolver valores al programa que lo llamó.
Los parámetros son una o más variables de memoria que se mencionan en la
declaración de un procedimiento con la intención de pasarle valores. Los parámetros
modifican el comportamiento del procedimiento o intercambian valores con otros
módulos del programa.
Los parámetros se declaran en un procedimiento encerrándolos entre paréntesis
después del nombre del procedimiento. Cuando se utilizan paréntesis, los parámetros
declarados son locales del procedimiento en cuanto a su ámbito. Las variables de
memoria locales no pueden modificarse con las siguientes subrutinas que se llamen.
Para más información sobre los ámbitos de las variables de memoria, consulte el
Capítulo 5.

Capítulo 4, Uso de los procedimientos y bloques de código 37


proc&cb.dwp Page 38 Wednesday, August 30, 1995 2:02 PM

En dBASE IV, los parámetros se declaran utilizando una sentencia PARAMETERS en el


procedimiento. Este método de declaración de parámetros se permite principalmente
para mantener la compatibilidad con dBASE IV; sin embargo, se desaconseja su uso
porque crea variables de memoria privadas, que pueden ser sustituidas
involuntariamente por otro procedimiento que utilice los mismos nombres de variable.
A diferencia de lo que sucede en dBASE IV, no es necesario que la sentencia
PARAMETERS sea la primera después de la sentencia PROCEDURE, ya que puede
aparecer en cualquier posición dentro del cuerpo del procedimiento. Pero ninguno de
los valores transferidos mediante la sentencia PARAMETERS está disponible para el
código de PROCEDURE antes de que se encuentre la sentencia PARAMETERS. Esto se
ilustra en el ejemplo siguiente:
LOCAL X
X = 10
MiProc(X)
RETURN

PROCEDURE MiProc
LOCAL Y
Y = 20
? Y && muestra 20
PARAMETERS Y&& el parámetro anula las variables locales
? Y && muestra 10
RETURN
Las dos formas de declarar parámetros no pueden mezclarse. Para un procedimiento
dado, puede declararlos usando o bien paréntesis o bien una sentencia PARAMETERS.
No es posible declarar algunos parámetros con paréntesis y otros con PARAMETERS.
Los dos declaraciones de clase siguientes son idénticas, excepto en que una declara un
parámetro con paréntesis y la otra con PARAMETERS.
Figura 4.1 Declaración de parámetros con ámbito local y privado

Declaración local Declaración privada

PROCEDURE Cuadrado(n) PROCEDURE Cuadrado


Num = n *n PARAMETERS n
RETURN Num = n*n
RETURN

38 Guía del programador


proc&cb.dwp Page 39 Wednesday, August 30, 1995 2:02 PM

El ejemplo siguiente cambia el procedimiento del ejemplo anterior de SaltaRegs para


que acepte una variable para el número de registros que se saltan y mueva el puntero de
registro en ese número. Este procedimiento se llamaría con cualquiera de las dos
primeras sentencias del ejemplo.
DO SaltaRegs WITH 10 && 10 es el número de registros a saltar

SaltaRegs(10) && Equivalente a la sentencia anterior

PROCEDURE SaltaRegs(Numregs) && Declaración de procedimiento con un


&& parámetro llamado Numregs
SKIP Numregs && Salta el número de registros especificado
&& en Numregs
IF EOF() && Si es final de archivo
GO TOP && Ir al primer registro de la tabla
ENDIF
RETURN
Si un procedimiento no devuelve un valor, puede omitir la sentencia RETURN al final.
Un procedimiento termina cuando procesa toda la entrada que se especifique y la
ejecución del programa continúa con la siguiente línea de código en el programa que
llamó el procedimiento. Es posible devolver el control del programa al módulo que
llamó el procedimiento con una sentencia RETURN que no tenga argumentos o a otro
procedimiento especificando su nombre en la sentencia RETURN. Para conocer las
opciones disponibles, consulte RETURN en la Referencia del lenguaje.

Llamada de procedimientos con parámetros


Esta sección proporciona ejemplos de cómo pasar parámetros a los procedimientos y
obtener la devolución de valores. Hay dos formas de pasar parámetros a un
procedimiento:
• Incluya el operador de llamada (paréntesis) después del nombre del procedimiento y
ponga los parámetros dentro de los paréntesis.
• Utilice la sintaxis DO <nombre procedimiento> WITH.
Ambas técnicas se muestran en el ejemplo siguiente.
PRIVATE total
Num1 = 3
Num2 = 5
Num3 = 10
respu1 = MiCalculo(Num1,Num2,Num3)
? respu1 && Devuelve 80 ((3+5)*10)
total = 0
DO MiCalculo WITH 4,8,9
? total && Devuelve 108 ((4+8)*9)

PROCEDURE MiCalculo( primero, segundo, tercero) && Declaración del procedimiento


&& y sus parámetros
total = (primero + segundo) * tercero && Expresión que evalúa los parámetros
RETURN total && Valor devuelto

Capítulo 4, Uso de los procedimientos y bloques de código 39


proc&cb.dwp Page 40 Wednesday, August 30, 1995 2:02 PM

Si llama un procedimiento con DO, éste no devuelve un valor, aunque se especifique


uno en la sentencia RETURN. Para que el procedimiento devuelva los valores
requeridos, necesita utilizar parámetros y variables de memoria. El ejemplo siguiente lo
ilustra.
PRIVATE valor
valor = 12
? cuadrado(valor) && Devuelve 144
DO Cuadrado WITH valor
? valor && Devuelve 12
DO Cuadrado1 WITH valor && Cuadrado1 actualiza la variable pasada como parámetro
? valor && Devuelve 144

PROCEDURE Cuadrado(mval) && Este es el procedimiento Cuadrado que acepta un parámetro


cu_val = mval*mval && Evalúa la expresión y la asigna a cu_val
RETURN cu_val && Devuelve un valor

PROCEDURE Cuadrado1(mval) && Este es el procedimiento cuadrado1


mval = mval*mval && Evalúa la expresión
RETURN
En el ejemplo siguiente, el procedimiento ACadena recibe un parámetro que podría ser
numérico, lógico o de carácter. ACadena convierte el parámetro en cadena, si es
necesario, elimina los espacios en blanco, añade un espacio y devuelve el resultado:
PROCEDURE ACadena
PARAMETERS NoEsCadena
LOCAL UnaCadena
DO CASE
CASE TYPE("NoEsCadena")$"NF" && Numérica o coma flotante
UnaCadena = STR(NoEsCadena,2)
CASE TYPE("NoEsCadena") = "L" && Lógica
IF NoEsCadena
UnaCadena = "True"
ELSE
UnaCadena = "False"
ENDIF
CASE TYPE("NoEsCadena") = "C" && Carácter
UnaCadena = NoEsCadena
OTHERWISE && Nada de lo anterior
UnaCadena = "?"
ENDCASE
UnaCadena = LTRIM(RTRIM(UnaCadena))
UnaCadena = Unacadena+" "
RETURN UnaCadena
Un procedimiento como ACadena es adecuado para pasar cualquier variable y recibir
un resultado con un cierto formato.
Empresa = "Central Lechera Asturiana"
Cantidad = "500000"
TodoOk = .T.
? ACadena(Empresa) + ACadena(Cantidad) + ACadena(TodoOk)
* Devuelve esta cadena: Central Lechera Asturiana 500000 .T.

40 Guía del programador


proc&cb.dwp Page 41 Wednesday, August 30, 1995 2:02 PM

El ejemplo siguiente muestra cómo pasar una matriz a un procedimiento transmitiendo


el nombre de la matriz. El código calcula la media de todos los números de la matriz que
recibe.
DECLARE aNum[5]
FOR i=1 TO 5
aNum[i]=i && Llena la matriz con los valores 1,2,3,4 y 5
NEXT i
Media=MatrizMe(aNum) && Pasa la matriz aNum
? Media && Devuelve 3 ((1+2+3+4+5)/5)

PROCEDURE MatrizMe(X) && Declaración de procedimiento con un parámetro


Sum=0 && Inicializa variable a cero
FOR i = 1 TO ALEN(X) && ALEN() devuelve el número de elementos de la matriz
Sum = Sum+X[i]
NEXT i
RETURN Sum/ALEN(X)
Como se muestra en la sentencia siguiente, también es posible pasar elementos de
matriz de forma individual.
DO MiProc with X[i],X[i+1]
Para más información sobre las matrices, consulte el Capítulo 5.

Variación del número de parámetros


El número de parámetros que se pasan puede ser menor o mayor que el declarado en el
procedimiento.
• Si envía menos parámetros que los declarados, los que faltan se definen como .F.
• Si envía más parámetros que los declarados, el procedimiento no tiene en cuenta los
sobrantes.
PCOUNT( ) permite determinar cuántos parámetros se han pasado. El ejemplo
siguiente muestra cómo utilizar PCOUNT( ).
DO MiProc WITH A,B,C && Envía tres parámetros
DO MiProc WITH A,B,C,D,E && Envía cinco parámetros

PROCEDURE MiProc(P1,P2,P3,P4) && Espera cuatro parámetros


IF PCOUNT() >= 4
* ejecuta la tarea deseada si se reciben los suficientes parámetros
ELSE
* mostrar mensaje
ENDIF
RETURN

Capítulo 4, Uso de los procedimientos y bloques de código 41


proc&cb.dwp Page 42 Wednesday, August 30, 1995 2:02 PM

El programa siguiente aprovecha la posibilidad de un número variable de parámetros.


Si no se envía ningún parámetro, la ventana se muestra durante dos segundos.
Sin embargo, puede llamar este procedimiento con un parámetro para cambiar el
tiempo de espera.
PROCEDURE VentanaEspera(TiempoEspera)
Msg = "Pulse una tecla para continuar"
DO CASE
CASE PCOUNT() = 0 && No enviado valor TiempoEspera
TiempoEspera = 2 && Asigna 2 segundos a TiempoEspera
CASE PCOUNT() > 1 && Si se reciben demasiados parámetros
&& visualiza un mensaje
Msg = "Aviso, demasiados parámetros. " + Msg
ENDCASE
Longitud = (LEN(Msg) + 16)/2 && Usada para determinar el tamaño de la
&& ficha a crear
DEFINE FORM Esperando;
FROM 10,40 - Longitud TO 14,40 + Longitud
DEFINE TEXT TextEspera OF Esperando;
AT 1,2;
PROPERTY ;
Width LEN(Msg)+11,;
Text Msg && Mensaje a mostrar en la ficha
Esperando.Open() && Abre la ficha
Tecla = INKEY(TiempoEspera) && Espera los segundos especificados en
&& TiempoEspera
Esperando.Close() && Cierra la ficha
RETURN
Las siguientes son algunas llamadas posibles a VentanaEspera:
VentanaEspera() && Espera 2 segundos
VentanaEspera(5) && Espera 5 segundos
VentanaEspera(3,5) && Espera 3 segundos y muestra un
&& mensaje de aviso

42 Guía del programador


proc&cb.dwp Page 43 Wednesday, August 30, 1995 2:02 PM

Protección de los valores de las variables de memoria


Todos los procedimientos y funciones tienen la capacidad de modificar los valores de
las variables de memoria existentes. En el Capítulo 5 se describen los tipos y ámbitos de
las variables de memoria. En muchos casos, eso es lo que se pretende que haga el
procedimiento —actuar sobre las variables actuales para que devuelvan los valores
deseados—. En otros casos, puede ser preferible proteger los valores actuales de las
variables de memoria. Es posible elegir el comportamiento preferido pasando
parámetros por referencia o por valor.
• La transmisión por referencia pasa una referencia a la variable. Los cambios que realice
el procedimiento en la variable se refleja en el valor de ésta cuando termina aquél.
• La transmisión por valor pasa el valor de la variable, pero no la variable en sí.
Cuando los parámetros se pasan por valor, la subrutina receptora almacena el valor
en otra variable y no modifica la original. Para pasar por valor, ponga paréntesis
alrededor de la variable o variables que se pasan.
El ejemplo siguiente muestra las diferencias entre pasar un parámetro por referencia y
hacerlo por valor. Los paréntesis hacen que dBASE evalúe el valor de X como una
expresión y pase el resultado de esa expresión.
x = 10
? x && x = 10
Do MiProc WITH x && Pasada por referencia. Es posible cambiar el valor de x
? x && x ahora = 11 (x+1, cambiada por MiProc)
DO MiProc WITH (x) && Pasada por valor. Es posible usar el valor de x, pero
&& no se puede cambiar
? x && x todavía = 11 (no cambiado por MiProc)

PROCEDURE MiProc(n)
? n && Muestra 10 en la primera llamada, 11 en la segunda
n = n+1
? n && Muestra 11 en la primera llamada, 12 en la segunda
?
RETURN

Capítulo 4, Uso de los procedimientos y bloques de código 43


proc&cb.dwp Page 44 Wednesday, August 30, 1995 2:02 PM

Uso de los archivos y bibliotecas de procedimientos


Durante la ejecución, dBASE busca procedimientos en ciertos archivos específicos.
Estos archivos se enumeran a continuación en el orden en que dBASE los busca:
1 Archivo objeto del programa que se ejecuta (.PRO)
2 Otros archivos objeto abiertos (.PRO) en la cadena de llamada, en el orden en que se
abrieron
3 Archivo especificado por SYSPROC = <nombrearchivo> en DBASEWIN.INI
4 Archivos abiertos con las sentencias SET PROCEDURE, SET
PROCEDURE...ADDITIVE o SET LIBRARY, en el orden en que se abrieron
5 Archivo objeto (.PRO) con el nombre especificado en la vía de acceso
6 Archivo de programa (.PRG) con el nombre especificado en la vía de acceso, que
dBASE compila de forma automática
Si hay más de un procedimiento con el mismo nombre, dBASE ejecuta el primero que
encuentra.
Para lograr un mejor rendimiento, acorte la vía de acceso para los procedimientos que
utilice. Para que los programas puedan utilizar los procedimientos compilados, sitúelos
en bibliotecas de procedimientos o dentro del propio programa.

Almacenamiento de procedimientos en los archivos de programa


Un archivo de programa puede contener hasta 193 procedimientos; sin embargo, es
posible abrir muchos archivos de procedimiento, cada uno con un máximo de 193, y un
archivo de biblioteca adicional. Por tanto, el número de procedimientos a los que puede
accederse sólo está limitado por la memoria disponible.
La ubicación de procedimientos en un programa es útil cuando sólo los llama ese
programa en particular. Tener el programa y los procedimientos que llama en el mismo
archivo puede simplificar su mantenimiento. También puede mejorar el rendimiento
ligeramente, porque dBASE no tiene que abrir otros archivos para encontrar los
procedimientos.
Los procedimientos incluidos en un programa están disponibles mientras éste se
ejecuta.

44 Guía del programador


proc&cb.dwp Page 45 Wednesday, August 30, 1995 2:02 PM

Archivos de programa que contienen un solo procedimiento


Es posible escribir un solo procedimiento en un archivo de programa (.PRG) del mismo
nombre, en cuyo caso no son necesarios los comandos FUNCTION o PROCEDURE. En
general, utilice una técnica como ésta sólo para comprobar y depurar el procedimiento.
En el ejemplo siguiente, MIPROG.PRG es un procedimiento denominado MiProgr que
puede llamarse con la sentencia DO MiProg WITH varmem1, varmem2.
* Miprog.prg
PARAMETER x,y && Es necesario incluir la sentencia PARAMETERS ya que no se
&& ha utilizado la sentencia PROCEDURE
x = x + y && Por lo tanto no se puede utilizar la sintaxis
&& PROCEDURE Miprog(x,y)
RETURN
* fin de Miprog.prg

Archivos de programa que contienen varios procedimientos


Un archivo de programa puede contener varios procedimientos que el programa (o sus
subrutinas y procedimientos) llame, lo cual se ilustra en el ejemplo siguiente.
*MiProgs.prg
DO MiProc
DO Prog2 && Prog2.prg es un archivo aparte en disco
&& que también puede llamar a MiProc o a MiOtroProc
Fecha = MiOtroProc()
RETURN

PROCEDURE MiProc
ƒ
RETURN

PROCEDURE MiOtroProc
Mvar = DATE() + 30
RETURN Mvar
* Fin de MiProgs.prg

Capítulo 4, Uso de los procedimientos y bloques de código 45


proc&cb.dwp Page 46 Wednesday, August 30, 1995 2:02 PM

Almacenamiento de procedimientos en archivos de procedimiento


Es posible crear archivos de procedimiento para agrupar conjuntos de procedimientos
utilizados en varios programas. Este método de organizarlos permite que cualquier
programa que elija utilice esos procedimientos. Muchos programadores construyen un
grupo de procedimientos básicos que mantienen en uno o más archivos de
procedimientos, dependiendo por ejemplo, de su tipo o propósito.
Para abrir un archivo de procedimiento, utilice SET PROCEDURE TO <nombrearchivo>.
Para abrir otros archivos de este tipo cuando ya hay uno abierto, utilice la opción
ADDITIVE de SET PROCEDURE:
SET PROCEDURE TO <archivo2> ADDITIVE
Sin la opción ADDITIVE, dBASE cierra todos los archivos de procedimiento actuales
antes de abrir otro.
SET PROCEDURE TO VideoLib && procedimientos en VideoLib están disponibles
SET PROCEDURE TO SonidoLib ADDITIVE && procedimientos en SonidoLib también lo están
SET PROCEDURE TO Sonidolib && procedimientos en VideoLib dejan de
&& estar disponibles
Para cerrar todos los archivos de procedimiento abiertos, utilice SET PROCEDURE TO
sin opciones. Para cerrar un archivo en concreto, utilice CLOSE PROCEDURE
<nombrearchivo>.

Almacenamiento de procedimientos en archivos de biblioteca


Visual dBASE permite el uso de archivos de biblioteca además de los archivos de
procedimiento, principalmente para mantener la compatibilidad con dBASE IV.
Después de un archivo de procedimiento, siempre se busca en uno de biblioteca.
En Visual dBASE, se busca en todos los archivos de procedimiento y de biblioteca
abiertos en el orden en que se abrieron; no hay ninguna diferencia sustancial entre los
archivos de procedimiento y los de biblioteca, excepto que sólo puede haber uno abierto
de los de biblioteca. Por esa razón, no aportan ninguna ventaja en Visual dBASE y es
más aconsejable el uso de los archivos de procedimiento.

46 Guía del programador


proc&cb.dwp Page 47 Wednesday, August 30, 1995 2:02 PM

Uso de los punteros de función y bloques de código


En versiones anteriores de dBASE, los tipos de datos sólo se aplican a los datos con que
puede trabajar el usuario, como nombres, cantidades o fechas. En Visual dBASE, los
tipos de datos también se aplican a algunos con los que trabajan los programadores,
como objetos, procedimientos o bloques de código. El uso del código como dato es una
parte crucial de la programación orientada a objetos.
Visual dBASE añade estos nuevos tipos de datos para trabajar con código:
• El puntero de función es un nuevo tipo de datos que almacena referencias a
procedimientos o funciones. Una variable del tipo puntero de función contiene una
referencia a un procedimiento o función que puede asignar a una variable de
memoria o a una propiedad, pasar como un parámetro o un valor devuelto o
llamarla directamente.
• El bloque de código es un nuevo tipo de datos que almacena grupos pequeños y sin
nombre de comandos o expresiones. Es posible utilizar los bloques de código en
expresiones, asignarlos a variables de memoria o propiedades de objetos, pasarlos
como parámetros o valores devueltos o llamarlos directamente.
Lo siguiente es la sintaxis y las normas para escribir un bloque de código de sentencia:
{[|<parámetros>|]; <sentencia> [; <sentencia> ...]}
• Las llaves, { }, son necesarias.
• Si pasa parámetros, debe delimitarlos mediante | |.
• Incluya un punto y coma (;) antes de cada sentencia.
Lo siguiente es la sintaxis y las normas para escribir un bloque de código de expresión:
{|[<parámetros>]| <expresión>}
• Las llaves, { }, son necesarias.
• Las barras verticales, ||, son necesarias aunque no se pasen parámetros. Si pasa
alguno, debe delimitarlos mediante ||.
Las normas para trabajar con datos de puntero de función y de bloque de código son las
mismas, con la diferencia de que los punteros de función hacen referencia al código y los
bloques de código contienen código. El resto de esta sección muestra algunos ejemplos.
Parámetros
Para utilizar parámetros con un bloque de código, sitúelos al principio del bloque de
código delimitados con caracteres de barra vertical (||). El ejemplo siguiente crea un
bloque de código con dos parámetros, a y b:
Iguales = {|a,b|;? LTRIM(STR(a))+" más "+LTRIM(STR(b))+" = "+LTRIM(STR(a+b))}
Ejecute el bloque de código como si se tratara de una función, poniendo los valores de
los parámetros entre los paréntesis del operador de llamada.
? Iguales(2,2) && Devuelve "2 más 2 = 4"
? Iguales(1,5) && Devuelve "1 más 5 = 6"

Capítulo 4, Uso de los procedimientos y bloques de código 47


proc&cb.dwp Page 48 Wednesday, August 30, 1995 2:02 PM

Expresiones evaluadas dinámicamente


Por medio de un puntero de función asignado a un bloque de código de expresión, es
posible hacer que la expresión se evalúe dinámicamente durante la ejecución. El código
siguiente muestra valores guardados en variables de memoria; los valores se evalúan
una vez, cuando se ejecuta la sentencia de asignación, y permanecen estáticos a menos
que se reasignen.
X = 5 && Guarda 5 en X
Y = 6 && Guarda 6 en Y
Z = X+ Y && Guarda 11 en Z
? Z && Devuelve 11
X = X+ 1 && X ahora es igual a 6
? Z && Todavía devuelve 11
Ahora compare el código anterior con el siguiente que emplea un bloque de código.
X = 5 && Guarda 5 en X
Y = 6 && Guarda 6 en Y
Z = { | Param1 , Param2 | Param1 + Param2 } && Declara un bloque de código
&& que suma dos parámetros
? Z(X,Y) && Devuelve 11
X = X+ 1 && X ahora igual a 6
? Z(X,Y) && Devuelve 12
En el segundo ejemplo, la variable z contiene un bloque de código de expresión.
Siempre que se ejecuta z, la expresión se evalúa empleando los parámetros que se pasan,
en este caso, x e y.
La variable de bloque de código z puede pasarse como parámetro a otras funciones o
procedimientos o utilizarse como una variable evaluada dinámicamente.
Controladores de sucesos
Los bloques de código son muy útiles para asignar código a una propiedad de control
de sucesos de un objeto, como se muestra en los ejemplos siguientes.
form.MiBoton.Onclick = {;CLOSE FORM form} && Bloque de código
form.IntroCantidad.Valid = {| | this.Value > 0} && Bloque de código
Este uso de los bloques de código facilita la observación del código y la determinación
de qué acciones están asignadas a una propiedad en particular.
El ejemplo siguiente crea una ficha denominada MiFicha y el botón BotonSiguiente.
El procedimiento SaltaRegs está asignado a la propiedad OnClick de BotonSiguiente,
creando un puntero de función.
* MiFicha.prg
MiFicha = NEW FORM()
BotonSiguiente = NEW PUSHBUTTON(MiFicha)
BotonSiguiente.OnClick = Saltaregs && Crea un puntero de función
PROCEDURE Saltaregs
SKIP
IF EOF()
GO TOP
ENDIF
RETURN

48 Guía del programador


proc&cb.dwp Page 49 Wednesday, August 30, 1995 2:02 PM

Importante Un procedimiento debe estar disponible, o en el archivo de programa actual o en un


archivo de procedimientos abierto, en el momento en que se asigna a una variable o a
una propiedad, ya que dBASE resuelve la referencia al procedimiento en el momento de
la asignación.
El ejemplo siguiente asigna un bloque de código a una variable denominada ProxReg y,
entonces asigna la variable a una propiedad de suceso.
ProxReg = {;SKIP;IF EOF();GO TOP;ENDIF}
BotonSiguiente.OnClick = ProxReg
Debido a la brevedad de este bloque de código, es posible anexar el propio bloque de
código al suceso, en lugar de asignarlo primero a una variable.
BotonSiguiente.OnClick = {;SKIP;IF EOF();GO TOP;ENDIF}
Un bloque de código anexado está aislado y no es sencillo reutilizarlo en otro punto del
programa. Para ello, es necesario copiarlo y anexarlo en todos los lugares en que se
desea utilizar. Los procedimientos son más convenientes para codificar tareas largas y
repetidas, mientras que los bloques de código son adecuados para tareas breves o
sucesos esporádicos.
El ejemplo siguiente asigna un bloque de código que contiene toda una sentencia
IF...ENDIF a una variable de memoria.
AbrirOCrear={;IF FILE("MiArch.dbf"); USE MiArch;ELSE; CREATE MiArch From StruArch;ENDIF}
En este ejemplo, AbrirOCrear contiene un bloque de código que comprueba la
existencia de MIARCH.DBF, lo abre si existe o lo crea a partir del archivo STRUARCH.
Para ejecutar el bloque de código, incluya AbrirOCrear() en un programa después de
crear la variable de memoria.
El código siguiente muestra cómo programar la misma tarea en un procedimiento, no
en un bloque de código.
DO AbrirOCrear
*...
PROCEDURE AbrirOCrear
IF FILE("MiArch.DBF")
USE MiArch
ELSE
CREATE MiArch From StruArch
ENDIF

Capítulo 4, Uso de los procedimientos y bloques de código 49


proc&cb.dwp Page 50 Wednesday, August 30, 1995 2:02 PM

50 Guía del programador


memvars.dwp Page 51 Wednesday, August 30, 1995 2:03 PM

Capítulo

Uso de las variables de memoria


Capítulo 5
5
Una variable de memoria, o variable simplemente, es una posición en memoria que tiene
un nombre y cuyo fin es contener datos. Una variable de memoria almacena una sola
unidad de datos, de forma similar a un campo de una tabla. Una matriz es un conjunto
de variables de memoria estructurado en filas y columnas, de forma similar a los
registros de una tabla. Los programadores suelen utilizar las variables de memoria para:
• Realizar cálculos y almacenar temporalmente sus resultados
• Controlar la ejecución de un programa mediante los comandos de control de flujo
• Almacenar valores constantes, como valores globales de configuración
Este capítulo presenta las variables de memoria, describe sus ámbitos y proporciona
técnicas para trabajar con ellas.
Estos capítulos también tratan temas relacionados con las variables de memoria:
• El Capítulo 25 describe las variables de memoria del sistema utilizadas para la
configuración de las impresoras. (El Capítulo 5 de la Referencia del lenguaje contiene
una descripción completa de todas las variables del sistema.)
• El Capítulo 18 describe comandos para crear variables automem para extraer y
sustituir datos en los campos con rapidez.
• El Capítulo 10 describe las variables de referencia a objeto, que hacen referencia a
objetos.

Capítulo 5, Uso de las variables de memoria 51


memvars.dwp Page 52 Wednesday, August 30, 1995 2:03 PM

Acerca de las variables de memoria


Las variables de memoria son almacenamientos temporales de valores. Su creación se
realiza con la asignación de un valor a un nombre empleando el comando STORE o el
operador de asignación igual (=). Para recuperar el valor, basta utilizar el nombre de la
variable en una expresión; al evaluar la expresión, dBASE sustituye el nombre por el
valor. El ejemplo siguiente demuestra asignaciones simples de variables de memoria y
utiliza las variables en expresiones:
? 5 * 3 && Expresión sin variables (devuelve 15)
STORE 5 TO mVar1 && Crea la variable mVar1 y le asigna el valor 5
mVar2 = 3 && Crea la variable mVar2 y le asigna el valor 3
&& (Alternativa a la sintaxis STORE)
? mVar1 * mVar2 && La misma expresión usando variables (devuelve 15)
mVar1 = 6 && La variable mVar1 se actualiza con el valor 6
? mVar1 * mVar2 && Devuelve 18
Al igual que los campos de una tabla, cada variable tiene un tipo de datos, que es el
mismo que el de la expresión que se asigna a la variable.
mVar1 = 5 && Asigna un 5 a la variable mVar1
? TYPE("mVar1") && Devuelve N
mVar2 = "Hola" && Asigna la expresión de tipo carácter "Hola" a mVar2
? TYPE("mVar2") && Devuelve C
Z = DATE() && Guarda la fecha actual en la variable Z
? TYPE("Z") && Devuelve D
Una variable permanece en memoria hasta que se libera. Puede liberarse explícitamente
mediante el comando RELEASE o CLEAR ALL. (CLEAR ALL libera todas las variables;
RELEASE sólo las especificadas.) Las variables también se liberan de forma automática
en diversos momentos dependiendo de su ámbito. La sección “Declaración de las
variables de memoria” de este capítulo describe los ámbitos.

52 Guía del programador


memvars.dwp Page 53 Wednesday, August 30, 1995 2:03 PM

Nombres de las variables de memoria


La ayuda en línea y el Capítulo 1 de Referencia del lenguaje enumeran las normas que
deben seguirse al dar nombre a las variables de memoria. Además de estas reglas, los
programadores suelen establecer sus propias convenciones para los nombres, lo que
mejora la legibilidad del programa y facilita la posterior modificación del código.
Las siguientes son algunas convenciones habituales para los nombres que le pueden ser
de utilidad:
• Elija un nombre que describa el contenido o la función de la variable, como
PrecioTotal o Contador.
• Comience el nombre de las variables con una letra que indique su tipo de datos y
ámbito.
cNombre = "Severin" && variable tipo carácter
nPeso = 185 && variable numérica
LOCAL lnContador && variable local numérica
• Si la variable corresponde a un campo de una tabla, déle un nombre similar, como
mDirección para un campo Dirección.
• Combine mayúsculas y minúsculas. Los nombres de variable pueden utilizarse
indistintamente en mayúsculas y minúsculas. Los ejemplos siguientes facilitan la
lectura de los nombres:
nTotalCuentCli = 129,54
cCodMat = "PK909"
• Si una variable tiene el mismo nombre que un campo de una tabla abierta en el área
de trabajo actual, dBASE supone que el nombre hace referencia al campo.
Para diferenciar la variable del campo, teclee "m->" antes del nombre de la variable.
USE CLIENTE
Nombre = "Víctor" && Nombre es una variable
? Nombre && Imprime el contenido del campo Nombre
? m->Nombre && Imprime el contenido de la variable Nombre
Los nombres de variable pueden iniciarse con el carácter de subrayado (_). Sin embargo,
Visual dBASE utiliza esta convención para las variables de memoria del sistema, por lo
que no debe utilizarla en los nombres de sus variables. Para más información sobre las
variables de memoria del sistema, consulte el Capítulo 25.

Capítulo 5, Uso de las variables de memoria 53


memvars.dwp Page 54 Wednesday, August 30, 1995 2:03 PM

Declaración de las variables de memoria


Muchos lenguajes de programación exigen que las variables de memoria se declaren
antes de utilizarlas. La declaración de una variable le asigna un ámbito, que determina su
vida y su disponibilidad. En Visual dBASE es posible crear variables sin declararlas, por
ejemplo empleando una sentencia como varm=100; sin embargo, es una práctica de
programación recomendable declarar explícitamente las variables. (Las matrices sí
deben declararse explícitamente con el comando DECLARE, o bien utilice NEW para
crear un objeto de matriz; consulte “Uso de las matrices” más adelante en este capítulo.)
Visual dBASE proporciona cuatro comandos para declarar las variables de memoria y
definir su ámbito: PUBLIC, PRIVATE, STATIC y LOCAL. Cuando una variable se
declara como pública o estática, Visual dBASE la crea y la inicializa con un valor de falso
(.F.). Cuando se declara como privada o local, Visual dBASE no la crea ni la inicializa.
Los ejemplos siguientes demuestran la declaración e inicialización de variables. Para ver
ejemplos más detallados de la declaración y uso de las variables, consulte los ejemplos
que hay más adelante en este capítulo.
PUBLIC mNombre && Declara mNombre como pública; se inicializa como .F.
? mNombre && Devuelve .F.
mNombre = SPACE(30) && Asigna un valor específico a mNombre
PRIVATE nCost && nCost no tiene ningún valor todavía
? nCost && Se produce un error; nCost no tiene valor asignado
nCost = 500 && nCost se inicializa con 500
PUBLIC mCodigo, mDesc && Declara más de una variable
LOCAL cNombre && cNombre no tiene ningún valor asignado
STATIC lAcabado && Se inicializa con .F.
STATIC nTotal = 1000 && Solo las variables estáticas pueden ser declaradas e
&& inicializadas en una misma sentencia

Explicación de los ámbitos de las variables de memoria


El ámbito de una variable de memoria determina:
• La vida de la variable, es decir, cuándo se libera
• La disponibilidad de la variable, es decir, bajo qué circunstancias una rutina puede
acceder o modificar el contenido de la variable
Cada tipo de variable tiene normas diferentes que determinan su vida y disponibilidad,
que se resumen en la Tabla 5.1. Para más detalles sobre las similitudes y diferencias
entre los ámbitos de las variables de memoria, consulte el comando PUBLIC en la
Referencia del lenguaje. Para ver ejemplos de código que demuestren algunas de las
normas relacionadas con el ámbito de las variables, consulte los programas de ejemplo,
más adelante en este capítulo.

54 Guía del programador


memvars.dwp Page 55 Wednesday, August 30, 1995 2:03 PM

Tabla 5.1 Vida y disponibilidad de las variables de memoria


Ámbito Vida Disponibilidad
PUBLIC Sólo se libera explícitamente con En todas las subrutinas, incluidas las de
RELEASE o CLEAR ALL nivel superior y nivel inferior
PRIVATE Se libera cuando termina de ejecutarse la En la subrutina que la creó y en las de nivel
rutina que la creó inferior
LOCAL Se libera cuando termina de ejectuarse la Sólo en la subrutina que la creó
rutina que la creó
STATIC Sólo se libera explícitamente con Sólo en la subrutina que la creó
RELEASE o CLEAR ALL

En versiones anteriores de dBASE, los programadores utilizaban el ámbito privado para


“ocultar” un nombre de variable a otras subrutinas. En Visual dBASE, el ámbito local es
más adecuado para este fin.
Si no declara explícitamente un ámbito, las variables que cree en la ventana de
comandos serán públicas y las que cree en un archivo de programa serán privadas.
En programas sencillos, el ámbito por defecto de privado suele ser suficiente, y puede
omitir la declaración siempre que inicialice cada variable en la subrutina en que se
utiliza o en una subrutina de nivel superior. Cuando escriba programas más complejos,
es esencial que defina el ámbito de las variables mediante su declaración con el fin de
mejorar la modularidad y la posibilidad de reutilización.
Cuando se hace referencia a una variable de memoria en un programa, Visual dBASE
busca primero una variable privada, local o estática que esté disponible con ese nombre.
Si no la encuentra, busca una variable pública. Esto se muestra en el ejemplo siguiente.
* Programa ABC
mvar = 100 && dBASE busca una variable privada, local o estática llamada mvar.
&& Si la encuentra le asigna el valor 100 (eliminando su valor anterior).
&& Si no la encuentra busca una variable pública llamada mVar.
&& Si la encuentra le asigna el valor 100 (eliminando su valor anterior).
&& Si no se encuentra ninguna variable llamada mVar, dBASE crea una
&& variable privada, y la inicializa con 100

* Programa XYZ
mvar1 = mvar + 170 && Como antes, dBASE busca una variable privada, local o
&& estática llamada mVar. Si no la encuentra busca una
&& pública. Si tampoco la encuentra, se produce un error

Capítulo 5, Uso de las variables de memoria 55


memvars.dwp Page 56 Wednesday, August 30, 1995 2:03 PM

Públicas
Las variables públicas, en ocasiones denominadas variables globales, tienen el ámbito
más amplio. Están disponibles para cualquier subrutina y no se liberan hasta que se
utilice RELEASE o CLEAR ALL o se salga de Visual dBASE.
Las variables públicas son muy útiles para definiciones que se aplican a toda una
aplicación, como nombres de acceso de un usuario o vías de acceso por defecto a los
datos. Sin embargo, no proporcionan protección contra la sobreescritura. En general,
una aplicación bien diseñada utiliza pocas variables públicas, o ninguna.
La Figura 5.1 ilustra la disponibilidad de las variables públicas. MiPub, declarada pública
en Sub1, está disponible en todas las subrutinas. MiPub nunca se libera a menos que se
haga explícitamente con RELEASE o CLEAR ALL.
Figura 5.1 Disponibilidad de las variables públicas

Ppal

Sub1
Sub2
PUBLIC MiPub

Sub1A Sub1B

= La variable está disponible después de ejecutar Sub1; sigue disponible cuando termina Ppal

Privadas
Las variables privadas están disponibles en la subrutina que las crea y en las de nivel
inferior.
La Figura 5.2 ilustra la disponibilidad de las variables privadas. MiPrv, declarada privada
en Sub1, está disponible en Sub1, Sub1A y Sub1B. MiPrv se libera cuando Sub1 termina
de ejecutarse.
Figura 5.2 Disponibilidad de las variables privadas

Ppal

Sub1
Sub2
PRIVATE MiPrv

Sub1A Sub1B

= La variable está disponible; se libera cuando termina Sub1

56 Guía del programador


memvars.dwp Page 57 Wednesday, August 30, 1995 2:03 PM

Locales
Las variables locales están disponibles sólo para el procedimiento o función en que se
declaran. Puesto que el ámbito local limita la disponibilidad de la variable a una sola
subrutina, el termino ocultación de datos se utiliza en ocasiones para describir la
asignación de un ámbito local a las variables.
La Figura 5.3 ilustra la disponibilidad de las variables locales. MiLoc, declarada local en
Sub1, está disponible sólo en Sub1. Cada vez que se llama Sub1, se reinicializa MiLoc.

Figura 5.3 Disponibilidad de las variables locales

Ppal

Sub1
Sub2
LOCAL MiLoc

Sub1A Sub1B

= La variable está disponible; se libera cuando termina Sub1

Cualquier tipo de valor temporal de una subrutina debería guardarse normalmente en


una variable local. Ejemplos de este uso son las variables de contador en bucles
FOR...NEXT o DO...WHILE.

Estáticas
Las variables estáticas son locales en disponibilidad pero públicas en cuanto a vida.
Es decir, una variable estática continúa existiendo cuando la subrutina que la declaró
termina de ejecutarse (como una variable pública), pero no está disponible a menos que
se llame la subrutina de nuevo. Al igual que una variable local, una estática sólo está
disponible en la subrutina que la creó; sin embargo, a diferencia de aquella, una variable
estática mantiene su valor anterior cada vez que se llama la subrutina que la creó.
En consecuencia, las variables estáticas son muy útiles para almacenar valores que no se
incrementan, como totales acumulados en un informe.
A diferencia de los otros tipos de variables, las estáticas pueden declararse e inicializarse
en una sola sentencia, por medio de la sintaxis siguiente:
STATIC <varmem> = <valor>

Capítulo 5, Uso de las variables de memoria 57


memvars.dwp Page 58 Wednesday, August 30, 1995 2:03 PM

La Figura 5.4 ilustra la disponibilidad de las variables estáticas. MiEst, declarada estática
en Sub1, está disponible sólo para Sub1. La primera vez que se llama Sub1, MiEst se
inicializa con un valor de 100. Las siguientes llamadas de Sub1 no reinicializan MiEst,
sino que se mantiene el valor anterior de MiEst.
Figura 5.4 Disponibilidad de las variables estáticas

Ppal

Sub1
Sub2
STATIC MiEst=100

Sub1A Sub1B

= La variable está disponible, y lo está incluso después de terminar Sub1 y Ppal

El ejemplo siguiente ilustra cómo la declaración de las variables de memoria como


públicas, privadas o locales limita o permite el acceso a ellas. Como muestra el código,
las variables privadas no pueden declararse públicas en una subrutina de nivel inferior;
por contra, las locales sí pueden. Sin embargo, los cambios realizados en las variables
declaradas públicas en una subrutina no se devuelven a un nivel superior del programa
en el que las mismas variables estén declaradas como locales.
* Programa prog1
PUBLIC c_ID && Disponible para todas las subrutinas
c_ID = "mi nombre"
nCiudad = "OR" && Por defecto esta variable es privada
LOCAL dFecha && Las subrutinas de más bajo nivel no tienen acceso a esta variable
dFecha = {12/12/95}
Do prog2
? c_ID && Devuelve "mi nombre", porque la variable con el mismo
&& nombre que existe en Prog2 es Privada
? nCuenta && Devuelve 45, porque nCuenta es declarada Pública en Prog2 y no está
&& declarada en Prog1
? dFecha && Devuelve {12/12/95}, porque dFecha ha sido declarada Local en Prog1
&& y no puede ser modificada en subrutinas de más bajo nivel
* Programa prog2
PUBLIC nCuenta, dFecha && nCuenta no está declarada aun, dFecha es local en Prog1
PUBLIC nCIUDAD && Se produce un error porque nCiudad ha sido declarada Privada
&& en Prog1
PRIVATE c_ID && Una nueva variable que no entra en conflicto con ninguna
&& variable Pública del mismo nombre
c_ID = "Nombre de esta persona"
nCuenta = 45
dFecha = {03/04/95}
RETURN

58 Guía del programador


memvars.dwp Page 59 Wednesday, August 30, 1995 2:03 PM

Importante Cuando se declaran e inicializan variables estáticas en una sola sentencia, Visual dBASE
las inicializa en una subrutina sólo la primera vez que se llama la subrutina; las
siguientes llamadas no reinicializan la variable estática. El ejemplo siguiente ilustra esta
conducta.
* Programa Prog1
PUBLIC nTotal
nTotal = 1000
Do Prog2
.
.
.
Do Prog2

* Programa Prog2
STATIC nVentas = 500 && nVentas se inicializa a 500 sólo la primera vez que se llama a
&& prog2
STATIC nProfPct
nProfPct = .03 && nProfPct se inicializa a .03 cada vez que se llama a Prog2
? nVentas && Devuelve 500 la primera vez que se llama a Prog2
&& Devuelve 800 (500 + 300) la segunda vez que se llama a Prog2
&& ya que a nVentas se le suma 300 más tarde en este programa
STATIC nMargen=0 && Se inicializa a 0 sólo la primera vez que se ejecuta Prog2
nMargen = nMargen + (nVentas * nProfPct)
? nMargen && Devuelve 15 (0+(500*.03)) la primera vez que se ejecuta Prog2
&& Devuelve 39 (15+(800*.03)) la segunda vez que se ejecuta Prog2
nTotal = nTotal + nVentas
? nTotal && Devuelve 1500 (1000 + 500) la primera vez que se llama a Prog2
&& Devuelve 2300 (1500 + 800) la segunda vez que se llama a Prog2
nVentas = nVentas + 300
RETURN

Capítulo 5, Uso de las variables de memoria 59


memvars.dwp Page 60 Wednesday, August 30, 1995 2:03 PM

Uso de las matrices


Una matriz es un grupo de variables de memoria relacionadas que se estructuran en filas
y columnas. Las variables individuales se denominan elementos o miembros de la matriz.
Puede entenderse que una matriz tiene una estructura interna similar a una hoja de
cálculo, donde cada elemento de la matriz equivale a una celda de la hoja de cálculo.
Al igual que sucede con otras variables de memoria, es posible pasar matrices como
parámetros a las funciones y obtener la devolución de valores.
Los programadores suelen utilizar las matrices para:
• Procesar una serie de variables de memoria relacionadas en varias pasadas.
• Trabajar con valores de una tabla sin mantener abierta la tabla, lo cual es
especialmente útil en un entorno multiusuario.
• Almacenar “tablas” de información temporales con un formato de fila/columna.
Las matrices pueden tener una, dos o más dimensiones. Las matrices unidimensionales
contienen una fila y un número especificado de columnas. Las bidimensionales
contienen un número especificado de filas y un número especificado de columnas.
Visual dBASE permite las matrices con más de dos dimensiones, aunque la mayoría de
las funciones de matrices sólo actúan en las de una o dos dimensiones, que son los tipos
utilizados habitualmente por los programadores.
El comando DECLARE le permite crear, o inicializar, una matriz. Para inicializar una
matriz unidimensional que contenga 6 elementos, se debe ejecutar el comando:
DECLARE aMatrNombre[6] && Los corchetes [ ] son necesarios al trabajar con matrices
Para inicializar una matriz bidimensional que contenga 3 filas y 4 columnas, con un total
de 12 elementos, se debe ejecutar el comando siguiente:
DECLARE aMatrNombre[3,4]
Cuando se inicializa una matriz, todos sus elementos tienen el valor .F. (falso).
Sin embargo, es posible situar valores de cualquier tipo de datos en cualquier elemento
de la matriz. Es decir, una misma matriz puede contener datos de fecha, numéricos, de
cadena y de cualquier otro tipo que pueda almacenarse en una variable de memoria.
Hay dos formas de hacer referencia a los elementos individuales de una matriz:
mediante los subíndices del elemento o mediante su número. Los subíndices de un
elemento indican la fila y columna en que está situado. Los números indican su posición
secuencial en la matriz. Los elementos se numeran de forma secuencial comenzando en
la primera fila y primera columna de la matriz. La Figura 5.5 muestra el número y los
subíndices de cada elemento de una matriz que contiene 3 filas y 4 columnas.

60 Guía del programador


memvars.dwp Page 61 Wednesday, August 30, 1995 2:03 PM

Figura 5.5 Números y subíndices de los elementos de una matriz bidimensional

Subíndice 2

Número de elemento 1 2 3 4

Subíndice 1 1,1 1,2 1,3 1,4


5 6 7 8

2,1 2,2 2,3 2,4


9 10 11 12

3,1 3,2 3,3 3,4

Para hacer referencia a un elemento individual de una matriz mediante sus subíndices:
aMatriz[3,3] = 50 && Guarda el valor 50 en la tercera fila, tercera columna

Manipulación de las matrices y los valores que contienen


Visual dBASE proporciona muchas funciones para manipular las matrices y sus valores.
Con estas funciones, es posible utilizar una matriz como estructura dinámica de datos
similar a las tablas. Además, puede trabajarse con las matrices como clases de objetos.
(Consulte “Uso de las matrices como objetos” en el Capítulo 10.)
La Tabla 5.2 resume los comandos y funciones para matrices de Visual dBASE.
Tabla 5.2 Resumen de las tareas de matrices y los comandos y funciones relacionados
Tarea de matriz Funciones Descripción
Referencia a elementos ASUBSCRIPT() Devuelve el subíndice de fila o columna de un elemento
especificado por su número de elemento.
AELEMENT() Devuelve el número de un elemento especificado por sus
subíndices de fila y columna.
Rellenado con valores AFILL() Rellena uno, varios o todos los elementos de una matriz
con un valor dado.
ADIR() Rellena una matriz con el listado de un directorio según
una máscara de archivos dada.
ADIREXT() Rellena una matriz con el listado de un directorio según
una máscara de archivos dada (Windows 95 solamente)
AFIELDS() Rellena una matriz con la información de la estructura de
una tabla abierta.
Búsqueda de valores ASCAN() Busca una expresión dada en una matriz.
Adición y borrado de ADEL() Borra un elemento, o una fila o columna de elementos,
elementos sin cambiar las dimensiones de la matriz.
AGROW() Añade un elemento, o una fila o columna de elementos, y
cambia las dimensiones de la matriz. AGROW() puede
transformar una matriz unidimensional en
bidimensional.
AINS() Inserta un elemento, o una fila o columna de elementos,
sin cambiar las dimensiones de la matriz.

Capítulo 5, Uso de las variables de memoria 61


memvars.dwp Page 62 Wednesday, August 30, 1995 2:03 PM

Tabla 5.2 Resumen de las tareas de matrices y los comandos y funciones relacionados (continuación)
Tarea de matriz Funciones Descripción
ARESIZE() Aumenta o disminuye el tamaño de una matriz.
ARESIZE() puede convertir una matriz unidimensional
en bidimensional.
Copia y ordenación de ACOPY() Copia elementos de una matriz a otra.
elementos
ASORT() Ordena los elementos de una matriz unidimensional o
las filas de una bidimensional.
Determinación del ALEN() Devuelve el número de elementos, filas o columnas de
tamaño una matriz dada.
Copia entre matrices y COPY TO ARRAY Copia datos de una tabla de base de datos a una matriz.
tablas
APPEND FROM Añade datos de una matriz en una tabla de base de datos.
ARRAY
REPLACE FROM Sustituye los datos de una tabla de base datos por los de
ARRAY una matriz.

Los ejemplos siguientes muestran el uso de algunas de las funciones enumeradas en


la Tabla 5.2.
AFILL(), ASORT()
El ejemplo siguiente muestra cómo puede una matriz contener los valores de una tabla
en un entorno multiusuario. Esta técnica es útil para leer los valores sin chocar con los
bloqueos de registro o archivo que otros usuarios puedan aplicar a la tabla.
Sin embargo, los valores que devuelve esta técnica no reflejan los cambios que se
realicen después de haber cerrado la tabla. Es decir, emplee esta técnica sólo cuando no
sea necesario que los valores devueltos reflejen la información más actual de la tabla.
USE <archivo> EXCLUSIVE && EXCLUSIVE asegura el acceso a todos los registros
&& en un entorno multiusuario
COUNT TO nRegistros
DECLARE aMatriz[nRegistros,3] && Se crean tres columnas y, por cada registro, una fila
AFILL(aMatriz,0) && Inicializa a 0 todos los elementos de la matriz
GO TOP
nReg = 1
SCAN
aMatriz[nReg,1] = salario && salario en la primera columna
aMatriz[nReg,2] = años_aqui && años en la empresa en la segunda columna
aMatriz[nReg,3] = edad && edad en la tercera columna
nReg = nReg + 1
ENDSCAN
USE && Permite a otros usuarios el acceso a la tabla
ASORT(aMatriz) && ASORT() requiere que todos los elementos a ordenar sean del
&& mismo tipo. Por esa razón usamos AFILL después de declarar la
&& matriz
? aMatriz[nRegistros,1] && El salario más alto se encuentra en la primera columna de
&& la última fila
ASORT(aMatriz,3) && Ordena por edad, el valor de la columna 3
? aMatriz[1,3] && Devuelve el más joven
? aMatriz[nRegistros,3] && Devuelve el más viejo

62 Guía del programador


memvars.dwp Page 63 Wednesday, August 30, 1995 2:03 PM

ARESIZE()
Este ejemplo muestra cómo cambiar el tamaño de la matriz del ejemplo anterior para
poder añadir más valores. En este caso, se añaden dos columnas en la matriz.
ARESIZE(aMatriz,nRegistros,5,1) && La matriz tiene ahora 5 columnas
&& El 1 final indica que los valores originales deben
&& continuar en su lugar
ALEN(), AGROW()
En este ejemplo se supone que no se sabe cuántas filas o columnas contiene la matriz, y
es necesario saberlo para poder realizar ciertas funciones. Se sabe que el sueldo de cada
empleado está en la primera columna, el nombre en la quinta, el apellido en la sexta y la
fecha de nacimiento en la última columna. Se quiere dar un aumento a todos y, además,
enviar tarjetas de felicitación a los que cumplan años en el mes actual. Para almacenar
los nombres de estas personas, se crea una segunda matriz.
DECLARE aCumplean[1,2] && Crea una matriz para guardar los nombres de las
&& personas a las que enviar tarjeta de felicitación
nNuevaFila = 1
nFilas = ALEN(aMatriz,1) && Devuelve el número de filas de la matriz de empleados
nCols = ALEN(aMatriz,2) && Devuelve el número de columnas de la matriz de empleados
nMesActual = MONTH(DATE()) && Devuelve el mes actual (1-12)

FOR mvar = 1 TO nFilas && Recorre todas las filas de la matriz de empleados
aMatriz[mvar,1] = aMatriz[mvar,1] * 1.03 && Dar a todos un 3% de aumento
IF MONTH(aMatriz[mvar,nCols]) = nMesActual && Compara mes de nacimiento con mes en
&& curso
aCumplean[nNuevafila,1] = aMatriz[mvar,5] && Nombre en primera fila de la matriz de
&& tarjetas
aCumplean[nNuevafila,2] = aMatriz[mvar,6] && Apellidos en la segunda columna de la
&& matriz de tarjetas
AGROW(aCumplean,1) && Añadir una fila a la matriz de tarjetas
nNuevaFila = nNuevaFila+1
ENDIF
NEXT

Capítulo 5, Uso de las variables de memoria 63


memvars.dwp Page 64 Wednesday, August 30, 1995 2:03 PM

Expansión de las variables de tipo carácter


En ocasiones es imposible saber qué argumentos se utilizarán en una línea de comandos
hasta que se ejecuta el programa. Por ejemplo, puede escribir una subrutina que procese
los registros de una tabla en función de un criterio introducido por el usuario, como
Provincia = "CO". En este caso, no puede introducir el criterio de búsqueda en el código
fuente porque no sabe cuál será. Además, algunos comandos, como FIND, precisan
valores literales como argumentos, y normalmente no se sabe qué valor se introducirá
hasta la ejecución. Para estas situaciones, dBASE proporciona el operador de macro (&).
El operador de macro emplea, en una línea de comando, el contenido de una variable de
memoria del tipo carácter en lugar del nombre de la variable. De esta forma, es posible
almacenar los argumentos de la línea de comandos en variables de carácter y extraerlos
con el operador de macro durante la ejecución. Este proceso se denomina sustitución de
macro o expansión de macro.
La sustitución de macro se realiza situando el operador de macro (&) justo antes del
nombre de la variable en una línea de comando. Durante la ejecución, dBASE evalúa el
contenido de la variable de carácter y emplea el resultado en lugar del ampersand y del
nombre de variable, como se muestra en el ejemplo siguiente.
lCondicion = "ventas > 10000"
COUNT FOR &lCondicion
Esta es la sintaxis para utilizar el operador de macro:
&<variable memoria carácter>[.]
El punto optativo (.) identifica el final del nombre de la variable. El punto también se
denomina terminador de macro. Utilice el punto cuando necesite distinguir entre el
nombre de la variable y el texto que le sigue en la línea de comando.
Existen algunas limitaciones en cuanto al uso de la expansión de macro en programas
de Visual dBASE:
• Puesto que dBASE realiza la sustitución de macro durante la ejecución, las macros no
pueden expandirse en las directivas de preprocesador, que se evalúan durante la
compilación. Para más información sobre estas directivas, consulte el Capítulo 7.
• Las macros sólo permiten expandir los nombres de variable, no los de campo.
• La sustitución de macro puede utilizarse para introducir verbos de comando, pero no
es recomendable. Puede provocar problemas con aplicaciones de versión compiladas
y reducirá la velocidad de ejecución del programa.
• Ni las variables locales ni las estáticas pueden expandirse, como se muestra en el
ejemplo siguiente.
LOCAL mvar1
mvar1 = "valor > 1000"
SET FILTER TO &mvar1 && Provoca un error de sintaxis

64 Guía del programador


memvars.dwp Page 65 Wednesday, August 30, 1995 2:03 PM

Los siguientes son algunos ejemplos de usos típicos de la expansión de macro.


En el ejemplo siguiente, se necesita cambiar un valor SET y se desea restaurarlo a su
estado original al terminar. Este es un uso muy común de las macros.
cseguro = SET("SAFETY") && Devuelve "ON" o "OFF"
SET SAFETY OFF
** Líneas del programa
SET SAFETY &cSeguro && Restablece el entorno original
El ejemplo siguiente crea una filtro dinámico en función de las preferencias del usuario.
cCriterio = GETEXPR("","Cree criterio", "L") && Deja al usuario construir una expresión
&& lógica
IF LEN(cCriterio) <> 0 && La expresión no puede estar vacía
SET FILTER TO &cCriterio && Se activa el filtro de la tabla con el criterio elegido
ENDIF
En el ejemplo siguiente, el usuario ha elegido en una lista un informe para su ejecución y
ha seleccionado un botón de radio “Impresora”. Las selecciones del usuario se han
almacenado en las variables cInforme y cDonde.
cInforme = <nombre del informe elegido por el usuario>
cDonde = "TO PRINT"
REPORT FORM &cInforme &cDonde

Capítulo 5, Uso de las variables de memoria 65


memvars.dwp Page 66 Wednesday, August 30, 1995 2:03 PM

Almacenamiento de las variables de memoria en archivos


Es posible guardar variables de memoria, incluso matrices, en un archivo y restaurarlas
posteriormente. Los programadores suelen utilizar archivos de memoria (.MEM) para
almacenar datos que, de otra forma, ocuparían un registro de una tabla, como los
valores de configuración de un programa o de una impresora. Utilice los comandos
SAVE y RESTORE para crear y recuperar los archivos de memoria respectivamente.
• SAVE guarda todas o algunas variables de memoria en un archivo de memoria.
Use una máscara de nombre con caracteres comodín (?, *) para guardar sólo ciertas
variables.
SAVE no guarda variables de estos tipos: puntero de función, referencia a objeto y
memoria del sistema.
• RESTORE restaura en la memoria las variables de un archivo de memoria.
Por defecto, RESTORE libera todas las variables de memoria existentes antes de
restaurar las que hay en el archivo de memoria. Para preservar las variables
existentes, utilice la opción ADDITIVE.
El ejemplo siguiente muestra un uso simple de los comandos SAVE y RESTORE.
* Programa principal de la versión demo de una aplicación
IF FILE("guardvar.mem") && ¿Existe el archivo de memoria?
RESTORE FROM guardvar && Si existe se usa
ELSE && Si no,
dPrimEjec = DATE() && guarda la fecha de la primera ejecución del programa
SAVE TO guardvar
ENDIF
IF DATE() - dPrimEjec >= 30 && ¿La primera ejecución tuvo lugar hace más de 30 días?
** Mostrar un mensaje recordando la necesidad de registrar el producto en los
** primeros 30 días
ENDIF
Por defecto, todos los valores devueltos por RESTORE son privados. Si desea que
tengan un ámbito diferente, debe declararlo antes de ejecutar el comando RESTORE y
debe utilizar la opción ADDITIVE. Esto se muestra en el ejemplo siguiente, que utiliza el
archivo de variables de memoria guardado en el ejemplo anterior.
* Programa 1 -- no hacer esto
PUBLIC dPrimEjec
IF FILE("guardvar.mem")
RESTORE FROM guardvar && dPrimEjec es ahora privada a pesar de la anterior
&& sentencia PUBLIC
ENDIF
RETURN
* Programa 2 -- esto funciona
PUBLIC dPrimEjec
IF FILE("guardvar.mem")
RESTORE FROM guardvar ADDITIVE && dPrimEjec es ahora pública
ENDIF
RETURN

66 Guía del programador


datatyp.dwp Page 67 Wednesday, August 30, 1995 2:04 PM

Capítulo

Uso de los tipos de datos


Capítulo 6
6
Visual dBASE permite diversos tipos de datos en las variables de memoria y en los
campos y proporciona funciones para manipular y dar formato a los datos de cada tipo.
Este capítulo presenta los tipos de datos y trata los más comunes, los que se aplican
tanto a las variables como a los campos. Los tipos de datos que se aplican sólo a las
variables se tratan en el Capítulo 5. Los que se aplican sólo a los campos se describen en
el Capítulo 18.
Para más información sobre los comandos y funciones que se citan en este capítulo,
consulte la Referencia del lenguaje. El Capítulo 1 de la Referencia del lenguaje describe las
reglas de cada tipo de datos.

Acerca de los tipos de datos


Un tipo de datos es una clasificación de los datos guardados en un campo o en una
variable de memoria. Cada tipo tiene reglas que controlan cómo se trabaja con los datos
y qué puede contener el campo o la variable. Por ejemplo, los datos numéricos sólo
pueden contener números, con los que es posible realizar cálculos. Los datos de tipo
carácter pueden contener letras, números o signos de puntuación, pero no es posible
realizar cálculos con los números.
En versiones anteriores de dBASE, los tipos de datos sólo se aplican a los datos con que
pueden trabajar los usuarios, como nombres, cantidades o fechas. En Visual dBASE, los
tipos de datos también se aplican a algunos de los datos con que trabajan los
programadores, como objetos, procedimientos o bloques de código. Por ejemplo, una
variable que hace referencia a un objeto es del tipo objeto. Una que hace referencia a un
procedimiento o función es del tipo puntero de función. Una variable que contiene una
expresión o una serie de comandos es del tipo bloque de código. Para más información
sobre el uso de los bloques de código y los punteros de función, consulte el Capítulo 4.

Capítulo 6, Uso de los tipos de datos 67


datatyp.dwp Page 68 Wednesday, August 30, 1995 2:04 PM

Al igual que los tipos de datos tradicionales, éstos nuevos tienen reglas que controlan
cómo se trabaja con ellos. Un bloque de código puede asignarse a una propiedad,
pasarse como parámetro o ejecutarse. Sin embargo, no es posible realizar cálculos con
un bloque de código.
Con estos tipos de datos nuevos, Visual dBASE amplía el concepto de lo que son datos
en las aplicaciones. En versiones anteriores de dBASE, los datos son con lo que trabaja el
usuario en las tablas. Con Visual dBASE, los datos son cualquier información sobre la
que actúa el programador o el usuario. La información puede ser la dirección de una
persona que se imprime o un objeto de botón sobre el que se hace clic.
La Tabla 6.1 enumera los tipos de datos existentes en Visual dBASE.
Tabla 6.1 Tipos de datos de Visual dBASE
Tipo de datos Símbolo
Carácter C
Variables o
campos de las Fecha D
tablas de dBASE Lógico (booleano) L
Numérico N
Sólo variables Indicador BM
Bloque de código CB
Puntero de función FP
Objeto O
Matriz A
Nulo U
Sólo campos de Binario B
tablas de dBASE (Coma) flotante F
Memo M
OLE O

68 Guía del programador


datatyp.dwp Page 69 Wednesday, August 30, 1995 2:04 PM

Formato por defecto de los datos


Visual dBASE no dispone de valores por defecto incorporados para el formato tipo
fecha, hora, monetario y número. Por el contrario, los valores por defecto se definen
mediante la opción Internacional del Panel de control, que es una utilidad de Windows
para personalizar y configurar el entorno Windows. La mayoría de las aplicaciones
Windows, en especial las utilizadas en más de un país, permiten que los usuarios
especifiquen esos formatos en el Panel de control, en lugar de disponer de valores por
defecto incorporados en las aplicaciones.
Si nunca ha utilizado el Panel de control, busque su icono en el grupo de programas
Principal. Para ayuda sobre el uso del Panel de control, consulte la documentación de
Windows.
Los valores por defecto del Panel de control pueden sustituirse de tres formas:
• Utilice el comando SET correspondiente, como SET CENTURY o SET SEPARATOR.
Los parámetros cambiados de esta forma son temporales; al salir de dBASE, se
restauran los valores por defecto.
• Cambie las definiciones en el archivo de configuración de Visual dBASE,
DBASEWIN.INI. Para ello, ejecute SET o elija Propiedades|Escritorio para cambiar
los valores de forma interactiva, o edite DBASEWIN.INI directamente. Los valores
que cambie de esta forma son permanentes (hasta que los cambie de nuevo); cada vez
que inicie dBASE, estos valores tienen prioridad sobre los del Panel de control. Para
una información completa sobre los valores de DBASEWIN.INI, consulte el
Apéndice C de la Guía del usuario.
• Inicie dBASE utilizando la opción de línea de comandos -c para especificar un
archivo de configuración que no sea DBASEWIN.INI. Esto también se trata en el
Apéndice C de la Guía del usuario.

Capítulo 6, Uso de los tipos de datos 69


datatyp.dwp Page 70 Wednesday, August 30, 1995 2:04 PM

Uso de los datos de carácter


Los datos de carácter son el tipo más común que procesan los programas. Consisten en
letras, números, signos de puntuación y cualquier otro símbolo, incluidos los espacios.
dBASE trata los datos de carácter como cadenas de símbolos; en consecuencia, los
campos o variables de carácter suelen denominarse “cadenas” o “datos de cadena”.
Aunque las cadenas de caracteres pueden contener números, no es posible realizar
operaciones matemáticas con números que están en cadenas. dBASE trata los números
de una cadena como símbolos, igual que cualquier letra o signo de puntuación. En las
tablas, los códigos postales y números telefónicos suelen guardarse en campos de tipo
carácter, ya que no se realizan cálculos con ellos. A la inversa, las cantidades e importes
que van a sumarse o cuya media se va a calcular se almacenan en campos numéricos o
flotantes.
Las cadenas de caracteres literales deben delimitarse en las expresiones.
Los delimitadores pueden ser apóstrofos ('), comillas (") o corchetes ([ ]). Debe utilizarse
el mismo delimitador a ambos lados de la cadena. Es decir, [venta] es una expresión de
caracteres válida, pero "venta] no lo es.
Nombre = "Pedro" && Guarda la cadena "Pedro" en la variable de carácter Nombre
NumClien = '1234' && Guarda la cadena "1234" en la variable de carácter NumClien
Cantidad = 1234 && Guarda el valor 1234 en la variable numérica Cantidad
La Tabla 6.2 muestra algunas formas habituales de manipular los datos de tipo carácter
en un programa.
Tabla 6.2 Manipulación de las cadenas de caracteres
Tarea Función Ejemplo
Medir la longitud LEN() LEN(Nombre_pieza)
Averiguar la posición de una AT() AT(“Mar”,“Lunes,Martes,...”)
subcadena en una cadena más RAT()
larga
Extraer una subcadena SUBSTR() SUBSTR(Cod_art,7,4)
Extraer desde la izquierda LEFT() LEFT(Nombre_pieza,9)
Extraer desde la derecha RIGHT() RIGHT(mCod_art,4)
Insertar una cadena en otra STUFF() STUFF(mPlazos,8,2,mDías)
Repetir caracteres REPLICATE() REPLICATE(“*”,16)
SPACE() mNombre = SPACE(30)
Concatenar cadenas operador + mNombre + mApellido, o bien
operador – mNombre – mApellido
Comparar cadenas empleando LIKE() LIST Cod_art FOR LIKE(“*-700-??30”,Cod_art)
caracteres comodín

70 Guía del programador


datatyp.dwp Page 71 Wednesday, August 30, 1995 2:04 PM

Comparación de cadenas de caracteres


Una tarea habitual al trabajar con datos de carácter es comparar el contenido de dos
cadenas. Normalmente se realizan comparaciones de cadenas al buscar datos o al
bifurcar la ejecución de un programa. Utilice operadores relacionales (como >, <, =) para
comparar las cadenas. El Capítulo 1 de la Referencia del lenguaje describe los operadores
relacionales empleados para la comparación de cadenas.
En las aplicaciones, con frecuencia es necesario saber si un usuario ha introducido algo
en un campo de entrada. Suponga que una aplicación muestra un cuadro de diálogo
para obtener un nombre; el cuadro de diálogo contiene un campo de entrada Nombre y
un botón Aceptar. Cuando el usuario sale del cuadro de diálogo, la aplicación debe
comprobar si el usuario ha introducido un nombre y, si es así, lo consulta en una tabla.
IF ISBLANK(Ficha.Nombre.Valor)
RETURN
ELSE
SCAN FOR Nombre = Ficha.Nombre.Valor
? Nombre
ENDSCAN
ENDIF

Comparación fonética
La comparación de cadenas carácter por carácter funciona bien si ambas cadenas están
escritas correctamente. Sin embargo, los errores ortográficos y de tecleo por parte del
usuario son inevitables. Para encontrar cadenas similares pero escritas de forma
incorrecta, compárelas por la forma en que sonarían. Para comparar cadenas
fonéticamente, genere un código fonético, o de sonido similar, para cada cadena.
Dicho código es una fórmula utilizada habitualmente para generar el valor fonético de
las palabras. Visual dBASE proporciona dos funciones para generar y comparar valores
por su código fonético:
• SOUNDEX() devuelve el código fonético de una cadena de caracteres.
• DIFFERENCE() devuelve un número (0 a 4) que representa el grado de similitud
fonética entre dos cadenas.
Los códigos fonéticos son específicos de cada idioma. Es decir, cada controlador de
idioma tiene su propia fórmula para generarlos. Para más información sobre los
controladores de idioma, consulte el Apéndice C.
Estos son algunos ejemplos de comparación fonética:
? "Juan"="Juana" && Devuelve .F.
? SOUNDEX("Juan")=SOUNDEX("Juana") && Devuelve .T.
DIFFERENCE("Juan","Juana") && Devuelve 4 (muy similares)
DIFFERENCE("Coche","Cochera") && Devuelve 3 (menos similares)
DIFFERENCE("dBASE","C") && Devuelve 1 (nada similares)

Capítulo 6, Uso de los tipos de datos 71


datatyp.dwp Page 72 Wednesday, August 30, 1995 2:04 PM

La función SOUNDEX() puede crear un índice en función de los valores fonéticos.


El ejemplo siguiente utiliza SOUNDEX() y DIFFERENCE() para encontrar nombres de
empresas que suenan de forma similar cuando una consulta de índice no logra
encontrar una coincidencia exacta. Si se encuentra al menos un nombre similar, se
muestra una lista con los nombres parecidos. El nombre de compañía está contenido en
el campo de entrada CoNombre.
USE Cuentas
INDEX ON SOUNDEX(Empresa) TAG SonidoComp && Crea un índice SOUNDEX()
INDEX ON Empresa TAG Empresa
IF SEEK(Ficha.CoNombre.Valor) && ¿Existe la empresa?
? Cuentas->Empresa && Sí. La muestra
ELSE && No, busca nombres similares
SET ORDER TO Sonidocomp && Usa el índice SOUNDEX()
IF SEEK(SOUNDEX(Ficha.CoNombre.Valor)) && ¿Son nombres similares?
SELECT (Cuentas) && Sí. Los muestra
LIST Apellidos WHILE ;
DIFFERENCE(Empresa,Ficha.CoNombre.Valor) >2
ELSE && No existen nombre similares
? "No hay nombres de empresa similares al que está buscando."
ENDIF
ENDIF

Concatenación de cadenas de caracteres


Al crear expresiones de caracteres, quizá necesite combinar dos o más cadenas.
Añada, o concatene, las cadenas empleando los operadores más (+) o menos (–).
• El operador + añade la cadena situada a la izquierda del operador al final de la
cadena de la derecha. Es el método de concatenación más común.
• El operador – elimina los espacios que haya al final de la cadena de la izquierda antes
de añadir la otra y entonces añade los espacios al final de la cadena de la derecha.
Este método es útil para alinear columnas de información.
? "Hola "+ "Juan." && Devuelve "Hola Juan."
? "Hola "- "Juan." && Devuelve "HolaJuan. "
El ejemplo siguiente utiliza los dos operadores, – y +, para mostrar la ciudad, la
provincia y el código postal. El operador – mueve los espacios del campo Ciudad a la
derecha de Provincia. Así, los códigos postales se alinean en vertical.
USE Clientes
LIST OFF ALL Ciudad - ", "+ Provincia + " " + Cod_post
Las ciudades se muestran como sigue:
Valencia, V 46012
Bilbao, BI 48032
Sevilla, SE 41006
Madrid, M 28015
Barcelona B 08023

72 Guía del programador


datatyp.dwp Page 73 Wednesday, August 30, 1995 2:04 PM

Extracción de parte de una cadena de caracteres


Al construir expresiones de caracteres, podría ser necesario extraer parte de una cadena,
por ejemplo, el nombre de pila de un campo Nombre. Una cadena que se extrae de otra
se denomina subcadena. Visual dBASE proporciona estas funciones para extraer
subcadenas de una cadena:
• LEFT() extrae una subcadena desde el extremo izquierdo de una cadena.
• RIGHT() extrae una subcadena desde el extremo derecho de una cadena.
• SUBSTR() extrae una subcadena desde cualquier posición de una cadena.
Estos son algunos ejemplos de extracción de subcadenas:
mCiudad = "Madrid"
mCod_post = "28015"
? UPPER(LEFT(mCiudad,1)+RIGHT(mCiudad,1)) && Devuelve MD
? SUBSTR(mCpostal,1,2) && Devuelve 28
mLocalidad = "Villaviciosa de Odón"
mProvincia = "Madrid"
mCod_post = "28670"
? UPPER(LEFT(mLocalidad,1)+RIGHT(mLocalidad,1)) && Devuelve VN
? SUBSTR(mCod_post,1,2) && Devuelve 28
Inverso = SUBSTR(mLocalidad,17,4)+ " " + SUBSTR(mLocalidad,1,2)
? Inverso
Al extraer subcadenas, en ocasiones es necesario averiguar la posición de la subcadena
en la cadena. Para ello, Visual dBASE proporciona estas funciones:
• AT() devuelve la posición inicial de una subcadena en una cadena, buscando y
contando desde la izquierda.
• RAT() devuelve la posición inicial de una subcadena en una cadena, buscando desde
la derecha y contando desde la izquierda.
AT() y RAT() devuelven 0 si la subcadena no se encuentra en la cadena.
Es posible extraer una subcadena sin conocer su posición en la cadena principal.
El ejemplo siguiente utiliza AT(), LEFT() y SUBSTR() para imprimir los elementos de
una lista separada por comas. El código utiliza la coma como marca que buscar y para
mostrar los elementos de la lista.
mListArch = "Mov_ctas,Cliente,Empleado,Pedidos,Proveeds"
DO WHILE "," $ mListArch && Mientras la lista contenga
&& una coma
? LEFT(mListArch, AT(",",mListArch)-1) && Muestra la primera palabra
mListArch = SUBSTR(mListArch, AT(",",mListArch)+1) && Elimina la primera palabra
ENDDO
? mListArch && Muestra la última palabra

Capítulo 6, Uso de los tipos de datos 73


datatyp.dwp Page 74 Wednesday, August 30, 1995 2:04 PM

Modificación o determinación del uso de mayúsculas y minúsculas


Varias funciones comprueban o cambian el uso de mayúsculas y minúsculas en una
cadena de caracteres. Una cadena que tiene todas las letras MAYÚSCULAS se dice que
está en mayúscula, una que las tiene todas minúsculas se dice que está en minúscula y una
que tiene la Primera Letra de cada palabra en mayúscula se dice que tiene un formato de
nombre propio.
• UPPER() convierte una cadena a mayúscula; ISUPPER() devuelve verdadero si el
primer carácter de una cadena es mayúscula.
• LOWER() convierte una cadena a minúscula; ISLOWER() devuelve verdadero si el
primer carácter de una cadena es minúscula.
• PROPER() convierte una cadena al formato de nombre propio. Utilice PROPER() con
cadenas que representan personas, lugares o cosas únicas, como “Vicente”,
“Estados Unidos”, “Gobierno”.
El ejemplo siguiente sustituye el contenido del campo Provincia de dos caracteres de la
tabla Clientes por letras en mayúscula.
USE Clientes
REPLACE ALL Provincia WITH UPPER(Provincia)
El ejemplo siguiente modifica el campo Nombre de la tabla Ciudades para convertir a
mayúscula la primera letra del país y a minúscula las demás letras.
USE Ciudades
REPLACE ALL Nombre WITH UPPER(LEFT(Nombre,1))+LOWER(SUBSTR(Nombre,2,10))
El código anterior funciona correctamente sólo si el nombre de la ciudad tiene una única
palabra. Una ciudad como “San Sebastián” se convertiría en “San sebastián”. Una forma
más adecuada de convertir los nombres al formato de nombre propio es utilizar
PROPER():
USE Ciudades
REPLACE ALL Nombre with PROPER(Nombre)
El controlador de idioma actual determina cómo trata Visual dBASE las diferencias entre
mayúsculas y minúsculas con ciertos caracteres especiales, como vocales acentuadas o
caracteres especiales de cada idioma. Para más información, consulte el Apéndice C.

74 Guía del programador


datatyp.dwp Page 75 Wednesday, August 30, 1995 2:04 PM

Eliminación de espacios
En algunas ocasiones, es necesario eliminar espacios al principio o al final de una
cadena.
• TRIM() y RTRIM() son funciones idénticas que eliminan espacios del lado derecho de
una cadena.
• LTRIM() elimina espacios del lado izquierdo de una cadena.
Los campos de una tabla, o los campos de entrada de una ficha, tienen anchos fijos para
albergar la longitud máxima de una cadena. Cuando se muestra una cadena en
particular, es posible eliminar los espacios extra del lado derecho para poder mostrar
otra cadena junto a ella. Este ejemplo elimina los espacios del campo Ciudad para
mostrar una dirección:
? TRIM(Ciudad) + ", " + Provincia + " "+Cod_Post && Muestra "Madrid, M 28015"
La sección “Concatenación de cadenas de caracteres” de este capítulo muestra otra
técnica para eliminar espacios mediante el operador menos (–).

Repetición de caracteres
Visual dBASE dispone de dos funciones para repetir caracteres en una cadena, evitando
así la necesidad de teclearlos manualmente:
• SPACE() repite el carácter de espacio el número especificado de veces, lo cual es útil
para inicializar las variables de caracteres.
• REPLICATE() repite cualquier carácter, o cadena, que especifique el número
indicado de veces, lo cual es útil para crear efectos visuales simples, como una fila de
asteriscos o caracteres de subrayado.
mNombre = SPACE(30) && Inicializa la variable con 30 espacios
mOcupacion = SPACE(45) && Inicializa la variable con 45 espacios
mLinComienzo = REPLICATE("*",30) && Crea una fila con 30 asteriscos
? mLinComienzo && Muestra ******************************

Capítulo 6, Uso de los tipos de datos 75


datatyp.dwp Page 76 Wednesday, August 30, 1995 2:04 PM

Uso de los datos numéricos


Los datos numéricos son cualquier tipo de datos con que se trabaja empleando
operaciones matemáticas, como suma, multiplicación o funciones matemáticas.
dBASE DOS proporciona dos formas de especificar datos numéricos (numéricos y
flotantes) y los procesa de forma diferente. En dBASE DOS, los valores del tipo
numérico pueden tener una precisión de hasta 20 dígitos y los valores flotantes, de 15.
Aunque Visual dBASE mantiene flotante como tipo de campo, no hay diferencia entre los
valores numéricos y flotantes. En los cálculos matemáticos, Visual dBASE proporciona
la misma precisión (19 dígitos) para ambos tipos. Visual dBASE retiene el tipo de campo
flotante para mantener la compatibilidad con dBASE DOS.
Si asigna un número a una variable de memoria, la función TYPE() siempre devuelve N.
TYPE() devuelve F sólo para los campos flotantes.
En dBASE DOS, SET PRECISION especifica el número de dígitos que se utilizan
internamente para los cálculos matemáticos. En Visual dBASE, SET PRECISION afecta a
las comparaciones de datos, pero no a los cálculos matemáticos.
La Figura 6.1 compara cómo afecta el valor de precisión a las operaciones numéricas en
Visual dBASE y en dBASE DOS:
Figura 6.1 Comparación de SET PRECISION en Visual dBASE y en dBASE DOS

Visual dBASE dBASE IV

* Este código da el mismo resultado que dBASE IV


SET DECIMALS TO 18 SET DECIMALS TO 18
SET PRECISION TO 19 SET PRECISION TO 19
x=0.12345678901234567 x=0.12345678901234567
y=0.12345678901234568 y=0.12345678901234568
? x=y && Devuelve .F. ? x=y && Devuelve .F.
? x+y && Devuelve 0.246913578024691350 ? x+y && Devuelve 0.246913578024691350

* Este resultado difiere de dBASE IV


SET PRECISION TO 16 SET PRECISION TO 16
? x=y && Ahora devuelve .T. ? x=y && Todavía devuelve .F.
? x+y && Todavía devuelve 0.246913578024691350 ? x+y && Ahora devuelve 0.246913578024691400

La Tabla 6.3 enumera algunas funciones numéricas generales de Visual dBASE.


Tabla 6.3 Funciones numéricas generales
Función Devuelve
ABS() El valor absoluto de un número (elimina el signo algebraico)
EXP() La base e elevada a una potencia especificada (exponente)
LOG() El logaritmo neperiano (en base e) de un número
LOG10() El logaritmo común (en base 10) de un número
MAX() El mayor de dos números
MIN() El menor de dos números
MOD() El resto (módulo) de un número dividido por otro

76 Guía del programador


datatyp.dwp Page 77 Wednesday, August 30, 1995 2:04 PM

Tabla 6.3 Funciones numéricas generales (continuación)


Función Devuelve
PI() El valor aproximado de pi (razón de la circunferencia de un círculo con su
diámetro)
RANDOM() Un número aleatorio
SIGN() El signo (positivo o negativo) de un número
SQRT() La raíz cuadrada de un número

El Capítulo 20 describe CALCULATE, SUM y otros comandos que resumen los datos
numéricos de las tablas.

Especificación de valores globales de visualización de los números


Varios comandos SET afectan al formato en que los números se muestran en pantalla o
se envían a la impresora. Estos parámetros no cambian el valor de los números, sólo la
forma en que se muestran. Una vez que se ejecuta un comando SET, los siguientes
comandos que muestran números, como ?, DEFINE ENTRYFIELD y BROWSE, utilizan
el formato especificado. La Tabla 6.4 enumera los comandos SET que especifican el
formato de los números.
Tabla 6.4 Parámetros de visualización de los números
Comando SET Parámetros de visualización
SET CURRENCY Especifica si el símbolo monetario se muestra a la izquierda o a la derecha del
número cuando se utiliza el símbolo de formato “$”.
SET CURRENCY TO Especifica el carácter que se utiliza como símbolo monetario cuando se usa el
símbolo de formato “$”.
SET DECIMALS Especifica el número de decimales que se muestran. SET DECIMALS no afecta a la
precisión interna de los números.
SET POINT Especifica el carácter que separa la parte decimal de un número de la parte entera.
SET SEPARATOR Especifica el carácter que separa cada grupo de tres dígitos de un valor mayor
de 999.

Los ejemplos siguientes demuestran el uso de algunos valores de visualización de


números para la divisa francesa.
Dinero = 543567.4532
SET CURRENCY RIGHT && Visualiza el símbolo monetario a la derecha
SET CURRENCY TO "FF" && Francos franceses
SET DECIMALS TO 2
SET POINT TO ","
SET SEPARATOR TO "."
? Dinero && Devuelve 543567,45
? Dinero Picture "999,999.99" && Devuelve 543.567,45
? Dinero Picture "@$999,999.99" && Devuelve 543567,45FF
? Dinero Function "$" && Devuelve 543567,45FF

Capítulo 6, Uso de los tipos de datos 77


datatyp.dwp Page 78 Wednesday, August 30, 1995 2:04 PM

Visualización de los números en las fichas


Los valores numéricos guardados en un campo o en una variable pueden mostrarse en
una ficha cuando se crea un control, como un campo de entrada, cuadro de texto o
cuadro de incremento, y se asocia un campo o variable de tipo numérico al control.
Cada uno de estos controles tiene dos propiedades especiales para especificar cómo se
muestran los datos:
• La propiedad Function especifica restricciones de formato o entrada para toda la
variable o todo el campo.
• La propiedad Picture especifica restricciones de formato o entrada para una parte de
la variable o del campo.
La Tabla 6.5 muestra algunos ejemplos de cómo asignar formato a los datos numéricos
mediante las propiedades Picture y Function.
Tabla 6.5 Uso de las propiedades Function y Picture para asignar formato a los números
Formato Valor de la propiedad
Mostrar un número con notación exponencial Function “^”
Limitar la visualización a dígitos, signos y Picture “999” o Picture “###” (el número real de 9 o
espacios en blanco de signos # varía)
Mostrar valores de cero como una cadena vacía Function “Z”
Mostrar ceros iniciales como ceros, signos Function “L”
monetarios o asteriscos
Mostrar un número con formato monetario Function “$”
estándar
Mostrar CR (haber) después de un número Function “C” y Function “X”, respectivamente
positivo y DB (debe) después de uno negativo
Mostrar números negativos entre paréntesis Function “(“
Centrar, alinear a la derecha o alinear a la Function “I”, Function “J” y Function “B”
izquierda un número

El código siguiente define un campo de entrada para mostrar el nombre de una persona.
El valor de la propiedad Picture restringe la introducción de datos sólo a letras; la
propiedad Function trunca los espacios iniciales y finales:
DEFINE ENTRYFIELD Peso OF This PROPERTY;
Width 3, Top 11, Left 21, Height 1, ColorNormal "bg+/b", ;
Border .F., Picture "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", Function "T", ;
DataLink "Diagnosis"

78 Guía del programador


datatyp.dwp Page 79 Wednesday, August 30, 1995 2:04 PM

Realización de cálculos financieros


Visual dBASE proporciona tres funciones para calcular cantidades en función del interés
aplicado a inversiones o préstamos.
• FV() calcula el valor futuro de un préstamo o inversión dados pagos periódicos
iguales a un tipo de interés fijo.
• PAYMENT() calcula la cantidad de pago periódico necesaria para pagar un préstamo
o inversión en función de unos valores dados de principal, plazo y tipo de interés.
• PV() calcula el valor presente de un préstamo o inversión dados pagos periódicos a
un tipo de interés fijo.
Por ejemplo, una inversión de 25.000 Pts por mes con un tipo de interés anual del 7 por
ciento compuesto mensual durante 10 años tendría el siguiente valor futuro (FV):
mValFut = FV(25000,.07/12,12*10)
? mValFut Function "$" && Devuelve 4327120Pt
Puesto que el valor futuro representa el total de los depósitos y los intereses generados,
la cantidad total de interés ganados después de 10 años se averigua como sigue:
? mValFut - (25000*120) && Devuelve 1327120Pt
Para determinar la hipoteca máxima que una persona puede asumir dados el nivel de
pago preferido y el tipo de interés actual, utilice PV(). Por ejemplo, si el comprador de
una casa desea pagar 70000 al mes, y las hipotecas a interés fijo y 30 años están
actualmente al 7 por ciento, utilice lo siguiente:
mValAct = PV(70000,.07/12,12*30)
? mValAct Function "$" && Devuelve 10521530Pt
Utilice PAYMENT() cuando conozca el principal y desee determinar la cantidad de
pago periódico. Por ejemplo, utilice lo siguiente para averiguar el pago mensual por una
hipoteca de 10000000 al 7 por ciento de interés durante 30 años:
mPagoMen = PAYMENT(10000000,.07/12,12*30)
? mPagoMen Function "$" && Devuelve 66530Pt

Realización de cálculos trigonométricos


Las aplicaciones de ingeniería y científicas suelen precisar la realización de cálculos
trigonométricos. La Tabla 6.6 enumera las funciones trigonométricas disponibles en
Visual dBASE. Estas funciones devuelven valores flotantes.
Tabla 6.6 Funciones trigonométricas
Función Devuelve
ACOS() El arcocoseno (inversa del coseno) de un número
ASIN() El arcoseno (inversa del seno) de un número
ATAN() La arcotangente (inversa de la tangente) de un número
ATN2() La arcotangente (inversa de la tangente) de un punto
dado

Capítulo 6, Uso de los tipos de datos 79


datatyp.dwp Page 80 Wednesday, August 30, 1995 2:04 PM

Tabla 6.6 Funciones trigonométricas (continuación)


Función Devuelve
COS() El coseno de un ángulo
DTOR() El valor en radianes de un ángulo medido en grados
RTOD() El valor en grados de un ángulo medido en radianes
SIN() El seno de un ángulo
TAN() La tangente de un ángulo

El ejemplo siguiente halla la longitud de una viga de par de tejado para una habitación
de 40 pies de ancho con un ángulo de 30 grados y un alero de 18 pulgadas (1.5 pies):
mViga = (40/2)/COS(DTOR(30)) + 1.5
? mViga && Devuelve 24.59

Truncado y redondeo de números


Las expresiones numéricas suelen producir resultados con decimales. Si un cálculo
precisa un resultado entero (sin decimales), es necesario redondear los decimales o
truncarlos. Visual dBASE proporciona varias funciones para truncar y redondear
números:
• CEILING() devuelve el entero más próximo que sea mayor o igual a un número dado.
• FLOOR() devuelve el entero más próximo que sea menor o igual a un número dado.
• INT() devuelve la parte entera de un número dado, truncando los decimales.
• ROUND() redondea un número dado a una cifra especificada de decimales.
Los ejemplos siguientes parten del ejemplo de la longitud de la viga de par de la sección
anterior.
? mViga && Devuelve 24,59
? CEILING(mViga) && Devuelve 25,00
? FLOOR(mViga) && Devuelve 24,00
? ROUND(mViga,1) && Devuelve 24,60
? INT(mViga) && Devuelve 24,00
? mViga - INT(mViga) && Devuelve la parte decimal, 0,59
SET DECIMALS TO 8 && Por defecto se obtienen dos decimales
? mViga && Devuelve 24,59401077
? ROUND(mViga,1) && Devuelve 24,60000000
SET DECIMALS TO 2
? mViga && Devuelve 24,59
El ejemplo siguiente convierte el resto decimal de mViga a pulgadas y octavos de
pulgada:
mPulgadas = 12*(mViga-INT(mViga)) && Valor decimal * 12 pulgadas/pie
mOctavos=8*(mPulgadas-Int(mPulgadas)) && Valor decimal * 8 octavos/pulgada
mLongViga=LTRIM(STR(INT(mViga))) + " Pies "; && Valor entero (pies)
+ LTRIM(STR(INT(mPulgadas))) + " " ; && Valor entero (pulgadas)
+ LTRIM(STR(mOctavos)) + "1/8 Pulgadas" && Octavos de pulgada
? mLongViga && Devuelve "24 pies 7 1/8 pulgadas"

80 Guía del programador


datatyp.dwp Page 81 Wednesday, August 30, 1995 2:04 PM

Uso de los datos de fecha


Los campos y variables del tipo fecha contienen valores que representan fechas del
calendario. Estos valores tienen algunas de las características de los números. Al igual
que éstos, dos fechas pueden compararse para comprobar cuál es anterior (menor que) o
posterior (mayor que). También es posible sumar o restar un número de días de una
fecha, calculando dBASE la fecha resultante. De forma similar, es posible restar una
fecha de otra para averiguar el número de días transcurridos entre ellas.
Las sentencias siguientes determinan una fecha anterior en 90 días a una fecha de
vencimiento para enviar recordatorios:
mPago = {01/01/95} && Podría ser el valor del campo
mPago90 = mPago - 90
? mPago90 && Devuelve 10/03/94
También es posible almacenar valores de hora rudimentarios en las fechas mediante la
adición o resta de fracciones de días, por ejemplo:
mFechaEnt = {07/01/1988} && 07/01/88
mFechaSal = mFechaEnt + .25 && 07/01/88
? mFechaEnt = mFechaSal && .F.
Aunque mFechaEnt y mFechaSal están en el mismo día, Visual dBASE almacena
internamente un valor de mFechaSal que es un cuarto de día mayor que el de mFechaEnt.
Si no desea que una comparación muestre diferencias inferiores a un día, convierta los
datos del tipo fecha al de carácter antes de compararlos, como sigue:
? DTOC(mFechaEnt) = DTOC(mFechaSal) && Devuelve .T.
El ejemplo siguiente inicializa una variable, mDif, con la diferencia entre la fecha de hoy
y la fecha de una transacción. La rutina emite una carta de recordatorio si la cuenta lleva
vencida entre 30 y 45 días, y una carta más imperiosa si venció hace más de 45 días.
mDif = DATE() – Fecha_trans && Fecha_trans es la fecha de la transacción
DO CASE
CASE mDif > 30 .AND. mDif <= 45 && La transacción se realizó hace más de 30 días y
&& menos de 45
DO Recordar
CASE mDif > 45 && La transacción se hizo hace más de 45 días
DO PagarYa
ENDCASE

Capítulo 6, Uso de los tipos de datos 81


datatyp.dwp Page 82 Wednesday, August 30, 1995 2:04 PM

Formato y visualización de las fechas


Las fechas pueden mostrarse con diferentes formatos. Por defecto, Visual dBASE utiliza
los valores de formato que se definen por medio de la opción Internacional del Panel de
control. También es posible extraer parte de una fecha, como el día de la semana, para
lograr formatos de visualización especiales.
La Tabla 6.7 muestra comandos y funciones útiles para trabajar con fechas:

Tabla 6.7 Comandos y funciones de fecha


Comando o función Descripción
CDOW(), DOW() Devuelve el día de la semana como una palabra o como un número,
respectivamente. dBASE considera el domingo como primer día de la
semana.
CMONTH(), MONTH() Devuelve el mes como una palabra o como un número,
respectivamente.
DAY() Devuelve el día del mes como un número.
DMY(), MDY() Devuelve la fecha con el formato DD Mes AA o Mes DD, AA,
respectivamente.
SET CENTURY Especifica el número de dígitos (2 o 4) en la parte del año de una fecha.
SET DATE Especifica el formato general de las fechas (MM/DD/AA, DD-MM-
AA, etc.) según las convenciones de cada país.
SET DATE TO Define la fecha del sistema.
SET MARK Especifica el carácter empleado para separar las partes de año, mes y
día de una fecha.
YEAR() Devuelve el año de una fecha como un número de cuatro dígitos.

Los ejemplos siguientes demuestran el formato y la visualización de las fechas:


mFecha = {01/06/94}
SET CENTURY OFF && Muestra el año con 2 dígitos
? CDOW(mFecha) && Devuelve Miércoles
? DOW(mFecha) && Devuelve 4
? CMONTH(mFecha) && Devuelve Junio
? MONTH(mFecha) && Devuelve 6
? DAY(mFecha) && Devuelve 1
? DMY(mFecha) && Devuelve 1 Junio 94
? MDY(mFecha) && Devuelve Junio 01, 94
SET CENTURY ON && Muestra el año con 4 dígitos
? mFecha && Devuelve 01/06/1994
? MDY(mFecha) && Devuelve Junio 01, 1994
SET DATE JAPAN
? mFecha && Devuelve 1994/06/01
SET DATE AMERICAN
? mFecha && Devuelve 06-01-1994
SET MARK TO "*"
? mFecha && Devuelve 06*01*1994
SET CENTURY OFF
? YEAR(mFecha) && Devuelve 1994, porque YEAR() ignora el valor de SET CENTURY

82 Guía del programador


datatyp.dwp Page 83 Wednesday, August 30, 1995 2:04 PM

Uso de los datos de hora


Aunque no hay un tipo de datos de hora, es posible almacenar y manipular datos de
hora como cadenas de caracteres. La función TIME() devuelve la hora actual como una
cadena de caracteres, que puede almacenar en una variable de memoria como sigue:
mAhora = TIME() && Devuelve una cadena como esta "14:30:43"
A continuación, puede dividir mAhora en subcadenas si desea utilizar sólo una parte de
la hora. Por ejemplo, si la hora es 2:30 y 43 segundos (14:30:43), la instrucción siguiente
sitúa la cadena 14:30 en mAhora.
mAhora = LEFT(mAhora,5) && Devuelve "14:30"
La Tabla 6.8 enumera los comandos y funciones que funcionan con datos de hora.
Tabla 6.8 Comandos y funciones de hora
Comando o función Descripción
ELAPSED() Devuelve el número de segundos transcurridos entre dos
horas especificadas.
SECONDS() Devuelve el número de segundos transcurridos desde las
12 a.m. (media noche).
SET TIME Define la hora del sistema.
TIME() Devuelve la hora del sistema.

El código siguiente utiliza las funciones de hora para calcular cuánto dura la ejecución
de una subrutina:
mComienzo = TIME() && Guarda la hora actual
DO rutina
mFin = TIME() && Guarda la hora en la que terminó de
&& ejecutarse el código
mDuracion = ELAPSED(mFin,mComienzo) / 3600 && Convierte segundos a horas
? mDuracion && Muestra las horas transcurridas
Para ver un ejemplo de validación de datos de hora en una ficha, consulte la página 225.

Capítulo 6, Uso de los tipos de datos 83


datatyp.dwp Page 84 Wednesday, August 30, 1995 2:04 PM

Uso de los datos lógicos


Los datos lógicos, también conocidos como datos booleanos, sólo pueden tener dos
valores: verdadero (.T.) o falso (.F.). Los campos y variables del tipo lógico suelen
almacenar datos de “sí o no / una cosa u otra“, como Casado, Pagado o Exento. En las
fichas, cree controles de casilla de verificación para mostrar y editar los valores lógicos.
Al escribir expresiones lógicas, no es necesario completar la expresión con “= .T.” o
“= .F.”. Un valor lógico es una expresión. Utilice FOR Exento en lugar de
FOR Exento = .T., aunque ambas son válidas.
Es posible comparar dos expresiones lógicas, como FOR Exento = Estado. Visual dBASE
evalúa la expresión y devuelve otro valor lógico (.T. o .F.) en función del resultado de la
comparación.
Visual dBASE acepta T (verdadero) o Y (sí) como valor positivo y F (falso) o N (no) como
negativo.
La rutina siguiente utiliza el operador lógico .NOT. y las funciones FOUND() y EOF()
para procesar los posibles resultados de una búsqueda con SET NEAR ON.
USE Proveeds ORDER Cod_prove
SET NEAR ON && El puntero se sitúa en el valor más parecido al buscado en
&& caso de no encontrarlo
mClave = Cod_Prove
SEEK mClave
DO CASE
CASE FOUND() && Se encuentra el valor exacto, muestra datos
? Cod_Prove, Proveedor, Telefono, Ciudad
CASE .NOT. EOF() && No se encuentra el proveedor, pero sí uno similar
? "Proveedor no encontrado"
? "Se muestra el siguiente proveedor (orden alfabético)"
? Cod_Prove, Proveedor, Telefono, Ciudad
OTHERWISE && No se encontró el proveedor ni uno similar
? "Proveedor no encontrado"
? "Puntero al final del archivo"
ENDCASE

84 Guía del programador


datatyp.dwp Page 85 Wednesday, August 30, 1995 2:04 PM

Uso de los tipos de datos nulos


Visual dBASE permite el uso de un tipo de datos nulo, principalmente por la
compatibilidad con tablas que no son de dBASE y admiten valores nulos en los campos.
dBASE utiliza la constante NULL para representar un valor nulo. NULL permite que
dBASE lea, escriba y compare valores de campos y variables que contienen NULL, en
oposición a 0 o una cadena vacía. Para obtener una explicación completa de NULL,
consulte el Capítulo 1 de la Referencia del lenguaje.

Uso de los tipos de datos que no son de dBASE


Las tablas que no son propias de dBASE, como las de Paradox, pueden contener campos
de tipos diferentes a los mencionados en la Tabla 6.1. Visual dBASE puede leer y escribir
en estos campos. Cuando se guardan datos de un tipo de campo que no es de dBASE en
una variable de memoria, Visual dBASE convierte los datos a un tipo permitido.
De igual forma, cuando se sustituye el contenido de un campo no dBASE por el de una
variable de memoria, Visual dBASE convierte los datos al tipo no dBASE.
Por ejemplo, las tablas de Paradox permiten campos monetarios para almacenar valores
monetarios. Si asigna el campo monetario a una variable de memoria, Visual dBASE
crea una variable numérica. De igual forma, puede guardar el contenido de una variable
numérica en un campo monetario.
El Capítulo 23 describe cómo Visual dBASE utiliza los tipos de datos de Paradox.
Si utiliza una subrutina que no es de dBASE, como una función C de una DLL,
Visual dBASE convierte los tipos de datos de los parámetros pasados o de los valores
devueltos en función de un prototipo que declare con el comando EXTERN.
Para más información, consulte el Capítulo 26.

Capítulo 6, Uso de los tipos de datos 85


datatyp.dwp Page 86 Wednesday, August 30, 1995 2:04 PM

Conversión entre tipos de datos


En ocasiones, es necesario convertir datos de un tipo a otro. Por ejemplo, si desea
mostrar texto que incluya cadenas de caracteres y una fecha o un número, convierta los
datos que no son de carácter a este tipo. De forma análoga, los cálculos con tipos mixtos
precisan que todos se conviertan a un mismo tipo.
La Tabla 6.9 enumera las funciones que convierten de un tipo de datos a otro.
Tabla 6.9 Conversión de tipos de datos
Conversión Función
Carácter a numérico VAL()
Numérico a carácter STR()
Numérico a flotante FLOAT()
Carácter a fecha CTOD()
Fecha a carácter DTOC()
Fecha a carácter (para indexación); DTOS()
devuelve una cadena con el formato
AAAAMMDD
Numérico, lógico o fecha a carácter TRANSFORM()

Los ejemplos siguientes representan algunas conversiones habituales de tipos de datos.


Para indexar por fecha y cantidad pedida, convierta los campos de fecha y numérico en
cadenas de caracteres:
INDEX ON DTOS(Fecha_pedido) + STR(Cantidad,8,2) TO Fecha_cant
El código siguiente extrae los dos caracteres del mes y del año de la fecha (suponiendo el
formato MM/DD/AA). A continuación, los concatena con el número de cliente para
generar un número de factura exclusivo:
mEste_an = RIGHT(DTOC(DATE()),2)
mEste_mes = LEFT(DTOC(DATE()),2)
mNum_Factura = mClient_id + mEste_an + mEste_Mes
El ejemplo siguiente incrementa en una unidad m_Id, un código de identificación de los
empleados (por ejemplo, B1234). Para ello, convierte los cuatro dígitos en datos
numéricos mediante la función VAL() e incrementa en uno el número resultante.
A continuación, utiliza STR() para convertir el número de nuevo en una serie de
caracteres y concatena el resultado con la letra inicial del código de identificación.
m_Id = LEFT(m_Id,1) + TRANSFORM(VAL(RIGHT(m_Id,4)) + 1,"@L 9999")

86 Guía del programador


preproc.dwp Page 87 Wednesday, August 30, 1995 2:05 PM

Capítulo

Uso de las directivas de


Capítulo 7
7
preprocesador
Las directivas de preprocesador son sentencias que se insertan en el código de dBASE y
que indican al compilador que actúe en el código antes de compilarlo. Por ejemplo, las
directivas pueden realizar una “búsqueda y sustitución” de texto, establecer una
compilación condicional o especificar opciones del compilador. Visual dBASE busca y
evalúa las directivas de preprocesador de forma automática siempre que se compila el
código. El acceso al preprocesador permite un mayor control sobre el proceso de
compilación y puede mejorar la velocidad de las aplicaciones. Este capítulo presenta el
preprocesador de Visual dBASE y describe cómo utilizar sus directivas.

Capítulo 7, Uso de las directivas de preprocesador 87


preproc.dwp Page 88 Wednesday, August 30, 1995 2:05 PM

Acerca del preproceso


Visual dBASE utiliza un compilador de una sola pasada con un preprocesador
incorporado. Cuando se compila un archivo de programa, por ejemplo con el comando
COMPILE, Visual dBASE explora el código en busca de directivas de preprocesador y
las evalúa, generando un archivo intermedio temporal, que es el que se compila.
(Para determinar si los programas se compilan de forma automática después de que se
modifique el código fuente, utilice SET DEVELOPMENT.) La Figura 7.1 resume el
proceso de compilación.
Figura 7.1 Preprocesado y compilación en Visual dBASE
Código fuente Archivo temporal Archivo objeto

#define User "ABC"


#define VerCtrl
#ifdef VerCtrl
version = "Antigua" Preprocesado
ƒ
#else version = "Antigua" Compilación
version = "Nueva" Codigo = "ABC111094"
#endif ƒ
ƒ
Codigo = User+DTOC(DATE())
ƒ

.PRG .PRO

Cada directiva comienza con el símbolo #; toda línea de código que tenga un signo #
inicial se considera una directiva de preprocesador. El carácter # puede ir precedido por
espacios. La Tabla 7.1 resume las directivas de preprocesador.
Tabla 7.1 Resumen de las directivas de preprocesador de Visual dBASE
Directiva Descripción
#define Define un identificador.
#if ... #endif Compila una sección de código si un identificador tiene cierto valor.
#ifdef ... #endif Compila una sección de código si un identificador está definido (con
#define).
#ifndef ... #endif Compila una sección de código si un identificador no está definido.
#include Inserta un archivo de código fuente (también denominado archivo include)
en la posición actual.
#pragma Especifica una opción del compilador.
#undef Anula la definición de un identificador.

88 Guía del programador


preproc.dwp Page 89 Wednesday, August 30, 1995 2:05 PM

Definición de constantes
Una forma sencilla de mejorar la velocidad de un programa es representar las
constantes con identificadores. Un identificador es un nombre descriptivo que se asigna a
un texto del programa mediante la directiva #define. Las ventajas de utilizar
identificadores se hacen palpables cuando se consideran las otras dos formas de
representar constantes en dBASE: valores literales y variables de memoria. Las secciones
siguientes comparan estas tres formas de definir constantes.

Uso de literales como constantes


El ejemplo siguiente emplea el valor literal 5000 para representar el número máximo de
clientes.
DO WHILE nClientActual < 5000
ƒ
nClientActual = nClientActual + 1
ENDDO
El uso de valores literales es muy simple pero tiene un inconveniente. Si se desea
aumentar el número máximo de clientes, es necesario buscar por el programa y cambiar
las apariciones de 5000 por el nuevo valor. Una búsqueda y sustitución global es
arriesgada porque es posible que no todas las apariciones de 5000 hagan referencia al
límite de clientes. Para simplificar esto, es posible utilizar una variable de memoria que
represente el valor constante.

Uso de variables de memoria como constantes


El uso de las variables de memoria para representar valores constantes facilita la
comprensión del código. Además, para cambiar el valor de una constante (por ejemplo,
para aumentar el número máximo de clientes) basta efectuar un único cambio en la línea
que inicializa la variable. El ejemplo siguiente utiliza una variable de memoria, no una
constante, para representar el número máximo de clientes.
nMaxClient = 5000 && inicializa nMaxClient
DO WHILE nClientActual < nMaxClient && usa nMaxClient como una constante
ƒ
nClientActual = nClientActual + 1
ENDDO
Sin embargo, el uso de una variable de memoria reduce ligeramente la velocidad del
programa porque dBASE necesita evaluar continuamente la variable, en este caso cada
vez que procesa el bucle DO WHILE. Además, cada variable que se define utiliza una
pequeña cantidad de memoria, y Visual dBASE aplica un límite en el número de
variables que pueden definirse. La definición de muchas constantes como variables de
memoria puede desperdiciar recursos de memoria preciosos.

Capítulo 7, Uso de las directivas de preprocesador 89


preproc.dwp Page 90 Wednesday, August 30, 1995 2:05 PM

Uso de identificadores para representar constantes


La mejor forma de representar constantes es definir identificadores con #define, lo cual
se hace de la misma forma que con las variables de memoria, excepto por la sentencia de
inicialización.
El ejemplo siguiente muestra el mismo código que el ejemplo anterior, pero utiliza la
directiva #define para definir nMaxClient como una constante. (Para una descripción
completa de la sintaxis de #define, consulte la Referencia del lenguaje.)
#define nMaxClient 5000 && define nMaxClient
DO WHILE nClientActual < nMaxClient && usa nMaxClient como una constante
ƒ
nClientActual = nClientActual + 1
ENDDO
Durante la compilación, el preprocesador sustituye todas las apariciones de nMaxClient
por 5000. Así se obtiene la eficacia de ejecución de los valores literales con la claridad y
comodidad de la programación con variables de memoria.
#define es especialmente útil al llamar funciones que no son de dBASE desde archivos
DLL que utilizan oscuros números enteros y hexadecimales, como el API de Windows.
El ámbito de un identificador es local al archivo en que se define. Es decir, el
preprocesador sustituye las apariciones de un identificador sólo en el mismo archivo de
programa que contiene la sentencia #define. Para definir constantes globales que se
emplean en varios archivos de programa, es necesario incluir la sentencia #define en
todos los archivos o utilizar la sentencia #include con un archivo include. La técnica de
#include es mucho más eficaz; para más información, consulte “Inserción de archivos
include” más adelante en este capítulo.

90 Guía del programador


preproc.dwp Page 91 Wednesday, August 30, 1995 2:05 PM

Expansión de expresiones
La sintaxis de la directiva #define le permite especificar parámetros que se insertarán en
el texto que sustituirá las apariciones del identificador. Puede ahorrar tiempo creando
identificadores que se expandan durante la compilación en expresiones incómodas o
que se utilizan con frecuencia.
Este es un resumen de la sintaxis de los parámetros de #define. Para una descripción
completa, consulte la Referencia del lenguaje:
#define <identificador> ( <lista parámetros> ) <texto de sustitución>

Nombre del Parámetro(s) que se Texto que sustituye al


identificador inserta en el texto de identificador, con los parámetros
sustitución que se insertan

En ocasiones es necesario crear una expresión que utiliza varias llamadas de función
incrustadas. Teclear estas expresiones y controlar los paréntesis son una tarea pesada, en
especial si se utilizan de forma repetida. La expresión siguiente convierte un valor
numérico en cadena, elimina los espacios iniciales y centra el texto.
nValor = 1200
? CENTER(LTRIM(STR(nValor)))
Para simplificar la introducción de esta expresión, puede crear un identificador que
combine las tres funciones en una e inserte el valor. Entonces, bastaría teclear el nombre
del identificador y los parámetros (entre paréntesis), en lugar de toda la expresión.
#define Val_Centrado(num) CENTER(LTRIM(STR(num)))
nValor = 12
?Val_Centrado(nValor) && centra '12'
?Val_Centrado(nValor + 20) && centra '32'
En el momento de la compilación, Val_Centrado(nValor) se expande en
CENTER(LTRIM(STR(nValor))).

Capítulo 7, Uso de las directivas de preprocesador 91


preproc.dwp Page 92 Wednesday, August 30, 1995 2:05 PM

Transmisión de parámetros: identificadores frente a procedimientos


Los parámetros insertados en los identificadores parecen parámetros pasados a los
procedimientos o funciones, y a los identificadores se les denomina con frecuencia
funciones en línea, pseudofunciones o macros de preprocesador. Sin embargo, la analogía con
los procedimientos tiene limitaciones importantes. No debe confundir la inserción de
parámetros en identificadores con la transmisión de parámetros en procedimientos.
El ejemplo siguiente muestra un identificador y un procedimiento que aparentemente
hacen lo mismo. Sin embargo, observe los resultados diferentes que se devuelven
cuando los parámetros pasados contienen una expresión (en este caso, nValor1+2).
PROCEDURE CentraProc(num1,num2)
respu = num1*num2
RETURN respu
#define Val_Centrado(num1,num2) CENTER(LTRIM(STR(num1 * num2)))
nValor1 = 12
nValor2 = 24
? CentraProc(nValor1,nValor2) && centra '288' (12*24)
?Val_Centrado(nValor1,nValor2) && centra también '288'
* En la línea siguiente el procedimiento evalúa nValor1+2 antes de realizar los cálculos
? CentraProc(nValor1+2,nValor2) && centra '366' ((12 + 2) * 24)
* Sin embargo el identificador no evalúa nValor1+2 antes de realizar los cálculos
* Así, el orden de prioridad matemática (multiplicación antes que suma) produce un
* resultado diferente
? Val_Centrado(nValor1+2,nValor2) && centra '60' (12+2*24),
&& que se evalúa como (12+(2*24))
Para asegurarse del agrupamiento adecuado de los operandos cuando se evalúan los
identificadores, debería añadir paréntesis en la sentencia #define como sigue.
#define Val_Centrado(num1,num2) CENTER(LTRIM(STR((num1)*(num2))))

92 Guía del programador


preproc.dwp Page 93 Wednesday, August 30, 1995 2:05 PM

Compilación condicional
El lenguaje dBASE siempre ha proporcionado comandos de control de flujo, como
IF...ELSE...ENDIF, que sirven para controlar qué comandos se activan durante la
ejecución. De forma análoga, por medio de las directivas condicionales de Visual
dBASE, como #if...#else...#endif, es posible controlar qué comandos se compilan.
Esta técnica, denominada compilación condicional, es especialmente útil para depurar o
desarrollar diferentes versiones de una aplicación.
Las condiciones para determinar qué comandos se compilan dependen de los
identificadores definidos con #define. Es posible definir dos tipos de condiciones:
• Es posible comprobar si existe un identificador con #ifdef (si está definido) o #ifndef
(si no lo está). Utilice #ifdef o #ifndef para comprobar los identificadores que se
definen sin ningún valor, como #define Debug.
• Es posible comprobar el valor de un identificador con #if. Utilice #if para comprobar
los identificadores que se definen con un valor, como #define Version 1.
Cada directiva condicional también tiene una opción “en los demás casos” (#else) para
seleccionar los comandos que se compilan cuando no se cumple la condición.
El ejemplo siguiente establece una compilación condicional para depuración. Si se ha
definido un identificador denominado Depurar, se compila el código de depuración;
de lo contrario, se compila DO MiProc.
* Comprueba la existencia del identificador Depurar
#ifdef Depurar && Si existe Depurar compila este código
SET ALTERNATE TO DEPURA.TXT
SET ALTERNATE ON && Abre un archivo ALTERNATE
LIST STATUS && y lista el entorno actual en dicho
LIST MEMORY && archivo
SET ALTERNATE TO
SET ALTERNATE OFF
#else && Si no existe Depurar compila este código.
DO MiProc
#endif
Puede lograr resultados similares con el comando IF...ELSE...ENDIF, pero las directivas
condicionales se evalúan durante la compilación, permitiéndole compilar sólo el código
que desea ejecutar, lo cual puede, a su vez, mejorar el rendimiento del programa.
Además, el código que no se depura por completo puede no compilarse. Con las
directivas condicionales, puede marcar con facilidad secciones incompletas de código y
compilarlas sólo cuando esté preparado para depurarlas.
También puede utilizar las directivas condicionales para desarrollar versiones
diferentes de un programa sin necesidad de mantener conjuntos distintos de código
fuente. La técnica es similar al ejemplo de la depuración anterior, excepto que se
comprueba el valor de un identificador, no su existencia.

Capítulo 7, Uso de las directivas de preprocesador 93


preproc.dwp Page 94 Wednesday, August 30, 1995 2:05 PM

El ejemplo siguiente define un identificador que representa el número de versión del


programa y sitúa un código específico de la versión entre las sentencias #if...#endif.
#define Version 1 && Número de versión
#if Version = 1
? "Versión 1" && Código para la versión 1
ƒ
#else
? "Versión 2" && Código para la versión 2
ƒ
#endif
Para compilar el código de la versión 2, basta cambiar la sentencia #define a
#define Version 2.

Compilación condicional para diferentes versiones de dBASE


Visual dBASE añade muchos elementos nuevos en el lenguaje que no se encuentran en
dBASE IV. Para ayudarle a mantener la compatibilidad de código con versiones
anteriores de dBASE, Visual dBASE crea automáticamente un identificador denominado
__dbasewin__ (la palabra “dbasewin” precedida y seguida por dos caracteres de
subrayado). Si utiliza #ifdef para comprobar la existencia de __dbasewin__, puede
compilar sólo el código destinado para Visual dBASE y conservar el código que no es de
Windows para procesarlo con el Compilador dBASE para DOS.
En el ejemplo siguiente, el código específico de Windows sólo se ejecuta cuando se
compila con Visual dBASE. El otro código dBASE se ejecuta cuando se procesa con el
Compilador dBASE para DOS.
#ifdef __dbasewin__
* código específico de Windows
#else
* otro código dBASE
#endif

94 Guía del programador


preproc.dwp Page 95 Wednesday, August 30, 1995 2:05 PM

Inserción de archivos include


Un archivo include (también denominado archivo de inclusión) es un archivo de programa
que se inserta en otro archivo de programa durante la compilación mediante la directiva
#include. Los archivos include suelen insertarse al principio (cabecera) de un archivo de
programa, aunque pueden situarse en cualquier lugar. Los archivos include son muy
útiles para insertar definiciones constantes globales creadas con #define.
Durante la pasada de preprocesado, Visual dBASE sustituye las líneas que comienzan
con #include por el contenido del archivo cuyo nombre se menciona en la sentencia
#include. Aunque pueden contener cualquier código de dBASE, los archivos include
suelen contener sólo directivas de preprocesador o definiciones de clase.
Por ejemplo, los identificadores que cree con #define se aplican sólo al archivo fuente
que contiene la sentencia #define. Si utiliza un identificador en varios archivos de
programa, debe incluir la sentencia #define en todos ellos. Para no tener que hacerlo así,
puede consolidar sus sentencias #define en un solo archivo include y utilizar #include
para insertar el archivo include en todos los archivos fuente que necesiten los
identificadores. La Figura 7.2 ilustra esta técnica.
Figura 7.2 Inserción de archivos include con #include

#define Ver1
#define Depurar
#define Verdad 1
#define Falso 0
ƒ
#include MISDEFS.H #include MISDEFS.H #include MISDEFS.H
*Código dBASE *Código dBASE *Código dBASE
MISDEFS.H ƒ ƒ #if Ver = 1
(archivo #if Ver = 1 #ifdef Depurar DO V1
include) ? "Versión 1" * Código a depurar #else
#else #else DO V2
? "Versión 2" * Otro código #endif
#endif #endif ƒ
ƒ ƒ

PPAL.PRG PROG1.PRG PROG2.PRG

Si necesita cambiar alguno de los identificadores, hágalo en el archivo include .


Después de la compilación, todos los archivos fuente que contienen la sentencia
#include MISDEFS.H utilizarán la nueva definición.

Capítulo 7, Uso de las directivas de preprocesador 95


preproc.dwp Page 96 Wednesday, August 30, 1995 2:05 PM

La Tabla 7.2 muestra algunos ejemplos de lo que puede almacenarse en los archivos
include:
Tabla 7.2 Ejemplos de archivos include
Lo que puede guardarse en un archivo include Ejemplo
Identificadores que representan valores de colores RGB. #define MiColor 56,62,155
Identificadores que representan valores devueltos por #define CTRL-W 23
INKEY(), READKEY(), LASTKEY(), NEXTKEY().
Constantes específicas de la aplicación. #define MAXARTICULOS 10000
Prototipos de función que se declaran con el comando EXTERN CWORD MiFunc() MiBibl.DLL
EXTERN. (Para más información, consulte el Capítulo 26.)
Constantes utilizadas en funciones del API de Windows. #define MB_ICONHAND HTOI("0010")
Definiciones de clases personalizadas. CLASS MiBoton OF PUSHBUTTON
ƒ
ENDCLASS

Los programadores suelen mantener bibliotecas de archivos include en las que


guardan expresiones y constantes #define que utilizan con frecuencia en un
subdirectorio \INCLUDE bajo el directorio principal de Visual dBASE. Para una
descripción de dónde busca Visual dBASE los archivos include, consulte #include
en la Referencia del lenguaje.
Visual dBASE proporciona un archivo include, WINAPI.H, que contiene útiles
definiciones de constantes y prototipos de función para llamar funciones del
API de Windows. Para más información, consulte el Capítulo 26.

Definición de la opción de cobertura


La directiva #pragma COVERAGE permite especificar si se crea o no un archivo de
cobertura cuando se compila el programa. Un archivo de cobertura es un archivo binario
que contiene información acumulativa sobre cuántas veces accede y sale dBASE de cada
bloque lógico (y por tanto lo ejecuta por completo) de un programa. (Para más
información sobre los archivos de cobertura, consulte el Capítulo 3.) Cuando se utiliza
#pragma COVERAGE(ON) en un archivo de programa o include, no es necesario ejecutar SET
COVERAGE ON antes de compilar el programa. Para desactivar la cobertura, utilice
#pragma COVERAGE(OFF).

96 Guía del programador


debug.dwp Page 97 Wednesday, August 30, 1995 2:06 PM

Capítulo

Depuración de programas
Capítulo 8
8
Depurar significa localizar y eliminar los errores de un programa. Visual dBASE dispone
de un potente Depurador que puede mostrar código de programa, inspeccionar
programas, definir puntos de ruptura, mostrar valores de variables y ver la pila en las
ventanas abiertas actualmente. La ejecución de un programa puede seguirse línea a
línea, o ir a puntos de ruptura específicos para ver dónde se producen los errores en el
programa. Después de corregir el código, es posible compilar y volver a ejecutar el
programa en el Depurador para comprobar si se ha solucionado el problema.

Acerca del Depurador


El Depurador es la principal utilidad para encontrar errores en los programas.
Cuando se ejecuta un programa en el Depurador, es posible:
• Controlar la ejecución del programa.
• Ver y cambiar los valores de variables, campos, matrices, objetos y expresiones.
• Ver las subrutinas que llama el programa principal y cuándo las llama.
• Detener la ejecución del programa en cualquier punto, incluidos los puntos de
ruptura que defina.

Tipos de errores
Los programas pueden contener tres tipos de errores:
• Errores de compilación o sintaxis
• Errores en tiempo de ejecución
• Errores lógicos

Capítulo 8, Depuración de programas 97


debug.dwp Page 98 Wednesday, August 30, 1995 2:06 PM

Errores de compilación o sintaxis


Cuando se compila un programa, el compilador notifica los errores de sintaxis que
encuentre en el programa. Un error de sintaxis, por ejemplo, podría ser una sentencia IF
que no tiene una sentencia ENDIF correspondiente. También podría ser la ausencia de
puntuación, como un punto y coma que falte al final de una línea para indicar que la
línea siguiente es una continuación, no una línea de comando nueva. Estos errores son
relativamente fáciles de encontrar y solucionar.

Errores en tiempo de ejecución


Es posible que un programa se compile y carezca de errores de sintaxis y aún así no se
ejecute porque contenga errores en tiempo de ejecución. Estos errores son resultado de que
el programa intenta hacer algo imposible, como abrir una tabla que no existe.
Los errores en tiempo de ejecución son fáciles de localizar, porque la ejecución del
programa se detiene cuando se producen. Cuando Visual dBASE encuentra un error de
sintaxis o en tiempo de ejecución, detiene la ejecución del programa y muestra la
ventana Error de programa, donde puede elegir:
• Cancelar la ejecución del programa.
• Ignorar el error y continuar la ejecución del programa.
• Suspender la ejecución del programa.
• Abrir el archivo de programa en el Editor de textos con el cursor situado en la línea
en que se produjo el error.
• Abrir el archivo de programa en el Depurador con el cursor situado en la línea en que
se produjo el error.

Errores lógicos
El código de un programa podría ser correcto sintácticamente y ejecutarse sin
experimentar ningún error en tiempo de ejecución, y aún así podría producir resultados
incorrectos. Por ejemplo, si hay un procedimiento que debe ejecutarse después de que
tenga lugar algún suceso, pero el suceso nunca ocurre al ejecutar el programa, podría
tratarse de un error lógico.
En dBASE DOS, algunos comandos activan el depurador y realizan otras tareas de
depuración:
• SET STEP
• SET ECHO
• SET TRAP
Estos comandos ya no son necesarios en Visual dBASE, porque todas las tareas de
depuración se realizan en el Depurador. Cuando Visual dBASE encuentra SET STEP ON
y SET ECHO ON, abre el Depurador. Cuando encuentra SET TRAP ON, no tiene en
cuenta este comando. Los valores OFF de estos tres comandos tampoco se tienen en
cuenta en Visual dBASE.

98 Guía del programador


debug.dwp Page 99 Wednesday, August 30, 1995 2:06 PM

Inicio del Depurador


El Depurador puede iniciarse desde dentro de Visual dBASE o como programa
independiente desde el Administrador de programas de Windows:
• En Visual dBASE:
• En la ventana Selector, seleccione Programas, haga clic con el botón derecho en el
programa que desea depurar, y elija Depurar en el Menú rápido.™
• Ejecute DEBUG en la ventana de comandos o en un archivo de programa o ficha.
• Cuando la ventana de comandos tenga el foco, haga clic en el botón Depurador de
la Barra rápida.
• Cuando la ventana de comandos tenga el foco, elija Programa|Depurar en el
menú.
• En el Administrador de programas de Windows, haga doble clic en el icono del
Depurador de Visual dBASE.
Figura 8.1 Ventanas del Depurador
La ventana Visor contiene puntos de evaluación
La ventana Módulo contiene el código fuente del definidos por el programador de variables, campos,
programa que se depura. matrices, objetos y expresiones.

La ventana Ruptura contiene puntos de ruptura La ventana Pila controla todas las llamadas del
definidos por el programador de detención de la programa a módulos y procedimientos.
ejecución del programa. La última línea describe la subrutina actual.

Cuando se inicia el Depurador, está activa la ventana Módulo. Para cambiar el foco de
ventana, haga clic en la que desea activar o elíjala en el menú Ventana. También puede
elegir Mosaico o Cascada en ese menú para disponer las ventanas en mosaico o en
cascada. Si minimiza alguna de las ventanas, elija Ventana|Organizar iconos para
ordenar los iconos de las ventanas.

Capítulo 8, Depuración de programas 99


debug.dwp Page 100 Wednesday, August 30, 1995 2:06 PM

También es posible cambiar la posición y orientación de la Barra rápida del Depurador y


reducir y agrandar la ventana de la aplicación Depurador mediante los tres botones
situados en el extremo derecho de la Barra rápida (Figura 8.2):
• Para mover la Barra rápida desde su posición horizontal bajo el menú del Depurador
a una paleta vertical móvil, haga clic en el botón de Barra rápida en paleta vertical
situado en el extremo derecho de la Barra rápida.
• Para volver a situar la paleta en la posición horizontal bajo el menú, haga clic en el
botón de Barra rápida al tamaño normal situado a la izquierda del grupo de tres
botones del extremo derecho de la Barra rápida.
• Para reducir el Depurador a una vista de sólo su menú y la Barra rápida (ocultando
su área de visualización de ventanas), haga clic en el botón Barra rápida de vista
reducida, que es el botón central del grupo de tres situado en el extremo derecho de
la Barra rápida. Haga clic en el botón de tamaño normal para volver el Depurador a
su tamaño anterior.
Figura 8.2 Barra rápida del Depurador
Carga otro Ejecuta el Carga otra vez Ejecuta la línea Devuelve el Depurador a su Transforma la
programa programa actual el programa fuente actual, tamaño original y la paleta vertical Barra rápida en
para su a toda velocidad. desde el disco. omitiendo las flotante a su posición horizontal una paleta
depuración. llamadas. bajo el menú. vertical flotante..

Sale del Detiene la Ejecuta la Inspecciona el campo, Evalúa una Reduce la vista del
Depurador. ejecución del línea fuente variable, matriz u expresión y Depurador a sólo su
programa. actual. objeto que indique. cambia el valor. menú y la Barra rápida.

Carga de un archivo de programa


Es posible cargar un archivo de programa al abrir el Depurador o después. Para cargar
un archivo al abrir el Depurador, realice una de las acciones siguientes:
• En la ventana Selector, seleccione Programas, haga clic con el botón derecho en el
programa que desea depurar y elija Depurar en el Menú rápido.
• Cuando la ventana de comandos esté activa, elija Programa|Depurar y especifique el
programa que desea depurar.
• En la ventana de comandos, teclee DEBUG <nombre archivo>, donde <nombre archivo>
es el archivo de programa que desea depurar.
• Incluya el comando DEBUG en el archivo de programa que desea depurar.
• Cuando se produzca un error en tiempo de ejecución en el programa, elija depurarlo.
Para cargar un archivo después de iniciar el Depurador, elija Archivo|Nuevo en el
menú del Depurador o haga clic en el botón Cargar archivo de la Barra rápida;
a continuación, especifique el archivo de programa en el cuadro de diálogo Seleccionar
aplicación.

100 Guía del programador


debug.dwp Page 101 Wednesday, August 30, 1995 2:06 PM

Cargue un archivo de ficha (.WFM) en el Depurador de la misma forma que se cargan


los archivos de programa, excepto por el clic derecho en los iconos de fichas en la
ventana Selector. Depurar no aparece en los Menús rápidos de los archivos de ficha.
Cuando se carga un programa en el Depurador, el archivo de programa se abre en la
ventana Módulo con la primera línea ejecutable resaltada. La barra de título del
Depurador muestra el nombre del archivo de programa. Al ejecutar el programa desde
el Depurador, el código fuente avanza en la ventana a la siguiente línea que recibe el
control del programa. Si lo desea, puede desplazarse por el código fuente sin afectar a la
ejecución del programa; el siguiente comando que se ejecutará permanece resaltado
independientemente de la posición del cursor en la ventana Módulo.
La ventana mostrada por el programa está en segundo plano mientras el Depurador
conserve el foco. Para interactuar con dicho programa, debe mover el foco a él
(utilice Alt+Tab o minimice el Depurador y haga clic en la ventana del programa).
Mientras interactúa con el programa, su código continúa desplazándose en la ventana
Módulo. Si cancela la ejecución del programa, el código permanece abierto en la ventana
Módulo.

Carga de subrutinas
Una vez que un programa está cargado en el Depurador, puede cargar una o todas sus
subrutinas. Si ejecuta un programa principal que llama otros archivos de programa, el
Depurador carga los archivos de subrutina conforme son necesarios. No obstante, es
posible cargar una subrutina en el Depurador antes de que la llame el programa.
Para cargar una subrutina desde el menú del Depurador:
1 Elija Archivo|Cargar módulo.
2 En el cuadro de diálogo Seleccionar módulo, especifique el archivo de programa de la
subrutina que desea cargar además del archivo de programa actual (el principal).
Para cargar una subrutina desde la ventana Módulo:
1 Haga clic con el botón derecho en la ventana Módulo para acceder a su Menú rápido.
2 Elija Cargar módulo.
3 En el cuadro de diálogo Seleccionar módulo, especifique el archivo de programa que
desea cargar en el Depurador.
Si elige Archivo|Nuevo al cargar un programa en el Depurador, Visual dBASE le
pregunta si desea terminar la sesión actual de depuración.

Capítulo 8, Depuración de programas 101


debug.dwp Page 102 Wednesday, August 30, 1995 2:06 PM

Desplazamiento en la ventana Módulo


Utilice los menús Archivo y Programa o el Menú rápido de la ventana Módulo para
desplazarse por el código fuente del programa que hay en dicha ventana de forma
independiente de la ejecución del programa en el Depurador.

Vuelta al editor de textos


El texto de un programa no puede editarse en la ventana Módulo. Para volver al editor
de textos con el fin de modificar las líneas de código:
1 En la ventana Módulo, desplace el cursor a la línea de código fuente donde desea
comenzar la edición.
2 Elija Programa|Solucionar en el menú del Depurador o Solucionar en el Menú
rápido de la ventana Módulo.

Desplazamiento a un número de línea


Para ir a un número de línea del código fuente del archivo de programa actual:
1 Elija Programa|Ir a línea en el menú del Depurador, o bien haga clic con el botón
derecho en la ventana Módulo y elija Ir a línea en el Menú rápido de esta ventana.
2 Especifique el número en el cuadro de diálogo Ir a línea.
Cuando se desplaza a un número de línea en la ventana Módulo, el cursor se sitúa al
principio de esa línea. Esto no afecta a la ejecución del programa, que permanece
detenida en la última línea que se procesó (con paso a paso o con seguimiento) o en el
último punto de ruptura que se ejecutó.
El Depurador registra todos los números de línea a los que se desplaza con Ir a línea en
una sesión de depuración. Para ir de nuevo a cualquiera de esos números de línea:
1 Elija Programa|Ir a línea en el menú del Depurador, o haga clic con el botón derecho
en la ventana Módulo y elija Ir a línea en el Menú rápido.
2 Haga clic en la flecha abajo situada a la derecha del cuadro de texto Número de línea
en el cuadro de diálogo Ir a línea.
3 Elija un número de línea anterior en el menú desplegable.

Desplazamiento del cursor a su posición anterior


Para devolver el cursor a su posición anterior, elija Programa|Línea anterior en el menú
del Depurador, o haga clic con el botón derecho en la ventana Módulo y elija Línea
anterior en el Menú rápido.

102 Guía del programador


debug.dwp Page 103 Wednesday, August 30, 1995 2:06 PM

Búsqueda de texto
Para encontrar una cadena de texto en el archivo de programa actual:
1 Elija Programa|Buscar en el menú del Depurador, o haga clic con el botón derecho
en la ventana Módulo y elija Buscar en el Menú rápido.
2 Especifique el texto que desea encontrar en el cuadro de diálogo Buscar.
La búsqueda comienza en la posición actual del cursor y distingue entre mayúsculas y
minúsculas. Si Visual dBASE encuentra el texto, la ventana se desplaza a la línea que
contiene el texto y el cursor se sitúa al principio de ese texto. Para encontrar apariciones
sucesivas del mismo texto, elija Programa|Buscar siguiente en el menú del Depurador o
Buscar siguiente en el Menú rápido de la ventana.
El Depurador mantiene una historia de todas las cadenas de texto que se buscan en una
sesión de depuración. Para encontrar cualquiera de estas cadenas de nuevo:
1 Elija Programa|Buscar en el menú del Depurador, o haga clic con el botón derecho
en la ventana Módulo y elija Buscar en el Menú rápido.
2 Haga clic en la flecha abajo situada a la derecha del cuadro de texto Cadena de
búsqueda en el cuadro de diálogo Buscar.
3 Elija una cadena de texto anterior en el menú desplegable.

Desplazamiento a un procedimiento
Para ir a un procedimiento o función del archivo de programa actual o de uno de sus
archivos de procedimientos o biblioteca:
1 Elija Programa|Ir a procedimiento en el menú del Depurador o haga clic con el botón
derecho en la ventana Módulo y elija Ir a procedimiento en el Menú rápido.
2 Especifique el nombre del procedimiento en el cuadro de diálogo Ir a procedimiento.
La búsqueda del procedimiento comienza en la posición actual del cursor en la ventana
Módulo. Si se halla en el programa que está depurando, el Depurador sitúa el cursor en
la primera línea del procedimiento. Si se halla en un archivo de procedimientos no
abierto, el Depurador lo carga en la ventana Módulo. Si el Depurador no puede
encontrarlo, el archivo de programa actual permanece en la ventana Módulo.
El Depurador mantiene una historia de todos los procedimientos que se buscan en una
sesión de depuración. Para ir de nuevo a cualquiera de estos módulos de programa:
1 Elija Programa|Ir a procedimiento en el menú del Depurador, o haga clic con el
botón derecho en la ventana Módulo y elija Ir a procedimiento en su Menú rápido.
2 Haga clic en la flecha abajo situada a la derecha del cuadro de texto Nombre de
procedimiento en el cuadro de diálogo Ir a procedimiento.
3 Elija un nombre de procedimiento en el menú desplegable.

Capítulo 8, Depuración de programas 103


debug.dwp Page 104 Wednesday, August 30, 1995 2:06 PM

Vuelta al procedimiento que ha realizado la llamada (origen)


Si ha llevado el cursor a un procedimiento mediante Ir a procedimiento, puede volver al
comando que lo ha llamado. Elija Programa|Origen en el menú del Depurador, o haga
clic con el botón derecho en la ventana Módulo y elija Origen en el Menú rápido.

Control de la ejecución del programa


El Depurador permite controlar la ejecución de un programa de las formas siguientes:
Tabla 8.1 Métodos de control de la ejecución de un programa en el Depurador
Método Tipo de control de la ejecución del programa
Animación Ejecuta el programa de forma continua, con pausas momentáneas en cada línea;
para determinar el periodo relativo de tiempo de pausa en cada línea, defina
Velocidad de animación.
Seguimiento Ejecuta el programa línea por línea, con pausas en cada una hasta que provoque la
ejecución de la siguiente; muestra la ejecución línea por línea de las subrutinas.
Paso a paso Ejecuta el programa línea por línea, con pausas en cada una hasta que provoque la
ejecución de la siguiente; no muestra la ejecución línea por línea de las subrutinas.
Con puntos de Los puntos de ruptura son lugares o condiciones definidos por el programador
ruptura que detienen la ejecución para permitir que evalúe la situación en que se detiene la
ejecución; por ejemplo, tal vez desee evaluar e incluso cambiar el valor de un dato
que afecta al programa en ese punto.
Detención Cuando ejecuta un programa a toda velocidad o con animación, puede detener la
ejecución en cualquier punto.

Animación
Cuando se anima un programa, el Depurador ejecuta el programa de forma continua
haciendo una pausa en cada línea de comando y actualizando la información en todas
las ventanas del Depurador con cada pausa. El programa realiza sus tareas como si lo
estuviera ejecutando en Visual dBASE, excepto que puede controlar su velocidad de
ejecución. El código fuente se desplaza en la ventana Módulo conforme se ejecutan las
líneas, y la palabra Ejecutando aparece a la derecha de la barra de estado.
Para animar un programa, elija Ejecutar|Animación en el menú del Depurador.
Para controlar la velocidad de animación, elija Opciones|Velocidad de animación y
especifique la velocidad en el cuadro de diálogo Velocidad de animación moviendo el
cuadro de la barra de desplazamiento. Si lo mueve hacia la derecha, aumenta la
velocidad de animación; a la izquierda, disminuye la velocidad de animación.
La velocidad de animación que especifique se aplica a todos los programas que anime.
Puede cambiarla antes de la animación de un programa o durante ella. Si ya está
animando un programa cuando elige Opciones|Velocidad de animación, la selección
con la barra de desplazamiento afecta a la velocidad de la animación actual.

104 Guía del programador


debug.dwp Page 105 Wednesday, August 30, 1995 2:06 PM

Seguimiento
El Depurador permite ejecutar un programa línea a línea, es decir, seguir el programa, y
detenerlo en cualquier línea. El seguimiento, o rastreo, ejecuta y detiene en cada línea el
programa principal y todas las subrutinas que llama. El método paso a paso (que se
explica en la sección siguiente) también ejecuta el programa principal, pero excluye las
llamadas a las subrutinas.
Cuando incluye la llamada a una subrutina, el Depurador carga ese módulo en la ventana
Módulo y se detiene en la primera línea de la subrutina. Puede continuar el rastreo en la
subrutina y en las subrutinas inferiores que llame ella, o puede excluir las llamadas que
haga a subrutinas inferiores.
Para incluir la llamada a una subrutina, comience con el programa principal y avance
paso a paso o sígalo hasta la sentencia que llama a la subrutina; a continuación, incluya
la llamada para comprobar su ejecución línea por línea en la ventana Módulo.
Para seguir un programa e incluir la llamada a una subrutina:
• Haga clic en el icono Incluir llamadas en la Barra rápida.
• Elija Ejecutar|Incluir llamadas.
• Pulse F7.
Figura 8.3 Seguimiento de una subrutina
La ejecución del programa se detiene en la primera línea de la función CallChangeEqForm, una
subrutina de la ficha Vuelos. Así se ha seguido CallChangeEqForm, no se ha excluido. En este
punto, el seguimiento o paso a paso de cada línea puede continuar en CallChangeEqForm.

Capítulo 8, Depuración de programas 105


debug.dwp Page 106 Wednesday, August 30, 1995 2:06 PM

Paso a paso
La ejecución paso a paso de un programa realiza un seguimiento del programa
principal, pero excluye las líneas que llaman subrutinas. Si ya ha determinado que no
hay errores en una subrutina en particular, puede excluir su ejecución y concentrarse en
el resto del programa. Aún así, la llamada a la subrutina se produce y ésta se ejecuta,
pero no se muestra su ejecución línea a línea en la ventana Módulo, por lo que no es
posible detenerla en ninguna de las líneas. El Depurador se detiene en la primera línea
de comando después de la subrutina.
Para ejecutar paso a paso un programa y excluir las líneas de comando que llaman
subrutinas:
• Haga clic en el icono Excluir llamadas en la Barra rápida.
• Elija Ejecutar|Excluir llamadas.
• Pulse F8.
Figura 8.4 Exclusión de la llamada a una subrutina
La línea que llama a la función CallChangeEqForm, es la línea siguiente que se ejecuta. Aunque la excluya,
CallChangeEqForm se ejecutará, pero no verá su ejecución línea a línea en la ventana Módulo ni podrá detenerla.

106 Guía del programador


debug.dwp Page 107 Wednesday, August 30, 1995 2:06 PM

Uso de los puntos de ruptura


Un punto de ruptura detiene la ejecución de un programa para que pueda evaluar las
variables, campos, matrices, objetos y expresiones, cambiar el valor de las variables,
matrices y objetos y comprobar en qué subrutina está el programa.
Cuando ejecuta un programa paso a paso o con seguimiento, en esencia lo está
deteniendo en cada línea de comando. Cuando sepa que no hay errores en ciertas partes
del programa, no hay necesidad de detenerse en cada línea; por el contrario, puede
definir puntos de ruptura en lugares clave, ejecutar el programa a toda velocidad y
evaluar los valores actuales en el punto de ruptura. Defina estos puntos cuando anime o
ejecute un programa a la velocidad máxima en el Depurador.
Por ejemplo, si sospecha que un error se produce en un punto en particular, como en la
llamada de una subrutina, puede definir un punto de ruptura en la línea de comando
que llama la subrutina. Desde ese punto, puede ejecutar el programa con seguimiento o
paso a paso para encontrar el problema.
Los puntos de ruptura también pueden definirse en una condición, no sólo en una
sentencia del programa. Por ejemplo, puede detener su ejecución cuando se asigne
cierto valor a una variable. La ejecución se detendrá la primera vez que se asigne ese
valor a la variable, pero no lo hará si nunca se asigna el valor a la variable.

Definición de puntos de ruptura


Para definir un punto de ruptura en un programa:
• En la ventana Módulo, sitúe el puntero a la izquierda de la línea de comando en que
definirá el punto de ruptura y, cuando se transforme en una mano, haga clic.
• En la ventana Módulo, desplace el cursor a la línea de comando que se convertirá en
punto de ruptura y haga alguna de las acciones siguientes:
• Elija Ruptura|Alternar Activar/Desactivar en el menú del Depurador, o haga clic
con el botón derecho en la ventana Ruptura y elija Alternar Activar/Desactivar en
su Menú rápido.
• Pulse F2.
• Elija Ruptura|Añadir en el menú del Depurador, o haga clic con el botón derecho en
la ventana Ruptura y elija Añadir en el Menú rápido; entonces, especifique el punto
de ruptura en el cuadro de diálogo Añadir punto de ruptura. Los tipos de puntos de
ruptura que puede especificar se tratan en las secciones siguientes.
El punto de ruptura definido aparece en la ventana Ruptura y, si es una línea de
comando específica, esa línea se resalta en la ventana Módulo.

Capítulo 8, Depuración de programas 107


debug.dwp Page 108 Wednesday, August 30, 1995 2:06 PM

Borrado de puntos de ruptura


Para eliminar puntos de ruptura de línea de comando, puede emplear algunos de los
métodos empleados para definirlos:
• En la ventana Módulo, desplace el puntero a la izquierda del punto de ruptura de
línea de comando que desea borrar y, cuando se transforme en una mano, haga clic.
• En la ventana Módulo, desplace el cursor al punto de ruptura de línea de comando
que desea borrar y haga una de las acciones siguientes:
• Elija Ruptura|Alternar Activar/Desactivar en el menú del Depurador, o haga clic
con el botón derecho en la ventana Ruptura y elija Alternar Activar/Desactivar en
el Menú rápido.
• Pulse F2.
Para eliminar un punto de ruptura de cualquier tipo, resáltelo en la ventana Ruptura y
haga una de las acciones siguientes:
• Elija Ruptura|Alternar Activar/Desactivar en el menú del Depurador, o haga clic
con el botón derecho en la ventana Ruptura y elija Alternar Activar/Desactivar en el
Menú rápido.
• Elija Ruptura|Borrar en el menú del Depurador, o haga clic con el botón derecho en
la ventana Ruptura y elija Borrar en el Menú rápido.
• Pulse F2.
Para eliminar todos los puntos de ruptura actuales, elija Ruptura|Borrar todos, o haga
clic con el botón derecho en la ventana Ruptura y elija Borrar todos en su Menú rápido.

Edición de puntos de ruptura


Para editar un punto de ruptura:
• Haga doble clic en el punto de ruptura en la ventana Ruptura.
• Seleccione el punto de ruptura en la ventana Ruptura y elija Ruptura|Editar en el
menú del Depurador o haga clic con el botón derecho en la ventana Ruptura y elija
Editar en el Menú rápido de esta ventana.
Puede editar su descripción en el cuadro de diálogo Editar punto de ruptura.

108 Guía del programador


debug.dwp Page 109 Wednesday, August 30, 1995 2:06 PM

Definición de puntos de ruptura globales o específicos de un programa


Un punto de ruptura puede crearse de forma específica para un archivo de programa o
global para todos los archivos de programa de la sesión actual del Depurador.
En el primer caso, puede especificar uno de los siguientes como punto de ruptura
específico de un programa:
• Una condición cuando se evalúa como verdadero
• Un número de línea de programa
Si especifica todos los archivos de programa, puede seleccionar una de las siguientes
como punto de ruptura global:
• Una condición cuando se evalúa como verdadera
• Una expresión cuando cambia su valor
La primera vez que abre el cuadro de diálogo Añadir punto de ruptura, el nombre del
archivo de programa actual aparece en el cuadro de texto Posición, y la casilla de
verificación Global no está marcada (). Si abre el cuadro de diálogo Editar punto de
ruptura para editar un punto de ruptura existente en el archivo de programa actual,
también aparece el nombre del programa en el cuadro de texto Posición y la casilla
Global también está sin marcar. Cualquier condición lógica, número de línea o número
de pasadas que especifique se aplican al archivo indicado en el cuadro de texto Posición.
Figura 8.5 Punto de ruptura específico de un programa

El programa actual que está depurando aparece


por defecto en el cuadro de texto Posición.
Este punto de ruptura sólo se aplica a
VUELOS.PRG, un módulo de VUELOS.WFM.
Cuando la condición se evalúa como verdadera, se
detiene la ejecución del programa.

Cuando la casilla de verificación Global está sin


marcar, el punto de ruptura que especifique sólo se
aplica al programa indicado en el cuadro de texto
Posición.

Cuando se marca la casilla de verificación Global, el cuadro de texto Posición es de sólo


visualización, desaparece el cuadro de texto Nº de línea y aparecen los botones de radio
Expresión verdadera y Expresión modificada. El botón de radio Expresión verdadera se
selecciona por defecto (Figura 8.6). Cuando selecciona el botón de radio Expresión
modificada, el cuadro de texto Condición se convierte en cuadro de texto Expresión
(Figura 8.7). Cualquier expresión que teclee en el cuadro de texto Condición o Expresión
se aplica a todos los archivos de programa que ejecute desde el Depurador en la sesión
de depuración actual.

Capítulo 8, Depuración de programas 109


debug.dwp Page 110 Wednesday, August 30, 1995 2:06 PM

Figura 8.6 Punto de ruptura global con condición verdadera

Este punto de ruptura se aplica a todos los archivos


de programa que depure en la sesión actual.

Si selecciona Expresión verdadera, lo que


especifique en el cuadro de texto Condición debe ser
una expresión que pueda ser verdadera o falsa.
Cuando la expresión se evalúa como verdadera en
un programa que depure, se detiene su ejecución.

Figura 8.7 Punto de ruptura global con expresión modificada

Este punto de ruptura se aplica a todos los archivos


de programa que depure en la sesión actual.

Si selecciona Expresión modificada, lo que


especifique en el cuadro de texto Expresión no tiene
que evaluarse como verdadero o falso. Cuando el
valor de la expresión cambia en un programa que
depure, se detiene su ejecución.

Ruptura en una expresión


En los cuadros de diálogo Añadir punto de ruptura y Editar punto de ruptura, puede
especificar que la ejecución del programa se detenga cuando una expresión se evalúe
como verdadera. Puede especificar una condición para un archivo de programa
específico o para todos los archivos de programa. Si especifica un punto de ruptura para
un solo archivo, teclee la expresión en el cuadro de texto Condición.
Si marca la casilla Global para que la selección afecte a todos los archivos de programa,
puede especificar cualquier expresión como punto de ruptura. Seleccione el botón de
radio Expresión verdadera para especificar una expresión que detenga la ejecución del
programa cuando se evalúe como verdadera; teclee la expresión en el cuadro de texto
Condición (Figura 8.6). Seleccione el botón de radio Expresión modificada para
especificar una expresión que detenga la ejecución del programa cuando su valor
cambie; teclee la expresión en el cuadro de texto Expresión (Figura 8.7).

110 Guía del programador


debug.dwp Page 111 Wednesday, August 30, 1995 2:06 PM

Ruptura en un número de línea específico


Si especifica un punto de ruptura para un archivo de programa específico, puede
definirlo en una línea de comando en los cuadros de diálogo Añadir punto de ruptura y
Editar punto de ruptura. Teclee el número de línea en el cuadro de texto Nº de línea.
Si ya ha definido un punto de ruptura en una línea de comando concreta y lo edita, el
número de esa línea aparece en el cuadro de texto Nº de línea del cuadro de diálogo
Editar punto de ruptura. Si posteriormente cambia el número, la nueva línea de
comando sustituye a la original como punto de ruptura.

Especificación de un número de pasadas


Una vez que especifique un punto de ruptura, específico o global, puede indicar
también que la ejecución del programa no se detenga hasta que esa condición tenga
lugar un cierto número de veces. El número de pasadas le permite especificar las veces que
desea que se produzca el punto de ruptura antes de detener la ejecución del programa.
Por ejemplo, si ha definido un punto de ruptura dentro una estructura de comandos,
como un bucle DO WHILE, quizá desee detener la ejecución cuando el programa
ejecute el bucle por tercera vez. Si el bucle no llega a ejecutarse o si sólo lo hace dos
veces, el programa continúa su ejecución sin detenerse en el punto de ruptura,
indicando quizá un error.
Para detener la ejecución de un programa cuando se produce un punto de ruptura un
cierto número de veces, especifíquelo en el cuadro de texto Número pasadas de los
cuadros de diálogo Añadir punto de ruptura o Editar punto de ruptura (Figura 8.8).
Figura 8.8 Especificación de un número de pasadas para un punto de ruptura

VUELOS.PRG es un módulo de VUELOS.WFM.

Un número de pasadas de 3 especifica que la


ejecución del programa se detenga la tercera vez
que la expresión lógica del cuadro Condición
(aviones->avion = “SAAB340”) sea verdadera al
ejecutar VUELOS.WFM en el Depurador.

Realización de una acción cuando se produce un punto de ruptura


Con un punto de ruptura global o uno específico del programa, puede especificar que se
realice una acción cuando se produzca el punto de ruptura. La acción podría ser una
expresión que reinicialice el valor de una variable o un comando DO que ejecute un
procedimiento. Teclee la acción, en código de Visual dBASE, en el cuadro de texto
Acción de los cuadros de diálogo Añadir punto de ruptura o Editar punto de ruptura
(Figura 8.9).

Capítulo 8, Depuración de programas 111


debug.dwp Page 112 Wednesday, August 30, 1995 2:06 PM

Figura 8.9 Especificación de una acción para un punto de ruptura

Cuando sea verdadera la condición del punto de


ruptura (aviones->avion = “SAAB340”), la ejecución
(de VUELOS.WFM, del que VUELOS.PRG es un
módulo) se detendrá y la acción DO ShowFlights se
ejecutará.

Ejecución de un programa a toda velocidad en el Depurador


Cuando anima, procesa paso a paso o sigue un programa, lo está ejecutando.
Sin embargo, la animación, paso a paso y seguimiento controlan la ejecución del
programa para que pueda ver sus resultados línea por línea. Cuando lo anima, puede
controlar la cantidad relativa de tiempo que se detiene antes de ejecutar cada línea y,
cuando lo sigue o lo procesa paso a paso, debe determinar cuándo se ejecuta la línea
siguiente.
Si ha definido puntos de ruptura en un programa, quizá desee animarlo o ejecutarlo a
toda velocidad. De esta forma, el programa se ejecutará normalmente hasta que se
produzca el primer punto de ruptura. Entonces, puede decidir si continúa ejecutándolo
normalmente o con los métodos de paso a paso, seguimiento o animación. La ejecución
de un programa en el Depurador le permite pasar rápidamente por partes del programa
que sabe que funcionan bien y concentrarse en las que son problemáticas.
Para ejecutar un programa en el Depurador a toda velocidad, sin detenerse en cada
línea, tiene las posibilidades siguientes:
• Haga clic en el botón Ejecutar de la Barra rápida.
• Elija Ejecutar|Ejecutar en el menú del Depurador.
• Pulse F9.

Ejecución hasta el cursor


Para ejecutar un programa hasta que llegue a la línea de código fuente en que ha situado
el cursor en la ventana Módulo, elija Ejecutar|Ejecutar hasta el cursor. En esencia,
equivale a especificar un punto de ruptura en la línea que contiene el cursor.

112 Guía del programador


debug.dwp Page 113 Wednesday, August 30, 1995 2:06 PM

Ejecución hasta una sentencia RETURN


Para ejecutar un programa hasta que vuelve de una subrutina a la rutina que la llamó,
elija Ejecutar|Hasta Return. En esencia, equivale a especificar un punto de ruptura en el
primer comando RETURN que encuentra Visual dBASE.

Especificación de parámetros para el programa


Si el programa acepta argumentos, o parámetros, puede especificarlos antes de ejecutarlo
en el Depurador. Elija Ejecutar|Argumentos de parámetros y especifíquelos en el
cuadro de diálogo Argumentos de parámetros.

Detención de la ejecución del programa


Además de las funciones de seguimiento, paso a paso y adición de puntos de ruptura,
puede detener el programa en cualquier punto mientras lo anima o lo ejecuta a la
velocidad máxima:
• Haga clic en el botón Detener de la Barra rápida.
• Elija Ejecutar|Detener en el menú del Depurador.
La detención del programa no es lo mismo que borrarlo de la memoria. Cuando ejecuta
y detiene un programa, equivale a detenerlo mientras lo sigue o lo ejecuta paso a paso.
La parada se realiza en la línea actual, que permanece resaltada en la ventana Módulo.
Dado que el programa continúa en memoria y está detenido, técnicamente aún está
ejecutándose; la palabra Ejecutando sigue mostrándose en el extremo derecho de la
barra de estado.

Restablecimiento de programas
El restablecimiento de un programa en el Depurador hace lo siguiente:
1 Detiene el programa si se está ejecutando.
2 Borra el programa de la memoria.
3 Carga el programa de nuevo en memoria y en el Depurador, comenzando en la
primera línea.
Para restablecer un programa que se está ejecutando, haga una de estas acciones:
• Haga clic en el botón Restablecer de la Barra rápida.
• Elija Ejecutar|Restablecer en el menú del Depurador.

Terminación y borrado de programas de la memoria


Ejecutar|Terminar borra el programa de la memoria y lo descarga del Depurador.
Este permanece abierto para que pueda cargar otro programa que desee depurar.

Capítulo 8, Depuración de programas 113


debug.dwp Page 114 Wednesday, August 30, 1995 2:06 PM

Depuración de controladores de sucesos


Para depurar los controladores de sucesos, utilice las mismas técnicas —animación,
paso a paso, seguimiento, puntos de ruptura, etc.— que para depurar otro código.
Estos son los pasos generales:
1 En el Depurador, cargue el archivo de programa que contiene el código del
controlador de sucesos.
2 Defina un punto de ruptura en el código del controlador de sucesos.
3 Ejecute el programa, bien a la velocidad máxima, bien paso a paso o con seguimiento.
Cuando el programa abre una ficha, ésta recibe el foco.
4 Interactúe con la ficha para disparar el suceso asociado al controlador de sucesos. Por
ejemplo, si el controlador es un procedimiento OnClick de un botón, haga clic en el
botón para disparar el suceso OnClick.
Cuando el controlador de sucesos llegue a la línea o condición que ha especificado como
punto de ruptura, el foco vuelve al Depurador. Entonces, puede inspeccionar, seguir,
procesar paso a paso o realizar otras tareas de depuración.

Figura 8.10 Asignación de un controlador de sucesos


En la ficha Animales, esta línea muestra el controlador de sucesos ONNAVIGATE vinculado con el suceso OnNavigate.

114 Guía del programador


debug.dwp Page 115 Wednesday, August 30, 1995 2:06 PM

Figura 8.11 Punto de ruptura en una rutina de control de sucesos


Esta línea muestra un punto de ruptura definido en la sentencia COPY TO ARRAY.
Cuando ejecute la ficha y pase a otro registro, la ejecución se detiene y el Depurador recibe el
foco en esta línea.

Visualización de la pila
Cuando se ejecuta un programa en el Depurador, éste controla las llamadas que aquél
hace a otros programas, procedimientos o funciones en la ventana Pila. Cada línea de la
ventana Pila indica el nombre de archivo y número de línea de las llamadas a subrutinas
y de las rutinas que efectúan la llamada en el orden en que se llamaron (vea la
Figura 8.3, “Seguimiento de una subrutina”). La ventana Pila es por tanto una vista del
flujo desde el programa a una subrutina, de vuelta al programa principal, o más abajo a
otro nivel de subrutina, y así sucesivamente.
Cada vez que se detiene la ejecución del programa, por ejemplo en cada línea cuando lo
procesa paso a paso, lo sigue y lo anima o en cada punto de ruptura, el Depurador
actualiza la ventana Pila.
Para mover el foco en la ventana Módulo a la posición de la llamada a una subrutina:
1 En la ventana Pila, seleccione la línea de la subrutina que desea ver en la ventana
Módulo.
2 Elija Stack|Ir a línea de fuente en el menú del Depurador o haga clic con el botón
derecho en la ventana Pila y elija Ir a línea de fuente en el Menú rápido.

Capítulo 8, Depuración de programas 115


debug.dwp Page 116 Wednesday, August 30, 1995 2:06 PM

Cuando la ventana Módulo desplace el código fuente hasta la línea de comando que
llama la subrutina, la ejecución del programa continúa detenida en el mismo lugar.
Si reanuda su ejecución, lo hará desde el punto en que se detuvo y la ventana Módulo
vuelve a la siguiente de línea de comando que se ejecuta.

Evaluación de las expresiones


Cuando ejecuta un programa desde el Depurador, puede ver el valor de las expresiones
—variables, campos, matrices, objetos o expresiones—. En el Depurador, puede ver
cualquier expresión en la ventana Visor, incluso las que no ocurren en el programa.
También puede establecer un punto de evaluación para una expresión antes de que el
programa inicialice las variables, campos, matrices u objetos que contiene.
Cada vez que se detiene la ejecución del programa, Visual dBASE evalúa los puntos de
evaluación y muestra sus valores actuales entre paréntesis en la ventana Visor. El valor
de un punto de evaluación se ve conforme cambia en cualquier lugar del programa. Por
ejemplo, puede comprobar si se está asignando un valor incorrecto a una variable o si se
está asignando un valor correcto pero en un lugar erróneo del programa.

Adición de puntos de evaluación


Para añadir un punto de evaluación:
• Resalte una expresión, por ejemplo una variable, o desplace el cursor a la expresión
en el código fuente de la ventana Módulo y haga una de las acciones siguientes:
• Elija Visor|Añadir en el menú del Depurador, o haga clic con el botón derecho en
la ventana Módulo y elija Visor en el Menú rápido.
• Pulse Ctrl+W.
La expresión seleccionada aparece en el cuadro de diálogo Añadir punto de
evaluación.
• Elija Visor|Añadir en el menú del Depurador, o haga clic con el botón derecho en la
ventana Visor y elija Añadir en su Menú rápido; introduzca la expresión que desea
evaluar en el cuadro de diálogo Añadir punto de evaluación.
• Pulse Ctrl+W e introduzca la expresión que desee evaluar en el cuadro de diálogo
Añadir punto de evaluación.
Figura 8.12 Cuadro de diálogo Añadir punto de evaluación

116 Guía del programador


debug.dwp Page 117 Wednesday, August 30, 1995 2:06 PM

Después de elegir Aceptar en el cuadro de diálogo Añadir punto de evaluación, la


expresión que especifique aparece en la ventana Visor. Si no ha ejecutado el programa
más allá de un punto en que se inicializan las variables, campos, matrices u objetos de la
expresión, los paréntesis situados a la derecha de la expresión aparecen vacíos en la
ventana Visor.
Figura 8.13 Evaluación de una expresión

Edición de los puntos de evaluación


Para editar un punto de evaluación existente:
1 Seleccione el punto de evaluación en la ventana Visor.
2 Elija Visor|Editar en el menú del Depurador, o haga clic con el botón derecho del
ratón en la ventana Visor y elija Editar en su Menú rápido.
3 Edite la descripción en el cuadro de diálogo Editar punto de evaluación.
En este cuadro de diálogo, puede hacer clic en la flecha abajo situada a la derecha del
cuadro de texto Expresión para ver un menú de los puntos de evaluación actuales.
Puede seleccionar cualquiera de ellos para editarlo en el cuadro de diálogo Editar punto
de evaluación. Sólo se actualizará el último que edite en el cuadro de diálogo cada vez
que elija Aceptar.

Capítulo 8, Depuración de programas 117


debug.dwp Page 118 Wednesday, August 30, 1995 2:06 PM

Cambio de los valores de los puntos de evaluación


Además de evaluar el valor de una expresión, es posible cambiarlo. Esto puede ser útil si
desea cambiar el curso de un programa. Por ejemplo, si el programa no está asignando
actualmente el valor correcto a una variable en el punto actual, puede añadir un punto
de evaluación para esa variable, detener ahí la ejecución del programa (por ejemplo,
definiendo un punto de ruptura en la línea en que el valor correcto debería asignarse a la
variable) y asignar el correcto manualmente en la ventana Visor.
Para cambiar el valor de una expresión que está evaluando en la ventana Visor:
1 Seleccione el punto de evaluación en la ventana Visor.
2 Elija Visor|Modificar en el menú del Depurador, o haga clic con el botón derecho en
la ventana Visor y elija Modificar en el Menú rápido.
3 En el cuadro de diálogo, sustituya el valor actual de la expresión por el que desea.
Cuando elija Aceptar, el valor que introduzca aparece entre paréntesis junto al punto de
evaluación en la ventana Visor. Entonces, puede continuar la ejecución del programa
para ver si el valor correcto en el lugar adecuado cambia el comportamiento del
programa de la forma deseada.

118 Guía del programador


debug.dwp Page 119 Wednesday, August 30, 1995 2:06 PM

Inspección de expresiones
La inspección de una expresión es diferente a su evaluación. Aunque puede definir un
punto de evaluación para una expresión en cualquier momento, la inspección de una
expresión puede establecerse sólo después de que se hayan inicializado las variables,
campos, matrices u objetos de la expresión. En otras palabras, la expresión debe tener
significado en el contexto actual del programa. Un punto de evaluación también
muestra un único valor de una matriz u objeto, mientras que la inspección de una
matriz o un objeto revela el valor de todos sus elementos o propiedades.

Establecimiento de inspecciones
Para inspeccionar una expresión:
• Resalte la expresión o desplace el cursor hasta ella en el código fuente de la ventana
Módulo y haga una de las acciones siguientes:
• Haga clic en el botón Inspeccionar de la Barra rápida.
• Elija Programa|Inspeccionar en el menú del Depurador o haga clic con el botón
derecho en la ventana Módulo y elija Inspeccionar en su Menú rápido.
• Pulse Ctrl+I.
La expresión seleccionada aparece en el cuadro de diálogo Inspeccionar.
• Haga clic en el botón Inspeccionar de la Barra rápida e introduzca la expresión en el
cuadro de diálogo Inspeccionar.
• Elija Programa|Inspeccionar en el menú del Depurador o haga clic con el botón
derecho en la ventana Módulo y elija Inspeccionar en el Menú rápido de la ventana;
entonces, introduzca la expresión que desea inspeccionar en el cuadro de diálogo
Inspeccionar.
• Pulse Ctrl+I e introduzca la expresión en el cuadro de diálogo Inspeccionar.
Cuando elija Aceptar, se abre la ventana de inspección que contiene la expresión
especificada y su valor. Si ha especificado una matriz o un objeto, también se muestran
todos los valores de los elementos de la matriz o de las propiedades del objeto.
Figura 8.14 Ventana de inspección
La ventana de inspección contiene, para Principal, los
valores de las propiedades del objeto.

Principal, un objeto ficha, contiene muchas más propiedades.


Puede desplazarse hacia abajo para verlas todas.

Capítulo 8, Depuración de programas 119


debug.dwp Page 120 Wednesday, August 30, 1995 2:06 PM

Ventana de inspección
Cada ventana de inspección tiene su propio Menú rápido, que puede abrir haciendo clic
con el botón derecho en la ventana. Cuando seleccione un elemento mencionado en una
ventana de inspección y abra su Menú rápido, tiene las opciones siguientes:
• Rango: define un rango (límites superior e inferior) para el elemento seleccionado.
• Modificar: cambia el valor del elemento seleccionado.
• Descender: establece una inspección (abre una ventana de inspección aparte) para el
elemento seleccionado en la ventana de inspección actual.
• Expandir Hex: muestra el valor hexadecimal del elemento seleccionado si dicho
elemento es una variable de cadena binaria.
• Inspeccionar nueva expresión: inspecciona otra expresión.
Las secciones siguientes describen cada una de estas opciones del Menú rápido.

Definición de un rango de valores


Para limitar el rango de valores para la variable, campo, matriz u objeto que está
inspeccionando:
1 Seleccione el elemento en la ventana de inspección.
2 Haga clic con el botón derecho en la ventana de inspección y elija Rango en su Menú
rápido.
3 En el cuadro de diálogo Definir rango, especifique un límite inferior y superior en los
cuadros de texto respectivos.
Por ejemplo, al comprobar VUELOS.WFM, quizá desee permitir sólo las fechas
comprendidas entre el 1 de enero de 1995 y el 31 de diciembre del mismo año para el
campo Vuelos->Fecha, representado por la variable Date, en el módulo
TOMARVLO.PRG de VUELOS.WFM.

Cambio del valor del elemento que está inspeccionando


Para cambiar el valor de la variable, campo, matriz u objeto que está inspeccionando:
1 Seleccione el elemento en la ventana de inspección.
2 Haga clic con el botón derecho en la ventana de inspección y elija Modificar en el
Menú rápido.
3 En el cuadro de diálogo Modificando “valor”, sustituya el valor actual del elemento
por el que desee.
Por ejemplo, puede comprobar VUELOS.WFM con el valor {2/14/95} asignado en el
campo Vuelos->Fecha, representado por la variable Date, en el módulo
TOMARVLO.PRG.

120 Guía del programador


debug.dwp Page 121 Wednesday, August 30, 1995 2:06 PM

Inspección de las propiedades de un objeto o de los elementos de una matriz


Es posible aislar una propiedad de un objeto o un elemento de una propiedad en su
propia ventana de inspección; en otras palabras, establezca una inspección sólo para la
propiedad o el elemento:
1 Seleccione la propiedad del objeto o el elemento de la matriz en la ventana de
inspección.
2 Haga clic con el botón derecho en la ventana de inspección y elija Descender en su
Menú rápido.
Se abre una nueva ventana de inspección únicamente con la propiedad de objeto o el
elemento de matriz y su valor.

Visualización de los valores hexadecimales de las cadenas


Si inspecciona una variable de cadena, puede ver su valor actual de forma hexadecimal:
1 Seleccione la variable en la ventana de inspección.
2 Haga clic con el botón derecho en la ventana de inspección y elija Expandir Hex en su
Menú rápido.
La expresión hexadecimal del valor actual de la variable aparece en el cuadro de diálogo
Hexadecimal.

Inspección de otra expresión


Si ya está trabajando en una ventana de inspección y desea inspeccionar otra
expresión diferente, puede hacer clic con el botón derecho en la ventana de
inspección y elegir Inspeccionar nueva expresión en su Menú rápido, en lugar de
elegir Programa|Inspeccionar en el menú del Depurador o Inspeccionar en el
Menú rápido de la ventana Módulo.

Evaluación, inspección y ámbito


El Depurador respeta el ámbito de las variables para las que define puntos de
evaluación y establece inspecciones. Por ejemplo, suponga que define un punto de
evaluación y establece una inspección para una variable de memoria local. Cuando la
ejecución de un programa entra en una subrutina en que la variable no tiene ámbito, el
valor del punto de evaluación se convierte en “No definido” y la ventana de inspección
de la variable aparece vacía. Si, no obstante, otra variable con el mismo nombre tiene
asignado un valor en la subrutina en que la variable local original no tiene ámbito, ese
nuevo valor se convierte en el valor del punto de evaluación y aparece en la ventana de
inspección. Para más información sobre el ámbito, consulte el Capítulo 5.

Capítulo 8, Depuración de programas 121


debug.dwp Page 122 Wednesday, August 30, 1995 2:06 PM

Evaluación y modificación de datos


Es posible evaluar y modificar el valor de una expresión sin tener que establecer un
punto de evaluación para ella ni inspeccionarla:
• Sitúe el cursor en la expresión o resáltela en el código fuente en la ventana Módulo y:
• Haga clic en el botón Evaluar/Modificar de la Barra rápida.
• Elija Programa|Evaluar/Modificar en el menú del Depurador, o haga clic con el
botón derecho en la ventana Módulo y elija Evaluar/Modificar en el Menú rápido.
La expresión que especifique aparece en el cuadro de diálogo Evaluar/Modificar
(Figura 8.15).
• Haga clic en el botón Evaluar/Modificar de la Barra rápida y especifique la expresión
en el cuadro de texto Expresión del cuadro de diálogo Evaluar/Modificar.
• Elija Programa|Evaluar/Modificar en el menú del Depurador, o haga clic con el
botón derecho en la ventana Módulo y elija Evaluar/Modificar en el Menú rápido;
entonces, especifique la expresión que desea evaluar en el cuadro de texto Expresión
del cuadro de diálogo Evaluar/Modificar.
Cuando la expresión que desea evaluar esté en el cuadro de texto Expresión del cuadro
de diálogo Evaluar/Modificar, haga clic en el botón Evaluar para ver el valor actual de
la expresión en el área Resultado.
En el cuadro de diálogo Evaluar/Modificar, puede cambiar el valor de la expresión
actual introduciendo una nueva expresión en el cuadro de texto Nuevo valor y
pulsando el botón Modificar. El tipo de datos de una expresión no puede modificarse.
Figura 8.15 Cuadro de diálogo Evaluar/Modificar

Haga clic en el botón Evaluar para


ver el valor actual de la expresión
introducida en el cuadro Expresión.

En el cuadro Nuevo valor, teclee


otro valor para la expresión del
cuadro de texto Expresión. Haga clic
en el botón Modificar para cambiar
el valor.

122 Guía del programador


debug.dwp Page 123 Wednesday, August 30, 1995 2:06 PM

Configuración del Depurador


Elija Opciones en el menú Depurador para definir las opciones del entorno para el
Depurador, guardar esas definiciones en un archivo .CFG o restaurar opciones
guardadas en un archivo .CFG existente. Puede crear muchos archivos .CFG con una
configuración distinta en cada uno. De esa forma, puede recuperar la configuración
específica que desea para la sesión de depuración actual.

Opciones por defecto de configuración


Para especificar las opciones de configuración que desea que se guarden en un archivo
.CFG, elija Opciones|Por defecto. Puede definir las siguientes opciones por defecto en el
cuadro de diálogo Valores por defecto:
• Escritorio: la disposición de las cuatro ventanas del Depurador y la posición de la
Barra rápida.
• Aplicación: la aplicación en particular que se depura por defecto.
• Puntos de ruptura: los puntos de ruptura definidos y enumerados actualmente en la
ventana Ruptura.
• Puntos de evaluación: los puntos de evaluación definidos y enumerados actualmente
en la ventana Visor.
Para cambiar la posición de la Barra rápida, haga clic en los botones siguientes del
cuadro de diálogo Valores por defecto:
• Normal
• Emergente
• Reducida
• Vertical

Definición de la velocidad de animación


Para definir una velocidad de animación por defecto, elija Opciones|Velocidad de
animación. En el cuadro de diálogo Velocidad de animación, mueva el cuadro de la
barra de desplazamiento hacia la derecha para aumentar la velocidad de animación y
hacia la izquierda para disminuirla. Si ya está animando un programa, la modificación
de la velocidad de animación afecta dinámicamente a la animación actual.

Definición de fuentes
Para definir la fuente de visualización por defecto para el texto en el Depurador, elija
Opciones|Fuente de visualización. Especifique sus preferencias en el cuadro de diálogo
Fuentes.

Capítulo 8, Depuración de programas 123


debug.dwp Page 124 Wednesday, August 30, 1995 2:06 PM

Definición de la vía de acceso por defecto a los archivos fuente


Para especificar una vía de acceso por defecto a la unidad y directorio en que se
encuentran los archivos que desea cargar en el Depurador, elija
Opciones|Vía de archivo fuente. En el cuadro de diálogo Vía de archivo fuente,
introduzca la vía de acceso por defecto.

Almacenamiento de las opciones de configuración en archivos .CFG


Para guardar las definiciones que especifique con las opciones Por defecto, Velocidad de
animación, Fuente de visualización y Vía de archivo fuente del menú Opciones, elija
Opciones|Guardar opciones. En el cuadro de diálogo Guardar configuración,
especifique el nombre del archivo de configuración. Por defecto, el Depurador propone
el nombre de archivo DEFAULT.CFG.
Si acepta el nombre por defecto DEFAULT.CFG, el Depurador buscará y, si lo
encuentra, utilizará este archivo cuando inicie el Depurador. Si desea utilizar otro
archivo .CFG, tendrá que cargarlo después de iniciar el Depurador; consulte la sección
siguiente, “Restauración de opciones de configuración.” Si el Depurador no encuentra
un archivo DEFAULT.CFG, utiliza sus definiciones originales.

Restauración de opciones de configuración


Hasta que crea y recupera un archivo .CFG, el Depurador utiliza la fuente del sistema de
Windows, el directorio de trabajo actual y la velocidad de animación máxima. También
se abre con todas las ventanas presentes y con la Barra rápida en su posición horizontal
bajo el menú. No hay definidos puntos de ruptura, puntos de evaluación ni aplicación.
Si el Depurador encuentra un archivo DEFAULT.CFG, lo utiliza. Para restaurar las
definiciones guardadas en un archivo .CFG que no sea DEFAULT.CFG, elija
Opciones|Recuperar opciones. En el cuadro de diálogo Recuperar configuración,
especifique el nombre del archivo .CFG que contiene las opciones que desea recuperar.

124 Guía del programador


debug.dwp Page 125 Wednesday, August 30, 1995 2:06 PM

Asociación del Depurador a copias de Visual dBASE


Es posible ejecutar varias copias de Visual dBASE y una sola copia del Depurador.
Si lo inicia desde Visual dBASE, el Depurador se asocia a la copia de Visual dBASE que
lo inició. Sin embargo, si tiene varias copias de Visual dBASE, puede ejecutar diferentes
aplicaciones en esas copias y especificar a qué aplicación de qué copia se asocia el
Depurador:
1 En el menú del Depurador, elija Archivo|Asociar.
2 En el cuadro de diálogo Asociar a la aplicación activa, especifique la aplicación que se
ejecuta en la copia de Visual dBASE en particular a la que se asocia el Depurador.

Modificación del directorio actual


Para cambiar el directorio para la sesión de depuración actual, elija
Archivo|Cambiar directorio. En el cuadro de diálogo Cambiar de directorio,
especifique la vía de acceso a otro directorio en el cuadro de texto Nuevo directorio.

Visualización de archivos de programa


Para ver un archivo de programa (o cualquier archivo de texto) en el Depurador, elija
Archivo|Abrir archivo de texto. El archivo se abre en una ventana sobre el Depurador.
Puede desplazarse por el archivo, pero no editarlo.

Capítulo 8, Depuración de programas 125


debug.dwp Page 126 Wednesday, August 30, 1995 2:06 PM

126 Guía del programador


p_obj.dwp Page 127 Wednesday, August 30, 1995 2:08 PM

Parte

II
Objetos y clases
Parte II

Esta parte presenta y trata las extensiones orientadas a objetos que se han añadido en el
lenguaje dBASE.
Si trabaja con la orientación a objetos por primera vez:
1 Comience con el Capítulo 9 para aprender la terminología y los conceptos básicos.
2 Hojee los Capítulos 10 y 11 para familiarizarse con los comandos y técnicas.
3 Lea el Capítulo 12 para aprender cómo diseñar aplicaciones orientadas a objetos.
Cuando comience a programar con objetos y clases, consulte los Capítulos 10 y 11 para
conocer detalles sobre la realización de tareas específicas.
Esta sección contiene los capítulos siguientes
• Capítulo 9, “Introducción a la programación orientada a objetos”
• Capítulo 10, “Uso de los objetos”
• Capítulo 11, “Uso de las clases”
• Capítulo 12, “Diseño orientado a objetos”

Parte II, Objetos y clases 127


p_obj.dwp Page 128 Wednesday, August 30, 1995 2:08 PM

128 Guía del programador


intoop.dwp Page 129 Wednesday, August 30, 1995 2:09 PM

Capítulo

Introducción a la programación
Capítulo 9
9
orientada a objetos
Este capítulo presenta los objetos y las clases y describe cómo trabajar con ellos
empleando el lenguaje dBASE. Aprenderá qué son los objetos y las clases, cómo crear
objetos y cómo manipularlos cambiando sus propiedades.
La orientación a objetos emplea técnicas avanzadas para la gestión de variables de
memoria. Antes de aprender acerca de los objetos, debería familiarizarse con los
conceptos básicos del uso de las variables de memoria. Para más información, consulte
el Capítulo 5.

Breve recorrido por la orientación a objetos


Por medio de las características orientadas a objetos de dBASE, es posible lograr con
sentencias de asignación simples (“esto” es igual a “aquello”) lo que solía precisar varias
líneas de código de dBASE. Es la denominada programación de propiedades, es decir,
programar cambiando los valores de las propiedades de un objeto. Para conocer cuán
fácil es, siga los ejemplos de esta sección para crear un objeto de ficha y manipular sus
propiedades.
En primer lugar, cree y abra un objeto de ficha tecleando estas dos líneas en la ventana
de comandos:
DEFINE FORM CompProp && Crea una ficha
CompProp.Open() && Abre la nueva ficha

Capítulo 9, Introducción a la programación orientada a objetos 129


intoop.dwp Page 130 Wednesday, August 30, 1995 2:09 PM

Acaba de crear una “copia” de la clase Form, denominada CompProp. La programación


de propiedades también se denomina programación de copias, porque se manipula
dinámicamente una copia de una clase. A continuación se muestra cómo aparece
CompProp:
Figura 9.1 Ficha CompProp

Lo que aporta tanta versatilidad a la programación de propiedades en Visual dBASE es


que, cuando cambia el valor de una propiedad, el objeto refleja el nuevo valor de
inmediato. Para ver cómo funciona, teclee las siguientes líneas de código y observe
cómo cambia la ficha CompProp (puede excluir los comentarios que comienzan con &&
en cada línea):
CompProp.Top = 10 && Mueve la ficha a la linea 10
CompProp.Top = CompProp.Top + 5 && Mueve la ficha 5 filas más abajo
CompProp.ColorNormal = "b+/GB" && Cambia el color
CompProp.WindowState = 1 && Minimiza la ficha
Comprop.WindowState = 0 && Restablece la posición de la ficha

130 Guía del programador


intoop.dwp Page 131 Wednesday, August 30, 1995 2:09 PM

Ahora, CompProp tiene el aspecto siguiente:


Figura 9.2 Nuevo aspecto de la ficha CompProp

Los objetos son extensibles. Es decir, es posible añadir nuevos miembros en un objeto
con una simple sentencia de asignación. Teclee estas líneas en la ventana de comandos
para añadir dos nuevas propiedades en la ficha:
CompProp.MiPropN = 123 && Añade una nueva propiedad llamada MiPropN
CompProp.MiPropC = "¡Hola!" && Añade una nueva propiedad llamada MiPropC
La adición de propiedades no tiene ningún efecto sobre la ficha —aún—. Para que una
propiedad haga algo con la ficha, puede añadir un método que lea su valor y realice
alguna acción con él. El código siguiente añade un método en CompProp asignando un
bloque de código a MiMetodo. Teclee esta línea en la ventana de comandos:
CompProp.MiMetodo = {;CompProp.Text = CompProp.text+CompProp.MiPropC}
Al ejecutarlo, MiMetodo añade la cadena almacenada en MiPropC en la barra de título de
la ficha. Para ejecutar MiMetodo, utilice el operador de llamada como sigue:
CompProp.MiMetodo() && Cambia el título

Capítulo 9, Introducción a la programación orientada a objetos 131


intoop.dwp Page 132 Wednesday, August 30, 1995 2:09 PM

Definir propiedades tecleando comandos es divertido, pero puede ser más fácil ver y
editar todas las propiedades de un objeto de forma interactiva. Para ello, arranque el
Inspector como sigue:
INSPECT(CompProp) && Muestra las propiedades del Inspector

Figura 9.3 Inspector con CompProp

Visual dBASE proporciona la variable de memoria del sistema _app que puede utilizar
para acceder al objeto de aplicación. Mediante este objeto, puede definir varias
propiedades de Visual dBASE. Teclee esta línea en la ventana de comandos:
? _app.FrameWin.Text && Muestra el título de la ventana de la aplicación
Ahora pruebe esto:
_app.FrameWin.Text = _app.FrameWin.Text+" personalizada"
Acaba de personalizar Visual dBASE con un título.
Ahora que ha comenzado a trabajar con los objetos, continúe leyendo para aprender
sobre cómo se implantan en Visual dBASE.

132 Guía del programador


intoop.dwp Page 133 Wednesday, August 30, 1995 2:09 PM

Acerca de los objetos


En el Diseñador de fichas, puede crear y manipular objetos del interface de usuario por
medio de utilidades interactivas, como el Inspector de objetos. Como programador,
trabaja con los mismos objetos como conjuntos de variables de memoria. El propio
objeto es un contenedor de variables, algunas de las cuales contienen valores de datos y
otras hacen referencia a rutinas.
Si ya ha trabajado con matrices, puede compararlas con los objetos, correspondiendo
cada propiedad de un objeto a un elemento de una matriz. (De hecho, las matrices son
un tipo de objeto en Visual dBASE. Para más detalles, consulte “Uso de las matrices
como objetos” en el Capítulo 10.)
• Una matriz es un conjunto de variables organizadas secuencialmente en filas y
columnas. Para hacer referencia a los elementos de una matriz, se utilizan números
que representan su posición en la matriz, como MiMatriz[1] para el primer elemento o
MiMatriz[3,2] para el segundo elemento de la tercera fila.
• Un objeto es un conjunto de variables sin una organización implícita —las variables
de un objeto son sólo miembros del objeto—. La referencia a los miembros de un
objeto se hace por su nombre, como MiObj.Elem para la propiedad Elem, o
MiObj.Precio para la propiedad Precio.
La Figura 9.4 ilustra la relación entre las variables de memoria, las matrices y los objetos
y muestra el código que los crea.
Figura 9.4 Un objeto es un conjunto de variables de memoria
Crea 3 variables de memoria. Crea una matriz con 3 elementos. Crea un objeto con 4 miembros.

Elem = "Pantalón" DECLARE MiMatriz[3] MiObj = NEW Object()


Talla = 12 MiMatriz[1] = "Pantalón" MiObj.Elem = "Pantalón"
Precio = 10095 MiMatriz[2] = 12 MiObj.Talla = 12
MiMatriz[3] = 10095 MiObj.Precio = 10095
MiObj.CalCoste = MiProc

Pantalón .
Elem Pantalón
MiMatriz[1] MiMatriz[2] MiMatriz[3]
Elem Talla 12
12 Pantalón 12 10095
1 2 3 Precio 10095
Talla MiObj
10095 CalCoste MiProc
Precio
MiMatriz

Todas las variables de memoria contenidas en un objeto son propiedades del objeto.
Algunas propiedades se convierten en métodos cuando le anexe código.

Capítulo 9, Introducción a la programación orientada a objetos 133


intoop.dwp Page 134 Wednesday, August 30, 1995 2:09 PM

Un método es una subrutina, como por ejemplo un procedimiento, una función o un


bloque de código, que se asocia a un objeto mediante un puntero de función.
Los métodos realizan acciones sobre un objeto. Por ejemplo, para cerrar una ficha, se
llama el método Close de la ficha. (El Capítulo 4 describe los punteros de función y los
bloques de código.)
Algunos métodos de objetos estándar, como una ficha o un campo de entrada, se
ejecutan de forma automática en respuesta a un suceso, como un clic del ratón.
Estos métodos se denominan sucesos, puesto que un “suceso” dispara su ejecución.
Por ejemplo, cuando el usuario hace clic en un objeto de botón, se ejecuta el suceso
OnClick del botón. Otros métodos se ejecutan sólo cuando se llaman explícitamente.
La Figura 9.5 ilustra cómo un objeto está formado por propiedades, algunas de las
cuales son métodos.
Figura 9.5 Miembros de un objeto
Propiedades

Nombre Juan
Talla 12
Precio 195
MiObj MiProc OtroProc
CalCoste MiProc

Un método Un procedimiento no
asociado al objeto

Las variables de un objeto (denominadas miembros del objeto) pueden ser del mismo
tipo de datos que las variables normales. En el ejemplo anterior, la propiedad Nombre
es del tipo carácter. Talla y Precio son propiedades numéricas. El tipo de CalCoste es
puntero de función; no contiene ningún valor como tal, sino que hace referencia a una
subrutina.

Contenedores de objetos
Los objetos pueden contener otros objetos. Las fichas son el ejemplo más común de esta
característica. El objeto de ficha es el objeto contenedor, o padre. Los campos de entrada,
botones y otros objetos del interface de usuario son los objetos hijos contenidos en las
fichas.
El código siguiente crea un objeto de ficha (MiFicha), un objeto de texto (Text1) y un
objeto de campo de entrada (Entrada1):
DEFINE FORM MiFicha PROPERTY Top 5, Left 3
DEFINE TEXT Text1 OF MiFicha PROPERTY Top 2, Left 2, Text "Nombre", Width 17
DEFINE ENTRYFIELD Entrada1 OF MiFicha PROPERTY Top 2, Left 11, Value "Juan"
OPEN FORM MiFicha

134 Guía del programador


intoop.dwp Page 135 Wednesday, August 30, 1995 2:09 PM

La Figura 9.6 ilustra el resultado de este código:


Figura 9.6 Contención de objetos

Text1.Top 2
Top 5 Text1.Left 2
Left 3 Text1.Value Nombre
Text1 Text1
MiFicha Hijo
Entrada1 Entrada1
Entry1

Padre
Entrada1.Top 2
Entrada1.Left 11
Entrada.Value Juan

Hijo

Top y Left son propiedades numéricas de MiFicha. Text1 y Entrada1 son del tipo objeto,
es decir, hacen referencia a otro objeto.
? MiFicha && variable de tipo "Objeto"
? MiFicha.Text1 && también variable de tipo "Objeto"
? MiFicha.Entrada1 && también variable de tipo "Objeto"
La relación padre-hijo entre objetos tiene implicaciones importantes para la forma en
que se trabaja con los objetos. Cuando se libera un objeto padre, también se liberan
todos sus objetos hijos. El código siguiente muestra que, cuando se libera MiFicha,
también se liberan Text1 y Entrada1
RELEASE OBJECT MiFicha && La ficha desaparece
? MiFicha && La variable existe, pero no hace referencia a ningún objeto
? MiFicha.Text1 && variable indefinida
? MiFicha.Entrada1 && variable indefinida
Todos los objetos que cree se basan en una clase. MiObj en la Figura 9.4 y en la Figura 9.5
se basa en la clase Object, y MiFicha, Text1 y Entrada1, en la Figura 9.6, se basan en las
clases Form, Text y Entryfield, respectivamente. Pero ¿qué es exactamente una clase?

Capítulo 9, Introducción a la programación orientada a objetos 135


intoop.dwp Page 136 Wednesday, August 30, 1995 2:09 PM

Acerca de las clases


Una clase es una especificación, o plantilla, según la cual crear varias copias de un objeto
en particular. Sin clases, tendría que definir las propiedades de cada nuevo objeto
cuando lo crea. Las clases se utilizan como “fábricas de objetos” para crear copias de
objetos idénticos. Por ejemplo, podría utilizar un botón de comando “Buscar”
personalizado en varias fichas diferentes. En lugar de crearlo desde cero cada vez,
puede declarar una clase Buscar para definir el botón de comando y, a continuación,
crear tantas copias de esa clase como desee.
Visual dBASE proporciona muchas clases estándar, como Form y Entryfield. Cuando se
crea una ficha en el Diseñador de fichas, se crea un nuevo objeto de ficha empleando las
especificaciones estándar de la clase Form.
La relación entre clase y objeto es similar a la que existe entre un tipo de datos y una
variable de memoria. Por ejemplo, el tipo de datos numérico especifica reglas que
constituyen datos numéricos y cómo pueden manipularse. Cuando se crea una variable
numérica, se crea una copia del tipo de datos numérico. Puede crear tantas variables
numéricas como guste, cada una con un valor distinto, pero todas las variables
numéricas comparten las mismas normas en cuanto a cómo puede manipularlas.
De forma similar, la clase Form especifica las características que tienen todas las fichas.
Cuando se crea una ficha, se crea una copia de la clase Form. Puede crear tantas fichas
como desee, y cada una puede mostrar datos distintos, pero todas comparten las
características comunes que especifica la clase Form.
Al declarar una clase personalizada como la clase “Buscar” mencionada antes, especifique
con código de dBASE las propiedades y métodos que tendrán los objetos de esa clase.

136 Guía del programador


intoop.dwp Page 137 Wednesday, August 30, 1995 2:09 PM

La Figura 9.7 ilustra la relación entre tipos de datos y variables, y entre clases y objetos.
Figura 9.7 Una clase es a un objeto lo que un tipo de datos es a una variable de memoria
Tipo de datos Clases
Tipo de datos: Numérico Clase estándar: Form Cl. personalizada: Elems
Variables y campos Crea una ventana con CLASS Elems
numéricos: estas propiedades: This.Elem = "Bolso"
This.Tamano = 12
• Suman, restan, • Top This.Coste = 1695
multiplican o This.Cantidad = 4
• Left
dividen. Procedure CalValor
• Width Val = this.Coste*;
• Utilícelos en
expresiones sin this.Cantidad
• Height
RETURN Val
delimitadores
• Open() ENDCLASS

Precio = 195 MiFicha = NEW FORM() MiObj = NEW Elems()

195 Top Elem


Precio Left Tamano
Width Coste
MiFicha Height MiObj Cantidad
Open() CalValor()
Objeto Objeto

Capítulo 9, Introducción a la programación orientada a objetos 137


intoop.dwp Page 138 Wednesday, August 30, 1995 2:09 PM

Resumen de la orientación a objetos


En este capítulo, ha aprendido cómo manipular objetos mediante la programación de
propiedades y lo que son los objetos y las clases. Los dos capítulos siguientes explican en
detalle cómo trabajar con objetos y clases. Como resumen, éstas son algunas de las cosas
que puede hacer con ellos en Visual dBASE:
Objetos
• Cree objetos a partir de las clases estándar o de las clases personalizadas que declare.
• Consulte el valor de cualquier propiedad y cambie el valor de muchas propiedades.
• Añada nuevas propiedades en un objeto con una sentencia de asignación simple.
• Vea de inmediato los resultados de cambiar el valor de una propiedad en un objeto
estándar.
Clases
• Declare clases personalizadas.
• Declare clases a partir de otras clases para que hereden sus propiedades y métodos.
• Cree una jerarquía de clases.

138 Guía del programador


objects.dwp Page 139 Wednesday, August 30, 1995 2:10 PM

Capítulo

10
Uso de los objetos
Capítulo 10

En el Capítulo 9 ha aprendido qué son los objetos y las clases y por qué son tan
versátiles. Este capítulo explica cómo crear y usar los objetos, presenta los contenedores
de objetos y describe cómo trabajar con jerarquías de objetos. Concluye con tareas más
avanzadas, como crear objetos personalizados y trabajar con matrices como objetos.

Creación de objetos
Para crear una variable de memoria, basta declarar un nombre. Este ejemplo crea una
variable local denominada nPrecio:
LOCAL nPrecio
nPrecio = 195
Para crear una matriz, se declara su nombre y el número de filas y columnas que
contiene. Este ejemplo crea una matriz con 5 filas y 2 columnas, denominada MiMatriz:
DECLARE MiMatriz[5,2]
MiMatriz[1,1] = "Primer elemento"
Para crear un objeto, se especifica la clase en que se basará el objeto. La clase es una
receta o plantilla que especifica las propiedades y métodos que constituyen un objeto.
Un objeto es una copia de una clase. Por ejemplo, una ficha es una copia de la clase Form.
La clase Form especifica que los objetos de ficha tendrán propiedades Height y Width
para determinar el tamaño, y otras propiedades también.
En el lenguaje dBASE, puede crear objetos de una de las dos formas siguientes:
• Utilice DEFINE para crear un objeto según la necesidad y definir los valores iniciales
de sus propiedades en un solo comando. Esta sintaxis resultará muy sencilla para los
programadores habituados a los comandos DEFINE de dBASE DOS.
• Utilice el operador NEW en una sintaxis de asignación de variable de memoria.
El operador NEW crea un objeto pero no especifica valores para sus propiedades.
Para asignarlos, utilice otras sentencias de asignación adicionales.

Capítulo 10, Uso de los objetos 139


objects.dwp Page 140 Wednesday, August 30, 1995 2:10 PM

El operador NEW suele utilizarse para crear un objeto cuando no es necesario


cambiar ninguna de sus propiedades. Por ejemplo, un archivo .WFM generado por el
Diseñador de fichas contiene una clase personalizada para la ficha con todos los
parámetros que se especificaron al diseñar la ficha. El archivo .WFM contiene una
línea que crea un objeto de ficha a partir de la clase mediante el operador NEW.
Suponga que FICHACLI.WFM es una ficha personalizada generada por el Diseñador
de fichas. Puede crear un objeto de ficha a partir del archivo .WFM como sigue:
DO FICHACLI.WFM && Ejecuta la sentencia NEW dentro de .WFM
O bien
SET PROCEDURE TO FICHACLI.WFM && Carga la declaración de clase en la memoria
oFicha = NEW FichaCli() && Crea un objeto ficha a partir de la declaración de
&& clase contenida en FICHACLI.WFM
Los programadores familiarizados con los lenguajes orientados a objetos, como C++
o Delphi, reconocerán el uso de NEW en la creación de objetos y pueden preferir
utilizar NEW en lugar de DEFINE.
A continuación se muestran dos ejemplos de código, uno junto al otro, con formas
equivalentes de crear un objeto de ficha:
Figura 10.1 Formas equivalentes de crear objetos: el comando DEFINE y el operador NEW

Formas equivalentes de crear un objeto de ficha


DEFINE FORM MiFicha FROM 5,5 TO 15,60 PROPERTY; MiFicha = NEW FORM()
Text "Mío" MiFicha.Text = "Mío"
MiFicha.Top = 5
MiFicha.Left = 5
MiFicha.Height = 11
MiFicha.Width = 56
Formas equivalentes de crear un objeto de texto

DEFINE Text MiTexto OF MiFicha AT 2,2 PROPERTY; MiTexto = NEW Text(MiFicha)


Text "¡Hola!",; MiTexto.Text = "¡Hola!"
FontName "Roman",; MiTexto.Top = 2
FontHeight 14,; MiTexto.Left = 2
ColorNormal "g+/r" MiTexto.FontName = "Roman"
MiTexto.FontHeight = 14
MiTexto.ColorNormal = "g+/r"
Formas equivalentes de abrir un objeto de ficha
OPEN FORM MiFicha MiFicha.Open()

Estas sintaxis alternativas son intercambiables, y es posible pasar de una a otra con
libertad, utilizando la que sea más conveniente en cada momento. Por ejemplo, en un
comando DEFINE, puede crear el objeto y definir sólo unas pocas propiedades.
DEFINE FORM MiFicha PROPERTY Top 10, Left 5
OPEN FORM MiFicha

140 Guía del programador


objects.dwp Page 141 Wednesday, August 30, 1995 2:10 PM

Entonces, puede emplear la notación de punto (descrita en “Uso del operador de punto”
más adelante en este capítulo) para cambiar algunas propiedades.
MiFicha.Top = 12
MiFicha.Sizeable = .F.
A continuación, puede utilizar INSPECT() para cargar el Inspector y cambiar las
propiedades interactivamente.
Figura 10.2 Inspector

Para añadir otro objeto en la ficha, utilice DEFINE o el operador NEW. Este ejemplo
emplea el operador NEW:
MiBoton = NEW PUSHBUTTON(MiFicha)

Capítulo 10, Uso de los objetos 141


objects.dwp Page 142 Wednesday, August 30, 1995 2:10 PM

Referencia a los miembros de un objeto


La sintaxis para hacer referencia a los miembros de un objeto es una extensión natural
de la forma en que se hace referencia a las variables de memoria y a los elementos de
una matriz. Para hacer referencia a las variables de memoria, se utiliza el nombre de la
variable en una expresión. De forma similar, para hacerla a los elementos de una matriz,
se utiliza el nombre de ésta seguido por el subíndice del elemento. El código siguiente lo
demuestra:
LOCAL cNombre && Declara una variable de memoria
cNombre = "Juan" && Asigna un valor
REPLACE Nombre with cNombre && Referencia a la variable en una
&& expresión
DECLARE MiMatriz[10,10] && Declara una matriz
MiMatriz[1,1] = "silla" && Asigna un valor a un elemento
? "El material solicitado era una " ,MiMatriz[1,1] && Referencia al elemento en una
&& expresión
Para hacer referencia a los miembros de los objetos, utilice el nombre de propiedad o
método precedido por una referencia a objeto. Esta referencia especifica el objeto de que es
miembro una propiedad o método. Una referencia a objeto puede ser uno de los
siguientes:
• El nombre de una variable que hace referencia al objeto, denominada
variable de referencia a objeto. Cuando se crea un objeto (con DEFINE o con el operador
NEW), también es posible crear una variable que hace referencia al objeto.
Por ejemplo:
DEFINE FORM MiFicha && Crea un objeto de ficha y la variable de referencia MiFicha
O bien
MiFicha = NEW Form() && Crea una ficha y la asigna a la variable de referencia MiFicha

MiFicha.top = 3 && Usa el nombre de la variable como referencia


MiFicha.ColorNormal = "w+/gb"
MiFicha.Open()
La sección “Uso de las variables de referencia a objeto” en la página 146 trata estos
aspectos en detalle.
• This, que representa una referencia estándar al objeto a que pertenezca la propiedad.
Visual dBASE crea automáticamente una referencia this cuando se declara una clase o
se ejecuta un controlador de sucesos. Utilice this en declaraciones de clase y en código
de control de sucesos para hacer referencia genéricamente a los objetos sin necesidad
de concretar un nombre. El programa siguiente lo demuestra:
*** Programa DemoThis.prg
SET PROCEDURE TO PROGRAM(1) ADDITIVE
DEFINE FORM MiFicha
MiFicha.OnGotFocus = MiOnGotFocus
MiFicha.Open()
Procedure MiOnGotFocus
this.text = "¡Tengo el foco!"
RETURN

142 Guía del programador


objects.dwp Page 143 Wednesday, August 30, 1995 2:10 PM

• Form, que representa una referencia estándar a la ficha que contiene un objeto.
Visual dBASE crea automáticamente una referencia form cuando se declara una clase
de ficha o se abre una ficha. Como this, utilice form en código de control de sucesos y
en declaraciones de clase para hacer referencia genéricamente a la ficha que contiene
el objeto. El programa siguiente lo demuestra:
*** Programa DemoForm.prg
SET PROCEDURE TO PROGRAM(1) ADDITIVE
DEFINE FORM MiFicha
DEFINE PUSHBUTTON MiBoton OF MiFicha
MiFicha.Miboton.OnClick = MiOnClick
OPEN FORM MiFicha
Procedure MiOnClick
Form.text = "¡Ha pulsado mi botón!"
RETURN
This y form se describen en profundidad en el Capítulo 14.
En los ejemplos previos, un operador de acceso a miembro separa la referencia a objeto del
nombre de propiedad o método. Este operador asocia un miembro a un objeto de forma
parecida a cómo el operador de alias (->) asocia un campo a una tabla. Los operadores
de acceso a miembro son el de punto y el de índice, que se describen a continuación.

Uso del operador de punto


El operador de punto ( . ) hace referencia a los miembros por su nombre. Ésta es la sintaxis:
<referencia a objeto>.<nombre de miembro>
El operador de punto es la forma más común de hacer referencia a las propiedades o
métodos de un objeto. La sintaxis del comando que contiene este operador suele
denominarse notación de punto.
DEFINE FORM MiFicha && Crea un objeto ficha
MiFicha.Left = 200 && Asigna un valor a la propiedad Left de MiFicha
MiFicha.Text = "Hola" && Asigna un valor a la propiedad Text de MiFicha
El resultado del operador de punto puede ser una referencia a objeto en sí, que hace
referencia a un objeto hijo. Para hacer referencia a propiedades de los objetos hijos,
puede crear una vía de referencias a objetos hasta llegar a la propiedad. Esto es similar a
la especificación de una vía de acceso a un archivo en disco, donde se indica cada
subdirectorio que conduce al archivo.
DEFINE PUSHBUTTON Miboton OF MiFicha && Crea un botón en la ficha
MiFicha.MiBoton.Top = 2 && Asigna un valor a la propiedad Top de MiBoton en
&& MiFicha

Capítulo 10, Uso de los objetos 143


objects.dwp Page 144 Wednesday, August 30, 1995 2:10 PM

Uso del operador de índice


El operador de índice ( [ ] ) hace referencia a los miembros por su número. Ésta es la
sintaxis:
<referencia a objeto>[<expN>]
Utilice el operador de índice para hacer referencia a un miembro cuya posición se
representa mediante un valor, no por su nombre. Lo más habitual es utilizar el operador
de índice para hacer referencia a los elementos de los objetos de una matriz. Es de las
matrices de donde toma su nombre el operador de índice; los corchetes encierran la
posición de índice que ocupa un elemento dentro de la matriz. En Visual dBASE, las
matrices se implementan como un tipo especial de objeto.
Para ver ejemplos del uso del operador de índice con las matrices, consulte el Capítulo 5.
Para ver ejemplos de matrices dispersas creadas a partir de la clase Object, consulte
“Creación de matrices dispersas” más adelante en este capítulo.
Este ejemplo muestra el uso del operador de índice con un objeto de la clase Object:
MiObj = NEW Object()
MiObj[1] = "Propiedad 1"
MiObj[5] = "Propiedad 5"
? MiObj[1]
? MiObj[5]

144 Guía del programador


objects.dwp Page 145 Wednesday, August 30, 1995 2:10 PM

Adición de propiedades y métodos personalizados


Además de los miembros estándar de un objeto, puede crear propiedades y métodos
personalizados. Para ello, emplee una de las dos posibilidades siguientes:
• Utilice el comando DEFINE con la opción CUSTOM, que marca el comienzo de una
lista de declaración de propiedades personalizadas, igual que la opción PROPERTY
inicia una lista de propiedades estándar. Siga la misma sintaxis para las propiedades
personalizadas que para las estándar.
• Utilice el operador NEW y teclee el nombre de la nueva propiedad o método después
del operador de punto.
La Figura 10.3 muestra formas equivalentes de añadir propiedades personalizadas, una
mediante DEFINE y la otra con NEW.
Figura 10.3 Adición de miembros personalizados en un objeto

Opción CUSTOM de DEFINE Operador NEW

DEFINE FORM FichaCli; FichaCli = NEW FORM()


PROPERTY; FichaCli.Top = 5
Top 5,; FichaCli.Left = 10
Left 10; FichaCli.Saludo = "Hola"
CUSTOM; FichaCli.NumDeLaSuerte = 19
Saludo "Hola",;
NumDeLaSuerte 19

Como implica la Figura 10.3, es necesario teclear con cuidado cuando se definen
propiedades con el operador de punto. Al igual que con un nombre tradicional de
variable de memoria, si comete un error de tecleo u ortográfico en el nombre de
miembro de un objeto en una sentencia de asignación, Visual dBASE supone que desea
añadir un nuevo miembro personalizado con el nombre cambiado y no comunica
ningún error.
MiFicha = NEW FORM() && Crea una ficha
MiFicha.Left = 10 && Cambia el valor de la propiedad Left a 10
MiFicha.Lefty = 12 && Añade una nueva propiedad llamada Lefty

Capítulo 10, Uso de los objetos 145


objects.dwp Page 146 Wednesday, August 30, 1995 2:10 PM

Uso de las variables de referencia a objeto


Uno de los conceptos más importantes que debe comprender al trabajar con objetos es la
diferencia que hay entre las variables de memoria tradicionales y las variables de
referencia a objeto. Si lo entiende, habrá superado el obstáculo más difícil con que puede
encontrarse para trabajar con los objetos.
Las variables de memoria tradicionales contienen valores. Es decir, una variable
numérica contiene un valor numérico; una de carácter contiene una cadena de
caracteres. Cuando se asigna una variable de memoria (Nombre) a otra (Nombre2), el
contenido de Nombre se copia en Nombre2. Los cambios posteriores que se efectúen en el
contenido de Nombre no tienen efecto sobre Nombre2. El ejemplo siguiente lo demuestra.
Nombre = "Juan" && Nombre contiene la cadena "Juan"
Nombre2 = Nombre && El valor de Nombre se copia a Nombre2; Nombre2 contiene la
&& cadena 'Juan'
Nombre = "Carmen" && Nombre contiene ahora la cadena "Carmen"
? Nombre && Devuelve "Carmen"
? Nombre2 && Devuelve "Juan"
Por el contrario, una variable de referencia a objeto contiene una referencia a un objeto,
no el objeto en sí. Cuando se asigna una variable de referencia a objeto a otra, no se
duplica el objeto; simplemente se tiene otra referencia al mismo objeto.
Las referencias a objeto son similares a los parámetros pasados mediante referencia en
un procedimiento. Cuando se pasa un parámetro mediante referencia, no se duplica la
variable, sólo se crea otra referencia a él dentro del procedimiento.
(El Capítulo 3 describe la transmisión de parámetros.)
Las variables de referencia a objeto pueden utilizarse de la misma forma que otras
variables de memoria. Por ejemplo, es posible pasarlas como parámetros, devolverlas
como resultado de una función y almacenarlas en matrices. El Capítulo 10 contiene
ejemplos de su uso.
El código siguiente crea un objeto de ficha y define algunas de sus propiedades. La
variable de referencia a objeto MiFicha hace referencia a la ficha.
MiFicha = NEW Form() && Crea un objeto ficha con la referencia MiFicha
OPEN FORM MiFicha && Abre la ficha
? TYPE("MiFicha") && Devuelve "O" para referencias a objetos
MiFicha.Text = "Mi ficha" && Cambia la propiedad Text
MiFicha.Top = 10 && Cambia la propiedad Top

146 Guía del programador


objects.dwp Page 147 Wednesday, August 30, 1995 2:10 PM

La Figura 10.4 ilustra el resultado del código anterior.


Figura 10.4 MiFicha hace referencia a un objeto de ficha

ƒ
Text MiFicha
Top 10
MiFicha
ƒ

A continuación, cree otra variable de referencia a objeto, MiFicha2, y utilice ambas


variables para manipular el objeto de ficha.
MiFicha2 = MiFicha && MiFicha y MiFicha2 se refieren a la misma ficha
? MiFicha2.Text && Devuelve "Mi Ficha"
MiFicha2.Text = "¡Mi ficha es grande!" && Cambia la propiedad Text de nuevo
? MiFicha2.Text && Devuelve "¡Mi ficha es grande!"
? MiFicha.Text && Devuelve "¡Mi ficha es grande!"
La Figura 10.5 ilustra el resultado del código anterior.
Figura 10.5 MiFicha y MiFicha2 hacen referencia al mismo objeto de ficha

ƒ
MiFicha Text ¡Mi ficha es grande!
Top 10
ƒ
MiFicha2

A continuación, reasigne MiFicha a una cadena de caracteres.


MiFicha = "Hola" && MiFicha es ahora una variable de caracteres
?MiFicha2.Text && Todavía devuelve "¡Mi ficha es grande!"
La Figura 10.6 ilustra el resultado del código anterior.
Figura 10.6 MiFicha2 hace referencia a una ficha y MiFicha es una variable de caracteres

Hola ƒ
MiFicha Text ¡Mi ficha es grande!
Top 10
ƒ
MiFicha2

Capítulo 10, Uso de los objetos 147


objects.dwp Page 148 Wednesday, August 30, 1995 2:10 PM

Estos ejemplos demuestran dos aspectos importantes de las variables de referencia a


objeto:
• Varias variables de referencia a objeto pueden hacer referencia al mismo objeto.
• Cuando se cambian las propiedades de un objeto, todas las referencias a ese objeto
reflejan el cambio.

Creación de objetos dentro de otros objetos


Para crear un objeto contenido en otro objeto, como un botón en una ficha, indique una
referencia al objeto padre mediante DEFINE o mediante el operador NEW, como sigue.
• Con DEFINE, utilice la opción OF <referencia a objeto padre>.
DEFINE FORM MiFicha && Crea el objeto padre
DEFINE PUSHBUTTON MiBoton OF MiFicha && Crea un objeto hijo
DEFINE crea sólo una referencia al objeto hijo: el que se crea como miembro del
objeto padre, como se muestra en la Figura 9.6. En muchos casos, ésta es la única
referencia necesaria.
• Con el operador NEW, pase una referencia al objeto padre como parámetro.
MiFicha = NEW FORM() && Crea el objeto padre
MiFicha.MiBoton = NEW PUSHBUTTON(MiFicha) && Pasa una referencia al padre como parámetro
A diferencia de DEFINE, el operador NEW puede crear dos referencias al objeto hijo:
una que es un miembro del padre (igual que con DEFINE) y otra referencia que se
especifica en una sentencia de asignación. En el código siguiente, Boton1 y Boton2 se
crean con una sola referencia, y Boton3 se define con dos referencias:
DEFINE FORM MiFicha && Crea el objeto padre
DEFINE PUSHBUTTON Boton1 OF MiFicha && MiFicha.Boton1 referencia al objeto hijo
MiFicha.Boton2 = NEW PUSHBUTTON(MiFicha) && MiFicha.Boton2 referencia al objeto hijo
Tercero= NEW PUSHBUTTON(MiFicha,"Boton3") && MiFicha.Boton3 y Tercero referencian al
&& mismo objeto hijo
Después, puede acceder al primer y segundo botón con MiFicha.Boton1.<nombre miembro>
o con MiFicha.Boton2.<nombre miembro> respectivamente, y al tercer botón con cualquiera
de estas referencias: MiFicha.Boton3.<nombre_miembro> o Tercero.<nombre_miembro>.

148 Guía del programador


objects.dwp Page 149 Wednesday, August 30, 1995 2:10 PM

La Figura 10.7 ilustra el resultado del código anterior.


Figura 10.7 Referencias a objetos hijos

ƒ
Boton1.Top
Boton1.Left
ƒ

ƒ
ƒ
Boton1 Boton1
Boton2.Top
Boton2 Boton2
Boton2.Left
MiFicha Boton3 Boton3
ƒ
ƒ

ƒ
Tercero.Top
Tercero.Left
Tercero ƒ

Disponer de una referencia adicional al objeto hijo es útil cuando está contenido en
varios niveles de objetos, como por ejemplo una opción de menú contenida en un menú
de cascada, contenido en un menú desplegable, contenido en una barra de menús.
En lugar de especificar toda la vía de las referencias a objeto, una referencia adicional
puede simplificar el acceso a la opción de menú. (El Capítulo 16 describe cómo crear
menús para las fichas.)

Referencia a objetos principales y derivados


En la mayoría de los casos, la referencia a las propiedades de un objeto se efectúa por
medio de los métodos asociados al objeto. Los métodos pueden hacer referencia a las
propiedades mediante la palabra clave this, como se ha descrito antes. No obstante, en
ocasiones, un método podría precisar hacer referencia a las propiedades de otro objeto.
Como ya se ha descrito en este capítulo, es posible hacer referencia a los miembros de
los objetos contenidos en otros objetos creando una vía de referencias mediante la
notación de punto. El siguiente es un ejemplo simple:
DEFINE FORM MiFicha && Crea el objeto padre
DEFINE PUSHBUTTON MiBoton OF MiFicha && Crea un botón en la ficha
MiFicha.MiBoton.Top = 2 && Se refiere a la propiedad Top de MiBoton en
&& MiFicha

Capítulo 10, Uso de los objetos 149


objects.dwp Page 150 Wednesday, August 30, 1995 2:10 PM

Para hacer referencia a los objetos padres, también puede utilizar la propiedad Parent
del objeto hijo. Todos los objetos estándar del interface de usuario, excepto las fichas,
tienen una propiedad Parent que contiene una referencia al objeto padre. En la mayoría
de los casos, la propiedad Parent hace referencia a una ficha. La excepción es el objeto
Menu, que puede contener otros objetos Menu. Este ejemplo crea un controlador de
sucesos OnClick para MiBoton que hace referencia a una propiedad de MiFicha
mediante la propiedad Parent:
MiFicha.MiBoton.OnClick = Hola
PROCEDURE Hola
this.Parent.Text = "¡Hola!"
RETURN
Para la mayoría de objetos de ficha, this.Parent equivale a form. Lo más común es
utilizar this.Parent en los controladores de sucesos OnClick para que los objetos de
menú hagan referencia al menú principal.

Creación de objetos personalizados


Hasta ahora, ha aprendido cómo crear y trabajar con objetos estándar del interface de
usuario para crear fichas. Además de los objetos del interface de usuario, puede crear
sus propios objetos con cualquier fin mediante la clase Object. Un objeto de la clase
Object no tiene propiedades ni métodos estándar. Es simplemente un contenedor vacío.
Para construir un objeto, cree el contenedor y añada los miembros que desee.
Como las matrices, utilice los objetos personalizados para almacenar temporalmente
grupos de variables relacionadas. (Consulte “Uso de las matrices como objetos” más
adelante en este capítulo.) Conforme aprenda más acerca de la programación orientada
a objetos, encontrará muchos usos para los objetos personalizados. Los siguientes son
algunos ejemplos:
• Cree el objeto oPerson para almacenar temporalmente los nombres y direcciones de
clientes o empleados.
oPerson = NEW Object() && crea un objeto vacío
* añade propiedades en el objeto nuevo para que sea un objeto de “Persona”
oPerson.Nombre = "Juan Ramón"
oPerson.Apellidos = "León"
oPerson.Direccion = "Julio Domingo, 28"
oPerson.Ciudad = "Madrid"
oPerson.Provincia = "MA"
• Cree un objeto Parámetros que contenga los parámetros que se pasarán a una función
o procedimiento. El tercer ejemplo de la sección siguiente demuestra cómo hacerlo.
La creación de objetos a partir de la clase Object es una forma rápida y sencilla de
experimentar con los objetos. Para disfrutar de las ventajas añadidas de la posibilidad de
reutilización, puede declarar una clase personalizada que sirva como plantilla para crear
copias múltiples del objeto. El Capítulo 11 describe cómo declarar clases.

150 Guía del programador


objects.dwp Page 151 Wednesday, August 30, 1995 2:10 PM

Transmisión de referencias a objeto como valores


devueltos y parámetros
Al igual que otras variables de memoria, es posible pasar referencias a objeto a los
procedimientos y funciones como parámetros, y devolverlas como valores. Los
siguientes son algunos ejemplos de casos en los que esta posibilidad le puede ser útil:
• Escriba una subrutina que recibe una referencia a objeto como parámetro y hace algo
con ella. Este ejemplo recibe una referencia a un objeto del interface de usuario y lo
centra en una ficha:
PROCEDURE CentraObjeto
PARAMETERS oObjeto && El objeto a centrar
LOCAL nAnchoFicha
nAnchoFicha = oObjeto.Parent.Width && Accede a la ficha a través de la propiedad
&& Parent
oObjeto.Left = (nAnchoFicha - oObjeto.Width) / 2
RETURN
• Declare una función que crea una copia de una clase y devuelve una referencia al
nuevo objeto. Este ejemplo devuelve una referencia a un botón:
Function BotNuevo(TextoBot,RefFicha,Ext_Sup,Ext_Izq)
LOCAL BotNuevo
BotNuevo = NEW PUSHBUTTON(RefFicha)
BotNuevo.Top = Ext_Sup
BotNuevo.Left = Ext_Izq
BotNuevo.Text = TextoBot
RETURN BotNuevo
MiFicha = NEW FORM() && Crea una ficha
MiFicha.B = BotNuevo("Hola",MiFicha,2,2) && Llama a BotNuevo para añadir un botón
MiFicha.B.Text && Devuelve Hola
• Consolide muchos parámetros en un solo objeto, para poder pasar un solo parámetro
(la referencia a objeto) en lugar de muchos.
Function BotNuevo(oRef) && Declara sólo un parámetro
LOCAL BotNuevo
BotNuevo = NEW PUSHBUTTON(oRef.RefFicha)
BotNuevo.Top = oRef.Ext_Sup
BotNuevo.Left = oRef.Ext_Izq
BotNuevo.Text = oRef.TextoBot
RETURN BotNuevo
MiFicha = NEW FORM()
oParams = NEW Object() && Crea un objeto vacío
oParams.TextoBot = "Hola" && y se llena con las propiedades
oParams.RefFicha = MiFicha && que se pasarán como parámetros
oParams.Ext_Sup = 2 && a la función
oParams.Ext_Izq = 2
MiFicha.B = BotNuevo(oParams) && Llamada a la función para añadir un botón
?MiFicha.B.Text && Devuelve Hola

Capítulo 10, Uso de los objetos 151


objects.dwp Page 152 Wednesday, August 30, 1995 2:10 PM

Liberación de objetos
Para las variables de memoria tradicionales, el ámbito, por ejemplo local o privado,
determina la vida de la variable. Por ejemplo, la vida de una variable local está limitada
a la subrutina en que se declara. (El Capítulo 5 describe los ámbitos de las variables.)
Un objeto existe hasta que se libera, con la siguiente excepción: un objeto se libera de
forma automática cuando se liberan todas las variables de referencia que hacen
referencia a él.
El ejemplo siguiente lo demuestra:
MiObj = NEW Object() && El objeto tiene una referencia: MiObj
MiObj2 = MiObj && El objeto tiene dos referencias: MiObj y MiObj2
RELEASE MiObj && La variable MiObj se libera, pero el objeto aún existe
RELEASE MiObj2 && MiObj2 se libera, y también el objeto
Como con las variables de memoria tradicionales, puede liberar un objeto de forma
explícita en cualquier momento de una de las dos formas siguientes:
• Ejecute el método Release del objeto. Todos los objetos del interface de usuario, como
los campos de entrada y los botones, tienen un método Release estándar.
• Utilice el comando RELEASE OBJECT, que libera el objeto al que hace referencia una
variable de referencia a objeto, pero no libera la variable.
Importante Observe la diferencia entre RELEASE OBJECT en el ejemplo siguiente y RELEASE en el
ejemplo anterior:
MiObj = NEW Object() && El objeto tiene una referencia: MiObj
MiObj.Nombre = "Juan" && Se añade una propiedad
MiObj2 = MiObj && El objeto tiene dos referencias: MiObj y MiObj2
RELEASE OBJECT MiObj && El objeto a que hace referencia MiObj se libera; MiObj
&& aún existe
? MiObj && Devuelve "Object" porque no hace referencia a nada
? EMPTY(MiObj) && Devuelve .T.
? MiObj.Nombre && Se produce un error: Intento de acceder a un objeto
&& liberado
RELEASE MiObj,MiObj2 && MiObj y MiObj2 se liberan
Cuando se crean objetos del interface de usuario, como fichas, botones y campos de
entrada, Visual dBASE crea automáticamente una referencia interna además de las
creadas por el programador. Así garantiza que al menos siempre exista una referencia
para cada objeto. En consecuencia, la única forma de liberar un objeto del interface de
usuario es mediante RELEASE OBJECT o el método Release del objeto.
MiFicha = NEW FORM() && La ficha tiene dos referencias: MiFicha y una interna
DEFINE TEXT MiTexto OF MiFicha && El objeto tiene dos referencias: MiTexto y una interna
DEFINE ENTRYFIELD MiEntrada OF MiFicha
RELEASE MiFicha && La ficha todavía tiene una referencia: la referencia interna
RELEASE MiTexto && El objeto no es liberado
Cuando se libera un objeto que contiene otro objeto, como una ficha que contiene un
botón de comando, también se liberan todos sus objetos hijos.

152 Guía del programador


objects.dwp Page 153 Wednesday, August 30, 1995 2:10 PM

Uso de las matrices como objetos


El Capítulo 9 presentaba los objetos como conjuntos de variables de memoria, similares
a las matrices. De hecho, las matrices son objetos en Visual dBASE. Cuando se crea una
matriz, se crea un objeto basado en la clase estándar Array. Las matrices pueden usarse
de la misma forma que en dBASE DOS. (El Capítulo 5 describe cómo utilizar las
matrices en la forma tradicional de dBASE DOS.) Sin embargo, como objetos, puede
trabajar con las matrices en Visual dBASE de estas formas nuevas:
• Recupere el tamaño con la propiedad Size. Las matrices tienen una propiedad Size que
representa el número de elementos de la matriz. En las matrices unidimensionales,
puede cambiar la propiedad Size para modificar su tamaño. En las
multidimensionales, la propiedad Size es de sólo lectura. Utilice el método Resize()
para cambiar el tamaño de las matrices multidimensionales.
• Active métodos estándar para añadir y borrar elementos, rellenarlos con valores,
cambiar el tamaño de la matriz y otras tareas. Todas las funciones de matrices, como
AADD() o AFIELDS(), tienen sus métodos correspondientes. Los métodos tienen el
mismo nombre que las funciones, sin la “A” inicial.
• Añada sus propios métodos personalizados. Como con cualquier objeto, puede añadir una
propiedad en una matriz que haga referencia a una subrutina personalizada.
• Declare clases de matriz personalizadas para crear matrices personalizadas con valores
por defecto que especifique o métodos personalizados que declare.
Para crear una matriz, puede utilizar el comando DECLARE o el operador NEW.
Con DECLARE, tiene que especificar las dimensiones de la matriz; con el operador
NEW, puede pasar las dimensiones como parámetros, o bien no especificar ningún
parámetro y añadir elementos con posterioridad mediante uno de los métodos de
matrices.
Importante A diferencia de otras clases estándar, DEFINE no sirve para crear un objeto de matriz.
La Figura 10.8 muestra formas equivalentes de crear e inicializar una matriz:
Figura 10.8 Formas equivalentes de crear e inicializar matrices

DECLARE Operador NEW

DECLARE MiMatriz[5,2] MiMatriz = NEW Array(5,2)


AFILL(MiMatriz,0) MiMatriz.Fill(0)
? MiMatriz[2,2] && Devuelve 0 ? MiMatriz[2,2] && Devuelve 0

Es posible utilizar los métodos de una matriz sea cual sea la forma en que se declaró.
DECLARE Matriz[6,10] && Declara una matriz de 6 filas y 10 columnas
Matriz.Dir() && Se rellena con un listado del directorio actual

Capítulo 10, Uso de los objetos 153


objects.dwp Page 154 Wednesday, August 30, 1995 2:10 PM

La Tabla 10.1 resume los métodos que hay disponibles para las matrices:
Tabla 10.1 Resumen de los métodos de la clase Array
Método Descripción
Add() Añade un elemento.
Delete() Borra un elemento, columna o fila.
Dir() Rellena la matriz con el listado del directorio actual.
DirExt() Rellena la matriz con el listado del directorio actual y los atributos extendidos
(Win95).
Element() Devuelve el número de elemento de un subíndice (fila y columna) especificado.
Fields() Rellena la matriz con la estructura de la tabla actual.
Fill() Inserta un valor especificado en uno o más elementos.
Grow() Añade un elemento, columna o fila.
Insert() Inserta un valor de falso (.F.) en un elemento especificado.
Resize() Aumenta o disminuye el tamaño de la matriz.
Scan() Busca en la matriz una expresión especificada.
Sort() Ordena los elementos de una matriz unidimensional o bidimensional.
Subscript() Devuelve el subíndice (fila y columna) del elemento cuyo número se especifica.

Grow(), Size
Este ejemplo crea una matriz con el mismo número de elementos que registros hay en
CLIENTE.DBF. Después, rellena la matriz con valores de la tabla, utiliza Grow() para
añadir una segunda columna, que rellena con valores de la tabla, y muestra el resultado.
USE Cliente.DBF
ObjMat=NEW ARRAY(RECCOUNT()) && Inicializa un objeto de matriz
GO TOP
SCAN && Rellena la matriz con el campo Nombre
ObjMat[RECNO()]=Cliente->Nombre && de Cliente.DBF
ENDSCAN

ObjMat.GROW(2) && Se usa Grow() para añadir una segunda


GO TOP && columna a la matriz, que contendrá el
SCAN && campo Ventas_ac.
ObjMat[RECNO(),2]=Cliente->Ventas_ac
ENDSCAN
FOR i = 1 TO ObjMat.Size && Size es una propiedad de ObjMat
? ObjMat[i,1], ObjMat[i,2] && Muestra el contenido de la matriz bidimensional
NEXT

154 Guía del programador


objects.dwp Page 155 Wednesday, August 30, 1995 2:10 PM

Add()
Este ejemplo simplifica el anterior utilizando el método Add() para rellenar la matriz.
USE Cliente.DBF
ObjMat=NEW ARRAY()
SCAN
ObjMat.Add(Customer->Name)
ENDSCAN
FOR i = 1 TO ObjMat.Size
? ObjMat[i]
NEXT
Scan(), Subscript()
Este ejemplo muestra otra forma de rellenar una matriz a partir de una tabla. Utiliza
Scan() para buscar una cadena en la matriz y Subscript() para mostrar su posición.
USE Animales.DBF
ObjMat=NEW ARRAY(RECCOUNT(),3) && Inicializa un objeto de matriz
COPY TO ARRAY ObjMat FIELDS Nombre,Area, Peso && Llena la matriz con valores de la tabla
Cadena = "Loro"
aElemento = ObjMat.Scan(Cadena) && Busca la cadena en la matriz
aFila = ObjMat.SUBSCRIPT(aElemento,1)
aCol = ObjMat.SUBSCRIPT(aElemento,2)
? Cadena +"Encontrado en la fila " + ;
LTRIM(STR(aFila)) + ", columna " + ;
LTRIM(STR(aCol))

Creación de matrices dispersas


Es posible añadir elementos en las matrices después de declararlas. Sin embargo, cada
elemento que añada tiene que ser el siguiente número de elemento. Por ejemplo, si una
matriz tiene 99 elementos, el método Add() añade el elemento 100. Una matriz dispersa es
una matriz con un número variable de elementos no contiguos; por ejemplo, puede
contener tres elementos, numerados como 1, 50 y 300.
Con las matrices dispersas, es posible utilizar la memoria con mayor eficacia mediante
la creación de matrices sólo con el número de elementos necesarios. Además, la posición
de índice de un elemento puede ser una parte significativa de los datos, no sólo un
número de posición.
Para crear matrices dispersas, cree objetos de la clase Object. Para añadir elementos,
especifique cualquier valor numérico con el operador de índice. El código siguiente crea
una matriz dispersa y añade tres elementos con posiciones de índice no contiguas.
oMatrDisp = NEW Object()
oMatrDisp[1] = "primer elemento"
oMatrDisp[50] = "segundo elemento"
oMatrDisp[300] = "tercer elemento"

Capítulo 10, Uso de los objetos 155


objects.dwp Page 156 Wednesday, August 30, 1995 2:10 PM

Suponga que desea crear una matriz que almacene los ganadores del premio Oscar a la
mejor película en la década de los 80. Con una matriz dispersa, puede utilizar el año
como posición de índice sin crear elementos no utilizados. El ejemplo siguiente crea una
matriz dispersa denominada MejorPeli, almacena las películas ganadoras del Oscar de
cada año y vincula la matriz con un cuadro de incremento de una ficha.
MejorPeli = NEW OBJECT()
MejorPeli[1980] = "Gente corriente"
MejorPeli[1981] = "Carros de fuego"
MejorPeli[1982] = "Gandhi"
MejorPeli[1983] = "La fuerza del cariño"
MejorPeli[1984] = "Amadeus"
MejorPeli[1985] = "Memorias de Africa"
MejorPeli[1986] = "Platoon"
MejorPeli[1987] = "El último emperador"
MejorPeli[1988] = "Rain Man"
MejorPeli[1989] = "Paseando a Miss Daisy"
FichaMejor = NEW FichaPeli()
FichaMejor.ReadModal()
CLASS FichaPeli OF FORM
this.MDI = .F.
this.Text = "Oscars de los Ochenta"
DEFINE TEXT Solicitud OF this ;
PROPERTY ;
top 3, ;
left 5, ;
width 20, ;
text "Elige un año"
DEFINE SPINBOX ans OF this ;
PROPERTY ;
top 3, ;
left 15, ;
rangemin 1980, ;
rangemax 1989, ;
rangerequired .T., ;
value 1980, ;
onChange {;form.Peli2.Text = MejorPeli[this.Value]}
DEFINE TEXT peli1 OF this ;
PROPERTY ;
top 7, ;
left 5, ;
width 20, ;
text "Mejor película..."
DEFINE TEXT peli2 OF this ;
PROPERTY ;
top 10, ;
left 5, ;
width 40, ;
height 2, ;
fontSize 15, ;
text MejorPeli[form.Ans.Value]
ENDCLASS

156 Guía del programador


objects.dwp Page 157 Wednesday, August 30, 1995 2:10 PM

La Figura 10.9 muestra el resultado del código anterior.


Figura 10.9 MEJORPEL.PRG: una matriz dispersa en acciónn

Creación de matrices asociativas


Las matrices asociativas se diferencian de las estándar en que utilizan una etiqueta, no
un número, como índice. Las matrices asociativas pueden utilizarse para facilitar la
categorización de elementos. Por ejemplo, suponga que tiene cuatro artículos de
diferentes colores y desea almacenar el número de serie asociado a cada color en una
matriz. Podría hacer lo siguiente:
NumSerie = NEW AssocArray()
NumSerie[“Rojo”] = “W4591R”
NumSerie[“Verde”] = “W4591V”
NumSerie[“Amarillo”] = “W4591A”
NumSerie[“Azul”] = “W4591Z”
Para mostrar el número de serie del artículo azul, ejecute el comando:
? NumSerie[“Azul”}
Puesto que las matrices asociativas no tienen índices numéricos, se comportan de forma
distinta a las estándar y, por tanto, tienen un conjunto diferente de métodos.
Tabla 10.2 Resumen de los métodos de la clase de matriz asociativa
Método Descripción
Count( ) Devuelve el número de elementos de la matriz asociativa.
IsIndex( ) Devuelve un valor lógico que indica si el argumento es un índice de la matriz
asociativa.
NextIndex( ) Devuelve el subíndice del elemento que sigue al elemento cuyo subíndice es el
argumento.
RemoveAll( ) Borra todos los elementos de la matriz asociativa.
RemoveKey( ) Borra el elemento cuyo subíndice es el argumento.

Count( )
Utilice Count( ) para determinar el número de elementos que contiene la matriz
asociativa. Para el objeto NumSerie del ejemplo anterior, NumSerie.Count( ) devuelve 4.

Capítulo 10, Uso de los objetos 157


objects.dwp Page 158 Wednesday, August 30, 1995 2:10 PM

IsIndex( )
Utilice IsIndex( ) para determinar si la etiqueta dada es un subíndice válido de la matriz.
Tenga en cuenta que las etiquetas utilizadas para subíndices diferencian mayúsculas y
minúsculas. Los ejemplos siguientes muestran los valores devueltos para el objeto
NumSerie.
? NumSerie.IsIndex(“Verde”) && Devuelve .T., es un subíndice la matriz
? NumSerie.IsIndex(“verde”) && Devuelve .F., uso distinto de mayús/minús
? NumSerie.IsIndex(“Blanco”) && Devuelve .F., no está en la matriz
NextIndex( ), FirstIndex
Utilice NextIndex( ) para determinar la secuencia de elementos en una matriz asociativa.
Puesto que las matrices asociativas utilizan etiquetas para los subíndices, en lugar de
números, no hay ninguna ordenación inherente de los elementos de la matriz. No es de
esperar que los elementos estén en el mismo “orden” en que se crearon. Llame el
método NextIndex( ) con el mismo nombre de un subíndice de elemento existente para
determinar qué elemento es el siguiente en el orden interno de los elementos en dBASE.
Si pasa el subíndice del último elemento de la matriz, NextIndex( ) devuelve .F.,
indicando que no hay ningún elemento siguiente. El ejemplo siguiente muestra un
procedimiento de repetición por los elementos de la matriz asociativa NumSerie en
orden. Para encontrar el primer elemento, se comprueba el valor de la propiedad
FirstIndex.
PROCEDURE ImprNumSerie
LOCAL cNumSerie, nRecuento, i

nRecuento = NumSerie.Count() && obtiene el número de elementos


cNumSerie = NumSerie.FirstIndex && obtiene el subíndice del primer elemento
* Observe el uso de una variable como subíndice en la línea siguiente
? NumSerie[cNumSerie] && imprime el primer número de serie

FOR i = 2 TO nRecuento && para cada uno de los elementos restantes


* Obtiene el subíndice del siguiente elemento de la matriz
cNumSerie = NumSerie.NextIndex(cNumSerie)
? NumSerie[cNumSerie] && imprime el siguiente número de serie
ENDFOR
RETURN
RemoveKey( ), RemoveAll( )
Utilice RemoveKey( ) y RemoveAll( ) para borrar uno o todos los elementos de una
matriz asociativa, respectivamente. Para borrar un solo elemento, llame RemoveKey( )
con el subíndice del elemento que desea borrar como argumento. Por ejemplo:
NumSerie.RemoveKey(“Azul”)&& Borra el elemento cuyo subíndice es Azul
RemoveKey( ) devuelve .T. si se borra el elemento, .F. si no se pudo borrar o no existía.
RemoveAll( ) borra todos los elementos de la matriz asociativa y deja la matriz con cero
elementos. Por ejemplo:
? NumSerie.Count() && devuelve 4
? NumSerie.RemoveAll() && borra todos los elementos
? NumSerie.Count() && devuelve 0

158 Guía del programador


classes.dwp Page 159 Wednesday, August 30, 1995 2:12 PM

Capítulo

11
Uso de las clases
Capítulo 11

En el Capítulo 9, ha aprendido acerca de la creación de objetos a partir de las clases


estándar. Una de las características más versátiles del modelo de objetos de
Visual dBASE es la capacidad de declarar clases personalizadas para crear objetos y de
declarar clases que se basen en otras clases. Este capítulo explica los conceptos básicos
del trabajo con las clases, presenta las jerarquías de clases y describe las técnicas de
creación y uso de clases en función de otras clases.

Declaración de clases
Para declarar una clase, utilice el comando CLASS...ENDCLASS, del que puede ver una
descripción completa en la Referencia del lenguaje. Éste es un resumen de la sintaxis:
CLASS <nombre clase> [(<parámetros>)][OF <nombre superclase>[(<parámetros>)]][CUSTOM]
[FROM <nombrearchivo>]
[PROTECT <listaPropiedades>]
<código constructor>
<declaraciones de métodos>
ENDCLASS
El cuerpo de una declaración de clases está formado por:
• Sentencia PROTECT, que declara qué propiedades y métodos de la clase están
disponibles sólo para la clase y sus descendientes.
• Código constructor, que se ejecuta cuando se crea un objeto de esa clase. El código
constructor es lo que realmente crea los miembros del objeto. Puede contener
cualquier comando de dBASE, aunque suele consistir sólo en sentencias de
asignación de propiedades y métodos. El propósito del código constructor es crear
las propiedades del objeto y definir sus valores iniciales.
• Las declaraciones de métodos, por ejemplo procedimientos o funciones. Un método
contiene código que se ejecuta en algún momento posterior a la creación del objeto.
El propósito de estas subrutinas es realizar acciones sobre las propiedades.

Capítulo 11, Uso de las clases 159


classes.dwp Page 160 Wednesday, August 30, 1995 2:12 PM

El código siguiente demuestra una declaración de clase simple:


CLASS Elemento && Nombre de la clase.
This.Elemento = "Bolso" && El código constructor inicializa las propiedades
This.taman = 12 && El código constructor continúa hasta la primera
This.Coste = 16.95 && declaración de un método, o hasta la sentencia
This.Cant = 4 && ENDCLASS en el caso de no existir métodos
Function CalcCoste && Declara un método.
LOCAL X
X=This.Coste*This.Cant
Return X
ENDCLASS && Final de la declaración de clase
Para crear un objeto de esta clase denominado MisElementos, utilice:
DEFINE Elemento MisElementos
O bien
MisElementos = NEW Elemento()
El ejemplo siguiente declara una clase con dos propiedades, crea un objeto de esa clase y
consulta el valor de las propiedades:
CLASS Numeros
this.Diez = 10
this.Veinte = 20
ENDCLASS
X = NEW Numeros() && Crea un objeto Numeros
? X.Diez && Devuelve 10
? X.Veinte && Devuelve 20

Referencias a miembros en una declaración de clase


Para hacer referencia a los miembros en una declaración de clase, preceda el nombre de
la propiedad con this o con form.
• This hace referencia al objeto creado por la clase. Toda sentencia de asignación que
comience con this declara una propiedad para los objetos creados por la clase.
Observe que cuando se utiliza this dentro del método de un control, this hace
referencia al control, no a la ficha en que está situado.
• Form hace referencia a la ficha que contiene el objeto creado por la clase. Utilice form
en las declaraciones de clase para que los controles, como botones o campos de
entrada, hagan referencia a la ficha que contiene el objeto creado por la clase.
La declaración de clase siguiente utiliza this para inicializar las propiedades Num y
NumCuadrado y hace referencia a Num en la sentencia de asignación de
NumCuadrado.
CLASS Cuadrado
this.Num = 0
this.NumCuadrado = this.Num * this.Num
ENDCLASS

160 Guía del programador


classes.dwp Page 161 Wednesday, August 30, 1995 2:12 PM

Es posible declarar variables de memoria en el código constructor (es decir, variables


que no se declaran ni se referencian como propiedades) para que almacenen
temporalmente valores mientras se crea el objeto. Las variables de memoria presentes
en el código constructor siguen las habituales reglas de ámbito aplicables a las clases.
Las variables de memoria pueden declararse locales, privadas y públicas; sin embargo,
el código no puede contener declaraciones de variables estáticas.
El código siguiente demuestra el uso de las variables de memoria en el código
constructor. La clase CentraBoton define un botón que se centra en la ficha de forma
automática. La variable nAnchoFicha permite calcular el centro.
CLASS CentraBoton OF PUSHBUTTON(f)
LOCAL nAnchoFicha
nAnchoFicha = this.Parent.Width && Accede a ficha a través de la propiedad Parent
this.Left = (nAnchoFicha- this.Width) / 2
RETURN

Asociación de métodos a una clase


Un método comienza como subrutina, por ejemplo un procedimiento, función o bloque
de código. Se convierte en método cuando lo asocie a una clase. Para asociar la subrutina
a una clase, asígnela a una variable de puntero de función o bloque de código que sea
miembro de la clase.
Éstas son las tres formas de asociar métodos a las clases:
• Declare una función o procedimiento dentro de la definición de clase. Por cada
subrutina que declare en la clase, Visual dBASE crea automáticamente una variable
de puntero de función con el mismo nombre dentro de la clase. Esta técnica
encapsula la subrutina en la clase, de forma que otra clase pueda tener un método
con el mismo nombre. El Diseñador de fichas declara los métodos de esta forma
cuando se utiliza el editor de procedimientos para anexar una subrutina a una
propiedad de suceso.
CLASS MiObj
this.texto = "Este es mi objeto" && Asigna algunas propiedades
this.Ext_sup = 10
this.Ext_izq = 5
PROCEDURE Suena && Estas declaraciones de procedimientos crean métodos
? chr(7) && Al crear un objeto a partir de esta clase
Return .T. && los miembros Suena y Adios son punteros de función
PROCEDURE Adios && Referencia al procedimiento
? "¡Adios a todos!"
RELEASE OBJECT this
RETURN .T.
ENDCLASS

Capítulo 11, Uso de las clases 161


classes.dwp Page 162 Wednesday, August 30, 1995 2:12 PM

• Declare una función o procedimiento fuera de la definición de clase, y asigne una


propiedad dentro de la clase al nombre de la subrutina. Así, puede situar la subrutina
en un archivo de procedimiento donde puede servir como método para otras clases.
CLASS MiObj
this.texto = "Este es mi objeto" && Asigna algunas propiedades
this.Ext_Sup = 10
this.Ext_Izq = 5

this.Suena = Suena && Crea un método a partir de un nombre de


this.Adios = Adios && procedimiento
ENDCLASS
PROCEDURE Suena && Produce un sonido
? chr(7)
Return .T.
PROCEDURE Adios && Muestra la cadena "¡Adios a todos!"
? "¡Adios a todos!"
RELEASE OBJECT this
RETURN .T.
• Asigne un bloque de código a una variable dentro de una definición de clase.
Es la forma más simple de declarar un método, en especial cuando el código del
método contiene sólo unos pocos comandos.
CLASS MiObj
this.texto = "Este es mi objeto" && Asigna algunas propiedades
this.Ext_Sup = 10
this.Ext_Izq = 5
this.Suena = {;?chr(7)} && Asigna los dos métodos mediante bloques de código
this.Adios = {;? "¡Adios a todos!";RELEASE OBJECT this}
ENDCLASS
En el ejemplo de Cuadrado en la página 160, el valor de la propiedad NumCuadrado se
computa cuando se crea el objeto. Si declara NumCuadrado como método en lugar de
como propiedad, puede calcular NumCuadrado en cualquier momento. El ejemplo
siguiente demuestra esto:
X = NEW Cuadrado2() && Crea el objeto Cuadrado2
X.Num = 5 && Cambia el valor de Num
? X.NumCuadrado() && Devuelve 25
X.Num = 6 && Cambia de nuevo el valor de Num
? X.NumCuadrado() && Devuelve 36
CLASS Cuadrado2
this.Num = 0
FUNCTION NumCuadrado
RETURN this.Num * this.Num
ENDCLASS

162 Guía del programador


classes.dwp Page 163 Wednesday, August 30, 1995 2:12 PM

Una subrutina puede servir como método para más de un objeto. El ejemplo siguiente
demuestra esto:
O = NEW Object()
O.x = 10
O.SumaUno = FuncSumaUno
? O.SumaUno() && Devuelve '11'
? O.x && Devuelve '11'
Y = NEW Object()
Y.x =30
Y.z = FuncSumaUno
? Y.z() && Devuelve '31'
FUNCTION FuncSumaUno
this.x = this.x + 1
RETURN this.x

Declaración de los parámetros de una clase


Al crear un objeto, es posible declarar parámetros en una declaración de clase para
pasarlos al código constructor. Declare los parámetros de clase de la misma forma en
que declara los parámetros de los procimientos. Éstas son las dos formas:
• Sitúe los parámetros entre paréntesis al final del nombre de la clase. Éste es el método
más recomendable porque los parámetros pasados de esta forma son locales en
ámbito a la clase.
• Use una sentencia PARAMETERS como primera línea del código constructor.
Esta forma de declaración puede ser más familiar para los programadores de dBASE;
sin embargo, los parámetros son privados en ámbito a la clase. Consulte el Capítulo 5
para comprender por qué el ámbito local es más adecuado para los parámetros.
Para una clase dada, puede declarar parámetros usando o bien paréntesis o bien una
sentencia PARAMETERS. No es posible mezclar los dos estilos.
Las dos declaraciones de clase siguientes son idénticas excepto que una declara un
parámetro con paréntesis y la otra mediante PARAMETERS.
Figura 11.1 Declaración de parámetros con ámbito local y privado

Declare un parámetro local Declare un parámetro privado

CLASS Cuadrado(n) CLASS Cuadrado


this.Num = n PARAMETERS n
this.NumCuadrado = n*n this.Num = n
ENDCLASS this.NumCuadrado = n*n
ENDCLASS

Capítulo 11, Uso de las clases 163


classes.dwp Page 164 Wednesday, August 30, 1995 2:12 PM

El código siguiente crea objetos de cualquiera de estas dos clases:


X = NEW Cuadrado(10) && Crea un objeto Cuadrado
? X.NumCuadrado && Devuelve 100
Y = NEW Cuadrado(5) && Crea un nuevo objeto Cuadrado
? Y.NumCuadrado && Devuelve 25

Protección de propiedades y métodos


Las propiedades y métodos de una clase pueden protegerse, u ocultarse, para que sólo
estén disponibles desde la clase y sus subclases. Alguien que utilice un objeto creado a
partir de la clase podría tener acceso directamente sólo a las propiedades y métodos que
no están designados como protegidos.
Protegiendo las propiedades y métodos, puede delinear claramente el interface de una
clase a partir de la implementación de su funcionalidad. Los usuarios de la clase
interactúan con ésta únicamente a través de los métodos y propiedades no protegidos
(el interface). La funcionalidad interna (la implementación) de la clase es invisible para
ellos y no debería estar disponible para poder usar la clase.
Esta separación de interface e implementación ofrece varias ventajas tanto al usuario
como al diseñador de una clase. El primero sabe exactamente cómo debe trabajar con la
clase y tiene menos propiedades y métodos de que preocuparse. Y debe utilizar los
medios que ha decidido el diseñador de la clase para trabajar con la clase. Observe el
ejemplo siguiente:
CLASS Empleado
PROTECT Salario, NSS, EsCorrecto, ObAutorizacion
This.SSN = “ - - “
This.Nombre = SPACE(30)
This.Departamento = SPACE(25)
This.Salario = 0

PROCEDURE DefNSS(cNSS)
IF This.NSS <> “ - - “
IF EsCorrecto()
This.NSSN= cNSS
ENDIF
ENDIF
RETURN

FUNCTION ObtnNSS
RETURN This.SSN

PROCEDURE DefSalario(nNuevoSalario)
IF ObAutorizacion()
This.Salario = nNuevoSalario
ELSE
? “Transacción no autorizada”
ENDIF
RETURN

164 Guía del programador


classes.dwp Page 165 Wednesday, August 30, 1995 2:12 PM

FUNCTION ObtSalario
RETURN This.Salario

FUNCTION EsCorrecto
RETURN EjecCompFicha()

FUNCTION ObtAutorizacion
RETURN EjecFichaObtContrasena()

ENDCLASS
En la clase Empleado, las propiedades Salario y NSS y los métodos EsCorrecto( ) y
ObtAutorizacion( ) están protegidos. El usuario de un objeto de la clase Empleado
puede tener acceso y cambiar directamente las propiedades Nombre y Departamento.
Para cambiar la propiedad Salario, el usuario debe emplear el método DefSalario( ).
Si intenta ver, o cambiar, el valor de Salario directamente, aparece el error “Propiedad
no accesible”. Si abre el Inspector para el objeto Empleado, la propiedad Salario no
aparecerá en la lista de propiedades.
Cuando ejecuta el método DefSalario( ), éste llama el método DefAutorizacion( ), que el
usuario no puede llamar directamente porque está protegido. DefAutorizacion( ) no
aparecerá en la lista de métodos en el Inspector.
Para que el usuario vea el valor actual de Salaioy, debe llamar a DefSalario( ).
ObtSalario( ) y DefSalario( ) son parte del interface de la clase; constituyen el medio por
el que un usuario interactúa con la clase. Salario y ObtAutorizacion( ) son parte de la
implementación; son valores y código internos a los que el usuario de la clase no
necesita tener acceso directamente para utilizarla.
La misma situación se produce para la propiedad NSS, que sólo puede leerse y
modificarse a través de los métodos no protegidos ObtNSS( ) y DefNSS( ). DefNSS( )
llama al método protegido EsCorrecto( ), al que no puede llamar el usuario.
Respecto al diseñador de la clase, el uso de propiedades y métodos protegidos le
permite garantizar la integridad de la clase protegiéndola de modificaciones no
deseadas en las propieades. El diseñador tiene libertad para cambiar cambiar cualquier
parte de la implementación sin miedo a romper el código escrito por los usuarios de la
clase, siempre que el interface permanezca igual. Por ejemplo, en la clase Empleado
anterior, DefSalario( ) llama actualmente el método protegido ObtAutorizacion( ).
DefSalario( ) podría cambiarse para llamar otro método o no llamar ninguno. Dado que
DefAutorizacion( ) está protegido, los usuarios no tendrán llamadas directas a él en su
código. Por tanto, no se verán afectados si DefAutorizacion( ) cambia, o desaparece por
completo, siempre que DefSalario( ) continúe dando el mismo resultado.

Capítulo 11, Uso de las clases 165


classes.dwp Page 166 Wednesday, August 30, 1995 2:12 PM

Acerca de las jerarquías de clases


Una clase que esté basada en otra clase se denomina subclase. Las subclases heredan los
miembros de la clase en que se basan. Después, es posible añadir o modificar los
miembros de la subclase. Así, puede utilizar clases existentes, como la clase estándar
Form o una que declare, para crear nuevas clases que sean similares. Cada vez que cree
una ficha con el Diseñador de fichas, dBASE la crea en función de la clase estándar
Form.
La principal ventaja de derivar subclases se manifiesta al construir una jerarquía de
clases, donde cada subclase herede gran parte de su funcionalidad de una superclase.
Una subclase modifica la funcionalidad anulando los miembros heredados o añadiendo
otros nuevos. El resultado es un mantenimiento del código más sencillo y una mayor
facilidad de reutilización.
La Figura 11.2 ilustra una jerarquía de clases de ejemplo.
Figura 11.2 Ejemplo de una jerarquía de clases

Clase: Base
Propiedad A
Método A

Clase: Derivada1 de Base Clase: Derivada2 de Base


Propiedad A Propiedad A
Método A Método A
Propiedad B Método B

Clase: Derivada3 de Derivada1


Propiedad A
Método A
Propiedad B
Método C

= declarada en esta clase = heredada

En esta figura:
• Base es una superclase de Derivada1 y Derivada2.
• Derivada1 y Derivada2 son subclases de Base.
• Derivada1 es una superclase de Derivada3.

166 Guía del programador


classes.dwp Page 167 Wednesday, August 30, 1995 2:12 PM

No confunda las clases heredadas con los objetos contenidos. (El Capítulo 10 describe los
contenedores de objetos.) Cuando un objeto contiene a otro, existe una copia de cada
objeto hijo asociado a su objeto padre. El objeto hijo es una parte del padre. Por otra parte,
cuando se deriva una subclase de una superclase, la primera sólo comparte las
características de la segunda. La subclase es un tipo de su superclase.

Declaración de una clase basada en otra clase


Para declarar una clase en función de otra, utilice la opción OF del comando
CLASS...ENDCLASS, que especifica la superclase de la cual hereda los miembros.
Si la declaración de la superclase está en otro archivo de código fuente, especifique su
nombre de archivo en la cláusula FROM. La siguiente es la sintaxis básica:
CLASS <nombre subclase> [(<parámetros>)] OF <nombre superclase> [(<parámetros>)]
[FROM <nombrearchivo>]
<código constructor>
<declaraciones de métodos>
ENDCLASS
Cuando se crea un nuevo objeto en una subclase, se ejecuta en primer lugar el código
constructor de la superclase, seguido por el código constructor de la subclase.
A continuación se presenta un ejemplo simple de cómo funcionan las subclases.
En primer lugar, ésta la declaración de una superclase y de una subclase:
CLASS Base
? "Clase Base" && Muestra un mensaje durante la construcción
this.X = 10 && Inicializa X
this.Y = 20 && Inicializa Y
ENDCLASS
CLASS Sub OF Base && La clase Sub contiene los miembros de Base: X e Y
?? ", Clase Sub" && Muestra un mensaje durante la construcción
this.Z = 100 && Declara una nueva propiedad sólo para esta clase
ENDCLASS
A continuación, cree copias de estas clases y consulte las propiedades.
b = NEW Base() && Devuelve 'Clase Base'
? b.X && Devuelve 10
? b.Y && Devuelve 20
s= NEW Sub() && Devuelve 'Clase Base, Clase Sub'
? s.X && Devuelve 10
? s.Y && Devuelve 20
? s.Z && Devuelve 100
Importante Cuando se crea un control a partir de una clase estándar, como Entryfield o Pushbutton,
es necesario especificar la ficha que contendrá el control. Por ejemplo, en el comando
siguiente, es necesaria la opción OF MiFicha.
DEFINE ENTRYFIELD MiEntrada OF MiFicha

Capítulo 11, Uso de las clases 167


classes.dwp Page 168 Wednesday, August 30, 1995 2:12 PM

De forma parecida, cuando se declara una subclase basada en una clase estándar, es
necesario declarar un parámetro para que la clase reciba la referencia a la ficha.
No importa qué nombre se da al parámetro.
El ejemplo siguiente declara una subclase en función de la clase estándar Pushbutton.
La clase MiBoton sirve como clase por defecto de los botones (de comando) y contiene
los parámetros iniciales de todos los botones derivados de esta clase. A continuación,
BotonSonido (un botón que produce un sonido) se deriva de MiBoton.
CLASS MiBoton(ficha) OF PUSHBUTTON(ficha) && Declara los parámetros de la ficha
this.text = "Mi botón" && Asigna las propiedades que tendrán
this.FontName = "Arial" && todos los botones
this.FontBold = .T.
this.FontWidth = 6
this.SetWidth = {;this.width = LEN(this.text)+10}
ENDCLASS
CLASS BotonSonido(ficha) OF MiBoton(ficha) && Declara los parámetros de la ficha
this.text = "Suena" && Sobrescribe la propiedad Text
this.Onclick = {;? CHR(7)} && Asigna un bloque de código a OnClik
this.SetWidth() && Llama al método heredado
ENDCLASS

Anulación de los miembros heredados


Cuando se deriva una subclase de otra clase, es posible sobrescribrir los miembros
heredados de la superclase mediante la reinicialización de las propiedades o la nueva
declaración de los métodos.
A continuación se muestra un ejemplo simple de anulación de un método heredado.
La clase Base contiene dos métodos y la subclase anula uno de ellos.
CLASS Base
FUNCTION Uno
RETURN "Base 1"
FUNCTION Dos
RETURN "Base 2"
ENDCLASS
CLASS Sub OF Base
FUNCTION Dos && Sobrescribe método Dos
RETURN "Sub 2"
ENDCLASS
Ahora, cree copias de estas clases y llame los métodos.
A = NEW Base()
B = NEW Sub()
? A.Uno() && Devuelve 'Base 1'
? A.Dos() && Devuelve 'Base 2'
? B.Uno() && Devuelve 'Base 1'
? B.Dos() && Devuelve 'Sub 2'

168 Guía del programador


classes.dwp Page 169 Wednesday, August 30, 1995 2:12 PM

Este es un ejemplo más realista:


CLASS BotonSonido(ficha) OF MiBoton(ficha)
this.text = "Suena"
this.Onclick = CLASS::ProcSonido
this.SetWidth()
PROCEDURE ProcSonido
? CHR(7)
RETURN
ENDCLASS
CLASS SuenaMueve(ficha) OF BotonSonido(ficha)
this.text = "Suena y se mueve" && Sobrescribe la propiedad Text
this.setWidth()
PROCEDURE OnClick
BotonSonido::ProcSonido()
this.Left = this.Left+1 && Incrementa la funcionalidad del método OnClik
RETURN
ENDCLASS

Referencia a métodos desde una clase


Como muestran los ejemplos de la sección anterior, puede utilizar la notación de punto
para hacer referencia a los métodos de un objeto. Como alternativa, es posible hacer
referencia a los métodos directamente en la clase. Esta técnica es muy útil para llamar a
métodos declarados en diferentes niveles de una jerarquía de clases. Por ejemplo, es
posible modificar un método heredado en una subclase llamándolo directamente desde
la superclase y añadiéndolo a su comportamiento en la subclase.
Para hacer referencia a un método desde una clase, use el operador de resolución de ámbito
(::). Igual que el operador de punto asocia un miembro a su objeto, el de resolución de
ámbito asocia un método a la clase en que está declarado. Ésta es la sintaxis:
<nombre de clase> | class | super :: <nombre del método>
Es posible especificar un nombre de clase o utilizar una de las palabras clave, class o
super. Class y super son a las clases lo que this y form son a los objetos: una forma de hacer
referencia a algo (en este caso, una clase) genéricamente sin concretar un nombre real.
• class hace referencia a la clase actual.
• super hace referencia a la superclase de la clase actual.

Capítulo 11, Uso de las clases 169


classes.dwp Page 170 Wednesday, August 30, 1995 2:12 PM

Al igual que this y form, class y super sólo son efectivos dentro de un contexto que
indique cuál es la clase actual. Es posible utilizar class o super:
• Dentro de una declaración de clase, donde la clase actual es la que se está declarando.
La clase siguiente se generó en el Diseñador de fichas. El suceso OnClick del botón de
comando tiene asigando el procedimiento BOTON1_ONCLICK de la clase actual.
CLASS Sonido OF FORM
this.EscExit = .T.
this.Text = "Ficha dBASE"
this.Width = 65.25
this.Top = 8.08
this.Left = 25.00
this.Height = 34.25
DEFINE PUSHBUTTON BOTON1 OF this;
PROPERTY;
OnClick class::BOTON1_ONCLICK,; && Asigna BOTON1_ONCLICK de la clase actual
text "BOTON1",;
Width 21.00,;
Top 12.00,;
Left 12.00,;
Height 4.00
PROCEDURE BOTON1_OnClick
? chr(7)
ENDCLASS
• Dentro de las declaraciones de un método, donde la clase actual es aquélla a partir de
la cual se creó el objeto que contiene el método.
El ejemplo siguiente modifica el ejemplo anterior de BotonSonido. El procedimiento
OnClick de la clase SuenaMueve hace referencia a ProcSonido de la clase
BotonSonido mediante super:
CLASS BotonSonido(ficha) OF MiBoton(ficha)
this.text = "Suena"
this.Onclick = CLASS::ProcSonido
this.SetWidth()
PROCEDURE ProcSonido
? CHR(7)
RETURN
ENDCLASS

CLASS SuenaMueve(ficha) OF BotonSonido(ficha)


this.text = "Suena y se mueve" && Sobrescribe la propiedad Text
this.SetWidth()
PROCEDURE OnClick
super::ProcSonido()
this.Left = this.Left+1 && Incrementa la funcionalidad del método OnClick
RETURN
ENDCLASS

170 Guía del programador


classes.dwp Page 171 Wednesday, August 30, 1995 2:12 PM

Por medio del operador de resolución de ámbito, es posible llamar un método desde
una clase, aunque no se haya creado ninguna copia de esa clase. En primer lugar, como
cualquier procedimiento o función, la declaración de una clase debe residir en el archivo
de programa actual o cargarse como archivo de procedimiento con SET PROCEDURE.
A continuación, llame el método especificando el nombre de clase con el operador de
resolución de ámbito antes del nombre del método. En realidad, un método incluido
dentro de una clase toma el nombre de clase como parte de su propio nombre.
Éste es un ejemplo simple:
? Miclase::DiHola() && Devuelve "¡Hola!"
CLASS Miclase
FUNCTION DiHola
? "¡Hola!"
RETURN .T.
ENDCLASS
El ejemplo siguiente demuestra una razón más práctica para llamar un método desde
una clase. Suponga que escribe una aplicación que crea muchas copias de una ficha
concreta. La aplicación necesita saber cuántas fichas se han creado y debe poder crear
una referencia a cualquiera de ellas.
En la declaración de clase siguiente, VariasFichas, el método CuantasFichas devuelve el
número de fichas creadas a partir de la clase. El método ReferFicha devuelve una
referencia a una ficha especificada. Puesto que no se sabe cuántas fichas existen ni si hay
una siquiera, es posible activar estos métodos directamente desde la clase.
? VariasFichas::CuantasFichas() && Devuelve el número de fichas creadas
PrimFicha = VariasFichas::ReferFicha(1) && Devuelve una referencia a la primera
&& ficha creada
* La siguiente línea devuelve una referencia a la última ficha creada
UltFicha = VariasFichas::ReferFicha(VariasFichas::CuantasFichas())
CLASS VariasFichas OF FORM
this.OnOpen = CLASS::FICHA_ONOPEN
PROCEDURE Ficha_OnOpen
PUBLIC XMatriz && En XMatriz se guardará una referencia de
&& cada ficha creada
IF EMPTY(XMatriz)
XMatriz = NEW Array(0)
ENDIF
this.Indice = XMatriz.Add(this)
this.Texto = STR(this.Indice)
RETURN
PROCEDURE CuantasFichas
IF .NOT. EMPTY(XMatriz)
RETURN XMatriz.Size
ELSE
RETURN 0
ENDIF
RETURN

Capítulo 11, Uso de las clases 171


classes.dwp Page 172 Wednesday, August 30, 1995 2:12 PM

PROCEDURE ReferFicha(i)
IF .NOT. EMPTY(XMatriz)
RETURN XMatriz[i]
ELSE
RETURN .F.
ENDIF
RETURN
ENDCLASS

Transmisión de parámetros a las superclases


El Capítulo 11 describe cómo declarar parámetros para una clase. Cuando las clases de
una jerarquía declaran parámetros, es posible pasarlos desde las subclases a las
superclases. Además de sus propios parámetros (si tiene), cada subclase necesita recibir
los parámetros requeridos por sus superclases. El siguiente es un ejemplo sencillo:
CLASS Cuadrado(n) && Declara un parámetro: n
this.Num = n
this.NumCuadrado = n*n
ENDCLASS
CLASS Cubo(n) OF Cuadrado(n) && Declara n en la subclase
this.NumCubo = n*this.NumCuadrado
ENDCLASS
X = NEW Cuadrado(10) && Crea un objeto Cuadrado, pasándole 10 como parámetro
? X.NumCuadrado && Devuelve 100
Y = NEW Cubo(20) && Crea un objeto Cubo, pasándole 20 como parámetro
? Y.NumCubo && Devuelve 8000
Los ejemplos de la sección siguiente muestran la transmisión de parámetros entre las
clases de una jerarquía.

172 Guía del programador


classes.dwp Page 173 Wednesday, August 30, 1995 2:12 PM

Creación de una jerarquía de clases


La mayor ventaja de derivar subclases se manifiesta al construir una jerarquía de clases,
donde cada subclase herede mucha de su funcionalidad de una superclase. Una
subclase puede modificar la funcionalidad sustituyendo los miembros heredados o
añadiendo otros nuevos. El resultado es un mantenimiento del código más sencillo y
una mayor facilidad de reutilización.
El Capítulo 12 describe el proceso de extraer tareas del programa para desarrollar
jerarquías de clases. El agrupamiento de tareas en clases y su ordenación en jerarquías es
el núcleo del diseño orientado a objetos. A continuación, se muestra un ejemplo de cómo
puede aplicarse una sencilla jerarquía de clases.
Suponga que está desarrollando una aplicación con diversas fichas y todas contienen
botones. Para lograr un interface que tenga un aspecto coherente, desarrolle un estilo
para esos botones. La siguiente declaración de clase define las características que
tendrán todos los botones de comando.
CLASS MiBoton(Ficha, Ext_Sup, Ext_izq) OF PUSHBUTTON(Ficha)
this.Top = Ext_sup && Asignado desde el parámetro Ext_Sup
this.Left = Ext_Izq && Asignado desde el parámetro Ext_Izq
this.text = "Botón" && Texto por defecto para ser sobrescrito
this.FontName = "Arial" && Fuente para todos los botones
this.FontBold = .T. && Todos los botones irán en negrita
this.FontWidth = 6 && Ancho de fuente para todos los botones
*-- declara un método mediante un bloque de código para establecer el ancho del botón
*-- una vez asignado el texto a mostrar
this.SetWidth = {;this.width = LEN(this.text)+10}
ENDCLASS
La clase MiBoton servirá como superclase de todos los botones de comando.
Ahora, suponga que desea que algunos botones produzcan un sonido cuando se
hace clic en ellos. La subclase siguiente, derivada de MiBoton, añade la capacidad
del sonido.
CLASS BotonSonido(Ficha, Ext_sup, Ext_izq) OF MiBotón(Ficha, Ext_Sup, Ext_Izq)
this.text = "Suena" && Texto por defecto para el botón
this.SetWidth() && Llama al método heredado para establecer la anchura
PROCEDURE OnClick && Asigna un procedimiento que produce el sonido
? CHR(7)
RETURN
ENDCLASS

Capítulo 11, Uso de las clases 173


classes.dwp Page 174 Wednesday, August 30, 1995 2:12 PM

Ahora, suponga que desea que un botón, además de emitir el sonido, se mueva cuando
se hace clic en él. La subclase siguiente, derivada de BotonSonido, hereda su
funcionalidad de sonido y la modifica.
CLASS SuenaMueve(Ficha, Ext_Sup, Ext_Izq) OF BotonSonido(Ficha, Ext_Sup, Ext_Izq)
this.text = "Suena y se mueve" && Texto por defecto para el botón
this.SetWidth() && Llama al método heredado para establecer la anchura
PROCEDURE OnClick && Declara una versión modificada del método OnClick
BotonSonido::OnClick() && Llama al método OnClick de BotonSonido
this.left = this.left+1 && Se añade movimiento al botón
RETURN
ENDCLASS
La subclase siguiente declara un botón sonoro que crece cuando se hace clic en él.
La idea es la misma que para SuenaMueve, pero la modificación se trata de otra forma.
En primer lugar, se anula OnClick con un bloque de código, no con un procedimiento.
Después, se llama el método OnClick sonoro mediante super en lugar de con el nombre
de la clase.
CLASS SuenaCrece(Ficha, Ext_Sup, Ext_Izq) OF BotonSonido(Ficha, Ext_Sup, Ext_Izq)
this.text = "Suena y crece"
this.SetWidth()
this.OnClick = {;Super::OnClick();this.Width = This.Width + 1}
ENDCLASS

174 Guía del programador


oodev.dwp Page 175 Wednesday, August 30, 1995 2:13 PM

Capítulo

Diseño orientado a objetos


Capítulo 12
12
El diseño orientado a objetos es una forma de diseñar aplicaciones. Visual dBASE, por
medio de la capacidad de crear objetos, declarar clases y declarar subclases a partir de
superclases, permite el diseño orientado a objetos. Este capítulo:
• Indica qué tipos de proyectos son más adecuados para el diseño orientado a objetos.
• Presenta algunos de los conceptos que subyacen en la sintaxis y técnicas tratadas en
los Capítulos 9 a 11, para que pueda aplicarlas mejor al codificar aplicaciones.
• Aplica los conceptos del diseño orientado a objetos al desarrollo de una aplicación.
Este capítulo supone que ya ha leído los tres capítulos anteriores, del 9 al 11.
Consúltelos para comprender mejor los términos utilizados aquí.

Cuándo utilizar el diseño orientado a objetos


El diseño orientado a objetos es la estrategia de diseño más recomendable para
desarrollar aplicaciones, aunque no es necesario para todos los casos. Si sólo está
diseñando sencillas fichas de introducción de datos, puede crear objetos y definir sus
propiedades sin considerar un planteamiento “orientado a objetos”. El Diseñador de
fichas se lo gestiona mediante la generación de una declaración de clase para la ficha.
El problema que pretende solucionar el diseño orientado a objetos es el de la complejidad.
Cuanto más complejo se hace un proyecto de programación, más necesario es diseñarlo
de una forma orientada a objetos.
El diseño orientado a objetos es muy adecuado en:
• Proyectos grandes. Con las técnicas orientadas a objetos, es más fácil modelar el mundo
real. Los problemas complejos pueden representarse con mayor sencillez en el código
y el programador puede gestionarlos más fácilmente.

Capítulo 12, Diseño orientado a objetos 175


oodev.dwp Page 176 Wednesday, August 30, 1995 2:13 PM

• Proyectos en que hay más de un programador. Debido a que las subrutinas (métodos) y
variables (propiedades) están “ocultas” dentro de los objetos, es menos probable que
el trabajo de un programador interfiera con el de otro. De esta forma es más fácil
compartir código entre programadores.
• Proyectos que se modifican a menudo. El mantenimiento y actualización del código
orientado a objetos es más fácil. Una vez que una clase está escrita y depurada, puede
modificar su funcionalidad con facilidad derivando una subclase sin tocar el código
original.

Transición del diseño procedural al orientado a objetos


Probablemente haya oído que aprender el diseño orientado a objetos es más fácil para
un programador inexperto que para uno experimentado y habituado al diseño
procedural. Eso es verdad sólo en parte. El diseño orientado a objetos en Visual dBASE
es una extensión natural de los principios procedurales.
Es importante señalar que el diseño orientado a objetos es un método de planificar y
organizar aplicaciones, no de codificarlas. Si conoce el lenguaje dBASE, sólo hay que
aprender unos pocos comandos y palabras clave nuevos para trabajar con los objetos y
las clases; la mayoría de las subrutinas que escriba en Visual dBASE son muy similares a
las escritas para versiones anteriores de dBASE. La diferencia está en la forma en que las
subrutinas utilizan las variables de memoria y en cómo logran la capacidad de
reutilización.

Fundamentos procedurales
Los programadores proceden de una gran variedad de orígenes y sus enfoques de la
programación varían ampliamente. Sin embargo, el principio de diseño común que
todos los programadores siguen al fin y al cabo es la modularidad. Es decir, es posible
escribir un programa de 300 líneas que se ejecute secuencialmente de principio a fin,
pero es más fácil dividir las tareas del programa en módulos. Así, el programa de 300
líneas se convierte en un programa de 30 líneas más manejable que ejecuta varias
subrutinas.
Y para dividir una tarea en subrutinas, la mayoría de programadores adoptan un
planteamiento en dos fases:
1 Comienzan con la tarea más general y la dividen en componentes más pequeños.
Es la descomposición de arriba a abajo del problema.
2 Categorizan los componentes y los agrupan en una estructura jerárquica. Es el
ensamblaje de abajo a arriba de los componentes.
Mediante el diseño orientado a objetos, los programadores realizan los mismos pasos
con otro enfoque. Un programador procedural divide un problema en procedimientos y
los ensambla en una aplicación. Un programador orientado a objetos divide un
problema en clases y las ensambla en jerarquías de clases para formar una aplicación.
Algunas de las razones para este diferente planteamiento se explican en las dos
secciones siguientes.

176 Guía del programador


oodev.dwp Page 177 Wednesday, August 30, 1995 2:13 PM

Problemas de ámbito de las variables de memoria


Las subrutinas de una aplicación procedural manipulan datos y almacenan algunos
valores en variables de memoria de forma temporal.
• Las variables públicas están disponibles en cualquier subrutina, por lo que en una
aplicación con muchas subrutinas, el programador se arriesga continuamente a
sobrescribir una variable ya declarada con el mismo nombre.
• Las variables privadas, aunque más limitadas en ámbito (sólo están disponibles en la
subrutina que las crea y en cualquier subrutina que se llame con posterioridad),
también presentan problemas de sobrescritura en aplicaciones que tienen muchos
niveles de anidación.
Los conflictos de las variables de memoria son aún más probables cuando un proyecto
implica a más de un programador intentando coordinar los nombres de las variables.
Además, si utiliza bibliotecas de procedimientos de algún proveedor, las variables de
estos procedimientos pueden estar en conflicto con las suyas.
Para ayudar a solventar los conflictos de las variables de memoria, Visual dBASE ofrece
dos nuevos ámbitos: local y estático. Las variables declaradas como locales o estáticas
están disponibles sólo dentro de la subrutina que las declara. Así, cualquier subrutina de
una aplicación puede utilizar una variable denominada X sin conflictos, siempre que X
se declare local o estática cada vez. (Para más información sobre las variables de
memoria locales y estáticas, consulte el Capítulo 5.)
Una forma aún mejor de proteger las variables de memoria es encapsularlas con las
subrutinas mediante las creación de objetos. De esta forma, en lugar de hacer referencia
a las variables de memoria, las subrutinas llaman a las propiedades de los objetos.
El encapsulado es combinar datos con las subrutinas que operan con ellos para formar
una nueva estructura, denominada objeto. El encapsulado oculta datos dentro de los
objetos para protegerlos de los datos que tienen el mismo nombre en algún otro de una
aplicación. La Figura 12.1 ilustra cómo se encapsulan en objetos las variables de
memoria y los procedimientos.
Figura 12.1 Encapsulado de código y datos en aplicaciones orientadas a objetos

Art Guantes

Talla 12

Precio 9995
MiObj CalcVal MiProc Procedimiento

ObtArt MiFunc Función

Capítulo 12, Diseño orientado a objetos 177


oodev.dwp Page 178 Wednesday, August 30, 1995 2:13 PM

Problemas para lograr la reutilización


Los programadores intentan escribir subrutinas que puedan utilizar en más de una
situación, como un procedimiento de mensajes que muestra errores, advertencias o
información de ayuda. Sin embargo, raramente pueden reutilizar el procedimiento tal
cual, y deben modificarlo ligeramente. Así, el programador se arriesga a crear nuevos
errores de programación y a romper todo el procedimiento.
Una forma mejor de escribir código reutilizable es declarar clases, donde clase contenga
las declaraciones de propiedad y procedimiento (método) utilizadas por los objetos
creados a partir de la clase.
La herencia implica la definición de una clase y su utilización para crear una jerarquía de
objetos descendientes, donde cada uno “hereda” los datos y subrutinas de sus ancestros.
La Figura 12.2 ilustra la herencia de una jerarquía de clases de ejemplo.
Figura 12.2 Ejemplo de una jerarquía de clases

Clase: Base
Propiedad A
Método A

Clase: Derivada1 de Base Clase: Derivada2 de Base


Propiedad A Propiedad A
Método A Método A
Propiedad B Método B

Clase: Derivada3 de Derivada1


Propiedad A
Método A
Propiedad B
Método C

= declarada en esta clase = heredada

La herencia es, en esencia, programar mediante la modificación del comportamiento.


Los distintos objetos que comparten algunos datos o un comportamiento pueden
heredar esa información de un objeto existente; así, es posible cambiar sólo lo que sea
necesario. La herencia facilita mucho la reutilización del código.

178 Guía del programador


oodev.dwp Page 179 Wednesday, August 30, 1995 2:13 PM

Si necesita adaptar una clase para un uso ligeramente diferente, no edite la declaración
de la clase. En su lugar, derive una subclase y sustituya sólo las propiedades o métodos
que desee cambiar. Los objetos creados a partir de la subclase pueden usarse de la
misma forma que los creados a partir de la superclase. Sólo varía el comportamiento.
El polimorfismo implica dar a un objeto de una jerarquía de clases la capacidad de aplicar
una acción que es común a todos los objetos de la jerarquía, de la forma que sea más
apropiada para ese objeto. El polimorfismo (literalmente, “con varias formas”) significa
que los objetos de una jerarquía pueden compartir la misma subrutina por su nombre,
pero la subrutina puede tomar una “forma” distinta dentro de cada objeto.
El comando SKIP de dBASE ilustra las ventajas del polimorfismo. SKIP funciona de
forma distinta dependiendo del estado del área de trabajo actual. Si no hay ningún
índice activo, SKIP va al siguiente número de registro. Si el área de trabajo está
indexada, SKIP avanza al siguiente registro del índice. Si hay definido un filtro, SKIP
pasa al siguiente registro que cumpla la condición del filtro. No es necesario indicar a
SKIP cuál es el área de trabajo; basta teclear SKIP, y el comando sabrá lo que hacer.

Fases de diseño
Ahora que conoce algunos de los conceptos que subyacen en la orientación a objetos, se
tratará cómo diseñar una aplicación. Cualquier proyecto de software, sea cual sea el
método de diseño, es un proceso de analizar una situación, dividirla en componentes,
organizarlos e implantarlos. En el diseño orientado a objetos, los componentes que se
utilizan para construir la aplicación son clases.
Las siguientes son las fases generales en el diseño de una aplicación orientada a objetos:
1 Identificación de las clases.
Es la fase de descomposición de arriba a abajo conocida por los programadores
procedurales. Analice los requisitos del proyecto —los datos que debe controlar, las
tareas que debe realizar, la salida que debe producir— y construya una lista de clases
que modele estos requisitos. En este sentido, una clase es cualquier entidad física o
conceptual que tenga un propósito bien definido.
Los candidatos a clases más habituales son:
• Grupos de información, como clientes, pedidos o inventarios.
• Interfaces con los datos (normalmente fichas) para tareas como añadir, editar o
mostrar datos. Muchas de las clases que identifique se basarán en una ficha.
• Tareas especiales, como resumir, compilar o buscar datos.
2 Identificación de las tareas y datos que están asociados a las clases.
Para cada clase, elabore una lista de los datos que utiliza la tabla y las tareas que
realiza con ellos; por ejemplo, qué datos mostrarán las fichas o si el usuario deberá
añadir o editar elementos.

Capítulo 12, Diseño orientado a objetos 179


oodev.dwp Page 180 Wednesday, August 30, 1995 2:13 PM

3 Identificación de las relaciones entre las clases.


Intente encontrar elementos que sean comunes entre las clases. Algunas tendrán que
ser independientes, pero otras podrán organizarse en una jerarquía. Si hay tres clases
que realizan la misma tarea o una similar, agrúpelas.
En la jerarquía, algunas clases pueden servir sólo como superclases, sin llegar nunca a
crear objetos. Por ejemplo, si dos clases contienen la misma información excepto por
unos pocos elementos, podría crear una tercera clase que contenga la información
común a las otras dos. A continuación, las dos clases se convierten en subclases de la
tercera, heredando su información común.
El objetivo debe ser “extraer” las tareas y datos que son comunes entre las clases para
reducir la duplicidad. El resultado es una jerarquía de clases.
4 Implantación de las clases.
Puesto que muchas de las clases crearán fichas, puede generar las declaraciones de
clase en el Diseñador de fichas. Cree otras clases no basadas en fichas escribiendo su
declaración mediante CLASS...ENDCLASS.
Estas fases de diseño no son obligatorias, y puede seguirlas en la medida en que sean
adecuadas a sus necesidades. Sin embargo, recuerde que el ámbito de los aspectos y
problemas que debe resolver en aplicaciones controladas por sucesos es mucho mayor
que en las controladas por menús. La recompensa de aprender y seguir las técnicas de
diseño orientado a objetos en el entorno Windows es grande, pero los peligros de no
tener en cuenta un buen diseño son muchos mayores.

Un ejemplo
El supuesto de esta sección ejemplifica la codificación de una aplicación orientada a
objetos.
Va a escribir un programa para un pequeño negocio de pedidos por correo.
Necesita controlar información sobre los empleados (tanto a tiempo parcial como a
jornada completa) y calcular su remuneración cada semana. También es necesario
mantener una lista de direcciones de los clientes que realizan un pedido y de los
posibles clientes que aún no han pedido.
1 Identifique las clases.
Las principales clases de información son:
• Empleados a jornada completa
• Empleados a tiempo parcial
• Clientes
• Posibles clientes
2 Identifique las tareas y datos que están asociados a las clases.
En cada clase, necesita un interface para la edición y visualización básicas. Para los
empleados, necesita calcular su retribución cada semana.

180 Guía del programador


oodev.dwp Page 181 Wednesday, August 30, 1995 2:13 PM

La Tabla 12.1 resume los datos y las tareas que hay asociados a cada clase:
Tabla 12.1 Datos y tareas para las clases de ejemplo
Clase Datos Tareas
Empleados a jornada Nombre y dirección Visualizar y editar datos
completa Beneficios Calcular la paga semanal.
Salario
Empleados a tiempo Nombre y dirección Visualizar y editar datos.
parcial Pago por hora Calcular la paga semanal.
Clientes Nombre y dirección Visualizar y editar datos.
Número de pedidos
Posibles clientes Nombre y dirección Visualizar y editar datos.
Tipo de negocio

3 Identifique las relaciones entre las clases.


Puesto que las cuatro clases están basadas en fichas y contienen información de
nombre y dirección, declare una superclase, FichaBase, de la que derivar las demás
clases. FichaBase contiene campos de entrada para mostrar nombres y direcciones y
además define las propiedades que son comunes a todas las fichas, como Maximize.
Este es un ejemplo de herencia.
A continuación, necesita almacenar la misma información para los posibles clientes y
para los que ya lo son, además de alguna información nueva. En lugar de crear una
nueva clase de cero, puede declarar una clase PosCliente basada en la clase Cliente.
La clase PosCliente hereda las características de la clase Cliente. Entonces, añada
campos de entrada para los campos que aparecen sólo en la tabla de posibles clientes.
Ahora, necesita almacenar la misma información para los empleados que están a
tiempo parcial y a jornada completa, excepto que la retribución de los primeros se
calcula de forma diferente. Comience declarando una clase TiempoParcial basada en
la clase Empleado. Ésta última contiene una subrutina, CalcPago(), para calcular la
retribución anual. En la clase TiempoParcial, es posible sobrescribir CalcPago() con
otra función que calcule por horas trabajadas y excluya la paga por vacaciones.
Este es un ejemplo de polimorfismo. El programa puede recuperar la retribución de
un empleado de la misma forma, invocando CalcPago(), sin tener en cuenta el tipo de
empleado. Mediante la ocultación de la implantación de la tarea, dispone de libertad
para cambiar los detalles de la implantación sin necesidad de cambiar el interface.
Suponga que los empleados a jornada completa reciben una bonificación especial
trimestral; simplemente cambie CalcPago() para la clase Empleado. El polimorfismo
mejora la posibilidad de reutilizar el código.

Capítulo 12, Diseño orientado a objetos 181


oodev.dwp Page 182 Wednesday, August 30, 1995 2:13 PM

La Figura 12.3 ilustra la jerarquía de clases:


Figura 12.3 Jerarquía de clases en el supuesto de ejemplo

Clase: FichaBase de FORM

Nombre y dirección

Clase: Cliente de FichaBase Clase: Empleado de FichaBase


Nombre y dirección Nombre y dirección

Campos del cliente Calcular pago

Clase: PosCliente de Cliente Clase: TiempParcial de Empleado


Nombre y dirección Nombre y dirección

Campos del cliente Calcular pago

Campos sólo de posibles cl.

= Declarada en esta clase = Heredada = Sustituida

4 Implante las clases.


El listado siguiente muestra el código fuente que implanta las clases.
*PEDIDOS.PRG

LOCAL F,P,C,R

F = NEW JORNADACOMPLETA ()
F.OPEN()
P = NEW TIEMPOPARCIAL ()
P.OPEN()
C = NEW CLIENTE ()
C.OPEN()
R = NEW POSCLIENTE ()
R.OPEN()

***------------------------------ CLASE: FichaBase-----------------------------


**

182 Guía del programador


oodev.dwp Page 183 Wednesday, August 30, 1995 2:13 PM

CLASS FICHABASE OF FORM


this.Text = "Ficha"
this.Width = 57.25
this.Top = 0.05
this.Left = 2.00
this.Height = 24.58
this.Minimize = .F.
this.Maximize = .F.
this.HelpFile = ""
this.HelpId = ""
DEFINE TEXT TF_NOMBRE OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Nombre",;
Width 9.00,;
Top 3.25,;
Left 7.50,;
Height 1.08,;
Border .F.
DEFINE TEXT TF_DIRECCION OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Dirección",;
Width 11.50,;
Top 5.42,;
Left 6.00,;
Height 1.17,;
Border .F.
DEFINE TEXT TF_CIUDAD OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Ciudad",;
Width 8.00,;
Top 9.58,;
Left 13.12,;
Height 1.08,;
Border .F.
DEFINE TEXT TF_PROVINCIA OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Provincia",;
Width 8.00,;
Top 9.58,;
Left 34.00,;
Height 1.08,;
Border .F.

Capítulo 12, Diseño orientado a objetos 183


oodev.dwp Page 184 Wednesday, August 30, 1995 2:13 PM

DEFINE TEXT TF_CPOSTAL OF THIS;


PROPERTY;
ColorNormal "N/W",;
Text "C. Postal",;
Width 10.62,;
Top 9.50,;
Left 44.12,;
Height 1.17,;
Border .F.
DEFINE ENTRYFIELD EF_NOMBRE OF THIS;
PROPERTY;
ColorNormal "",;
Width 37.50,;
Top 3.17,;
Left 17.75,;
Height 1.42,;
Border .T.,;
Value "",;
ColorHighLight ""
DEFINE ENTRYFIELD EF_DIRECCION OF THIS;
PROPERTY;
ColorNormal "",;
Width 37.25,;
Top 5.25,;
Left 17.75,;
Height 1.42,;
Border .T.,;
Value "",;
ColorHighLight ""
DEFINE ENTRYFIELD EF_CIUDAD OF THIS;
PROPERTY;
ColorNormal "",;
Width 18.00,;
Top 7.75,;
Left 16.75,;
Height 1.33,;
Border .T.,;
Value "",;
ColorHighLight ""
DEFINE ENTRYFIELD EF_PROVINCIA OF THIS;
PROPERTY;
ColorNormal "",;
Width 3.00,;
Top 7.75,;
Left 38.12,;
Height 1.33,;
Border .T.,;
Value "",;
ColorHighLight ""

184 Guía del programador


oodev.dwp Page 185 Wednesday, August 30, 1995 2:13 PM

DEFINE ENTRYFIELD EF_CPOSTAL OF THIS;


PROPERTY;
ColorNormal "",;
Width 8.75,;
Top 7.67,;
Left 45.00,;
Height 1.42,;
Border .T.,;
Value "",;
ColorHighLight ""
ENDCLASS

***------------------------------ CLASE: Empleado------------------------------


**
CLASS EMPLEADO OF FICHABASE
DEFINE ENTRYFIELD EF_PRIMA OF THIS;
PROPERTY;
ColorNormal "",;
Width 8.75,;
Top 14.92,;
Left 21.62,;
Height 1.42,;
Border .T.,;
Value "",;
ColorHighLight ""
ENDCLASS

***------------------------------ CLASE: TiempoParcial--------------------------


**
CLASS TIEMPOPARCIAL OF EMPLEADO
this.text = "Inf. empleados a tiempo parcial"
DEFINE TEXT TF_SALARIO OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Salario hora",;
Width 19.25,;
Top 14.92,;
Left 1.25,;
Height 1.25,;
Border .F.
DEFINE TEXT TF_BRUTO_MENSUAL OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Pago bruto ",;
Width 18.88,;
Top 14.75,;
Left 32.88,;
Height 1.50,;
Border .F.

Capítulo 12, Diseño orientado a objetos 185


oodev.dwp Page 186 Wednesday, August 30, 1995 2:13 PM

DEFINE TEXT TF_PAGO OF THIS;


PROPERTY;
ColorNormal "N/W*,;
Text "",;
Width 18.25,;
Top 17.00,;
Left 38.75,;
Height 1.17,;
Border .T.
DEFINE TEXT TF_HORAS OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Horas",;
Width 7.00,;
Top 17.92,;
Left 4.50,;
Height 1.08,;
Border .F.
DEFINE ENTRYFIELD EF_HORAS OF THIS;
PROPERTY;
ColorNormal "",;
Width 8.75,;
Top 17.67,;
Left 12.88,;
Height 1.42,;
Border .T.,;
Value "",;
ColorHighLight ""
DEFINE PUSHBUTTON PB_CALCPAGO OF THIS;
PROPERTY;
OnClick CLASS::PB_CALCPAGO_ONCLICK,;
ColorNormal "N/W",;
Text "Calcular pago",;
Width 18.12,;
Top 19.08,;
Left 39.25,;
Height 3.17,;
Group .T.
Procedure PB_CALCPAGO_OnClick
TEXTPAGO = STR(VAL(FORM.EF_PRIMA.VALUE) * VAL(FORM.EF_HORAS.VALUE))
FORM.TF_PAGO.TEXT = TEXTPAGO
ENDCLASS

186 Guía del programador


oodev.dwp Page 187 Wednesday, August 30, 1995 2:13 PM

***------------------------------ CLASE: JornadaCompleta------------------------


**
CLASS JORNADACOMPLETA OF EMPLEADO
this.text = "Inf. empleados en jornada completa"
this.left = 60.00
DEFINE TEXT TF_SALARY OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Salario anual",;
Width 16.25,;
Top 14.92,;
Left 1.25,;
Height 1.25,;
Border .F.
DEFINE TEXT TF_BENEFI OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Beneficios",;
Width 18.62,;
Top 16.3,;
Left 2.62,;
Height 1.08,;
Border .F.
DEFINE CHECKBOX CB_SALUD OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Seguro médico",;
Width 21.88,;
Top 17.08,;
Left 12.50,;
Height 1.58,;
Value .F.,;
Group .T.
DEFINE CHECKBOX CB_DENTAL OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Seguro dental",;
Width 21.00,;
Top 19.08,;
Left 12.50,;
Height 1.58,;
Value .F.,;
Group .T.

Capítulo 12, Diseño orientado a objetos 187


oodev.dwp Page 188 Wednesday, August 30, 1995 2:13 PM

DEFINE CHECKBOX CB_COCHEEMP OF THIS;


PROPERTY;
ColorNormal "N/W",;
Text "Coche de empresa",;
Width 16.12,;
Top 20.92,;
Left 12.50,;
Height 1.58,;
Value .T.,;
Group .T.
DEFINE TEXT TF_BRUTO_MENSUAL OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Pago bruto ",;
Width 18.88,;
Top 14.75,;
Left 32.88,;
Height 1.50,;
Border .F.
DEFINE TEXT TF_PAGO OF THIS;
PROPERTY;
ColorNormal "N/W*",;
Text "",;
Width 18.25,;
Top 17.00,;
Left 38.75,;
Height 1.17,;
Border .T.
DEFINE PUSHBUTTON PB_CALCPAGO OF THIS;
PROPERTY;
OnClick CLASS::PB_CALCPAGO_ONCLICK,;
ColorNormal "N/W",;
Text "Calcular pago",;
Width 17.12,;
Top 19.08,;
Left 39.25,;
Height 3.17,;
Group .T.

PROCEDURE PB_CALCPAGO_OnClick
form.TF_PAGO.TEXT = STR(VAL(form.EF_PRIMA.VALUE)/26)
RETURN
ENDCLASS

188 Guía del programador


oodev.dwp Page 189 Wednesday, August 30, 1995 2:13 PM

***------------------------------ CLASE: Cliente-------------------------------


**
CLASS CLIENTE OF FICHABASE
this.text = "Información cliente"
this.top = 2
DEFINE LINE LINE1 OF THIS;
PROPERTY;
ColorNormal "N",;
Width 1,;
Top 12.83,;
Left 1.25,;
Bottom 12.83,;
Right 56.00
DEFINE TEXT TF_NUMPED OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Número de pedidos",;
Width 22.62,;
Top 14.75,;
Left 3.38,;
Height 1.42,;
Border .F.
DEFINE ENTRYFIELD EF_NUMPED OF THIS;
PROPERTY;
ColorNormal "",;
Width 6.75,;
Top 14.67,;
Left 25.75,;
Height 1.42,;
Border .T.,;
Value "",;
ColorHighLight ""
ENDCLASS

***------------------------------ CLASE: PosCliente-----------------------------


**
CLASS POSCLIENTE OF CLIENTE
this.text = "Inf. posible cliente"
this.top = 2
this.left = 60.00
DEFINE TEXT TF_EXPECTED OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Esperado",;
Width 10.88,;
Top 16.00,;
Left 3.62,;
Height 1.08,;
Border .F.

Capítulo 12, Diseño orientado a objetos 189


oodev.dwp Page 190 Wednesday, August 30, 1995 2:13 PM

DEFINE TEXT TF_TIPONEG OF THIS;


PROPERTY;
ColorNormal "N/W",;
Text "Tipo de negocio",;
Width 19.25,;
Top 19.00,;
Left 3.25,;
Height 1.17,;
Border .F.
DEFINE ENTRYFIELD EF_TIPONEG OF THIS;
PROPERTY;
ColorNormal "",;
Width 24.62,;
Top 19.00,;
Left 25.75,;
Height 1.42,;
Border .T.,;
Value "",;
ColorHighLight ""
ENDCLASS

190 Guía del programador


oodev.dwp Page 191 Wednesday, August 30, 1995 2:13 PM

Cómo aprender más


Visual dBASE incorpora la programación orientada a objetos en el lenguaje dBASE por
primera vez. La Guía del usuario incluye una relación de libros que tratan de Visual
dBASE, y algunos de ellos informan sobre la programación orientada a objetos.
Estos otros libros le ayudarán a aprender más sobre los principios de la orientación a
objetos:
• Object-Oriented Technology: A Manager’s Guide, de David A.Taylor, proporciona una
descripción conceptual y agradable del diseño y programación orientados a objetos
de forma genérica, sin ser específica de un lenguaje.
• Designing Object-Oriented Software, de Rebecca Wirfs-Brock, Brian Wilkerson y Lauren
Wiener, recorre los principios básicos del diseño orientado a objetos.
Si está familiarizado con otros lenguajes orientados a objetos, como C++ y Delphi de
Borland, los aspectos orientados a objetos de estos lenguajes pueden ayudarle a
comprender mejor la implantación de Visual dBASE. Encontrará incluso que algunos
elementos del lenguaje Visual dBASE, como NEW(), this y ::, tienen equivalentes directos
en otros lenguajes orientados a objetos.
• C++ Primer, de Stanley B. Lippman, proporciona explicaciones claras de los
mecanismos de clase implantados en C++.

Capítulo 12, Diseño orientado a objetos 191


oodev.dwp Page 192 Wednesday, August 30, 1995 2:13 PM

192 Guía del programador


p_forms.dwp Page 193 Wednesday, August 30, 1995 2:14 PM

Parte

III
Fichas
Parte III

Esta parte presenta los planteamientos que pueden adoptarse al construir aplicaciones
con fichas y trata la creación de fichas en cuanto a su relación con la programación.
Para ayuda sobre el uso del Diseñador de fichas, consulte la Guía del usuario.
Si sólo desea escribir controladores de sucesos para una ficha:
1 Lea el Capítulo 2 para una introducción para inexpertos sobre los programas
controlados por sucesos.
2 Consulte el Capítulo 14, “Escritura de controladores de sucesos,” para ver
instrucciones y ejemplos.
Para aprender sobre la creación de aplicaciones con fichas:
1 Lea el Capítulo 13, “Creación de aplicaciones con fichas.”
2 Busque en los Capítulos 14 y 15 detalles sobre el control de sucesos y el uso de
controles personalizados.
Lea el Capítulo 16, “Uso del código generado”, para comprender cómo trabajar con las
Utilidades de dos vías: el Diseñador de fichas y el Diseñador de menús.
Esta sección contiene los capítulos siguientes:
• Capítulo 13, “Creación de aplicaciones con fichas”
• Capítulo 14, “Escritura de controladores de sucesos”
• Capítulo 15, “Creación de controles y fichas personalizados”
• Capítulo 16, “Uso del código generado”

Parte III, Fichas 193


p_forms.dwp Page 194 Wednesday, August 30, 1995 2:14 PM

194 Guía del programador


intevnt.dwp Page 195 Wednesday, August 30, 1995 2:14 PM

Capítulo

Creación de aplicaciones con fichas


Capítulo 13
13
Por medio del Diseñador de fichas, puede crear todas las fichas que constituyen una
aplicación. Para unirlas, debe comprender cómo funcionan las fichas modales y no
modales y desarrollar una estrategia para la forma en que una ficha abre otra.
Este capítulo presenta los aspectos que debe tener en cuenta al programar con fichas, y
plantea las tres estrategias básicas que puede seguir. Para una breve introducción a la
escritura y vinculación de controladores de sucesos mediante el Diseñador de fichas,
consulte “Escritura de controladores de sucesos con el Diseñador de fichas” en el
Capítulo 14.

Acerca de las fichas modales y no modales


En dBASE DOS (y en la mayoría de las aplicaciones DOS), cuando se activa una
ventana, ésta toma el control del interface de usuario. Es decir, el usuario no puede
cambiar a otra ventana ni seleccionar una opción de menú sin salir de la ventana actual.
En el entorno Windows, las ventanas de este tipo se denominan modales. Una ventana
modal establece un nuevo modo del que el usuario debe salir antes de volver al entorno
no modal, en que el usuario puede cambiar libremente de una ventana a otra y de una
aplicación a otra.
Normalmente, la mayoría de las fichas de una aplicación Windows son no modales.
Para acciones que precisen una respuesta específica antes de que puedan tener lugar
otras, la aplicación muestra una ficha modal.
Piense en este ejemplo. Suponga que está creando una sencilla aplicación para elaborar
etiquetas de correo. Necesita una ficha para editar las direcciones y una forma de
introducir un nombre que buscar. Podría utilizar el menú para buscar nombres, pero
prefiere personalizar un cuadro de diálogo propio. La Figura 13.1 muestra la ficha de
edición (EditCli) y el cuadro de diálogo de búsqueda (BuscaCli).

Capítulo 13, Creación de aplicaciones con fichas 195


intevnt.dwp Page 196 Wednesday, August 30, 1995 2:14 PM

Figura 13.1 Ejemplo de controladores de sucesos para una ficha de entrada de datos y un cuadro de diálogo

Editar clientes
Buscar cliente
Nombre
Nombre
Dirección Asigne código al suceso
Valid de estos campos de
Ciudad entrada para comprobar Aceptar Cancelar
que no se dejan vacíos.
Provincia CP

Asigne código al suceso Asigne código al suceso


Buscar OnClick de este botón OnClick de este botón
para buscar el nombre y para volver a EditCli sin
volver a la ficha EditCli. buscar un nombre.
Asigne código al suceso OnClick de
este botón para ejecutar la ficha
BuscaCli.

La ficha EditCli de la Figura 13.1 es una ficha no modal. El usuario puede introducir
información dentro de esta ficha o cambiar el foco a otra. Cuando el usuario pulse el
botón Buscar, la aplicación debe saber el nombre qué buscar antes de continuar.
Es por lo que BuscaCli es una ficha modal; el usuario debe salir de la ficha BuscaCli
antes de hacer ninguna otra cosa. Muchas fichas modales son cuadros de diálogo que
obtienen información necesaria antes de volver a una ficha no modal.
Una propiedad relacionada con las fichas es MDI. Una ficha MDI tiene características
específicas que la hacen compatible con el interface MDI (de múltiples documentos) de
Windows. MDI es una característica de Windows que le permite abrir varias ventanas
dentro de la ventana de aplicación. Casi todas las aplicaciones Windows utilizan MDI
para gestionar varios documentos o varias vistas de un mismo documento dentro de la
ventana principal de la aplicación. Si desea que una ficha aparezca como ventana MDI,
defina la propiedad MDI como verdadero.
La lista siguiente enumera algunas de las características de las ventanas MDI:
• Al igual que las ventanas de aplicación, es posible cambiar su posición y tamaño. Si
MDI es .T., no se tienen en cuenta las propiedades Moveable y Sizeable.
• Son ventanas hijas de la ventana de aplicación y se listan en el menú Ventana, aun
cuando estén activas.
• Tienen un cuadro del menú Control, botones de maximización y minimización y una
barra de título que contiene el nombre de la ventana. Si MDI es .T., no se tienen en
cuenta las propiedades SysMenu, Maximize y Minimize.
• Cuando están activas, sus menús sustituyen a los de la barra de menús principal.
(Cuando la propiedad MDI es falso (.F.), los menús se muestran en la barra de menús
de la ficha.)
• Para cerrar la ficha, puede pulsar Ctrl+F4; para cerrar la ventana de aplicación, pulse
Alt+F4.
Importante Una ficha MDI no puede ser modal. Antes de abrir una ficha modal, defina como falso
(.F.) la propiedad MDI de la ficha.

196 Guía del programador


intevnt.dwp Page 197 Wednesday, August 30, 1995 2:14 PM

Apertura de fichas modales y no modales


Excepto por la propiedad MDI, que debe ser .F. para las fichas modales, no hay
diferencia en la forma de crear fichas modales y no modales. El que una ficha sea modal
o no se especifica según la forma de abrirla.
• Para mostrar una ficha no modal, utilice el método Open() de la ficha o el comando
OPEN FORM.
• Para mostrar una ficha modal, utilice el método ReadModal() de la ficha o la función
READMODAL().
La Figura 13.2 muestra un ejemplo de código para una ficha modal y otra no modal.
Figura 13.2 Código de ejemplo para crear fichas modales y no modales

Ficha modal Ficha no modal

DEFINE FORM MiModal FROM 10,10 TO 15,30 PROPERTY; DEFINE FORM MiNoModal FROM 10,10 TO 15,30
MDI .F.
DEFINE TEXT DiHola OF MiModal PROPERTY; DEFINE TEXT DiHola OF MiNoModal PROPERTY;
Top 1,; Top 1,;
Left 7,; Left 7,;
Text "¡Hola!" Text "¡Hola!"
DEFINE PUSHBUTTON DiAdios OF MiModal PROPERTY; DEFINE PUSHBUTTON DiAdios OF MiNoModal PROPERTY;
Top 3,; Top 3,;
Left 5,; Left 5,;
Text "Adiós",; Text "Adiós",;
OnClick {;form.CLOSE()} OnClick {;form.CLOSE()}

MiModal.ReadModal() MiNoModal.Open()

? CHR(7)+CHR(7)+CHR(7) && suena tres veces ? CHR(7)+CHR(7)+CHR(7) && suena tres veces

En este ejemplo, observe los siguientes aspectos:


• La ficha MiModal se abre con su método ReadModal(), y MiNoModal se abre con su
método Open(). De forma alternativa, MiModal podría abrirse con la función
READMODAL(), y MiNoModal con el comando OPEN FORM.
• La propiedad MDI de MiModal es .F., lo que la convierte en una ventana no MDI.
Las ventanas MDI no pueden ser modales.

Capítulo 13, Creación de aplicaciones con fichas 197


intevnt.dwp Page 198 Wednesday, August 30, 1995 2:14 PM

Modalidad y ejecución de un programa


Además de especificar si la ficha es modal o no, la forma en que se abre una ficha tiene
implicaciones importantes respecto a cómo se ejecuta el código de un programa.
• Mientras una ficha modal permanece abierta, se detiene la ejecución del programa.
• La apertura de una ficha no modal simplemente muestra la ficha, continuando la
ejecución del programa.
En la Figura 13.2, la línea siguiente se encuentra en el mismo lugar en ambos ejemplos
de código, pero se ejecuta en momentos diferentes:
? CHR(7)+CHR(7)+CHR(7) && Suena tres veces
En el código de la izquierda, la ejecución se detiene en la línea, MiModal.ReadModal(), y los
tres pitidos se producen después de que se cierre la ficha modal. En el código de la
derecha, la ejecución no se detiene y los tres pitidos suenan inmediatamente después de
abrir la ficha no modal.
Puesto que la ejecución del programa no se detiene, es necesario pensar con cuidado la
disponibilidad de variables de memoria al abrir las fichas no modales. El programa que
cree una ficha no modal puede terminar su ejecución por completo, dejando la ficha
abierta y activa. Las variables de memoria que utilicen los métodos o controladores de
sucesos de la ficha deben definirse con el ámbito adecuado para que estén disponibles.
Una alternativa mejor a las variables de memoria tradicionales es declarar propiedades
personalizadas para guardar valores temporales. Una propiedad está disponible en
tanto exista el objeto en que está contenida, sea cual sea el programa que se ejecuta.
La Figura 13.3 cambia el código de la ficha no modal de la Figura 13.2, mostrando un
mensaje cuando se hace clic en el botón de comando. El código de la izquierda declara
una variable pública para almacenar el mensaje. El de la derecha, que muestra la técnica
preferible, crea una propiedad personalizada de la ficha.

198 Guía del programador


intevnt.dwp Page 199 Wednesday, August 30, 1995 2:14 PM

Figura 13.3 Disponibilidad de variables de memoria en fichas no modales

Variable pública en una ficha no modal Propiedad personalizada en una ficha no modal
PUBLIC DisparoFinal DEFINE FORM MiNoModal FROM 10,10 TO 15,30 PROPERTY;
DisparoFinal = "Adiós, mundo cruel" MDI .T.;
CUSTOM DisparoFinal "Adiós, mundo cruel"
DEFINE FORM MiNoModal FROM 10,10 TO 15,30 PROPERTY;
MDI .T. DEFINE TEXT DiHola OF MiNoModal PROPERTY;
Top 1,;
DEFINE TEXT DiHola OF MiNoModal PROPERTY; Left 7,;
Top 1,; Text "¡Hola!"
Left 7,; DEFINE PUSHBUTTON DiAdios OF MiNoModal PROPERTY;
Text "¡Hola!" Top 3,;
DEFINE PUSHBUTTON Adios OF MiNoModal PROPERTY; Left 5,;
Top 3,; Text "Adiós",;
Left 5,; OnClick ProcCerrar,;
Text "Adiós",; Width 8
OnClick ProcCerrar,;
Width 8 MiNoModal.Open()

MiNoModal.Open() PROCEDURE ProCerrar


? form.DisparoFinal
PROCEDURE ProcCerrar form.close()
? DisparoFinal RETURN
form.close()
RETURN

Todo procedimiento asociado a los métodos o controladores de sucesos de la ficha debe


estar situado en un archivo de procedimiento cargado actualmente. Para comprobar la
disponibilidad de los procedimientos, dBASE realiza un SET PROCEDURE..ADDITIVE
implícito para cualquier archivo de programa (incluido un archivo .WFM) que cree una
ficha. Así, los métodos, controladores de sucesos y otros procedimientos declarados en
el archivo están disponibles para su ejecución. Los procedimientos permanecen
cargados hasta que se liberen todas las fichas creadas en el archivo.

Capítulo 13, Creación de aplicaciones con fichas 199


intevnt.dwp Page 200 Wednesday, August 30, 1995 2:14 PM

Estrategias de programación
Como ya se ha mencionado en este capítulo, la mayoría de fichas de una aplicación
controlada por sucesos son no modales, permitiendo así que el usuario cambie el foco a
otras fichas, o incluso acceda a la ventana de comandos o al Selector. Aunque esta
libertad de movimientos es beneficiosa para el usuario, requiere un diseño esmerado
por parte del programador, que debe garantizar que la aplicación pueda coexistir con
otras utilidades.
Por esta razón, quizá no desee escribir una aplicación totalmente controlada por
sucesos. Las secciones siguientes describen tres enfoques que puede seguir.
Puede adoptar un solo planteamiento o una combinación de los mismos.

Interface modal
Abra todas las fichas como modales, empleando el método ReadModal() de la ficha o la
función READMODAL(). En un interface de usuario modal:
• Cada ficha retiene el foco en exclusiva, forzando que el usuario salga de la ficha antes de
continuar. Aunque es más restrictivo para el usuario, este enfoque es más sencillo
para los programadores, porque no tienen que tener en cuenta tantas acciones
posibles que el usuario puede realizar.
• El interface de Visual dBASE es inaccesible mientras se ejecuta la aplicación. Esto impide
que el usuario interfiera con las tablas empleadas en la aplicación mientras ésta se
ejecuta.
• La ejecución del programa se detiene cuando se muestra cada ficha, facilitando la gestión de
los ámbitos de las variables y de la disponibilidad de los procedimientos.
En cierto sentido, un interface modal también está controlado por sucesos (es posible
asignar controladores de sucesos a las fichas y los controles), aunque mantiene el control
sobre la secuencia de acciones, como una aplicación controlada por menús.
El código siguiente crea las fichas que se muestran en la Figura 13.1.
DEFINE FORM EDITCLI FROM 12.5,24.5 TO 31.5,70.38;
PROPERTY;
EscExit .T.,;
View "EDITCLI.QBE",;
Text "Editar clientes",;
Minimize .F.,;
Maximize .F.,;
MDI .F. && Es necesario para crear una ficha modal
DEFINE TEXT TEXTO1 OF EditCli;
PROPERTY;
ColorNormal "N/W",;
Text "&Nombre",;
Width 10.00,;
Top 1.00,;
Left 2.00,;
Height 1.00

200 Guía del programador


intevnt.dwp Page 201 Wednesday, August 30, 1995 2:14 PM

DEFINE ENTRYFIELD ENTRADA1 OF EditCli;


PROPERTY;
ColorNormal "",;
Width 30.00,;
Top 1.00,;
Left 13.00,;
Height 1.50,;
Border .T.,;
DataLink "CLIENTE->NOMBRE"
*** El resto de textos y entradas siguen las mismas reglas que los anteriores. Se han
*** omitido del listado para ahorrar espacio.
DEFINE PUSHBUTTON BOTON1 OF EditCli;
PROPERTY;
OnClick BOTON1_ONCLICK,;
ColorNormal "",;
Text "Buscar",;
Width 10.00,;
Top 15.00,;
Left 17.00,;
Height 2.00
EditCli.ReadModal() && Abre la ficha como modal
PROCEDURE BOTON1_OnClick
DO BUSCACLI.PRG
RETURN
El código siguiente crea el cuadro de diálogo modal para buscar un cliente por su
nombre.
* BuscaCli.prg
*
DEFINE FORM BuscaCli FROM 11,29.88 TO 18.25,72.20;
PROPERTY;
EscExit .T.,;
Text "Buscar un cliente",;
Minimize .F.,;
Maximize .F.,;
MDI .F. && Es necesario para crear una ficha modal
DEFINE ENTRYFIELD ENTRADA1 OF BuscaCli;
PROPERTY;
ColorNormal "",;
Width 26.00,;
Top 1.00,;
Left 14.00,;
Height 2.00,;
Border .T.,;
Value "",;
MaxLength 25

Capítulo 13, Creación de aplicaciones con fichas 201


intevnt.dwp Page 202 Wednesday, August 30, 1995 2:14 PM

DEFINE TEXT TEXTO1 OF BuscaCli;


PROPERTY;
ColorNormal "N/W",;
Text "Introduzca un nombre",;
Width 11.00,;
Top 1.00,;
Left 2.00,;
Height 2.00
DEFINE PUSHBUTTON BOTON1 OF BuscaCli;
PROPERTY;
OnClick BOTON1_ONCLICK,;
ColorNormal "",;
Text "Aceptar",;
Width 10.00,;
Top 5.00,;
Left 18.00,;
Height 2.00,;
Default .T.
DEFINE PUSHBUTTON BOTON2 OF BuscaCli;
PROPERTY;
OnClick {;CLOSE FORM BuscaCli},;
ColorNormal "",;
Text "Cancelar",;
Width 10.00,;
Top 5.00,;
Left 30.00,;
Height 2.00
READMODAL(BuscaCli) && Abre la ficha como una ficha modal
PROCEDURE Boton1_onclick
IF .NOT. EMPTY(BuscaCli.Entrada1.Value)
LOCAL lExact
lExact = SET("EXACT")
SET EXACT off
SEEK(UPPER(TRIM(BuscaCli.Entrada1.Value)))
SET EXACT &lExact
ENDIF
CLOSE FORM BuscaCli
RETURN

202 Guía del programador


intevnt.dwp Page 203 Wednesday, August 30, 1995 2:14 PM

Interface no modal
Abra la mayoría de fichas como no modales, mediante el método Open de la ficha o el
comando OPEN FORM. Utilice las fichas modales con moderación para obtener
información necesaria del usuario. Este enfoque sirve mejor al usuario y proporciona
estas ventajas:
• El usuario puede ejecutar más de un programa simultáneamente. En una aplicación grande,
por ejemplo, el usuario puede activar la ficha de entrada de pedidos, rellenar la
mitad, imprimir una relación de inventario y volver para terminar el pedido.
Esto reproduce el flujo de trabajo en el mundo real.
• El usuario puede acceder al interface de Visual dBASE, incluidos la ventana de comandos
y el Selector, lo cual es muy útil para escribir programas de utilidad que
proporcionen funciones especiales de búsqueda o visualización que complementen,
sin sustituir, el interface de Visual dBASE. Sin embargo, es posible desactivar el
interface de Visual dBASE mediante SHELL( ).
• La ejecución del programa continúa hasta su conclusión y no se detiene cuando se muestran
las fichas. El resto del programa se ejecuta mediante controladores de sucesos que
responden a las acciones del usuario.
Un interface no modal está completamente controlado por sucesos. Una vez que se
muestra la ficha inicial, el usuario es quien controla la secuencia de acciones.
El código siguiente crea las fichas que se muestran en la Figura 13.1.
* EditCli.prg
*
SET PROCEDURE TO PROGRAM(1) ADDITIVE
DEFINE FORM EDITCLI FROM 12.5,24.5 TO 31.5,70.38;
PROPERTY;
EscExit .T.,;
View "EDITCLI.QBE",;
Text "Ficha dBASE",;
Minimize .F.,;
Maximize .F.
DEFINE TEXT TEXTO2 OF EditCli;
PROPERTY;
ColorNormal "N/W",;
Text "&Nombre",;
Width 10.00,;
Top 1.00,;
Left 2.00,;
Height 1.00

Capítulo 13, Creación de aplicaciones con fichas 203


intevnt.dwp Page 204 Wednesday, August 30, 1995 2:14 PM

DEFINE ENTRYFIELD ENTRADA1 OF EditCli;


PROPERTY;
ColorNormal "",;
Width 30.00,;
Top 1.00,;
Left 13.00,;
Height 1.50,;
Border .T.,;
DataLink "CLIENTE->NOMBRE"
*** El resto de textos y entradas siguen las mismas reglas que los anteriores. Se han
*** omitido del listado para ahorrar espacio.
DEFINE PUSHBUTTON BOTON1 OF EditCli;
PROPERTY;
OnClick BOTON1_ONCLICK,;
ColorNormal "",;
Text "Buscar",;
Width 10.00,;
Top 15.00,;
Left 17.00,;
Height 2.00
OPEN FORM EditCli && Abre la ficha como no modal
PROCEDURE BOTON1_OnClick
DO BUSCACLI.PRG
RETURN
El código siguiente crea el cuadro de diálogo modal para buscar un cliente por su
nombre.
* BuscaCli.prg
*
DEFINE FORM BuscaCli FROM 11,29.88 TO 18.25,72.20;
PROPERTY;
EscExit .T.,;
Text "Buscar un cliente",;
Minimize .F.,;
Maximize .F.,;
MDI .F. && Necesario para crear una ficha modal
DEFINE ENTRYFIELD ENTRADA1 OF BuscaCli;
PROPERTY;
ColorNormal "",;
Width 26.00,;
Top 1.00,;
Left 14.00,;
Height 2.00,;
Border .T.,;
Value "",;
MaxLength 25

204 Guía del programador


intevnt.dwp Page 205 Wednesday, August 30, 1995 2:14 PM

DEFINE TEXT TEXTO1 OF BuscaCli;


PROPERTY;
ColorNormal "N/W",;
Text "Buscar por nombre",;
Width 11.00,;
Top 1.00,;
Left 2.00,;
Height 2.00
DEFINE PUSHBUTTON BOTON1 OF BuscaCli;
PROPERTY;
OnClick BOTON1_ONCLICK,;
ColorNormal "",;
Text "Aceptar",;
Width 10.00,;
Top 5.00,;
Left 18.00,;
Height 2.00,;
Default .T.
DEFINE PUSHBUTTON BOTON2 OF BuscaCli;
PROPERTY;
OnClick {;CLOSE FORM BuscaCli},;
ColorNormal "",;
Text "Cancelar",;
Width 10.00,;
Top 5.00,;
Left 30.00,;
Height 2.00
BuscaCli.ReadModal() && Abre la ficha como modal
PROCEDURE Boton1_onclick
IF .NOT. EMPTY(BuscaCli.Entrada1.Value)
LOCAL lExact
lExact = SET("EXACT")
SET EXACT off
SEEK(UPPER(TRIM(BuscaCli.Entrada1.Value)))
SET EXACT &lExact
ENDIF
CLOSE FORM BuscaCli
RETURN

Capítulo 13, Creación de aplicaciones con fichas 205


intevnt.dwp Page 206 Wednesday, August 30, 1995 2:14 PM

Interface no modal orientado a objetos


Cree fichas mediante la declaración de clases personalizadas y la visualización de copias
de estas clases como fichas no modales. El Diseñador de fichas genera código orientado
a objetos. El enfoque orientado a objetos es el medio más versátil para implantar un
interface no modal controlado por sucesos.
Para más información sobre la programación orientada a objetos, consulte los
Capítulos 9 a 12.
El código siguiente, generado por el Diseñador de fichas, declara clases para crear las
fichas de la Figura 13.1.
* EditCli.wfm
* Generado el 03/30/94
*
LOCAL f
f = NEW EDITCLI ()
f.Open()
CLASS EDITCLI OF FORM
this.EscExit = .T.
this.View = "EDITCLI.QBE"
this.Text = "Editar clientes"
this.Width = 45.88
this.Top = 12.50
this.Left = 24.50
this.Height = 19.00
this.Minimize = .F.
this.Maximize = .F.
DEFINE TEXT TEXTO1 OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "&Nombre",;
Width 10.00,;
Top 1.00,;
Left 2.00,;
Height 1.00
DEFINE ENTRYFIELD ENTRADA1 OF THIS;
PROPERTY;
ColorNormal "",;
Width 30.00,;
Top 1.00,;
Left 13.00,;
Height 1.50,;
Border .T.,;
DataLink "CLIENTE->NOMBRE"

*** El resto de textos y entradas siguen las mismas reglas que los anteriores. Se han
*** omitido del listado para ahorrar espacio.

206 Guía del programador


intevnt.dwp Page 207 Wednesday, August 30, 1995 2:14 PM

DEFINE PUSHBUTTON BOTON1 OF THIS;


PROPERTY;
OnClick CLASS::BOTON1_ONCLICK,;
ColorNormal "",;
Text "Buscar",;
Width 10.00,;
Top 15.00,;
Left 17.00,;
Height 2.00
PROCEDURE BOTON1_OnClick
DO BUSCACLI.WFM
RETURN
ENDCLASS
El código siguiente crea el cuadro de diálogo modal para buscar un cliente por su
nombre.
* BuscaCli.wfm
* Generado el 03/30/94
*
LOCAL f
f = NEW BUSCACLI()
f.ReadModal()
CLASS BUSCACLI OF FORM
this.EscExit = .T.
this.Text = "Buscar un cliente"
this.Width = 42.38
this.Top = 11.00
this.Left = 29.88
this.Height = 8.25
this.Minimize = .F.
this.Maximize = .F.
this.MDI = .F.
DEFINE ENTRYFIELD ENTRADA1 OF THIS;
PROPERTY;
ColorNormal "",;
Width 26.00,;
Top 1.00,;
Left 14.00,;
Height 2.00,;
Border .T.,;
Value "",;
MaxLength 25

Capítulo 13, Creación de aplicaciones con fichas 207


intevnt.dwp Page 208 Wednesday, August 30, 1995 2:14 PM

DEFINE TEXT TEXTO1 OF THIS;


PROPERTY;
ColorNormal "N/W",;
Text "Introduzca un nombre",;
Width 11.00,;
Top 1.00,;
Left 2.00,;
Height 2.00
DEFINE PUSHBUTTON BOTON1 OF THIS;
PROPERTY;
OnClick CLASS::BOTON1_ONCLICK,;
ColorNormal "",;
Text "Aceptar",;
Width 10.00,;
Top 5.00,;
Left 18.00,;
Height 2.00,;
Default .T.
DEFINE PUSHBUTTON BOTON2 OF THIS;
PROPERTY;
OnClick {form.Close()},;
ColorNormal "",;
Text "Cancelar",;
Width 10.00,;
Top 5.00,;
Left 30.00,;
Height 2.00
PROCEDURE Boton1_onclick
IF .NOT. EMPTY(form.Entrada1.Value)
LOCAL lExact
lExact = SET("EXACT")
SET EXACT OFF
SEEK(UPPER(TRIM(form.Entrada1.Value)))
SET EXACT &lExact
ENDIF
form.Close()
RETURN

ENDCLASS

208 Guía del programador


intevnt.dwp Page 209 Wednesday, August 30, 1995 2:14 PM

Definición del comportamiento de la tecla Intro


En Visual dBASE, la pulsación de Intro cuando la ventana de ficha tiene el foco (pero éste
no está en un objeto editor), valida la ficha. (Para detalles sobre la validación de una
ficha, consulte el Capítulo 14.) Tab y Mayús+Tab mueven el foco a los objetos siguiente y
anterior, respectivamente.
Aunque el comportamiento de la tecla Intro en Windows es preferible en una aplicación
de Windows, si lo desea puede conservar el comportamiento del DOS para mantener la
coherencia con las aplicaciones dBASE DOS. Para ello, Visual dBASE dispone del
comando SET CUAENTER, que permite especificar el comportamiento de la tecla Intro.
• SET CUAENTER OFF provoca que Intro se comporte como en dBASE DOS,
desplazando el foco al objeto siguiente.
• SET CUAENTER ON sigue el comportamiento de Windows y valida la ficha.
El valor por omisión de SET CUAENTER es ON. Ctrl+Intro valida la ficha sea cual sea el
valor de SET CUAENTER.

Cómo aprender más


El modelo de interface de usuario controlado por sucesos que cuenta con más
aceptación es la norma CUA (acceso común de usuarios) desarrollada por IBM.
El propio entorno Windows adapta muchas de las directrices de la CUA, con ligeras
diferencias. Los objetos del interface de usuario presentes en el lenguaje de Visual
dBASE se diseñaron teniendo en cuenta la CUA y aportan la posibilidad de crear
interfaces de usuario conformes con esta norma.
Para ayudarle a diseñar los más eficaces interfaces de usuario controlados por sucesos,
se sugiere la lectura de las siguientes publicaciones:
• Object-oriented Interface Design: IBM Common User Access Guidelines, de IBM, presenta
los conceptos CUA y proporciona una referencia con directrices para el uso de cada
elemento del interface de usuario.
• The Windows Interface: An Application Design Guide, de Microsoft Press.

Capítulo 13, Creación de aplicaciones con fichas 209


intevnt.dwp Page 210 Wednesday, August 30, 1995 2:14 PM

210 Guía del programador


events.dwp Page 211 Wednesday, August 30, 1995 2:15 PM

Capítulo

Escritura de controladores
Capítulo 14
14
de sucesos
Cuando se programa con fichas, no se escribe código que se ejecute de forma secuencial.
Por el contrario, se escriben controladores de sucesos que se anexan a la ficha y/o a sus
controles y que se ejecutan cuando se producen los sucesos correspondientes.
Este capítulo explica cómo escribir controladores de sucesos, detalla la secuencia de su
ejecución y proporciona ejemplos de control de sucesos.
Para una introducción general a los programas controlados por sucesos, consulte el
Capítulo 2.

Acerca de los controladores de sucesos


Un controlador de sucesos es un procedimiento, función o bloque de código. El código
incluido en un controlador de sucesos sigue las normas habituales de control de flujo,
donde los comandos se ejecutan línea por línea y bifurcan o repiten su ejecución según
sentencias condicionales, como DO WHILE...ENDDO o IF...ENDIF. La diferencia con
los procedimientos tradicionales radica en el contexto en que se ejecutan.
En una aplicación tradicional controlada por menús, los procedimientos se llaman en
puntos fijos del código del programa. Para los programadores es más fácil conocer el
entorno en que se ejecutará el procedimiento, por ejemplo qué variables de memoria
están disponibles, porque pueden ver qué código se ejecuta antes del procedimiento.
En una aplicación controlada por sucesos, el programador simplemente asigna los
procedimientos de control de sucesos, que se ejecutan siempre que tiene lugar el suceso.
El procedimiento puede hacer pocas suposiciones en cuanto a las variables de memoria
que están disponibles. Debería ser realmente genérico, capaz de ejecutarse en cualquier
entorno dado del programa.

Capítulo 14, Escritura de controladores de sucesos 211


events.dwp Page 212 Wednesday, August 30, 1995 2:15 PM

La mejor forma de escribir controladores de sucesos es mediante referencias genéricas,


como this y form, a los objetos sobre los que actúa el código (como se muestra en el
ejemplo siguiente). El Capítulo 9 describe los detalles del uso de this y form para hacer
referencia a los miembros de un objeto.
El código siguiente crea una ficha que contiene un botón y asigna un controlador de
sucesos, AlertClick, al suceso OnClick del botón.
DEFINE FORM CompruSuc
DEFINE PUSHBUTTON MiBoton OF CompruSuc
CompruSuc.MiBoton.OnClick = AlertClick && Asigna un controlador de sucesos a OnClick
La Figura 14.1 muestra controladores de sucesos AlertClick equivalentes, uno con
referencias fijas y otro con referencias genéricas:
Figura 14.1 Ejemplo de controladores de sucesos con referencias fijas y genéricas

Controlador con referencias fijas Controlador con referencias genéricas


PROCEDURE AlertClick PROCEDURE AlertClick

* cambia el título de la ventana * cambia el título de la ventana


CompruSuc.Text = "¡Ha pulsado mi botón!" form.Text = "¡Ha pulsado mi botón!"

* cambia el texto del botón * cambia el texto del botón


CompruSuc.MiBoton.Text = "¡Hmphf!" this.Text = "¡Hmphf!"
RETURN RETURN

Los dos ejemplos de código anteriores realizan las mismas tareas, pero el de la izquierda
sólo funciona cuando se asigna a un botón denominado MiBoton de una ficha
denominada CompruSuc. El código de la derecha funciona con cualquier botón de
cualquier ficha.
Importante Las referencias this y form están disponibles sólo para los procedimientos vinculados
directamente a las propiedades de sucesos, no para los siguientes procedimientos que se
llamen. Si un procedimiento de control de sucesos llama a otro, y el segundo
procedimiento usa this o form, es necesario pasarle this o form como parámetro(s) al
segundo procedimiento.
El ejemplo siguiente modifica el ejemplo de la derecha de la Figura 14.1, pasando form a
un segundo procedimiento.
PROCEDURE AlertClick
form.Text = "!Ha pulsado mi botón!" && Cambia el título de la ventana
this.Text = "¡Hmphf!" && Cambia el texto del botón
DO Devuelve WITH form
RETURN

PROCEDURE Devuelve(RefForm)
RefForm.ColorNormal = "r/w*"
SLEEP 2
RefForm.ColorNormal = "r/w"
RETURN

212 Guía del programador


events.dwp Page 213 Wednesday, August 30, 1995 2:15 PM

Escritura de controladores de sucesos con el Diseñador de fichas


Los ejemplos de este capítulo se concentran en el código de control de sucesos.
Para crear fichas, probablemente usará el Diseñador de fichas y sus utilidades para
escribir el código. El ejemplo siguiente recorre el proceso de escribir y vincular
controladores de sucesos utilizando el Diseñador de fichas. Crea dos controladores de
sucesos para un botón que controla cuántas veces se ha hecho clic en él. (Para seguir este
ejemplo, debería estar familiarizado con el Diseñador de fichas y sus utilidades.
Para una introducción básica, consulte el Capítulo 8 de la Guía del usuario.)
1 Inicie el Diseñador de fichas y sitúe un botón en una ficha vacía.
2 Haga clic con el botón derecho del ratón sobre el botón y elija Inspector en su Menú
rápido para mostrar el Inspector.
Figura 14.2 Apertura del Inspector

3 Seleccione el suceso OnOpen del botón y haga clic en el botón Utilidades. Aparece el
editor de procedimientos.

Capítulo 14, Escritura de controladores de sucesos 213


events.dwp Page 214 Wednesday, August 30, 1995 2:15 PM

4 Teclee el breve procedimiento que se muestra en la Figura 14.3:


Figura 14.3 Controlador de sucesos OnOpen para un botón

La línea, this.Contar = 0, inicializa una propiedad personalizada del botón. Puesto que
el controlador de sucesos OnOpen está vinculado al botón, this hace referencia al objeto
de botón. Cuando el usuario abre la ficha, PUSHBUTTON_OnOpen se ejecuta y crea la
propiedad Contar del botón.
5 Cierre la ventana del editor de procedimientos.
Acaba de crear un controlador de sucesos vinculado al suceso OnOpen del botón.
Ahora, cree un controlador para el suceso OnClick.
6 Seleccione el suceso OnClick del botón en el Inspector y haga clic en el botón
Utilidades. Aparece un procedimiento vacío en el editor de procedimientos.

214 Guía del programador


events.dwp Page 215 Wednesday, August 30, 1995 2:15 PM

7 Teclee el breve procedimiento que se muestra en la Figura 14.4:


Figura 14.4 Controlador de sucesos OnClick para un botón

La primera línea de PUSHBUTTON1_OnClick incrementa la propiedad Contar.


La segunda línea hace referencia a la propiedad Text, que almacena el valor de Contar
convertido a cadena. La propiedad Text aparece como rótulo del botón.
8 Dejando abierto el editor de procedimientos, sitúe otro botón en la ficha.
Para que el segundo botón también controle sus clics, puede vincular los controladores
de sucesos del primer botón con el segundo sin necesidad de volver a escribir código.
9 Haga clic con el botón derecho del ratón en la ventana del editor de procedimientos
para mostrar su Menú rápido y, entonces, elija Vincular suceso para mostrar el
cuadro de diálogo Vincular suceso.

Capítulo 14, Escritura de controladores de sucesos 215


events.dwp Page 216 Wednesday, August 30, 1995 2:15 PM

10 Seleccione form.pushbutton2 en la lista de objetos y seleccione OnClick en la lista de


sucesos, como se muestra en la Figura 14.5:
Figura 14.5 Vinculación de un controlador de sucesos a otro suceso

El cuadro de diálogo Vincular suceso vincula el controlador de sucesos mostrado


actualmente en el editor de procedimientos con el suceso del objeto que elija.
11 Haga clic en Aceptar para vincular el suceso. El panel de la parte superior del editor
de procedimientos (que se muestra en la Figura 14.6) indica que el procedimiento está
vinculado con form.pushbutton1.OnClick y form.pushbutton2.OnClick.
Figura 14.6 Controlador de sucesos vinculado a dos sucesos

Este panel muestra los sucesos con que está vinculado


PUSHBUTTON1_OnClick.

Este cuadro combinado muestra PUSHBUTTON1_OnClick


como procedimiento actual.

12 Seleccione el procedimiento PUSHBUTTON1_OnOpen en el cuadro combinado


(que se muestra en la Figura 14.6) y realice los mismos pasos para vincularlo con el
suceso OnOpen del segundo botón.
13 Guarde la ficha, cambie al modo de ejecución y haga clic en cada uno de los botones
varias veces.
Observe que cada botón tiene su propio contador.

216 Guía del programador


events.dwp Page 217 Wednesday, August 30, 1995 2:15 PM

Figura 14.7 Ficha de ejemplo con dos botones

Este ejemplo demuestra los siguientes aspectos importantes:


• La creación de la variable Contar como propiedad del objeto de botón encapsula la
variable, impidiendo así que se sobrescriba. Ambos botones tienen una propiedad
Contar, pero cada una se actualiza de forma independiente.
• El uso de this para hacer referencia a las propiedades hace que los controladores de
sucesos sean genéricos y puedan reutilizarse. Las expresiones this.Contar y this.Text
hacen referencia a las propiedades Contar y Text del objeto con que estén vinculados
los controladores de sucesos. Si hubiera especificado la referencia al nombre del
objeto (mediante pushbutton1.Contar en lugar de this.Contar), no podría vincular con
facilidad el controlador de sucesos con otro botón.

Averiguación de cuándo tienen lugar los sucesos


No todos los sucesos son provocados directamente por el usuario. Algunos sucesos
provocan que ocurran otros. Por ejemplo, un clic en un botón que no tiene ya el foco
intenta darle el foco. Por tanto, antes de ejecutar el controlador de sucesos OnClick del
botón:
1 Se ejecuta su controlador de sucesos When para determinar si el botón puede recibir
el foco.
2 Si el botón puede recibir el foco, se ejecuta su controlador de sucesos OnGotFocus.
3 Entonces, se ejecuta el controlador de sucesos Onclick del botón.
La secuencia de ejecución de los controladores de sucesos se complica más si otro
control tiene el foco cuando el usuario hace clic en un botón. Es posible que el control
que pierde el foco tenga controladores de sucesos (como Valid y OnLostFocus) que se
insertan en la secuencia de ejecución.
Si un solo clic del ratón puede provocar la ejecución de cinco o más controladores de
sucesos, puede parecer muy difícil la tarea de asignar a cada control los controladores
de sucesos adecuados. En realidad, es más sencillo de lo que parece. No es necesario
tener en cuenta todos los sucesos que pueden tener lugar mientras una ficha está activa,
sino sólo los que tienen importancia para la ficha. Si no se asigna código a una
propiedad de un controlador de sucesos, Visual dBASE pasa al siguiente controlador de
sucesos hasta procesar todos los sucesos.

Capítulo 14, Escritura de controladores de sucesos 217


events.dwp Page 218 Wednesday, August 30, 1995 2:15 PM

El programa siguiente demuestra una forma sencilla de experimentar con los


controladores de sucesos y aprender cómo se ejecutan. Asigna bloque de código para
cada suceso, que muestra un mensaje en la ventana de comandos indicando que se
produjo el suceso. Al ejecutar la ficha, verá qué sucesos tienen lugar conforme pase de
un botón a otro.
* VerSuces.wfm
LOCAL f
f = NEW VERSUCES ()
f.Open()
CLASS VERSUCES OF FORM
this.OnSize = {;? "OnSize"}
this.OnMouseMove = {;? "OnMouseMove"}
this.OnRightMouseUp = {;? "OnRightMouseUp"}
this.OnRightMouseDown = {;? "OnRightMouseDown"}
this.OnLeftMouseUp = {;? "OnLeftMouseUp"}
this.OnLeftMouseDown = {;? "OnLeftMouseDown"}
this.OnLostFocus = {;? "OnLostFocus"}
this.OnGotFocus = {;? "OnGotFocus"}
this.OnOpen = {;? "OnOpen "}
this.OnSelection = {;? "OnSelection"}
this.OnClose = {;? "OnClose "}
this.OnMove = {;? "OnMove"}
this.Text = "¡Aprenda acerca de los sucesos de fichas!"
this.Width = 30.00
this.Height = 10.00
this.Top = 21.17
this.Left = 31.00
DEFINE PUSHBUTTON P1 OF THIS;
PROPERTY;
OnRightMouseUp {;? "P1.OnRightMouseUp"},;
OnRightMouseDown {;? "P1.OnRightMouseDown"},;
OnLeftMouseUp {;? "P1.OnLeftMouseUp"},;
OnLeftMouseDown {;? "P1.OnLeftMouseDown"},;
OnLostFocus {;? "P1.OnLostFocus"},;
OnGotFocus {;? "P1.OnGotFocus"},;
OnClick {;? "P1.Onclick"},;
Text "Botón 1",;
Width 8.00,;
Height 2.00,;
Top 2.00,;
Left 2.00,;
Group .T.
DEFINE PUSHBUTTON P2 OF THIS;
PROPERTY;
OnRightMouseUp {;? "P2.OnRightMouseUp"},;
OnRightMouseDown {;? "P2.OnRightMouseDown"},;
OnLeftMouseUp {;? "P2.OnLeftMouseUp"},;
OnLeftMouseDown {;? "P2.OnLeftMouseDown"},;
OnLostFocus {;? "P2.OnLostFocus"},;
OnGotFocus {;? "P2.OnGotFocus"},;
OnClick {;? "P2.Onclick"},;

218 Guía del programador


events.dwp Page 219 Wednesday, August 30, 1995 2:15 PM

Text "Botón 2",;


Width 8.00,;
Height 2.00,;
Top 2.00,;
Left 12.00,;
Group .T.
ENDCLASS
La Figura 14.8 muestra la ejecución de VERSUCES.WFM.

Figura 14.8 VERSUCES.WFM

Escritura de código de inicialización y salida de las fichas


Algunas fichas, en especial las basadas en tablas, pueden necesitar código de
inicialización para configurar el entorno al abrir la ficha. De forma parecida, algunas
fichas precisan la ejecución de un código de salida cuando se cierra la ficha. Utilice el
suceso OnOpen de la ficha para asignar código de inicialización, y CanClose y OnClose
para asignar el de salida. Además, cada control estándar de la ficha tiene un suceso
OnOpen que se ejecuta cuando se abre la ficha. La asignación de código al suceso
OnOpen de cada control permite realizar la inicialización control por control.
• Los controladores de sucesos OnOpen suelen realizar procedimientos de
inicialización, como declarar variables de memoria o abrir tablas no especificadas en
la propiedad View de la ficha.
• Los controladores de sucesos CanClose sirven para determinar si existen condiciones
tales que pueda cerrarse una ficha. El suceso CanClose se ejecuta antes de que la ficha
se cierre efectivamente, y por tanto antes del suceso OnClose. CanClose devuelve un
valor lógico, que debe ser .T. para que la ficha se cierre. CanClose puede emplearse
para tareas de salida que deben realizarse mientras la ficha aún esté abierta.

Capítulo 14, Escritura de controladores de sucesos 219


events.dwp Page 220 Wednesday, August 30, 1995 2:15 PM

• Los controladores de sucesos OnClose suelen realizar procedimientos de salida,


como liberar la ficha o las variables de memoria. El controlador de sucesos CanClose,
si lo hay, debe devolver .T. para poder cerrar la ficha.
• Al crear fichas con el Diseñador de fichas, también es posible introducir código de
inicialización en la sección de cabecera del archivo .WFM. El código de esa sección no
se ejecuta siempre que se abre la ficha; sólo lo hace cada vez que se ejecuta el archivo
.WFM. El Capítulo 16 muestra cómo escribir código en la cabecera de los archivos
.WFM.
Muchos sucesos pueden ejecutarse cuando se abren y se cierran las fichas.
Las dos secciones siguientes detallan la secuencia de ejecución.

Ejecución de sucesos al abrir una ficha


Cuando el usuario abre una ficha, la ventana de la ficha recibe el foco. Además, se
evalúan todos los controles de la ficha para comprobar si pueden recibir el foco, y el
primer control disponible lo recibe. La Figura 14.9 representa la secuencia de ejecución
de los controladores de sucesos cuando se abre una ficha con tres campos de entrada
(E1, E2 y E3):
Figura 14.9 Secuencia de sucesos al abrir una ficha que tiene tres controles

.T. o .F. .T. o .F.


form.E1.When form.E2.When form.E3.When

.T. o .F.
¿Al menos un .F.
When de control La ficha no se
evalúa como .T.? abre.

.T.

form.E1.OnGotFocus form.OnGotFocus form.OnOpen

form.E3.OnOpen form.E2.OnOpen form.E1.OnOpen

Si abre una ficha sin que haya otras fichas abiertas previamente:
1 Se ejecutan los controladores de sucesos When de todos los controles de la ficha.
Si hay al menos uno que se evalúe como .T., continúe en el paso 2. De lo contrario, la
ficha no se abre.
2 Se ejecuta el controlador de sucesos OnOpen de la ficha. La ventana se abre y recibe el
foco.
3 Se ejecuta el controlador de sucesos OnOpen de todos los controles de la ficha.

220 Guía del programador


events.dwp Page 221 Wednesday, August 30, 1995 2:15 PM

4 Se ejecuta el controlador de sucesos OnGotFocus de la ficha. El primer control en el


orden de tabulación recibe el foco.
5 Se ejecuta el controlador de sucesos OnGotFocus del primer control en el orden de
tabulación.

Ejecución de sucesos al cerrar una ficha


Cuando el usuario cierra una ficha, la ventana de la ficha y el control actual pierden el
foco, y la ficha desaparece de la pantalla. La Figura 14.10 representa la secuencia de
ejecución de los controladores de sucesos cuando se cierra una ficha:
Figura 14.10 Secuencia de sucesos al cerrar una ficha

Valid del .T. OnLostFocus form.OnLostFocus form.OnClose


control actual del control actual
.F.

La ficha no se
cierra.

Cuando se cierra una ficha:


1 Se ejecuta el controlador de sucesos Valid del control actual. Si devuelve .F., no puede
cerrarse la ficha.
2 Se ejecuta el controlador de sucesos OnLostFocus del control actual.
3 Se ejecuta el controlador de sucesos CanClose de la ficha. Si devuelve .F., no puede
cerrarse la ficha.
4 Si la ficha tiene el foco, se ejecuta el controlador de sucesos OnLostFocus de la ficha.
5 Se ejecuta el controlador de sucesos OnClose de la ficha.

Capítulo 14, Escritura de controladores de sucesos 221


events.dwp Page 222 Wednesday, August 30, 1995 2:15 PM

Ejecución de procedimientos
El controlador de sucesos más común es uno que realiza una acción en respuesta a un
clic del ratón. Estos controladores se vinculan con los sucesos OnClick de botones u
opciones de menú. Los controladores de sucesos OnClick pueden ser de cualquier tipo,
aunque muchos realizan acciones simples como cerrar la ficha actual o abrir otra.
El siguiente controlador de sucesos OnClick puede anexarse a un botón Aceptar o a una
opción de menú Archivo|Salir:
PROCEDURE CierraFicha
form.Close()
form.Release()
RETURN
Los sucesos OnClick se gestionan en las fichas:
• Asignando subrutinas directamente a la propiedad OnClick de botones u opciones
de menú. Es el método más recomendable en la mayoría de fichas.
• Gestionándolos para toda la ficha con ON SELECTION o con el sucesos OnSelection.
Este método podría ser preferible para los programadores familiarizados con las
técnicas de menús de dBASE DOS o para los acostumbrados a las técnicas de
programación de Windows.
Si se hace clic en un botón o se elige una opción de menú, se valida una ficha. Cuando el
usuario valida una ficha, ocurre lo siguiente:
• Si la ficha contiene uno o más botones, y el seleccionado (o el botón por defecto si no
se seleccionó ninguno) tiene un controlador de sucesos OnClick, se ejecuta éste.
• Se ejecuta el controlador de sucesos OnSelection de la ficha o la subrutina ON
SELECTION (lo que se haya especificado).
El valor de la propiedad Id del botón seleccionado (o por defecto), o de la opción de
menú elegida, se pasa al controlador de sucesos OnSelection o al procedimiento ON
SELECTION.
El ejemplo siguiente muestra una declaración de clase de una ficha que contiene cuatro
campos de entrada y un botón. El usuario pulsa Intro en un campo para marcarlo y
desactivarlo. El clic en el botón de comando desmarca todos los campos de entrada.
El controlador de sucesos OnSelection utiliza el parámetro Id para determinar qué
campo desactivar.
CLASS MarcaCampos OF FORM
this.OnSelection = CLASS::FICHA_ONSELECTION
this.Text = "Mire como funciona OnSelection"
this.Width = 55.38
this.Top = 6.19
this.Left = 30.75
this.Height = 16.00
this.Minimize = .F.
this.Maximize = .F.

222 Guía del programador


events.dwp Page 223 Wednesday, August 30, 1995 2:15 PM

DEFINE TEXT TEXTO1 OF THIS;


PROPERTY;
Text "Pulse Intro en un campo para marcarlo",;
Width 49.00,;
Top 1.00,;
Left 6.00,;
Height 1.00
DEFINE ENTRYFIELD ENTRADA1 OF THIS;
PROPERTY;
Width 11.00,;
Top 4.00,;
Left 4.00,;
Height 2.00,;
Value "",;
ID 1 && Asigna un identificador a cada campo
DEFINE ENTRYFIELD ENTRADA2 OF THIS;
PROPERTY;
Width 11.00,;
Top 4.00,;
Left 19.00,;
Height 2.00,;
Value "",;
ID 2 && Asigna un identificador a cada campo
DEFINE ENTRYFIELD ENTRADA3 OF THIS;
PROPERTY;
Width 11.00,;
Top 8.00,;
Left 4.00,;
Height 2.00,;
Value "",;
ID 3 && Asigna un identificador a cada campo
DEFINE ENTRYFIELD ENTRADA4 OF THIS;
PROPERTY;
Width 11.00,;
Top 8.00,;
Left 19.00,;
Height 2.00,;
Value " ",;
ID 4 && Asigna un identificador a cada campo
DEFINE PUSHBUTTON BOTON1 OF THIS;
PROPERTY;
OnClick CLASS::BOTON1ONCLICK,;
Text "Desmarcar todos los campos",;
Width 15.00,;
Top 12.00,;
Left 10.00,;
Height 3.00

Capítulo 14, Escritura de controladores de sucesos 223


events.dwp Page 224 Wednesday, August 30, 1995 2:15 PM

PROCEDURE Ficha_OnSelection(Identif) && Recibe el identificador como parámetro


DO CASE
CASE Identif = 1
form.entrada1.colornormal = 'w/gb'
form.entrada1.enabled = .f.
form.entrada1.before.setfocus()
CASE Identif = 2
form.entrada2.colornormal = 'w/gb'
form.entrada2.enabled = .f.
form.entrada2.before.setfocus()
CASE Identif = 3
form.entrada3.colornormal='w/gb'
form.entrada3.enabled = .f.
form.entrada3.before.setfocus()
CASE Identif = 4
form.entrada4.colornormal='w/gb'
form.entrada4.enabled = .f.
form.entrada4.before.setfocus()
ENDCASE
RETURN
PROCEDURE boton1onclick
form.entrada1.enabled = .t.
form.entrada2.enabled = .t.
form.entrada3.enabled = .t.
form.entrada4.enabled = .t.
form.entrada1.ColorNormal = "n/w"
form.entrada2.ColorNormal = "n/w"
form.entrada3.ColorNormal = "n/w"
form.entrada4.ColorNormal = "n/w"
ENDCLASS

224 Guía del programador


events.dwp Page 225 Wednesday, August 30, 1995 2:15 PM

Trabajo con más de una ficha


Visual dBASE facilita la creación de aplicaciones con muchas fichas. Cuando se abre más
de una ficha, es posible proporcionar una sesión de datos independiente para cada una
o hacer que compartan una vista. Cualquier ficha puede abrirse también como ventana
no modal o como cuadro de diálogo modal. El comando CREATE SESSION y la
propiedad View de la ficha proporcionan un entorno de datos independiente a las
fichas. La nueva propiedad DesignView de la ficha simplifica el diseño de fichas que
compartan datos. Al guardar una ficha, el diseñador incluye un parámetro opcional
para abrir la ficha como cuadro de diálogo.

Uso de sesiones para crear entornos de datos independientes


El comportamiento por defecto del Selector de dBASE abre fichas en sesiones aparte.
Cuando una ficha abre y cierra tablas, las demás fichas no se ven afectadas. Esta
conducta es adecuada para las fichas que no emplean las mismas tablas o vistas, y
permite que el usuario cambie de una ficha a otra como si fueran aplicaciones distintas.
Muchas aplicaciones utilizan una ficha o menú principal para abrir otras fichas.
Es posible abrir fichas adicionales en cualquier suceso. Los casos más habituales son el
OnClick de menús y botones. En ambos casos, la apertura de una ficha adicional implica
tres pasos, como se muestra en el código siguiente.
Procedure PUSHBUTTON1_OnClick
CREATE SESSION && paso 1 - crea una sesión
SET TALK OFF && paso 2 - parámetros específicos de la sesión
DO form2.wfm && paso 3 - abre la ficha
El primer paso es crear una sesión, que debe existir antes de crear una copia de la ficha.
Las sesiones se enlazan con la siguiente ficha que se defina. Si define una antes de crear
una sesión, no se vinculan.
El segundo paso es crear parámetros específicos de la sesión. Crear una sesión es similar
a iniciar otra copia de dBASE. Cada nueva sesión comienza con un entorno limpio. Los
parámetros que se aplicaban a la sesión anterior, no se aplican a la actual. Aunque TALK
estuviera como OFF en la sesión anterior, será ON por defecto en la nueva.
El tercer paso es abrir la ficha. Para abrirla como ventana no modal, basta que ejecute
DO con el archivo WFM. Si no incluye la extensión WFM, dBASE busca un PRG.
Si desea que cierta ficha se abra siempre en otra sesión, puede añadir el código para
crear una sesión y definir el entorno en la sección de encabezado de la ficha. Muchas de
las fichas de ejemplo que vienen con Visual dBASE utilizan esta técnica.

Capítulo 14, Escritura de controladores de sucesos 225


events.dwp Page 226 Wednesday, August 30, 1995 2:15 PM

Uso compartido del entorno de datos entre las fichas


Si dispone de dos fichas que trabajan con las mismas tablas o vistas, quizá desee
mantenerlas en la misma sesión. Esto es práctico para las fichas que proporcionan
disposiciones diferentes para los mismos datos. Por ejemplo, suponga que utiliza una
ficha de búsqueda con un control Browse para localizar registros rápidamente y una
ficha de actualización que muestra un solo registro para editar una tabla. En este caso,
puede hacer que las dos fichas compartan la misma vista en la misma sesión. Así, se
asegura de que el desplazamiento del puntero de registro con el control Browse de la
primera ficha también lo mueve en la segunda.
Para crear fichas que comparten una vista, es útil seleccionar que una ficha se abra en
primer lugar. En el caso de las fichas de búsqueda y actualización, la primera que debe
abrirse es la de búsqueda, la cual puede diseñar y abrir como haría con cualquier otra
ficha. Es decir, puede definir la propiedad View y abrirla dentro de una sesión.
Para una segunda ficha, como la de actualización, no defina la propiedad View, sino
DesignView. Esto proporciona un entorno de datos en el Diseñador de fichas.
DesignView le permite utilizar la Paleta de campos para añadir rápidamente campos, y
los Generadores de propiedades visuales para definir las propiedades DataLink y
DataSource.
Si definiera la propiedad View para la segunda ficha y la ejecutara en la misma sesión
que la primera, se cerrarían y reabrirían las tablas. Eso resitúa el puntero de registro, que
no suele ser lo deseado al compartir datos entre fichas. La propiedad DesignView sólo
crea la vista cuando se está en el Diseñador de fichas, por lo que debe asegurarse de que
exista el entorno de datos adecuado en el momento de la ejecución.
Para abrir la segunda ficha que compartirá datos con la primera, ejecute el archivo WFM
desde un suceso de la primera ficha sin crear una sesión. Siguiendo con el ejemplo de
búsqueda y actualización, podría crear un UpdateButton en la ficha de búsqueda que
abriera la de actualización. El OnClick necesita una sola línea de código que puede
aparecer en un bloque de código. El siguiente es un ejemplo de llamada a una ficha
denominada Actualiz.WFM desde el suceso OnClick de un UpdateButton.
Form.UpdateButton.OnClick = { ; DO Actualiz.WFM }

226 Guía del programador


events.dwp Page 227 Wednesday, August 30, 1995 2:15 PM

Validación de datos
La validación de los datos introducidos por el usuario es una tarea importante en las
fichas de entrada de datos. Las tres propiedades siguientes se complementan para
realizar la validación de datos en los objetos de campo de entrada, cuadro de
incremento, cuadro combinado y Browse:
• Valid se ejecuta cuando el usuario realiza un cambio en el valor del control e intenta
salir de él (por ejemplo, pulsando Tab). El controlador de sucesos Valid debe evaluar
el nuevo valor y devolver .T. si es válido o .F. si no lo es. El valor devuelto .F. impide
que el usuario pueda salir del control.
• ValidRequired determina si el controlador de sucesos Valid se ejecuta cada vez que el
foco sale del control (incluso cuando contiene un valor por defecto) o sólo cuando el
usuario efectúa cambios.
• ValidErrorMsg especifica un mensaje que se muestra en la barra de estado cuando el
controlador de sucesos Valid devuelve un valor de .F.
El siguiente es un ejemplo del uso de estas propiedades para validar datos.
Si solicita al usuario que introduzca una hora, es lógico que desee asegurarse de que sea
una hora válida. El siguiente controlador de sucesos de la propiedad Valid valida la
hora introducida por el usuario.
DEFINE ENTRYFIELD Tiempo OF FichaTiempo; && Entrada para la hora
PROPERTY;
Left 15,;
Width 5,;
Picture "99:99",;
Valid ValHora,; && Asigna un controlador de sucesos
ValidRequired .T.,; && Se quiere validar
ValidErrorMsg "Hora no válida",; && Asigna un mensaje de error

PROCEDURE Valhora && Procedimiento para validar la hora


LOCAL Hrs,Mins && Variables locales para valores
&& temporales
Hrs=VAL(LEFT(this.Value,2)) && Extrae la hora
Mins=VAL(RIGHT(this.Value,2)) && Extrae los minutos
IF Hrs<0 .OR. Hrs>23 .OR. Mins<0 .OR. Mins>59 && Comprueba los valores
EsValida =.F.
ELSE
EsValida =.T.
ENDIF
RETURN EsValida

Capítulo 14, Escritura de controladores de sucesos 227


events.dwp Page 228 Wednesday, August 30, 1995 2:15 PM

Respuesta a los cambios en los datos


Cuando el usuario cambia el valor de un control, va a otro registro o añade uno, la ficha
puede responder mediante los sucesos OnChange, OnNavigate y OnAppend,
respectivamente.
OnChange
El ejemplo siguiente asigna el procedimiento ColorBalance al suceso OnChange del
campo de entrada Balance. Si el valor del campo es positivo, ColorBalance cambia el
color de fondo a verde; si es negativo, lo cambia a rojo.
DEFINE ENTRYFIELD Balance OF DeclaracionFicha;
PROPERTY;
OnChange CLASS::ColorBalance,; && Asigna un controlador de sucesos
ColorNormal "",;
ColorHighLight "",;
Width 24.00,;
Top 5.00,;
Left 8.00,;
Height 1.00,;
Picture "99999.99",;

PROCEDURE ColorBalance && Procedimiento para asignar color a la


&& entrada del balance
IF form.Balance.Value < 0 && Si el balance es negativo
this.colorhighlight = "w+/r" && cambia el fondo a rojo
ELSE && Si es positivo
this.colorhighlight = "w+/g" && cambia el fondo a verde
ENDIF
RETURN
Consulte un ejemplo similar que utiliza OnNavigate en la página 230.
OnChange, When
El ejemplo siguiente muestra un cuadro de diálogo para introducir pagos. El usuario
selecciona la forma de pago por medio de un cuadro combinado. En función de la
selección, el controlador de sucesos OnChange del cuadro combinado cambia el texto de
cabecera de un campo de entrada que solicita un número de cheque o de tarjeta de
crédito.
* Pagos.prg
DECLARE FormaPago[3] && Matriz para visualizar las formas de pago
FormaPago[1]="Efectivo"
FormaPago[2]="Cheque"
FormaPago[3]="Cargo"
LOCAL f
f = NEW Pago() && Crea la ficha
f.ReadModal() && Abre la ficha
CLASS Pago OF FORM
this.Text = "Introduzca un pago"
this.MDI = .F.
this.Width = 55.00
this.Top = 2.00

228 Guía del programador


events.dwp Page 229 Wednesday, August 30, 1995 2:15 PM

this.Left = 2.00
this.Height = 15.00
DEFINE TEXT TEXTO1 OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Forma de pago",;
Width len(this.text),;
Top 2.00,;
Left 4.00,;
Height 2.00
DEFINE COMBOBOX CUADROELEM1 OF THIS;
PROPERTY;
Width 16.00,;
Top 3,;
Left 4.00,;
Height 6.50,;
Style 1,;
DataSource "ARRAY FormaPago",; && Enlaza el cuadro combinado con la matriz
Value FormaPago[1],; && Inicializa Value con el primer elemento
OnChange class::CambiaCuadElem && Asigna un controlador de sucesos
DEFINE TEXT TEXTO2 OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text " ",;
Width len(this.text)+15,;
Top 2.00,;
Left 22.00,;
Height 2.00
DEFINE ENTRYFIELD ENTRADA1 OF THIS;
PROPERTY;
Width 17.00,;
Top 3.00,;
Left 20.00,;
Height 2.00,;
Value 0,;
Border .T.,;
When {form.Texto2.Text <> " "} && Accesible solo si el pago no es en efectivo
DEFINE TEXT TEXTO3 OF THIS;
PROPERTY;
ColorNormal "N/W",;
Text "Cantidad pagada",;
Width len(this.text)+5,;
Top 6.00,;
Left 20.00,;
Height 2.00
DEFINE ENTRYFIELD ENTRADA2 OF THIS;
PROPERTY;
Width 17.00,;
Top 7.00,;
Left 20.00,;
Height 2.00,;

Capítulo 14, Escritura de controladores de sucesos 229


events.dwp Page 230 Wednesday, August 30, 1995 2:15 PM

Value 0,;
Border .T.,;
Picture "999999.99"

PROCEDURE CambiaCuadElem && Controlador de sucesos para el cuadro


&& combinado
DO CASE && Muestra el texto de la cabecera para Entrada1
CASE this.Value = "Efectivo"
form.Texto2.Text = " "
CASE this.Value = "Cheque"
form.Texto2.Text = "Número de cheque"
CASE this.Value = "Cargo"
form.Texto2.Text = "Número tarjeta de crédito"
ENDCASE
RETURN
ENDCLASS
CanNavigate, OnNavigate
El ejemplo siguiente asigna bloque de código a los sucesos CanNavigate y OnNavigate
de un objeto Browse. El suceso CanNavigate comprueba que el archivo se ha bloqueado
satisfactoriamente antes de permitir que el usuario se desplace a otro registro. La
propiedad ShowRecNo se define como .F. para eliminar la columna de número de
registro. En su lugar, el controlador de sucesos OnNavigate define la propiedad Text de
un objeto de texto para que muestre el número de registro actual.
DEFINE BROWSE EXAMINA1 OF THIS;
PROPERTY;
CanNavigate [| |; FLOCK()]
OnNavigate {;form.DisplayRec.Text = "Registro:"+STR(RECNO(),6)},;
Width 49.25,;
Top 15.00,;
Left 7.00,;
Height 9.13,;
ShowRecNo .F.,;
Alias "CLIENT"
El ejemplo siguiente cambia el color de un campo de entrada Balance vinculado con una
tabla de cuentas por recibir. Si la cantidad es positiva, el color se define como verde; si es
negativa, cambia a rojo. Este controlador de sucesos podría asignarse al suceso
OnNavigate de una ficha que contenga el campo Balance.
PROCEDURE ColorBalance
IF form.Balance.Value < 0 && Si el balance es negativo
this.colorhighlight = "w+/r" && cambia el color de fondo a rojo
ELSE && Si es positivo
this.colorhighlight = "w+/g" && cambia el color de fondo a verde
ENDIF
RETURN

230 Guía del programador


events.dwp Page 231 Wednesday, August 30, 1995 2:15 PM

Refresh( )
En ocasiones, si una ficha utiliza datos que pueden cambiar una fuente situada fuera de
la ficha (por ejemplo, otra ficha), quizá sea necesario forzar que la ficha actualice sus
vínculos de datos para asegurar que se muestran los datos más actuales. Para lograrlo,
utilice el método Refresh( ) de la ficha. El ejemplo siguiente muestra cómo puede
emplearse Refresh( ) en el controlador de sucesos OnGotFocus para que los datos se
actualicen siempre que la ficha reciba el foco.
PUBLIC f
USE ANIMALES
f = NEW fForm()
f.open()

CLASS fForm OF Form()


This.OnGotFocus = {;This.Refresh()}
DEFINE BROWSE b OF This;
PROPERTY;
Height Form.Height,;
Left 0,;
Top 0,;
Width Form.Width
ENDCLASS

Gestión de los sucesos de cambio de foco


Para gestionar un cambio de foco en la mayoría de las fichas, basta definir
StatusMessage o ColorHighlight para que muestren un mensaje o cambien el color.
Para un mayor control, es posible gestionar el cambio de foco de muchos objetos, e
incluso de la propia ventana de la ficha, mediante OnGotFocus y OnLostFocus.
Por ejemplo, podría desear cambiar qué opciones de menú se ofrecen en función del
control que esté seleccionado actualmente. Los siguientes controladores de sucesos
corresponden a las propiedades OnGotFocus y OnLostFocus de un objeto Browse,
respectivamente. Cuando el objeto Browse recibe el foco, se activa el menú Edición
previamente definido; cuando pierde el foco, se atenúa el menú.
PROCEDURE BrowseMenus && Se asigna a la propiedad OnGotFocus de un
&& objeto Browse
form.Principal.Edita.Enabled = .T.
RETURN
PROCEDURE NoBrowseMenus && Se asigna a la propiedad OnLostFocus de un
&& objeto Browse
form.Principal.Edita.Enabled = .F.
RETURN
Muchos sucesos pueden ejecutarse cuando las fichas o controles cambian el foco.
Las dos secciones siguientes detallan la secuencia de ejecución de los sucesos.

Capítulo 14, Escritura de controladores de sucesos 231


events.dwp Page 232 Wednesday, August 30, 1995 2:15 PM

Secuencia de ejecución al desplazarse entre controles


Los usuarios inician sucesos cuando mueven el foco entre los controles de una ficha.
La Figura 14.11 representa el flujo de sucesos cuando el foco está en un campo de
entrada (E1) y el usuario hace clic en otro campo de entrada (E2).
Figura 14.11 Desplazamiento entre controles de la misma ficha

.T. .T.
form.E1.Valid form.E2.When form.E1.OnLostFocus form.E2.OnGotFocus

.F. .F.

El foco sigue en E1.

Cuando el campo E1 tiene el foco y el usuario pulsa Tab para ir a E2:


1 Se ejecuta el controlador de sucesos Valid de E1. Si devuelve .T., continúe en el paso
2. De lo contrario, E1 falla su prueba de validación y el foco permanece en E1.
2 Se ejecuta el controlador de sucesos When de E2. Si devuelve .T., continúe en el
paso 3. De lo contrario, E2 no puede recibir el foco, que permanece en E1.
3 E1 acaba de perder el foco, por lo que se ejecuta el controlador de sucesos
OnLostFocus de E1. El foco pasa a E2.
4 E2 acaba de recibir el foco, por lo que se ejecuta el controlador de sucesos
OnGotFocus de E2.

Secuencia de ejecución al desplazarse entre fichas


Cuando las fichas se abren con OPEN FORM, es posible mostrar dos o más al mismo
tiempo. Aunque todas las fichas abiertas están disponibles, sólo una tiene el foco en
cada momento. Un usuario asigna el foco a una ficha haciendo clic en el cuerpo de la
ficha o pulsando Ctrl+F6 para pasar cíclicamente por las fichas MDI abiertas.
Cuando el foco cambia de una ficha (la actual) a otra (la nueva):
1 La ficha actual pierde el foco y se ejecuta su controlador de sucesos OnLostFocus.
2 La nueva ficha recibe el foco y se ejecuta su controlador de sucesos OnGotFocus.
3 Siempre que una ficha recibe el foco, uno de sus controles tiene el foco.
• Si usuario ha cambiado el foco sin seleccionar un control de la nueva ficha, por
ejemplo pulsando Ctrl+F6, no hay ningún cambio de foco en el control de la nueva
ficha y no se produce ninguna otra ejecución.
• Si el usuario selecciona específicamente un control de la nueva ficha, por ejemplo
haciendo clic en él, se ejecuta la secuencia de sucesos mencionada en
“Secuencia de ejecución al desplazarse entre controles”.

232 Guía del programador


events.dwp Page 233 Wednesday, August 30, 1995 2:15 PM

Provisión de ayuda en línea


La mayoría de aplicaciones proporcionan algún nivel de ayuda para el usuario.
El tipo más simple de ayuda es un objeto de texto que etiqueta el contenido de otro
objeto, por ejemplo “Introduzca el nombre” junto a un campo de entrada de nombres.
También es posible mostrar mensajes en la barra de estado, mediante la propiedad
StatusMessage, que aparecen cuando el objeto recibe el foco.
Para proveer ayuda más amplia, puede mostrar ventanas de ayuda mediante el suceso
OnHelp. OnHelp se ejecuta cuando el usuario pulsa F1. Asigne un controlador al suceso
OnHelp de una ficha para mostrar Ayuda para el conjunto de la ficha o asigne un
controlador de sucesos OnHelp a cada control de la ficha para dar ayuda contextual.
Por ejemplo, puede crear una tabla que contenga texto de ayuda, con un registro por
cada ficha de la aplicación y el texto de ayuda almacenado en un campo memo.
Asigne el procedimiento siguiente al suceso OnHelp de cada ficha.
PROCEDURE MuestraAyuda
AreaActual = WORKAREA() && Guarda el área de trabajo actual
SELECT ArchAyud && ARCHAYUD.dbf se abre con el alias ArchAyud
SEEK(this.Text) && La propiedad Text de la ficha sirve como clave de
&& búsqueda
IF FOUND() && Muestra una ficha llamada VentanaAyuda, la cual
OPEN FORM VentanaAyuda && contiene un objeto editor enlazado con un campo memo
ENDIF
SELECT (AreaActual) && Selecciona el área de trabajo original
RETURN
Los programadores familiarizados con la utilidad Ayuda de Windows pueden crear un
archivo de ayuda Windows (.HLP) para una aplicación de Visual dBASE. Para ello, es
necesario el Compilador de ayuda de Windows. En un archivo .HLP, una palabra clave
identifica cada pantalla (denominada “tema”) de ayuda. Para utilizar un archivo .HLP
con una ficha, emplee las propiedades HelpFile y HelpId.
• HelpFile especifica el nombre del archivo .HLP que contiene los temas de ayuda del
objeto.
• HelpId especifica la palabra clave del tema que proporciona ayuda para el objeto.
Después de haber definido las propiedades HelpFile y HelpID, la pulsación de F1
ejecuta el programa de ayuda de Windows, WINHELP.EXE, y muestra el tema
especificado en el archivo .HLP.

Capítulo 14, Escritura de controladores de sucesos 233


events.dwp Page 234 Wednesday, August 30, 1995 2:15 PM

Gestión de los sucesos de desplazamiento de las fichas


A menos que defina las propiedades Moveable y Sizeable como .F., los usuarios pueden
cambiar la posición y tamaño de las ventanas de ficha. La mayoría de las fichas no
necesitan responder a esos sucesos. Sin embargo, en ocasiones puede ser necesario que
las ventanas de ficha se sitúen de cierta forma. Para gestionar esos sucesos, asigne
código a OnMove u OnSize.
OnMove, OnSize
El ejemplo siguiente crea dos fichas, Maria y Corderito. Ambas aparecen siempre una
junto a la otra. El usuario no puede mover Corderito, pero si mueve Maria, Corderito se
mueve con ella. El procedimiento MueveMaria se asigna a los sucesos OnMove y
OnSize de Maria.
* Maria.prg
SET PROCEDURE TO PROGRAM(1) ADDITIVE
Maria = NEW FORM()
Maria.Text = "María"
Maria.Width = 25
Maria.Top = 2.00
Maria.Left = 2.00
Maria.Height = 15.00
Maria.MDI = .F.
Maria.OnMove = MueveMaria
Maria.OnSize = MueveMaria
Maria.OnClose = {;this.Corderito.Close()}

Maria.Corderito = NEW FORM()


Maria.Corderito.Top = Maria.Top
Maria.Corderito.Width= 30
Maria.Corderito.left = Maria.Left+Maria.Width+1
Maria.Corderito.height= 5
Maria.Corderito.MDI= .F.
Maria.Corderito.moveable = .F.
Maria.Corderito.Text1 = NEW TEXT(Maria.Corderito) &&
Maria.Corderito.Text1.Text = "Corderito"
Maria.Open()
Maria.Corderito.Open()

PROCEDURE MueveMaria(nLeft,nTop)
this.Corderito.left=this.left+this.width +1
this.Corderito.top=this.top
RETURN

234 Guía del programador


events.dwp Page 235 Wednesday, August 30, 1995 2:15 PM

OnSize
Este ejemplo se ha tomado de CLOCK.PRG, del directorio EJEMPLOS, y muestra la
hora en una ventana. Cuando el usuario cambia el tamaño de la ventana del reloj, el
procedimiento ReSizeText redimensiona el texto que muestra la hora.
PROCEDURE CambiaTamText
local t && Crea una referencia al nuevo objeto
t = form.timeText
t.width = form.width && Adapta el tamaño del objeto texto
t.height = form.height && al de la ventana de la ficha
t.fontsize=(form.width+form.height)/2*1.5 && Redimensiona la fuente
RETURN

Gestión de los sucesos del ratón


En muchas fichas, basta gestionar los clics del ratón sólo mediante el suceso OnClick,
que se aplica a los botones (de comando) y a las opciones de menú. Para un mayor
control, utilice los sucesos enumerados en la Tabla 14.1 para gestionar los clics del ratón
en cualquier objeto de una ficha. Con estos sucesos, es posible gestionar acciones como
mantener pulsado un botón del ratón, soltarlo y hacer doble clic, para casi todos los
controles. Además, es posible gestionar los clics que se producen en combinación con
ciertas pulsaciones, como Ctrl o Alt.
Tabla 14.1 Resumen de los sucesos del ratón
Botón del ratón Doble clic Pulsar Soltar
Izquierdo OnLeftDblClick OnLeftMouseDown OnLeftMouseUp
Central OnMiddleDblClick OnMiddleMouseDown OnMiddleMouseUp
Derecho OnRightDblClick OnRightMouseDown OnRightMouseUp

dBASE pasa tres parámetros a los sucesos mencionados en la Tabla 14.1, y también a
OnSize y Key:
• El parámetro de marcas es un valor de un solo byte que indica si las teclas Ctrl o Alt o
un botón del ratón estaban pulsados cuando se produjo el suceso. Use las funciones
de manipulación de bits, como BITSET() o BITAND(), para interpretar el valor.
• El parámetro de columna indica la columna en que está situado el puntero del ratón.
• El parámetro de fila indica la fila en que está situado el puntero del ratón.
En el código de los controladores de sucesos, siga la sintaxis normal para recibir
parámetros, como se describe en el Capítulo 4. Para más información sobre el parámetro
de marcas, consulte en la Referencia del lenguaje la entrada correspondiente a cualquiera
de los sucesos del ratón.

Capítulo 14, Escritura de controladores de sucesos 235


events.dwp Page 236 Wednesday, August 30, 1995 2:15 PM

El ejemplo siguiente utiliza BITAND() para interpretar el parámetro de marcas y


muestra la tecla que pulsa el usuario. Este procedimiento puede anexarse a cualquiera
de los sucesos de la Tabla 14.1:
PROCEDURE MuestraMarcaRaton(nMarca,nCol,nFila)
nTeclaMayus=4
nTeclaCtrl =8
IF BITAND(nMarca,nTeclaMayus)= nTeclaMayus
? " Tecla Mayús"
ENDIF
IF BITAND(nMarca,nTeclaCtrl)=nTeclaCtrl
? " Tecla Ctrl"
ENDIF
RETURN

236 Guía del programador


custctl.dwp Page 237 Wednesday, August 30, 1995 2:17 PM

Capítulo

Creación de controles y fichas


Capítulo 15
15
personalizados
Un control personalizado es un objeto del interface de usuario, como un campo de entrada
o un botón, que tiene un aspecto o comportamiento personalizado, y puede crearse en
dBASE declarando una clase basada en una clase estándar. También puede utilizar los
controles VBX como personalizados. Una ficha personalizada es una ficha que sirve de
base para crear otras fichas. Este capítulo presenta los controles y fichas personalizados,
muestra cómo utilizarlos en las fichas y las aplicaciones, y proporciona ejemplos de
controles que quizá desee crear.

Acerca de los controles personalizados


El uso de controles personalizados es una forma de simplificar la elaboración de fichas
mediante la creación de objetos reutilizables con propiedades o controladores de
sucesos predefinidos con ciertos valores. Por ejemplo, podría desarrollar convenciones
estilísticas propias para las fichas y utilizar siempre las mismas propiedades de fuente y
color para los campos de entrada. En lugar de definir esas propiedades para cada campo
de entrada, puede crear un control personalizado de este tipo y con esas propiedades
predefinidas. Entonces, en lugar de utilizar los objetos de campo de entrada estándar,
sitúe los personalizados en las fichas.
Al utilizar el Diseñador de fichas de Visual dBASE, puede “heredar visualmente” los
controles personalizados existentes para crear controles personalizados nuevos y más
especializados.
Mediante el Diseñador de fichas de Visual dBASE, es posible “heredar visualmente” los
controles personalizados existentes para crear controles personalizados nuevos y más
especializados.

Capítulo 15, Creación de controles y fichas personalizados 237


custctl.dwp Page 238 Wednesday, August 30, 1995 2:17 PM

Los controles personalizados de dBASE son declaraciones de clase que especifican


definiciones personalizadas para las propiedades y los controladores de sucesos.
Puede escribir manualmente el código de la clase o adaptar el código generado por el
Diseñador de fichas. El archivo BOTONES.CC, que se encuentra en el subdirectorio
PERSONALIZADO, contiene clases de controles personalizados para botones comunes,
como Aceptar y Cancelar.
Los controles VBX son objetos del interface de usuario escritos en C o Pascal y
compilados en un tipo especial de DLL con una extensión .VBX. Al igual que los
controles personalizados de dBASE, los controles VBX suelen mejorar el aspecto o
ampliar la funcionalidad de algunos controles comunes, como cuadros de lista y
botones de radio. Por ejemplo, una casilla de verificación puede indicar si una función
está activada o desactivada; el control VBX, SWITCH.VBX, realiza la misma labor con
un interface más adornado que parece un interruptor eléctrico. Muchas empresas de
programación proporcionan bibliotecas de controles VBX. Visual dBASE permite el uso
de controles VBX de la versión 1.

Instalación de controles personalizados


Los controles personalizados pueden instalarse para que aparezcan en la página
Personalizado de la paleta de controles en el Diseñador de fichas. A continuación, puede
ponerlos en las fichas igual que cualquier control estándar.
Siga estos pasos para instalar controles personalizados en dBASE utilizando el menú:
1 Guarde las clases personalizadas dBASE en un archivo de programa con la
extensión .CC (utilizada para los controles personalizados).
2 En el Diseñador de fichas, elija Archivo|Definir controles personalizados.
3 Seleccione el archivo .CC en el cuadro de diálogo que aparece.
Cuando instala un control personalizado, dBASE añade automáticamente una línea en
la sección [CustomClasses] del archivo de configuración, DBASEWIN.INI.
Como alternativa a la instalación desde el menú, puede añadir la línea en
DBASEWIN.INI manualmente para instalar los controles.
Éste es un ejemplo de una sentencia que instala un archivo .CC en DBASEWIN.INI:
[CustomClasses] && Marca el comienzo de la sección de clases
&& personalizadas
CC0=C:\VISUALDB\EJEMPLOS\BOTONES.CC && Instala BOTONES.CC
CC1=C:\CTRLPERS\MIBIBLCC.CC && Instala MIBIBLCC.CC
Las líneas que instalan archivos de controles personalizados tienen la sintaxis siguiente:
CCn = <nombre archivo .CC>
donde n es cualquier número entero entre 0 y 99. El número n no tiene un significado
especial; es sólo un número interno que utiliza dBASE. (Para más información sobre las
configuraciones de DBASEWIN.INI, consulte el Apéndice C de la Guía del usuario.)

238 Guía del programador


custctl.dwp Page 239 Wednesday, August 30, 1995 2:17 PM

Para instalar controles VBX, siga las mismas instrucciones que para los controles
personalizados de dBASE y seleccione el archivo .VBX. Puesto que los archivos .VBX
son un tipo especial de DLL, dBASE añade la línea en la sección [DLLs] de
DBASEWIN.INI (no en la sección [CustomClasses]). Éste es un ejemplo:
[DLLs] && Marca el comienzo de la sección de Dlls
DLL0=C:\VISUALDB\EJEMPLOS\DBTIMER.VBX && Instala el control DBTIMER
DLL1=C:\VISUALDB\EJEMPLOS\SWITCH.VBX && Instala el control SWITCH
Como alternativa a la instalación de los controles VBX en el archivo DBASEWIN.INI, puede
instalarlos de forma individual utilizando LOAD DLL. El código siguiente carga los mismos
controles VBX especificados en el anterior, excepto que lo hace en un archivo de programa
(o en la ventana de comandos), en lugar de en DBASEWIN.INI::
LOAD DLL C:\DBASEWIN\EJEMPLOS\VCR1.VBX && Instala el control VCR1
LOAD DLL C:\DBASEWIN\EJEMPLOS\DBTIMER.VBX && Instala el control DBTIMER
LOAD DLL C:\DBASEWIN\EJEMPLOS\SWITCH.VBX && Instala el control SWITCH
La Figura 15.1 muestra los controles personalizados de dBASE contenidos en
BOTONES.CC y los controles VCR1.VBX, SWITCH.VBX y DBTIMER.VBX, listados en
la paleta de controles después de instalarlos:
Figura 15.1 Solapa Personalizado de la paleta de controles

Solapa Personalizado Solapa VBX de la


de la Paleta de Paleta de controles
controles

Capítulo 15, Creación de controles y fichas personalizados 239


custctl.dwp Page 240 Wednesday, August 30, 1995 2:17 PM

Declaración de clases de controles personalizados


El Capítulo 11 describe en detalle la declaración de clases. Consúltelo para obtener
ayuda sobre la escritura de declaraciones de clase partiendo de cero para crear controles
personalizados. Una forma más sencilla de generar las declaraciones de clase es colocar
los controles en el Diseñador de fichas y, entonces, adaptar el código DEFINE generado
en el archivo .WFM para crear una declaración CLASS para un archivo .CC.
Los controles personalizados existentes pueden heredarse visualmente dentro del
Diseñador de fichas para crear nuevos controles personalizados.
La Figura 15.2 compara la sentencia DEFINE que crea un campo de entrada
(a partir de un archivo .WFM) con una declaración CLASS idéntica de un archivo .CC.
Figura 15.2 Comparación de código DEFINE del archivo .WFM y de código CLASS del archivo .CC

Código DEFINE de un archivo .WFM Código CLASS de un archivo .CC


DEFINE ENTRYFIELD Personalz OF THIS; CLASS Personalz(f,n) OF ENTRYFIELD(f,n) CUSTOM
PROPERTY; this.ColorNormal = "N/GB"
ColorNormal "N/GB",; this.MousePointer = 3
MousePointer 3,; this.Value = ""
Value "",; this.FontBold = .F.
FontBold .F.,; this.FontSize = 11.00
FontSize 11.00,; this.FontName = "Helvetica"
FontName "Helvetica",; this.Border = .T.
Border .T.,; this.ColorHighLight = "N/GB+"
ColorHighLight "N/GB+" ENDCLASS

Es fácil guardar un control como personalizado desde dentro del Diseñador de fichas.
Consulte la Guía del usuario para obtener información sobre el almacenamiento de los
controles personalizados. Para convertir manualmente una sentencia DEFINE en una
declaración CLASS de un control personalizado:
1 Cambie la línea que comienza con DEFINE por una declaración CLASS como la que se
muestra en la Figura 15.2.
• La palabra clave CUSTOM al final de la línea especifica que la declaración de clase
crea un control personalizado.
Importante • Los parámetros f y n reciben una referencia a la ficha y al nombre del control,
respectivamente. Las subclases basadas en clases estándar suelen precisar sólo un
parámetro que haga referencia a la ficha. Las clases de control personalizado
necesitan un parámetro de cadena adicional que represente el nombre del control.
Como en el caso del parámetro de ficha, es posible asignar cualquier nombre al
parámetro adicional. Este ejemplo utiliza n.
2 Elimine la línea que contiene PROPERTY;.

240 Guía del programador


custctl.dwp Page 241 Wednesday, August 30, 1995 2:17 PM

3 En cada línea que asigne una propiedad,


• Añada this. antes del nombre de la propiedad.
• Añada un signo igual (=) después del nombre de la propiedad.
• Elimine los caracteres ,; situados al final de la línea.
4 Si ha creado procedimientos como controladores de sucesos, añada la declaración del
procedimiento después de la última sentencia de asignación de propiedad.
5 Añada la línea ENDCLASS al final para terminar la declaración de clase.

Ejemplos de controles personalizados de dBASE


Los siguientes son algunos ejemplos de clases de controles personalizados:
Botón Aceptar
La clase siguiente, tomada de BOTONES.CC en el subdirectorio EJEMPLOS, declara un
botón Aceptar personalizado.
CLASS BotonOK(f,n) of PUSHBUTTON(f,n) CUSTOM
this.Height = 2
this.Width = 10
this.UpBitmap = "Resource #20 DBAS0009.dll"
this.DisabledBitmap = "Resource #21 DBAS0009.dll"
this.Text = "&Aceptar"
ENDCLASS
Campo de entrada de contraseña
Este ejemplo crea un campo para la introducción de una contraseña. Utiliza la
propiedad Key para registrar cada pulsación y muestra un asterisco en su lugar.
CLASS Clave(f,n) OF ENTRYFIELD(f,n) CUSTOM
this.ColorNormal = ""
this.Value = ""
this.Border = .T.
this.ColorHighLight = ""
this.Width = 8
this.Height = 2

this.RealValue = ""

PROCEDURE Key(nCaracter, repeticion, indicadores)


this.RealValue = this.RealValue + chr(nCaracter)
this.Value = this.Value + "*"
RETURN .F.
ENDCLASS
Cuadro de incremento para el desplazamiento entre registros
Este ejemplo crea un cuadro de incremento que desplaza el puntero de registro en una
tabla. Podría situarlo en una ficha basada en tabla para facilitarle al usuario el
desplazamiento por los registros, en especial si utiliza SHELL() para desactivar el menú
y las barras rápidas de Visual dBASE.

Capítulo 15, Creación de controles y fichas personalizados 241


custctl.dwp Page 242 Wednesday, August 30, 1995 2:17 PM

CLASS CuadroRegistro(f,n) OF SPINBOX(f,n) CUSTOM


this.Value = RECNO() && Asigna el número de registro actual
this.RangeMin = 1 && El número de registro más bajo
this.RangeMax = RECCOUNT() && El número de registro más alto
PROCEDURE OnChange
GOTO this.Value && Mueve el puntero de registro a Value
RETURN
ENDCLASS

Uso de los controles VBX


Es posible situar controles VBX en las fichas y definir sus métodos, propiedades y
sucesos como con cualquier control estándar de dBASE. El creador del control debió
incluir ayuda sobre las propiedades y uso de los controles VBX. Visual dBASE
simplemente proporciona el medio de situarlos en las fichas de dBASE.
Las propiedades, sucesos y métodos disponibles son los definidos en el archivo .VBX.
Algunas propiedades, como Top y Left, podrían tener el mismo nombre que las
disponibles en los controles de dBASE.
dBASE también añade las siguientes propiedades estándar en cada control VBX.
(Para más información sobre estas propiedades, consulte la Referencia del lenguaje):
• HWind contiene un identificador (handle) de la ventana de la ficha en que se sitúa el
control. El identificador es un valor numérico exclusivo asignado por Windows.
• Parent contiene una referencia a la ventana de ficha en que se sitúa el control.
• Before contiene una referencia al objeto que precede inmediatamente al control en el
orden de tabulación.
Además, dBASE añade estas propiedades sólo para VBX:
• VBStream contiene información utilizada por el control VBX para leer el archivo
.BFM generado por el Diseñador de fichas. Cuando se guarda una ficha que contiene
un control VBX, el Diseñador de fichas crea otro archivo, <nombre ficha>.BFM, con
información binaria sobre el estado inicial de todos los controles VBX de la ficha.
Importante El archivo .BFM debe estar presente cuando se ejecute la ficha.
• FormGetsKeys es una propiedad lógica que especifica si las siguientes pulsaciones de
desplazamiento las recibe la ficha o el control VBX: Ctrl+Fin, Ctrl+W, Intro, Esc, Tab,
Mayús+Tab. El valor por defecto es .T., es decir, la ficha recibe las pulsaciones de teclas.
Quizá desee definir FormGetsKeys como .F. para los controles VBX que requieren
estas pulsaciones, como un control de un tratamiento de textos o una hoja de cálculo.
Si FormGetsKeys es .F., el control VBX recibe las pulsaciones, no la ficha.
• Translate es una propiedad lógica que especifica si se traducen las cadenas de
caracteres entre los juegos de caracteres OEM y ANSI. El valor por defecto es .T.

242 Guía del programador


custctl.dwp Page 243 Wednesday, August 30, 1995 2:17 PM

Para mantener la compatibilidad con versiones anteriores de dBASE, Visual dBASE


utiliza el juego de caracteres OEM. Los controles VBX, sin embargo, utilizan el juego
de caracteres ANSI. Cuando Translate es .T., las cadenas asignadas a las propiedades
del control se convierten de OEM a ANSI, y las cadenas tomadas del control se
convierten de ANSI a OEM.
En muchos casos, no es necesario definir la propiedad Translate. Defínala como .F.
sólo si el control contiene datos binarios o valores de caracteres que deben
permanecer intactos por otras razones.
El siguiente es un ejemplo de declaración de ficha que utiliza SWITCH.VBX. Cuando se
activa (enciende) el interruptor, la ficha se colorea en rojo; cuando está desactivado, la
ficha está en blanco.
CLASS INTERRUPTOR OF FORM
this.MousePointer = 1
this.Text = "BISWITCH"
this.Width = 20.00
this.Top = 15.88
this.Left = 28.00
this.Height = 10.00
this.Minimize = .F.
this.Maximize = .F.
LOAD DLL SWITCH.VBX
DEFINE BISWITCH BISWITCH1 OF THIS;
PROPERTY;
VBStream "SWITCH.BFM 56",;
Width 12.00,;
Top 1.00,;
Left 3.50,;
Height 6.00,;
OnOff CLASS::BISWITCH1_ONOFF,;
OnOn CLASS::BISWITCH1_ONON

PROCEDURE BISWITCH1_OnOff
form.text = "Interruptor -- Apagado"
this.caption = "Apagado"
form.colornormal = "n/w"
RETURN

PROCEDURE BISWITCH1_OnOn
form.text = "Interruptor -- Encendido"
this.caption = "Encendido"
form.colornormal = "bg/r*"
RETURN
ENDCLASS

Capítulo 15, Creación de controles y fichas personalizados 243


custctl.dwp Page 244 Wednesday, August 30, 1995 2:17 PM

La Figura 15.3 muestra SWITCH.WFM en ejecución:


Figura 15.3 SWITCH.WFM en modo de ejecución

Si hace clic en el interruptor, cambia el color de fondo y el


título del interruptor y de la ventana de la ficha.

Acerca de las fichas personalizadas


Las fichas personalizadas son una forma de reutilizar código que ya ha creado y
proporcionan un alto nivel de coherencia en las aplicaciones. Con controles
personalizados, es posible crear un modelo, o plantilla, de las características básicas de
las fichas de la aplicación. Entonces, basta realizar adiciones, o personalizaciones, en el
juego de características básicas de las fichas siguientes.
La Figura 15.4 muestra una ficha personalizada que sirve para crear fichas de entrada
estándar. La ficha contiene un logotipo de empresa, botones estándar y un rectángulo
para contener los datos o información que aparecerán en la ficha. Los botones de la ficha
personalizada se han tomado de la biblioteca de controles personalizados
BOTONES.CC, que debe estar cargada antes de utilizar la ficha personalizada.
Figura 15.4 Ficha personalizada para crear una ficha de entrada

Éste es el código de la ficha personalizada:


CLASS XYZTEMPL OF FORM Custom
this.Text = "Ficha"
this.TopMost = .F.
this.Left = 28.666
this.Height = 21.6465
this.Width = 101
this.Top = 8.7051
this.ColorNormal = "/BTNFACE"
this.PageNo = 1
this.OnOpen = {;this.BeginAppend()}

244 Guía del programador


custctl.dwp Page 245 Wednesday, August 30, 1995 2:17 PM

DEFINE RECTANGLE RECTANGULO1 OF THIS;


PROPERTY;
Border .T.,;
Text "",;
Height 3.6484,;
Left 50.332,;
Width 49.334,;
Top 17.4102,;
ColorNormal "BtnText/BtnFace",;
PageNo 1
DEFINE RECTANGLE RECTANGULO2 OF THIS;
PROPERTY;
Border .T.,;
Text "",;
Height 12.001,;
Left 3,;
Width 96.666,;
Top 4.9395,;
ColorNormal "W+/W",;
PageNo 1
DEFINE TEXT TEXTO1 OF THIS;
PROPERTY;
Border .T.,;
Text "XYZ, S.L.",;
Alignment 4,;
FontItalic .T.,;
FontName "Times New Roman",;
FontSize 36,;
Left 3,;
Height 3.4121,;
Width 35.666,;
Top 0.7051,;
ColorNormal "B+/RG*",;
PageNo 1
DEFINE OKBUTTON BOTONACEPTAR1 OF THIS;
PROPERTY;
Group .T.,;
OnClick {;Form.SaveRecord(); Form.Close()},;
Left 52,;
Height 1.5303,;
Width 14.166,;
Top 18.4102
DEFINE CANCELBUTTON BOTONCANCELAR1 OF THIS;
PROPERTY;
Group .T.,;
OnClick {;Form.AbandonRecord(), Form.Close()},;
Left 68,;
Height 1.5303,;
Width 14.166,;
Top 18.4102

Capítulo 15, Creación de controles y fichas personalizados 245


custctl.dwp Page 246 Wednesday, August 30, 1995 2:17 PM

DEFINE HELPBUTTON BOTONAYUDA1 OF THIS;


PROPERTY;
Group .T.,;
Left 84,;
Height 1.5303,;
Width 14.166,;
Top 18.4102
ENDCLASS
Observe que la definición de la ficha personalizada difiere de la de una clase estándar
sólo por la adición de la palabra clave CUSTOM en la línea de comando CLASS.
A partir de la definición de una ficha personalizada, es posible crear fichas nuevas,
como las de la Figura 15.5.
Figura 15.5 Fichas creadas a partir de una clase personalizada

Cuando se crea una ficha basada en una personalizada, hereda todas sus propiedades.
Observe el código de la ficha Información de producto mostrada en la Figura 15.5:
** END HEADER -- no elimine esta línea*
* Generado el 05/17/95
*
parameter bModal
local f
f = new XYZPRODFORM()
if (bModal)
f.mdi = .F. && ficha no MDI
f.ReadModal()
else
f.Open()
endif
CLASS XYZPRODFORM OF XYZTEMPL From C:\DBW55\EJEMPLOS\XYZTEMPL.CFM
this.TopMost = .F.
this.Text = "Información de producto"
this.Left = 28.666
this.Height = 21.6465
this.Width = 101
this.Top = 8.7051

246 Guía del programador


custctl.dwp Page 247 Wednesday, August 30, 1995 2:17 PM

DEFINE TEXT TEXTO2 OF THIS;


PROPERTY;
Border .F.,;
Text "Núm. artículo",;
Left 6,;
Height 0.7646,;
Width 11.666,;
Top 5.6465,;
ColorNormal "BtnText/BtnFace",;
PageNo 1
DEFINE ENTRYFIELD CAMPOENT1 OF THIS;
PROPERTY;
ColorHighLight "WindowText/Window",;
Value "",;
Border .T.,;
Left 6,;
Height 1,;
Width 15.666,;
Top 6.5879,;
ColorNormal "WindowText/Window",;
PageNo 1
DEFINE TEXT TEXTO3 OF THIS;
PROPERTY;
Border .F.,;
Text "Descripción",;
Left 6,;
Height 0.7646,;
Width 11.666,;
Top 8,;
ColorNormal "BtnText/BtnFace",;
PageNo 1
DEFINE ENTRYFIELD CAMPOENT2 OF THIS;
PROPERTY;
ColorHighLight "WindowText/Window",;
Value "",;
Border .T.,;
Left 6,;
Height 1.001,;
Width 30.666,;
Top 8.9395,;
ColorNormal "WindowText/Window",;
PageNo 1
DEFINE TEXT TEXTO4 OF THIS;
PROPERTY;
Border .F.,;
Text "Precio",;
Left 6,;
Height 0.7656,;
Width 5.5,;
Top 10.8223,;
ColorNormal "BtnText/BtnFace",;
PageNo 1

Capítulo 15, Creación de controles y fichas personalizados 247


custctl.dwp Page 248 Wednesday, August 30, 1995 2:17 PM

DEFINE ENTRYFIELD CAMPOENT3 OF THIS;


PROPERTY;
ColorHighLight "WindowText/Window",;
Value "",;
Border .T.,;
Left 6,;
Height 1.001,;
Width 14.666,;
Top 11.7637,;
ColorNormal "WindowText/Window",;
PageNo 1
DEFINE RECTANGLE RECTANGULO3 OF THIS;
PROPERTY;
Border .T.,;
Text "",;
Height 3.1768,;
Left 5,;
Width 18.666,;
Top 13.1758,;
ColorNormal "BtnText/BtnFace",;
PageNo 1
DEFINE RADIOBUTTON BOTONRADIO1 OF THIS;
PROPERTY;
Value .T.,;
Text "En stock",;
Group .T.,;
Left 7,;
Height 1.1182,;
Width 15,;
Top 13.6465,;
ColorNormal "BtnText/BtnFace",;
PageNo 1
DEFINE RADIOBUTTON BOTONRADIO2 OF THIS;
PROPERTY;
Value .F.,;
Text "Pedido especial",;
Group .F.,;
Left 7,;
Height 1,;
Width 15.666,;
Top 15.0586,;
ColorNormal "BtnText/BtnFace",;
PageNo 1
DEFINE TEXT TEXTO5 OF THIS;
PROPERTY;
Border .F.,;
Text "Imagen",;
Left 53,;
Height 0.7646,;
Width 8.666,;
Top 5.6465,;
ColorNormal "BtnText/BtnFace",;
PageNo 1

248 Guía del programador


custctl.dwp Page 249 Wednesday, August 30, 1995 2:17 PM

DEFINE RECTANGLE RECTANGULO4 OF THIS;


PROPERTY;
Border .T.,;
Text "",;
Height 9.7646,;
Left 53,;
Width 43.666,;
Top 6.5879,;
ColorNormal "BtnText/BtnFace",;
PageNo 1
DEFINE IMAGE IMAGEN1 OF THIS;
PROPERTY;
DataSource "",;
Left 54,;
Height 8.8232,;
Width 41.666,;
Top 7.0586,;
PageNo 1
ENDCLASS
Observe que la línea:
CLASS XYZPRODFORM OF XYZTEMPL From C:\DBW55\EJEMPLOS\XYZTEMPL.CFM
designa la clase de la que se deriva la clase XYZProdForm, y el archivo fuente en que se
almacena la definición de la ficha personalizada. Al igual que sucede con los controles
personalizados y los archivos de biblioteca .CC, es posible definir y almacenar cualquier
número de fichas personalizadas en los archivos de biblioteca de fichas personalizadas.
Estas bibliotecas tienen la extensión .CFM.
Desde el Diseñador de fichas de Visual dBASE, puede utilizar el cuadro de diálogo
Guardar como personalizado para guardar un diseño de ficha en un archivo de biblitoteca
de fichas personalizadas, y el cuadro de diálogo Establecer clase de ficha personalizada para
designar una ficha personalizada como clase básica para crear fichas nuevas. Consulte la
Guía del usuario para ver detalles sobre la creación de bibliotecas de fichas
personalizadas desde el Diseñador de fichas.

Capítulo 15, Creación de controles y fichas personalizados 249


custctl.dwp Page 250 Wednesday, August 30, 1995 2:17 PM

Controles personalizados no dBASE mediante PaintBox


La clase PaintBox proporciona a los programadores avanzados y de productos
complementarios un vínculo directo entre el API de Windows y un objeto de control de
dBASE. El PaintBox contiene un suceso OnPaint además de los controladores de sucesos
estándar. dBASE da un control total sobre el redibujo de la región PaintBox.
Utilice la clase PaintBox para añadir controles no VBX escritos en un lenguaje compilado
como C o C++.

250 Guía del programador


fdcode.dwp Page 251 Wednesday, August 30, 1995 2:18 PM

Capítulo

Uso del código generado


Capítulo 16
16
El Diseñador de fichas, el Diseñador de menús y el Diseñador de menús de ventana
generan código dBASE basado en las fichas y menús que diseñe visualmente.
En la mayoría de los proyectos, no es necesario editar el código generado. Sin embargo,
algunos proyectos pueden precisar que adapte o añada algo en el código generado para
su uso en una aplicación.
Los Diseñadores son utilidades de dos vías. Es decir, puede modificar el código
generado con un editor de texto y volver a cargarlo en los diseñadores. Esta
característica le permite desarrollar programas a su manera, disponiendo los objetos de
forma visual, refinando el código y volviendo a los Diseñadores cuando le convenga.
Este capítulo trata temas destinados a programadores que deseen trabajar con el código
generado por los Diseñadores.

Capítulo 16, Uso del código generado 251


fdcode.dwp Page 252 Wednesday, August 30, 1995 2:18 PM

Acerca del código del Diseñador de fichas


Un archivo .WFM generado por el Diseñador de fichas tiene tres secciones principales:
• La cabecera del programa está al principio. En el editor de procedimientos, introduzca
la cabecera del programa seleccionando Cabecera en el cuadro combinado
(consulte la Figura 16.1). La cabecera puede contener cualquier código que desee
ejecutar antes de crear la ficha, aunque habitualmente consta de:
• Comentarios que describen la ficha. Para ver consejos sobre la introducción de
comentarios en un programa, consulte el Capítulo 3.
• Un comando CREATE SESSION, que comienza una sesión para la ficha con un
nuevo juego de áreas de trabajo. Podría utilizar CREATE SESSION en fichas
basadas en tabla para asegurarse de que no afecten a la ficha los cambios en la
tabla realizados en el Selector de archivos o en otros programas que se ejecuten
actualmente, como cerrar la tabla o definir un filtro. Sin embargo, los cambios en
los datos se reflejan en la ficha tanto si utiliza CREATE SESSION como si no lo usa.
Para más información sobre las sesiones, consulte el Capítulo 21.
• Directivas de preprocesador para declarar constantes o insertar archivos de
inclusión. Para más información sobre estas directivas, consulte el Capítulo 7.
• Declaraciones de variables de memoria. Para información sobre las variables de
memoria, consulte el Capítulo 5.
• La declaración de clase de la ficha es el cuerpo principal del código. El Diseñador de
fichas genera muchas de las subclases, incluyendo los valores de las propiedades de
la ventana de la ficha y las definiciones de cada control. Los procedimientos de
control de sucesos se introducen mediante el Editor de procedimientos.
• Las declaraciones de los procedimientos de soporte aparecen después de la declaración
de clase, y son aquéllos a los que llaman los procedimientos de control de sucesos.
Se introducen en el Editor de procedimientos seleccionando General en el cuadro
combinado que se muestra en la Figura 16.1.

252 Guía del programador


fdcode.dwp Page 253 Wednesday, August 30, 1995 2:18 PM

La Figura 16.1 muestra dónde introducir el código para cada sección en el Editor de
procedimientos.
Figura 16.1 Editor de procedimientos
Elija Cabecera para
introducir la cabecera.
Seleccione General para
introducir procedimientos.
Seleccione un controlador
de sucesos para editarlo.

El Diseñador de fichas es una utilidad versátil para generar y modificar el código de un


programa. Puede usarlo para generar un archivo .WFM, modificarlo después con un
editor de texto y, por último, volver al Diseñador de fichas para editar la ficha
visualmente.
Incluso puede escribir su propio código para crear una ficha y, después, utilizar el
Diseñador de fichas para modificarla de forma visual. El código que creó la ficha
originalmente —comandos en la ventana de comandos o un archivo de programa— se
desecha. Cuando guarde la ficha, el Diseñador de fichas simplemente genera un nuevo
archivo .WFM en función del estado actual de la ficha.
Si realiza cambios en el código con un editor de texto y ejecuta el Diseñador de fichas,
tenga en cuenta lo siguiente:
• El Diseñador de fichas siempre genera una declaración de clase para la ficha, sea cual
sea la forma en que se creó la ficha en el código original.
• El Diseñador de fichas genera valores literales para todas las definiciones de
propiedad. Por ejemplo, si el código original utiliza una expresión como DATE() para
inicializar un campo de fecha, el archivo .WFM define el campo con una cadena
literal que representa la fecha de hoy. Si el código utiliza los métodos NextRow() o
NextCol() para definir las propiedades Top o Left de los controles, el archivo .WFM
define las propiedades con los valores literales de las coordenadas.
La Figura 16.2 ilustra cómo se estructura el código de un archivo .WFM.

Capítulo 16, Uso del código generado 253


fdcode.dwp Page 254 Wednesday, August 30, 1995 2:18 PM

Figura 16.2 Explicación del archivo .WMF del Diseñador de fichas


La cabecera de programa de
* Esta es mi primera ficha dBASE.
este ejemplo sólo contiene
comentarios. Termina con la ** END HEADER -- no borrar esta línea*
* Generado el 07/12/94
línea ** END HEADER...
LOCAL f
f = NEW HOLA()
f.Open()
CLASS HOLA OF FORM
this.MousePointer = 1
this.Text = "Mi primera ficha dBASE"
this.ColorNormal = "N/GB"
this.Maximize = .F.
this.Minimize = .F. Las sentencias que
this.HelpId = "" comienzan con “this”
this.HelpFile = "" asignan propiedades para la
this.Top = 5.94 ventana de ficha.
this.Left = 60.40
this.Width = 55.80
this.Height = 12.94
DEFINE TEXT TEXTO1 OF THIS;
PROPERTY;
Text "¡Hola a todos!",;
FontSize 24.00,;
FontItalic .T.,;
Border .F.,;
ColorNormal "N/GB",;
Top 2.00,;
Left 8.00,;
Height 2.00,;
El cuerpo principal del .WFM Width 42.00
consiste en la declaración de Un comando DEFINE crea
DEFINE PUSHBUTTON BOTON1 OF THIS; cada control de la ficha y
clase, que comienza con una PROPERTY;
sentencia CLASS y termina con especifica sus propiedades.
Group .T.,;
ENDCLASS. OnClick CLASS::BOTON1_ONCLICK,;
Text "Adiós",;
FontSize 10.00,;
Default .T.,;
ColorNormal "N/W",;
Top 6.00,;
Left 18.00,;
Height 3.00,;
Width 19.00

Procedure BOTON1_OnClick
form.Texto1.Text = "Adiós"
DO WHILE form.Height >= 1 .and. form.Width >= 1
form.Height = form.Height - 1 Los procedimientos de
form.Width = form.Width - 1 control de sucesos siguen a
form.Top = form.Top + .5 la definición del último
form.Left = form.Left + .5 control y preceden a la
DO HacerRuido sentencia ENDCLASS.
ENDDO
form.Close()
RETURN
Los procedimientos de ENDCLASS
soporte siguen a la sentencia
ENDCLASS. En el Diseñador PROCEDURE HacerRuido
de fichas, introduzca estos ? CHR(7)
procedimientos eligiendo RETURN
General en la lista Método.

254 Guía del programador


fdcode.dwp Page 255 Wednesday, August 30, 1995 2:18 PM

Acerca del código de los menús


Mediante el Diseñador de menús, es posible crear un sistema de menús completo para
una ficha, incluyendo una barra de menús, menús desplegables y menús en cascada.
Cada objeto de menú proporciona propiedades estándar para especificar teclas de
método abreviado, opciones verificadas, opciones atenuadas y otras características de
los menús. El código puede anexarse directamente a los objetos de menú mediante el
suceso OnClick, facilitando así la reutilización de todo un menú, o una parte, en otra
ficha.
El Diseñador de menús guarda el código en un archivo con la extensión .MNU, que se
vincula a una ficha especificándolo en la propiedad MenuFile de la ficha.
La Figura 16.3 muestra un ejemplo de un sistema de menús para una ficha.
Figura 16.3 Menús de ejemplo
Menú desplegable

Barra de menús

Menú en cascada

Capítulo 16, Uso del código generado 255


fdcode.dwp Page 256 Wednesday, August 30, 1995 2:18 PM

El código siguiente, generado por el Diseñador de menús, crea los menús que se
muestran en la Figura 16.3:
Figura 16.4 Ejemplo de un archivo .MNU generado por el Diseñador de menús
La cabecera de ** END HEADER -- no borrar esta línea*
programa contiene * Generado el 13/07/94
comentarios.
Parameter ObjFichaj
NEW MYMENU(ObjFicha,"Raiz")
CLASS MYMENU(ObjFicha,Nombre) OF MENU(ObjFicha,Nombre) El texto que defina
this.Text = "" para el rótulo de
DEFINE MENU ARCHIVO OF THIS; menú se guarda en
PROPERTY; la propiedad Text.
Text "Archivo" El Diseñador de
DEFINE MENU ABRIR OF THIS.ARCHIVO; menús utiliza este
PROPERTY; texto para hacer
Text "Abrir" referencia al objeto.
DEFINE MENU GUARDAR OF THIS.ARCHIVO;
PROPERTY;
Text "Guardar"
DEFINE MENU GUARDAR_COMO OF THIS.ARCHIVO;
PROPERTY;
Text "Guardar como..."
DEFINE MENU IMPORT OF THIS.ARCHIVO;
PROPERTY;
Text "Importar"
DEFINE MENU TEXTO OF THIS.ARCHIVO.IMPORTAR; Un comando
PROPERTY; DEFINE crea cada
El cuerpo principal
Text "Texto" opción de menú.
del .MNU consiste
en la declaración de DEFINE MENU SONIDO OF THIS.ARCHIVO.IMPORTAR; El sangrado del
PROPERTY; código muestra la
clase, que comienza
con una sentencia Text "Sonido" jerarquía de las
DEFINE MENU IMAGEN OF THIS.ARCHIVO.IMPORTAR; opciones de menú.
CLASS y termina
con ENDCLASS. PROPERTY;
Text "Imagen"
DEFINE MENU EDICION OF THIS;
PROPERTY;
Text "Edición"
DEFINE MENU CLIENTES OF THIS.EDICION;
PROPERTY;
Text "Clientes"
DEFINE MENU PEDIDOS OF THIS.EDICION;
PROPERTY;
Text "Pedidos"
DEFINE MENU ARTICULOS OF THIS.EDICION;
PROPERTY;
Text "Artículos"
DEFINE MENU AYUDA OF THIS;
PROPERTY;
Text "Ayuda"
ENDCLASS

256 Guía del programador


fdcode.dwp Page 257 Wednesday, August 30, 1995 2:18 PM

El modelo de generación de menús se basa en la jerarquía de los objetos de menús y en


cómo unos objetos contienen a otros, no en el tipo de menú. El programador no define
explícitamente barras de menús, menús desplegables o de cascada, sino que construye
una jerarquía de objetos de menú, donde cada uno contiene otro o ejecuta una acción.
Igual que una ficha contiene controles, los objetos de menú contiene otros objetos de
menú. dBASE determina automáticamente dónde aparecen los menús en función de su
nivel en la jerarquía.
La Figura 16.5 ilustra la jerarquía de menús de la ficha de la Figura 16.3. Cada nodo de la
jerarquía que conduce a otro nodo es un objeto de menú que contiene otro objeto de
menú. Los nodos situados en el extremo de una rama tienen una acción asignada al
suceso OnClick.
Figura 16.5 Ejemplo de jerarquía de menús

MiMenu Barra de
menús

Opciones de
Archivo Edición Ayuda la barra

Opciones
Abrir Guardar Guardar como Cerrar Importar Clientes Pedidos Artículos desplegables
Opciones de
Texto Sonido Imagen menú en
cascada
= Objeto de menú que contiene otro objeto de menú
= Objeto de menú que inicia una acción

La estructura de un sistema de menús puede modificarse fácilmente cambiando el que


un objeto de menú esté contenido en otro. La opción OF de DEFINE especifica qué
objeto contiene al que se define. Por ejemplo, en la Figura 16.5 el menú Importar es un
elemento del menú Archivo. Para transformar el menú Importar de cascada en
desplegable anexado a la barra de menús, basta cambiar la opción OF de
Form1.MiMenu.File a Form1.MiMenu como sigue:
DEFINE MENU Importar OF Ficha1.MiMenu PROPERTY Text "Importar"
La jerarquía de menús es ahora la que se muestra en la Figura 16.6.
Figura 16.6 Nueva jerarquía de menús

MiMenu

Archivo Importar Edición Ayuda

Abrir Guardar Guardar c. Cerrar Texto Sonido Imagen Clientes Pedidos Artículos

= Objeto de menú que contiene otro objeto de menú


= Objeto de menú que inicia una acción

Capítulo 16, Uso del código generado 257


fdcode.dwp Page 258 Wednesday, August 30, 1995 2:18 PM

Visual dBASE no impone un límite en cuanto al número de niveles o de menús en


cascada. Sin embargo, un sistema de menús bien diseñado no contiene demasiados
menús en cascada. Una jerarquía de menús poco profunda permite que los usuarios
realicen las acciones con un mínimo de pasos.
La Tabla 16.1 señala algunas propiedades de menús que puede definir para añadir
características que personalicen sus menús.
Tabla 16.1 Resumen de las propiedades de los menús
Propiedad Característica que proporciona
Checked Añade una marca de verificación junto al texto de un menú o una opción de
menú.
Enabled Especifica si el menú o la opción de menú está disponible. Si Enabled se
define como .F., el texto aparece atenuado.
ShortCut Especifica una pulsación o una combinación de pulsaciones que el usuario
puede pulsar para elegir la opción de menú. Las pulsaciones de método
abreviado, también conocidas como aceleradores, proporcionan un rápido
acceso a una opción de menú mediante el teclado.
Por ejemplo, puede definir ShortCut para la opción de menú
“Salir sin guardar” como Ctrl+Q.
Separator Designa una opción de menú como barra separadora. Una barra separadora
aparece como una línea horizontal sin texto; el usuario no puede elegir ni
pasar el foco a una barra separadora. Utilice estas barras para comenzar un
grupo de opciones de menú relacionadas.

Adición de características comunes de Windows en una barra


de menús
El objeto barra de menús situado en la parte superior del árbol de menús tiene
propiedades especiales que permiten la asignación automática de funcionalidad a
ciertos menús para que se comporten de la forma habitual en una aplicación Windows
común. Estas propiedades se resumen en la Tabla 16.2.
Tabla 16.2 Resumen de propieades de la barra de menús
Propiedad Característica que proporciona
EditCopyMenu Permite que el menú copie texto resaltado al portapapeles
EditCutMenu Permite que el menú corte texto resaltado al portapapeles
EditPasteMenu Permite que el menú pegue texto del portapapeles en la ficha
EditUndoMenu Permite que el menú deshaga la última operación de corte
OnInitMenu( ) Ejecuta código cuando se inicializa (abre) el sistema de menús
WindowMenu Asocia una lista seleccionable de las ventanas abiertas en la aplicación al
menú designado

258 Guía del programador


fdcode.dwp Page 259 Wednesday, August 30, 1995 2:18 PM

El código del menú siguiente muestra cómo utilizar las propiedades de las barras de
menús:
** END HEADER -- no elimine esta línea*
* Generado el 05/17/95
*
Parameter FormObj
NEW MENUEJEMPLO(FormObj,"Root")
CLASS MENUEJEMPLO(FormObj,Name) OF MENUBAR(FormObj,Name)
This.OnInitMenu = CLASS::CompBorrarTodo

DEFINE MENU ARCHIVO OF THIS;


PROPERTY;
Text "&Archivo"
DEFINE MENU EDIT OF THIS;
PROPERTY;
Text "&Edición"
DEFINE MENU DESHACER OF THIS.EDIT;
PROPERTY;
Text "&Deshacer"
DEFINE MENU CORTAR OF THIS.EDIT;
PROPERTY;
Text "C&ortar"
DEFINE MENU COPIAR OF THIS.EDIT;
PROPERTY;
Text "&Copiar"
DEFINE MENU PEGAROF THIS.EDIT;
PROPERTY;
Text "&Pegar"
DEFINE MENU BORRAR_TODO OF THIS.EDIT;
PROPERTY;
Text "&Borrar todo"
DEFINE MENU VENTANA OF THIS;
PROPERTY;
Text "Ve&ntana"
DEFINE MENU CERRAR_TODAS OF THIS.WINDOW;
PROPERTY;
Text "&Cerrar todas"
DEFINE MENU AYUDA OF THIS;
PROPERTY;
Text "&Ayuda"

* Añade funcionalidad estándar a los menús


This.EditCopyMenu = This.Edicion.Copiar
This.EditCutMenu = This.Edicion.Cortar
This.EditPasteMenu = This.Edicion.Pegar
This.EditUndoMenu = This.Edición.Deshacer
This.WindowMenu = This.Ventana

Capítulo 16, Uso del código generado 259


fdcode.dwp Page 260 Wednesday, August 30, 1995 2:18 PM

PROCEDURE CmpBorrarTodo
IF ALIAS() == ""
This.Edicion.Borrar_todo.Enabled = .F.
ENDIF
RETURN

ENDCLASS
En el código anterior, después de definir los menús, ciertos menús de teclas se asignan a
propiedades de la barra de menús que les dan automáticamente la funcionalidad que
requieren. Por ejemplo, cuando This.Edicion.Copiar se asigna a la propiedad
EditCopyMenu de la barra de menús, el menú Copiar toma las características
siguientes:
• El menú permanece atenuado a menos que haya texto resaltado en un objeto
adecuado de la ficha, como un campo de entrada o editor.
• Cuando hay texto resaltado, se activa el menú Copiar.
• Cuando se selecciona el menú Copiar, el texto resaltado se copia en el portapapeles
de Windows.
Las otras propiedades EditxxxMenu funcionan de forma similar.
La propiedad WindowMenu sólo es útil con los menús de nivel superior de las fichas
MDI. En el menú asignado a WindowMenu, se le añadirá automáticamente un menú
por cada ventana hija abierta. Esta característica supone un medio para que el usuario
cambie de una ventana a otra en la aplicación mediante los menús.
El código del menú anterior ilustra también el uso del controlador de sucesos
OnInitMenu para ajustar el sistema de menús. En el ejemplo, ese controlador
comprueba si hay una tabla abierta en el área de trabajo actual cuando se inicializa el
menú. Si no hay ninguna tabla abierta, la opción Borrar todo del menú Edición está
desactivada.
Los menús de una ficha pueden cambiarse mientras ésta está abierta. Por ejemplo,
puede cambiar las opciones de menú que se ofrecen dependiendo del control que esté
seleccionado actualmente. A continuación se muestran controladores de sucesos para
las propiedades OnGotFocus y OnLostFocus de un objeto Browse, respectivamente.
Cuando el objeto Browse recibe el foco, se activa el menú Edición definido previamente;
cuando pierde el foco, se atenúa el menú.
PROCEDURE MenusBrowse && Se asigna a OnGotFocus del objeto Browse
form.Root.Edicion.Enabled = .T.
RETURN
PROCEDURE NoMenusBrowse && Se asigna a OnLostFocus del objeto Browse
form.Root.Edicion.Enabled = .F.
RETURN

260 Guía del programador


fdcode.dwp Page 261 Wednesday, August 30, 1995 2:18 PM

Uso del código de los menús de ventana


Los menús de ventana son similares a los menús normales; de hecho, los menús se
definen en un objeto Popup de la misma forma que en uno MenuBar. No obstante, hay
una diferencia en la forma en que un menú se de ventana se “asocia” a una ficha.
Los menús se asocian a las fichas mediante la asignación del archivo .MNU del menú a la
propiedad MenuFile de la ficha. Los menús de ventana se asocian a las fichas asignando
un objeto Popup a la propiedad PopupMenu de la ficha. (Estos menús se definen en
archivos .POP, que son muy similares a los .MNU, pero las fichas no tienen una
propiedad para asignar el archivo Popup a una ficha.)
La asociación de un Popup a una ficha se logra con mayor facilidad mediante el
controlador de sucesos OnOpen de la ficha. El código necesario se muestra en el ejemplo
siguiente:
CLASS FICHAEJEMPLO OF FORM
this.OnOpen = CLASS::FICHA_ONOPEN
this.Left = 42
this.Top = 5
this.Height = 20
this.ColorNormal = "/BTNFACE"
this.Width = 60

PROCEDURE Ficha_OnOpen
SET PROCEDURE TO EJEMPLO.POP ADDITIVE
THIS.POPUPMENU = NEW EMERGEJEMPLO(THIS, "MiEmerg")
RETURN

ENDCLASS
En el código anterior, el archivo del Popup (en este caso, EJEMPLO.POP) se abre como
archivo de procedimientos para que la ficha pueda utilizarlo. Entonces, se crea un nuevo
objeto Popup, que se asigna a la propiedad PopupMenu de la ficha. Observe que una
referencia a la ficha (This) y un nombre para el objeto Popup (MiEmerg, en el ejemplo )
deben pasarse como parámetros al constructor. Como parámetro de nombre, puede
utilizar cualquiera que elija.

Capítulo 16, Uso del código generado 261


fdcode.dwp Page 262 Wednesday, August 30, 1995 2:18 PM

Plano de coordenadas
Cuando dispone visualmente las fichas en el Diseñador de fichas, no es necesario que se
preocupe de las coordenadas en que se pone cada control. Simplemente ponga los
controles donde desee y el Diseñador de fichas genera las coordenadas. Sin embargo, si
escribe el código que crea los objetos de ficha, necesita introducir las coordenadas
manualmente.
Cada ventana de ficha tiene una fuente asociada (especificada por la propiedad
ScaleFontName) que determina el tamaño del plano de coordenadas en que se sitúan los
objetos en la ficha. Si no define esa propiedad, el plano se basa en la fuente
MS Sans Serif. Esto es, la altura de las filas depende de la altura de línea de la fuente
ScaleFontName, y el ancho de las columnas depende de la anchura de carácter de esa
fuente. Aunque cada control que ponga en la ficha podría tener una fuente distinta, las
coordenadas para situar los controles (definidas por las propiedades Top, Left, Height y
Width) siempre se basan en la fuente ScaleFontName.
La Figura 16.7 muestra controles con diferentes fuentes y tamaños que se han situado en
el plano de coordenadas.
Figura 16.7 Plano de coordenadas

La propiedad Top de
este cuadro es 6.
Su propiedad Left es 3.
La propiedad Top de este
botón de comando es 16. Su
propiedad Left es 21.

La propiedad Left de un control especifica el número de columnas desde el margen


izquierdo de la ficha. Su propiedad Top especifica las filas desde la parte superior.
Las fuentes proporcionales, como Helvetica o Times, tienen anchuras diferentes para
cada carácter —una i ocupa menos espacio que una w. Si especifica una fuente
proporcional en la propiedad ScaleFontName de una ficha, dBASE utiliza la anchura
media de los caracteres como ancho de columna. Por esta razón, el uso de una fuente no
proporcional simplifica el trabajo con las coordenadas de una ficha.
En conjunto, la altura de fila y el ancho de columna de un plano de coordenadas
constituyen una unidad de carácter. Propiedades como Height y Width utilizan unidades
de carácter para determinar el tamaño y posición de los objetos. Por ejemplo, la
propiedad Height expresa la altura en unidades de carácter, como en este ejemplo:
MiFicha.MiObj.Height = 2.4 && 2.4 unidades de carácter
La altura real del objeto depende del número de unidades de carácter y del tamaño de la
unidad de carácter.

262 Guía del programador


fdcode.dwp Page 263 Wednesday, August 30, 1995 2:18 PM

Para la ubicación precisa de los controles, puede especificar unidades de carácter con
valores decimales, por ejemplo,
DEFINE PUSHBUTTON OK OF MiFicha AT 3.5, 1.5 PROPERTY TEXT "Aceptar"

Direcciones relativas
Para permitir el direccionamiento relativo, los objetos de ficha tienen métodos
NextRow() y NextCol(). NextRow() devuelve la siguiente fila disponible en el plano de
coordenadas, dado el último control dibujado; NextCol() devuelve la siguiente columna
disponible. Por ejemplo, los controles siguientes se alinearán correctamente sean cuales
sean las fuentes que especifique:
FontHeight 14
DEFINE TEXT T1 OF MiFicha AT MiFicha.NextRow()+1,2;
PROPERTY Text PROPERTY Text "First line", FontName "Times", FontHeight 24
DEFINE TEXT T1 OF MiFicha AT MiFicha.NextRow()+1,2
PROPERTY Text "Second line", FontName "Helvetica", FontHeight 14
DEFINE TEXT T1 OF MiFicha AT MiFicha.NextRow()+1,2
PROPERTY Text "Tercera línea", FontName "Arial", Height 12
DEFINE TEXT T1 OF MiFicha AT ROW(),MiFicha.NextCol()
PROPERTY Text "Otra opción de la tercera línea"

Capítulo 16, Uso del código generado 263


fdcode.dwp Page 264 Wednesday, August 30, 1995 2:18 PM

264 Guía del programador


p_table.dwp Page 265 Wednesday, August 30, 1995 2:19 PM

Parte

IV
Tablas
Parte IV

Esta parte trata temas relativos a la programación con tablas.


Si nunca ha programado en dBASE antes, los Capítulos 17 a 20 le enseñan los conceptos
básicos del uso de las tablas.
Los capítulos que componen esta sección son:
• Capítulo 17, “Uso de las tablas”
• Capítulo 18, “Uso de los registros y campos”
• Capítulo 19, “Ordenación y relación de registros”
• Capítulo 20, “Búsqueda y resumen de registros”
• Capítulo 21, “Programación para un entorno compartido”
• Capítulo 22, “Uso de las transacciones”
• Capítulo 23, “Uso de tablas que no son de dBASE”

Parte IV, Tablas 265


p_table.dwp Page 266 Wednesday, August 30, 1995 2:19 PM

266 Guía del programador


tables.dwp Page 267 Wednesday, August 30, 1995 2:19 PM

Capítulo

17
Uso de las tablas
Capítulo 17

Las tablas son el principal medio de almacenar datos en Visual dBASE.


En Visual dBASE, un solo archivo de datos se denomina tabla y un conjunto de tablas y
otros archivos asociados se denomina base de datos.
Este capítulo trata las tareas y técnicas básicas para crear y utilizar tablas con el lenguaje
dBASE. Para información sobre cómo trabajar con las tablas empleando los menús y
diseñadores de Visual dBASE, consulte la Guía del usuario.
Visual dBASE permite el acceso directo a las tablas de dBASE, de Paradox y SQL.
(Para acceder a las tablas SQL, es necesario haber instalado SQL Link de Borland.)
A menos que se indique lo contrario, los comandos y funciones de este capítulo
funcionan con todos estos tipos de tabla. (Para más información sobre diferencias
específicas en el uso de los comandos y funciones de dBASE para acceder a las tablas de
Paradox y SQL, consulte el Capítulo 23.) Para acceder a otros archivos de datos,
impórtelos en una tabla de dBASE. Para más información, consulte los comandos
IMPORT y APPEND FROM en la Referencia del lenguaje.

Capítulo 17, Uso de las tablas 267


tables.dwp Page 268 Wednesday, August 30, 1995 2:19 PM

Creación de tablas
En Visual dBASE, es posible crear tablas de dBASE (.DBF), Paradox (.DB) y SQL.
Para crear una tabla, defina y dé un nombre a los tipos de campo que contiene. Por cada
campo, especifique su nombre, tipo y anchura, e indique también si el campo tendrá
una etiqueta de índice. (En las tablas de Paradox, defina un índice principal.)
Las definiciones de campos constituyen colectivamente la estructura de una tabla.
Es posible crear una tabla nueva y rellenar todas las descripciones de los campos
manualmente. O bien puede copiar la estructura de una tabla existente, realizar cambios
en las descripciones de los campos y guardar la estructura con otro nombre de tabla.
Para crear una nueva tabla, utilice CREATE. Este comando activa el Diseñador de
tablas, que le permite definir interactivamente los campos dentro de una tabla.
Para más información sobre el uso del Diseñador de tablas, consulte la Guía del usuario.
Para crear una tabla a partir de la estructura de una existente, utilice COPY
STRUCTURE o CREATE...FROM junto con COPY STRUCTURE EXTENDED.
CREATE...FROM crea una tabla basada en las definiciones de campos contenidas en un
archivo de descripción de tabla. Para crear uno de estos archivos, copie la estructura de
una tabla abierta con COPY STRUCTURE EXTENDED. El ejemplo siguiente demuestra
cómo hacerlo:
USE TBLMODEL && Abre la tabla a copiar
COPY TO TBLDESC STRU EXTE && Copia su estructura
USE TBLDESC && Abre el archivo de descripción de la tabla
BROWSE && Hace cambios en la descripción de los campos
CREATE NUEVATBL FROM TBLDESC && Crea la tabla nueva
Visual dBASE también proporciona el comando CREATE...STRUCTURE EXTENDED,
que le permite crear una nueva tabla basada en la estructura de la tabla abierta
actualmente.
También pueden crearse tablas .DBF de dBASE y SQL mediante el comando
CREATE TABLE de SQL. Este comando es especialmente conveniente porque puede
especificar los campos y crear la tabla en un solo comando. Para más información,
consulte el Capítulo 9 de la Referencia del lenguaje.

268 Guía del programador


tables.dwp Page 269 Wednesday, August 30, 1995 2:19 PM

Apertura y cierre de tablas


Para acceder a la información de una tabla, ábrala primero con el comando USE.
Después de abrir una tabla, utilice cualquiera de los comandos que operan en las tablas
para acceder a los datos.
Cuando haya terminado de trabajar con una tabla, debería cerrarla. Las tablas abiertas
ocupan memoria, y mantenerlas abiertas cuando no son necesarias puede reducir el
rendimiento general. USE, especificado sin un nombre de tabla, cierra la tabla que hay
en el área de trabajo actual o en una especificada. El ejemplo siguiente demuestra la
apertura y cierre de tablas mediante USE:
SELECT 1 && Selecciona el área 1 como área de trabajo actual
USE NOMBRES && Abre la tabla NOMBRES.DBF
USE && Cierra la tabla NOMBRES.DBF
USE NOMBRES IN 3 && Abre la tabla NOMBRES.DBF en el área de trabajo 3
USE && NOMBRES.DBF todavía sigue abierta
USE IN 3 && Ahora NOMBRES.DBF está cerrada
Se supone que las tablas que abra están en la ubicación actual de los directorios de
archivos, a menos que incluya la vía de acceso, por ejemplo:
USE C:\CUENTAS\NOMBRES && Especifica una vía de acceso para la tabla
Algunas de las demás opciones del comando USE son: NOUPDATE, que abre la tabla
para sólo lectura; EXCLUSIVE, que impide el acceso de otros usuarios en un entorno
compartido; AGAIN, que abre una segunda copia de una tabla en otra área de trabajo.
Para una descripción y una relación completa de las opciones del comando USE,
consulte la Referencia del lenguaje.
El parámetro Sesiones del cuadro de diálogo Propiedades|Escritorio también afecta a
los parámetros de entorno de las tablas abiertas y cerradas desde la ventana de
comandos. Para más información, consulte “Uso de las sesiones” en el Capítulo 21 y el
comando CREATE SESSIONS en la Referencia del lenguaje.
dBASE cierra automáticamente todas las tablas abiertas cuando sale, pero es preferible
que cerrarlas explícitamente. Los comandos CLOSE ALL y CLOSE TABLES cierran
todas las tablas en todas las áreas de trabajo. Los programadores suelen ejecutar este
comando justo antes de un punto de salida de las aplicaciones.

Capítulo 17, Uso de las tablas 269


tables.dwp Page 270 Wednesday, August 30, 1995 2:19 PM

Uso de las bases de datos


Al crear o abrir una tabla, también puede especificar su posición en una base de datos.
Una base de datos especifica una ubicación para las tablas o en una base de datos SQL o
en un directorio de su disco duro o servidor de archivos. (Las bases de datos o los alias de
base de datos se crean mediante la utilidad de configuración BDE; para más información,
consulte la Guía del usuario.)
Los comandos OPEN DATABASE y CLOSE DATABASE abren y cierran,
respectivamente, una base de datos. Si define y abre más una base de datos a la vez,
seleccione la base de datos actual mediante el comando SET DATABASE. Para
especificar una base de datos que no sea la actual, indique su nombre (delimitado o
encerrado entre caracteres de dos puntos) antes del de la tabla.
OPEN DATABASE Cuentas && Abre la primera base de datos
OPEN DATABASE Ventas && Abre una segunda base de datos
SET DATABASE TO Cuentas && Selecciona Cuentas como base de datos actual
USE NOMBRES && Abre una tabla en la base de datos actual
USE :Ventas:Clientes && Abre una tabla en la base de datos Ventas
CLOSE DATABASES && Cierra todas las bases de datos
Siempre que abra una base de datos con los comandos USE, OPEN DATABASE o SET
DATABASE, pero no especifique explícitamente una contraseña, Visual dBASE muestra
un cuadro de diálogo que le solicita que introduzca la información, como nombre de
usuario y contraseña, que es necesaria para abrir y acceder a la base de datos
especificada. Entonces, puede acceder a las tablas existentes o crear o copiar nuevas
tablas en la base de datos.

Especificación de nombres y tipos de tablas


Especifique la tabla que se utilizará con un nombre de tabla literal o con una expresión
de caracteres que se evalúe como un nombre de tabla. Si almacena un nombre de
archivo en una variable de memoria, sitúe paréntesis alrededor del nombre de la
variable en el comando USE. Los paréntesis indican a dBASE que evalúe la variable en
primer lugar y entonces pase el resultado al comando.
USE NOMBRES && Abre la tabla Nombres
USE "NOMBRES" && Abre la tabla Nombres
cArchivo = "CIUDADES" && Guarda el nombre de la tabla en una variable
USE (cArchivo) && Abre la tabla Ciudades
USE &cArchivo && Usa el operador de sustitución de macro para abrir una tabla
Si no especifica una extensión de archivo, USE busca primero una tabla del tipo
especificado por el parámetro SET DBTYPE. Por defecto, DBTYPE se define como
DBASE; también puede definirse como PARADOX.

270 Guía del programador


tables.dwp Page 271 Wednesday, August 30, 1995 2:19 PM

Para abrir explícitamente una tabla de otro tipo, incluya la extensión con el nombre del
archivo. Para mayor claridad en el código, puede especificar la opción TYPE con el
comando USE para sustituir el valor del parámetro DBTYPE sin especificar la extensión
del archivo.
USE FACTURAS TYPE PARADOX && Abre una tabla Paradox
SET DBTYPE TO PARADOX && Hace que el formato de tabla por defecto sea Paradox
USE DEVOLUC && Abre DEVOLUC.DB
USE NOMBRES.DBF && Abre una tabla dBASE
USE ELEM TYPE DBASE && Abre una tabla dBASE

Referencias a tablas: alias


Un alias es un nombre alternativo de una tabla abierta, con un máximo de 32 caracteres
de longitud. Puede definir un alias para una tabla al abrirla con USE. Después de definir
un alias, puede utilizarlo en lugar del nombre real de la tabla al hacer referencia a ella en
un comando o función. Los alias hacen más legible el código fuente mediante la
sustitución de nombres habitualmente enigmáticos por nombres con un significado
claro.
SELECT A
USE CTAMAR93 ALIAS Cuentas && Una tabla cuyo nombre cambia cada mes
? ALIAS() && Devuelve "Cuentas"
? Fecha_Trans && Muestra un campo del área de trabajo actual
? A->Fecha_Trans && Muestra el mismo campo especificando la letra del
&& área de trabajo
? Cuentas->Fecha_Trans && Muestra el mismo campo especificando el alias de la tabla
No confunda los nombres de alias de tabla con los alias de base de datos. Un alias de base
de datos designa una posición para las tablas, bien sea una tabla SQL, bien un directorio
de su disco duro o del servidor de archivos. Para crear un alias de base de datos, use la
utilidad de configuración IDAPI. Para más información sobre el uso de dicha utilidad,
consulte la Guía del usuario.

Capítulo 17, Uso de las tablas 271


tables.dwp Page 272 Wednesday, August 30, 1995 2:19 PM

Uso de varias tablas: áreas de trabajo


Es posible acceder a datos de varias tablas simultáneamente. Para ello, debe utilizar las
áreas de trabajo. Un área de trabajo es una posición en memoria que está reservada para
una tabla abierta y sus archivos asociados, como índices y archivos memo y binarios.
Toda tabla que abra necesita su propia área de trabajo. El número máximo de áreas de
trabajo que puede abrir simultáneamente es 225.
A menos que especifique otra área de trabajo, los comandos y funciones que acceden a
datos operan en el área de trabajo actual. Por ejemplo, si hay dos tablas abiertas, el
resultado del comando LIST varía dependiendo de qué área de trabajo sea la actual.
Si la primera área de trabajo es la actual, LIST enumera los registros de la tabla uno; si la
segunda es la activa, se muestran los registros de la tabla dos.
Utilice el comando SELECT para definir el área de trabajo actual. Haga referencia a las
áreas de trabajo por:
• Su número (1 a 225)
• Su letra (A para el área de trabajo 1, B para el área 2, y así sucesivamente hasta J)
• El nombre de alias de la tabla abierta en un área de trabajo
SELECT 1 && Selecciona el área 1 como área de trabajo actual
USE NOMBRES && Abre NOMBRES.DBF en el área de trabajo actual
LIST && Lista los registros de NOMBRES.DBF
SELECT B && Selecciona el área de trabajo 2
USE CIUDADES ALIAS Lugares && Abre CIUDADES.DBF con el alias Lugares
LIST && Lista los registros de CIUDADES.DBF
SELECT NOMBRES && Selecciona el área de trabajo 1
SELECT Lugares && Selecciona el área de trabajo 2
El uso de los nombres de tabla o de alias en los comandos SELECT proporciona una
ventaja sobre el uso de los números o letras de áreas de trabajo. Si cambia de área de
trabajo, no es necesario reescribir el comando SELECT. Es decir, si abre una tabla en otra
área de trabajo, cambian su número y letra, pero no el nombre de tabla o de alias.
La opción IN del comando USE indica a dBASE que abra o cierre una tabla en un área
de trabajo sin convertir ese área en la actual. Algunos otros comandos de dBASE que
acceden a datos, como GO, SKIP y LIST STRUCTURE, también disponen de opciones
IN para operar sobre registros de otra área de trabajo.
SELECT 1 && Selecciona el área 1 como área de trabajo actual
USE NOMBRES && Abre NOMBRES.DBF en el área de trabajo 1
USE PROVINC IN 3 ORDER Provinc && Abre PROVINC.DBF en el área de trabajo 3
&& El área de trabajo 1 es todavía el área actual
SELECT C && El área de trabajo 3 es ahora el área actual
GO 10 && Va al registro 10 en PROVINC.DBF
GO 20 IN A && Va al registro 20 en NOMBRES.DBF

272 Guía del programador


tables.dwp Page 273 Wednesday, August 30, 1995 2:19 PM

Además, algunas funciones de dBASE, como por ejemplo EOF(), FOUND() y SEEK(),
también proporcionan opciones que especifican un área de trabajo.
SELECT 1 && Selecciona el área 1 como área de trabajo actual
SEEK("MA", 3) && Busca la provincia "MA" en el área de trabajo 3
&& Es necesario que la tabla este indexada por el campo de
&& búsqueda
IF FOUND(3) && Se determina si el registro fue encontrado
ƒ
ENDIF
Cuando acceda a datos de más de una tabla, probablemente deseará abrir una tabla en
la siguiente área de trabajo disponible. La función SELECT() devuelve el número de la
siguiente área de trabajo disponible.
USE NOMBRES && Abre NOMBRES.DBF
USE CIUDADES IN SELECT() && Abre la tabla en un área de trabajo disponible
USE PROVINC IN SELECT() && Abre la tabla en un área de trabajo disponible
Utilice estos comandos y funciones para obtener información sobre las áreas de trabajo:
• LIST STATUS muestra el área de trabajo en que está abierta cada tabla e indica qué
área de trabajo es la actual.
• DBF() devuelve el nombre de la tabla abierta en un área de trabajo específica.
• WORKAREA() devuelve el número del área de trabajo actual.

Cambio de nombre de las tablas


Visual dBASE proporciona dos formas de cambiar un nombre de tabla en disco:
• RENAME asigna otro nombre a cualquier tipo de archivo. Con RENAME especifique
la extensión del archivo. Si la tabla tiene un archivo .MDX o .DBT asociado, cambie su
nombre también.
• RENAME TABLE da el mismo nombre a una tabla y a sus archivos asociados, por
ejemplo archivos de índice de producción (.MDX) y memo (.DBT). En el caso de
tablas de Paradox, RENAME TABLE cambia el nombre de toda la familia de la tabla,
incluidos los índices y los archivos .VAL. Al utilizar RENAME TABLE, especifique la
opción TYPE para indicar el tipo de tabla, dBASE o Paradox, que desea renombrar.
RENAME NOMBRES.DBF TO PERSONAS.DBF && Renombra NOMBRES.DBF como PERSONAS.DBF
RENAME TABLE NOMBRES TO PERSONAS TYPE DBASE && Renombra la tabla NOMBRES y todos los
&& archivos asociados a ella
Cuando cambie el nombre de una tabla, asegúrese también de que actualiza las fichas,
informes, etiquetas o archivos de programa que hacen referencia al nombre antiguo de
la tabla.

Capítulo 17, Uso de las tablas 273


tables.dwp Page 274 Wednesday, August 30, 1995 2:19 PM

Copia de tablas
Visual dBASE proporciona varias opciones para realizar copias de una tabla, incluida la
copia de datos a las tablas de una base de datos. Es posible copiar toda una tabla, sólo
ciertos registros o sólo la estructura sin registros. También es posible copiar archivos
DOS individuales especificando su extensión.
• COPY copia toda una tabla o ciertos registros y campos seleccionados. La tabla que
desee copiar debe estar abierta. COPY también proporciona una opción TYPE que le
permite copiar una tabla de un tipo a otro, dBASE o Paradox. Si copia una tabla .DBF
que contiene campos memo a otra tabla .DBF, COPY crea automáticamente un
archivo memo para la nueva tabla. Si hay un índice activo, los registros se copian a la
nueva tabla en el orden del índice; sin embargo, COPY no crea un archivo .MDX para
la nueva tabla a menos que especifique la opción WITH PRODUCTION.
• COPY...WITH PRODUCTION copia una tabla de dBASE a otra tabla de dBASE con
otro nombre y también copia sus archivos asociados de índice de producción (.MDX)
y memo (.DBT).
• COPY STRUCTURE realiza una copia vacía de una tabla sin copiar sus registros.
La tabla de que desea copiar la estructura debe estar abierta antes de ejecutar el
comando COPY STRUCTURE.
• COPY TABLE copia una tabla y sus archivos asociados que tienen el mismo nombre,
como los archivos de índice de producción (.MDX) y memo (.DBT). Utilice COPY
TABLE para duplicar una tabla con rapidez. Para las tablas de Paradox, COPY
TABLE copia toda la familia de la tabla, incluidos los índices y los archivos .VAL.
No es necesario que estén abiertas las tablas que copie.
• COPY FILE copia cualquier tipo de archivo especificado por su extensión. Si copia
una tabla, debe estar cerrada antes de ejecutar el comando COPY FILE. Este comando
no copia los archivos .DBT ni .MDX con la tabla; cópielos por separado.
COPY FILE PROG.PRG TO PROVIEJO.PRG && Copia el archivo PROG.PRG
USE NOMBRES
COPY TO PERSONAS TYPE PARADOX && Copia NOMBRES.DBF a una tabla Paradox
COPY STRUCTURE TO CLIENTES && Copia la estructura de la tabla NOMBRES.DBF
COPY TABLE NOMBRES TO AGENDA && Copia la tabla NOMBRES.DBF y todos sus
&& archivos asociados a otra tabla dBASE

274 Guía del programador


tables.dwp Page 275 Wednesday, August 30, 1995 2:19 PM

Modificación de la estructura de una tabla


Después de crear una tabla, es posible que necesite realizar cambios en su estructura.
Ésta puede modificarse incluso después de introducir datos en la tabla. Es posible:
• Añadir un campo.
• Eliminar un campo.
• Cambiar el ancho o tipo de datos de un campo.
• Reordenar la secuencia de campos.
LIST STRUCTURE muestra información sobre la estructura de una tabla.
MODIFY STRUCTURE, que activa el Diseñador de tablas, es el principal comando
utilizado para cambiar la estructura de una tabla. El Diseñador de tablas le permite
cambiar interactivamente las definiciones de los campos. Para más información sobre su
uso, consulte la Guía del usuario.
De forma alternativa, puede cambiar la estructura de una tabla por completo bajo el
control de un programa creando una copia modificada de la tabla utilizando COPY TO
STRUCTURE EXTENDED y CREATE FROM. La sección “Creación de tablas” de este
capítulo describe cómo funcionan estos comandos en conjunto y proporciona un
ejemplo.
También puede utilizar el comando ALTER TABLE de SQL para añadir y/o borrar
campos en una tabla, incluso varios campos en un solo comando. Para más información,
consulte el Capítulo 9 de la Referencia del lenguaje.

Borrado de tablas
Utilice DELETE TABLE, DELETE FILE, ERASE o el comando DROP TABLE de SQL
para borrar una tabla del disco de forma permanente.
ERASE borra un archivo del disco. DELETE FILE realiza la misma función que ERASE.
Ninguno de los dos comandos borra los archivos asociados, como archivos de índice o
memo; bórrelos por separado.
DELETE TABLE y DROP TABLE borran una tabla y sus archivos asociados con el
mismo nombre, como .MDX o .DBT. Sin embargo, no borran archivos, como por
ejemplo, fichas, informes, etiquetas o archivos de programa, que se utilicen con la tabla.
En el caso de las tablas de Paradox, DELETE TABLE o DROP TABLE borran toda la
familia de la tabla, incluidos los índices y los archivos .VAL.
La tabla que desee borrar, y sus archivos asociados, debe estar cerrada antes de ejecutar
el comando ERASE, DELETE FILE, DELETE TABLES o DROP TABLE.
ERASE TEMP.DBF && Borra el archivo TEMP.DBF
DELETE FILE TEMP.DBF && Hace la misma operación
DELETE TABLE TEMP.DBF && Borra la tabla TEMP.DBF y todos sus archivos asociados
Siempre que borre una tabla, asegúrese también de actualizar las fichas, informes,
etiquetas o archivos de programa que puedan depender de la información de la tabla.

Capítulo 17, Uso de las tablas 275


tables.dwp Page 276 Wednesday, August 30, 1995 2:19 PM

Protección de datos
La accesibilidad de los datos puede limitarse a las personas que deben trabajar con ellos.
dBASE proporciona una utilidad interna, denominada PROTECT, que le permite crear
y mantener seguridad en las aplicaciones. PROTECT aporta mayor seguridad a una
base de datos mediante:
• El impedimento de que usuarios no autorizados se conecten al sistema
• El control de los archivos y los campos a los que puede tener acceso cada usuario
• El cifrado de los datos de las tablas
PROTECT sólo funciona con tablas .DBF de dBASE. Sin embargo, Visual dBASE
también proporciona la posibilidad de la administración de Contraseñas principales de
las tablas de Paradox. Por supuesto, Visual dBASE también respeta la seguridad de los
servidores de bases de datos SQL. Para más información sobre la definición de
seguridad, consulte la Guía del usuario.

276 Guía del programador


recfld.dwp Page 277 Wednesday, August 30, 1995 2:20 PM

Capítulo

Uso de los registros y campos


Capítulo 18
18
En Visual dBASE, puede ver y editar los datos de las tablas de forma interactiva,
realizando operaciones desde el escritorio, o puede diseñar programas y fichas de
dBASE para recuperar y actualizar los datos.
Este capítulo describe métodos de visualización y actualización de datos de las tablas
por medio de los comandos y funciones del lenguaje dBASE. (Para información sobre la
visualización y edición de datos mediante el interface de usuario, consulte la
Guía del usuario. Para más información sobre la edición de datos en un entorno
compartido, consulte el Capítulo 21 de esta guía.)
A menos que se indique lo contrario, los comandos y funciones de este capítulo
funciona de la misma forma con las tablas de Paradox, dBASE y SQL. (Para más
información sobre diferencias específicas al utilizar comandos y funciones de dBASE
para acceder a las tablas de Paradox y SQL, consulte el Capítulo 23.)

Acerca del trabajo con registros


Dentro de las tablas, los registros suponen el medio principal de almacenar información
que posteriormente pueda buscarse y recuperarse. Los campos individuales de un
registro almacenan todos los elementos que identifican un registro específico de forma
exclusiva. Por ejemplo, una tabla puede almacenar información sobre cada uno de los
clientes o empleados de una empresa. Cada registro contendría campos, como nombre,
número de teléfono y dirección, con la información correspondiente a un cliente o
empleado en particular.
La adición de registros y la edición de datos son dos de las operaciones básicas que se
realizan en los programas de dBASE. Sin embargo, antes de poder comenzar a realizar
estas tareas, debe comprender algunos conceptos básicos del trabajo con los registros,
como el desplazamiento por ellos y la elección de comandos para añadir o actualizar
datos. Este capítulo contiene información sobre el uso de comandos y funciones, tanto
de forma interactiva como en los programas, para añadir y actualizar datos en los
campos y registros.

Capítulo 18, Uso de los registros y campos 277


recfld.dwp Page 278 Wednesday, August 30, 1995 2:20 PM

Los temas tratados en este capítulo son los siguientes:


• Uso del comando BROWSE
• Desplazamiento por los registros
• Especificación y delimitación de campos
• Adición de registros
• Actualización de los registros
• Marcado y borrado de los registros
• Uso de las variables Automem
• Uso de los campos memo, binarios y OLE
También es posible diseñar objetos personalizados para ver y editar datos de las tablas
de forma interactiva. Por ejemplo, es posible definir campos en una ficha y vincularlos
con ciertos campos de una tabla mediante las propiedades DataSource y DataLink.
Para más información sobre el diseño de fichas personalizadas, consulte la Parte III,
“Fichas” de esta guía.

Uso del comando BROWSE


En Visual dBASE, la forma más rápida de comenzar a editar datos es utilizar el comando
BROWSE, que puede ejecutarse desde la ventana de comandos o desde dentro de un
programa dBASE; por ejemplo:
USE PEDIDOS
BROWSE
Los comandos de dBASE que se introducen en la ventana de comandos producen los
mismos resultados que trabajando con los menús y diseñadores del interface.
Por ejemplo, el comando BROWSE muestra varios registros en una ventana
redimensionable. Esta misma ventana puede aparecer cuando se abre una tabla con el
Selector. (Las opciones del escritorio controlan si una tabla se abre en modo Browse,
Edición o Edición en columnas.) Al utilizar BROWSE, es posible mostrar registros de la
tabla, editar los datos de los campos y añadir nuevos registros al final de la tabla.

Opciones del comando BROWSE


El comando BROWSE proporciona ciertas opciones que puede especificar al ejecutarlo.
Por ejemplo, puede especificar una opción FIELDS para seleccionar los campos de una
tabla que se incluyen en la ventana:
BROWSE FIELDS NUM_PED,FECH_ENVIO,PAGADO

278 Guía del programador


recfld.dwp Page 279 Wednesday, August 30, 1995 2:20 PM

Otras opciones del comando, como NOAPPEND, NOEDIT y NODELETE, especifican


los tipos de cambios que pueden realizarse en una tabla. FOR, WHILE y otras opciones
de ámbito seleccionan los registros que se incluyen en la ventana BROWSE. (Para más
información sobre las opciones del comando BROWSE, consulte la Referencia del
lenguaje.) Además, el comando SET FIELDS limita los campos que están disponibles con
BROWSE.

Edición de datos registro por registro


Para mostrar y editar datos registro por registro, puede pulsar F2 o elegir Ver|Formato
Ficha en la ventana Registros de tabla. Es equivalente a ejecutar el comando EDIT en la
ventana de comandos o dentro de un programa de dBASE. También puede usar RePág,
AvPág o las teclas de dirección para moverse entre los campos o registros de la ventana.
También puede editar datos mediante el comando APPEND. Al ejecutarlo, aparece una
ventana en que se muestran los campos de un nuevo registro vacío. Una vez ejecutado
ese comando, puede desplazarse por la tabla para editar registros existentes.
USE PEDIDOS && Abre la tabla PEDIDOS.DBF
APPEND && Abre una ventana y sitúa el puntero de registro
&& al final de la tabla
La opción BLANK de APPEND es un comando de programación; por tanto, no muestra
la tabla en una ventana de edición, sino que añade un registro vacío al final de la tabla.

Almacenamiento de los cambios


dBASE guarda los cambios realizados en una tabla cuando se produce el
desplazamiento desde un registro añadido o modificado. Los cambios se almacenan en
una memoria intermedia de registros y, se escriben en el disco conforme se va llenando
la memoria intermedia. Para proteger los datos aún más y reducir la posibilidad de que
se pierdan, puede definir SET AUTOSAVE ON, que indica al programa que escriba en
disco los cambios que se efectúan en cada registro.
Para escribir los cambios en el disco de inmediato (con SET AUTOSAVE OFF), utilice el
comando FLUSH después de añadir o editar registros. Los cambios también se escriben
en el disco cuando se sale de los comandos BROWSE, EDIT o APPEND pulsando Ctrl+W,
cuando se cierra la ventana y se elige guardar los cambios, o cuando se eligen opciones
que guardan y salen de la tabla en el menú Archivo de Visual dBASE.
Para salir sin guardar los cambios en el registro actual, puede pulsar Esc o Ctrl+Q, o elija
opciones que abandonan los cambios en el último registro y salen de la tabla en el menú
Archivo de Visual dBASE.

Capítulo 18, Uso de los registros y campos 279


recfld.dwp Page 280 Wednesday, August 30, 1995 2:20 PM

Desplazamiento por los registros


Antes de editar datos, debe identificar los registros que desea modificar. Los comandos
que hacen referencia a registros individuales y mueven el puntero de registro suponen
una forma de desplazarse por una tabla y seleccionar los registros que desea editar.
Puede moverse adelante y atrás por una tabla, ir al primer o al último registro de una
tabla o ir a un registro específico.
dBASE proporciona otros comandos y funciones que le permiten contar los registros de
una tabla que cumplen una condición en particular o determinar el número, tipo o
nombre de los campos de una tabla.

Uso de los números de registro e indicadores


Cada vez que se añade un nuevo registro en una tabla de dBASE, Visual dBASE le
asigna un número de registro exclusivo. Al trabajar con la tabla, utilice números de
registro para identificar registros individuales y seleccionar registros concretos para
operaciones que muestran, actualizan o borran datos. Utilice también números de
registro cuando desee bloquear un registro antes de actualizarlo.
Cuando abre una tabla por primera vez, dBASE sitúa el puntero en el primer registro.
Entonces, puede utilizar comandos y funciones para ir a otros registros y procesarlos.
A diferencia de las tablas de dBASE, las de Paradox y SQL no tienen números de
registro fijos. Cuando dBASE recupera datos de uno de estos dos tipos de tablas, asigna
dinámicamente un marcador especial, denominado indicador. Aunque los indicadores
son similares a los números de registro en las tablas de dBASE, hay algunas diferencias.
Para más información, consulte el Capítulo 23.

Desplazamiento a un registro específico


El comando GO (o GOTO) permite situar el puntero en un registro en particular de una
tabla de dBASE abierta, por ejemplo:
GO 23 && Sitúa el puntero de registro en el registro 23
DISPLAY && Muestra el registro actual, el número 23
GO TOP && Sitúa el puntero de registro al principio de la tabla
? RECNO() && Muestra el número del registro actual
GO BOTTOM && Sitúa el puntero de registro al final de la tabla
gReg = 20 && Guarda un número de registro en una variable de memoria
GO gReg && Sitúa el puntero de registro en el valor indicado por gReg

280 Guía del programador


recfld.dwp Page 281 Wednesday, August 30, 1995 2:20 PM

GO puede utilizarse para situar el puntero de registro en una tabla de Paradox o SQL;
sin embargo, no puede especificarse un valor numérico, sino que debe emplearse el
indicador asignado a un registro en particular con las funciones BOOKMARK() o
RECNO().
GO TOP && Sitúa el puntero de registro al principio de la tabla
LOCATE FOR NOMBRE = "Case"&& Sitúa el puntero el primer registro en que NOMBRE = "Case"
gBkmark = BOOKMARK() && Guarda un indicador en una variable de memoria
GO TOP && Va al principio de la tabla
GO gBkmark && Sitúa el puntero de registro en la posición indicada por
&& el valor de la variable gBkmark
Aunque es posible utilizar indicadores asignados en comandos y funciones en lugar de
un número de registro, no puede mostrarse directamente el valor de un indicador.
Sin embargo, puede crear expresiones que comparan el valor relativo de un indicador
respecto a otro, por ejemplo,
IF BkMark1 < BkMark2 && Comprueba si el primer indicador va antes que el segundo en
&& la tabla

Uso de las opciones de ámbito


La mayoría de los comandos y funciones que operan con registros también permiten
especificar una opción <ámbito>. Así, puede seleccionar los registros que desea procesar.
Por ejemplo, con los comandos BROWSE y EDIT, puede especificar un número de
registro individual, un grupo de números de registro, ALL (todos los registros) o REST
(los registros restantes de una tabla).
BROWSE REST && Muestra el resto de la tabla
A menos que especifique un ámbito concreto, la mayoría de comandos y funciones de
dBASE operan con el registro actual. Sin embargo, consulte la sintaxis de cada comando
o función para determinar los registros en que actuará. Por ejemplo, BROWSE y EDIT
operan sobre todos los registros de una tabla si no especifica el ámbito o incluye las
opciones FOR o WHILE. DISPLAY muestra el registro actual si no se incluyen opciones
adicionales.

Desplazamiento hacia adelante y hacia atrás


El comando SKIP desplaza el puntero de registro hacia adelante o hacia atrás desde su
posición actual, por ejemplo:
SKIP 5 && Mueve el puntero de registro 5 posiciones hacia adelante
SKIP -10 && Mueve el puntero de registro 10 posiciones hacia atrás
Los índices que especifique al abrir una tabla con el comando USE controlan el orden en
que se sitúan los registros recuperados. La ordenación de los registros también afecta a
muchos otros comandos y funciones que muestran registros o actúan sobre ellos. Para
más información sobre el uso de los índices, consulte el Capítulo 19.

Capítulo 18, Uso de los registros y campos 281


recfld.dwp Page 282 Wednesday, August 30, 1995 2:20 PM

Determinación de la posición del puntero de registro


La función RECNO() devuelve el número del registro actual en las tablas de dBASE.
En el caso de las de Paradox y SQL, dBASE devuelve el valor de un indicador asignado
al registro actual. Las funciones BOF() y EOF() pueden utilizarse para indicar cuándo se
ha llegado al principio o al final de una tabla.
USE EMPRESA
LOCATE FOR PROVINCIA = "MA" && Localiza el primer registro en el que PROVINCIA = "MA"
gReg = RECNO() && Guarda el número de registro
GO TOP && Sitúa el puntero de registro al principio de la tabla
DO WHILE .NOT. EOF() && Procesa registros hasta llegar al final del archivo
? EMPRESA, VENTAS_AC
SKIP
ENDDO
GO gReg && Sitúa el puntero en la posición indicada por gReg

Recuento de los registros


También es posible utilizar la función RECCOUNT() o el comando COUNT para
determinar el número de registros que hay en la tabla actual. COUNT permite
especificar una condición que seleccione los registros que se cuentan.
USE EMPRESA
Cnt_Reg = RECCOUNT() && Cuenta todos los registros de la tabla
COUNT TO Cnt_Condi && Cuenta todos los registros en los que PROVINCIA = "MA"
FOR PROVINCIA = "MA"
? Cnt_Condi/Cnt_Reg && Calcula el porcentaje de registros en que PROVINCIA="MA"
El comando SET FILTER afecta a los registros procesados por el comando COUNT, pero
no a los procesados por la función RECCOUNT().

282 Guía del programador


recfld.dwp Page 283 Wednesday, August 30, 1995 2:20 PM

Especificación y delimitación de campos


Muchos comandos de dBASE especifican una lista que identifica los campos de la tabla
actual, o de una tabla abierta en otra área de trabajo, que se muestran o que procesa una
operación de base de datos. Esto es útil, por ejemplo, en situaciones en que no es
necesario ver o editar todos los campos de una tabla o cuando se ven campos de
registros relacionados de más de una tabla.

Especificación de una lista de campos


El comando SET FIELDS especifica una lista de campos que puede utilizarse con
comandos que incluyen una opción de lista de campos en la sintaxis. Mediante SET
FIELDS ON u OFF, también es posible activar o desactivar la lista de campos incluidos
con el comando SET FIELDS TO. CLEAR FIELDS borra la lista de campos definida por
el comando SET FIELDS.
USE EMPRESA
SET FIELDS TO EMPRESA, PROVINCIA && Selecciona la inclusión de dos campos en la lista
LIST ALL FOR PROVINCIA = "MA" && Lista todos los registros en los que PROVINCIA="MA"
SET FIELDS también proporciona otras opciones, como la definición de campos
calculados y la utilización para sólo lectura de campos especificados.

Uso de campos de más de un área de trabajo


El comando SET FIELDS también puede utilizarse para incluir campos de varias tablas
abiertas en distintas áreas de trabajo. Al especificar campos de una tabla que no está
abierta en el área de trabajo actual, utilice la notación alias->campo.
SELECT 1
USE PEDIDOS ORDER Num_Ped && Abre la tabla en el área de trabajo 1
SELECT 2
USE ELEMENTO ORDER Num_Ped && Abre la tabla en el área de trabajo 2
SET RELATION TO Num_Ped INTO PEDIDOS && Establece relación entre ELEMENTO y PEDIDOS.DBF
SET FIELDS TO PEDIDOS->NUM_CLI,; && Define una lista de campos de una o más tablas
NUM_PED, EXISTENC, CANTIDAD, TOTAL; && Califica los campos del área de trabajo
&& no actual
Nota Es posible incluir campos de tablas de cualquier área de trabajo; sin embargo, es
necesario utilizar SET RELATION para establecer cualquier conexión entre tablas
abiertas en áreas de trabajo diferentes. Para una explicación de las opciones y relaciones
de tablas, consulte el Capítulo 19.

Capítulo 18, Uso de los registros y campos 283


recfld.dwp Page 284 Wednesday, August 30, 1995 2:20 PM

Uso de información sobre los campos


Utilice la función FLDLIST() para devolver la lista actual de campos definida por el
comando SET FIELDS. Además, puede obtener información sobre los campos de una
tabla utilizando funciones como FIELD(), FLDCOUNT() y FLENGTH(). FIELD()
devuelve el nombre de un campo de una tabla según su número de campo.
FLDCOUNT() devuelve el número de campos de una tabla, mientras que FLENGTH()
devuelve la longitud de un campo especificado.
USE EMPRESA
SET FIELDS TO EMPRESA, CIUDAD
? FLDLIST() && Devuelve los campos de la lista actual
DECLARE MatrCamp[FLDCOUNT()] && Crea una matriz de tantos elementos como campos tiene
&& la tabla. La matriz será usada para copiar valores
&& desde los campos de un registro o a ellos
FOR PtrMatr = 1 TO FLDCOUNT() && Bucle hasta que PtrMatr alcance el valor de FLDCOUNT()
? FIELD(PtrMatr) && Devuelve los nombres de los campos de la lista
NEXT

Delimitación de los nombres de campo


Las normas definidas para la asignación de nombres a los campos de las tablas de
dBASE no permiten la introducción de espacios ni otros caracteres especiales en los
nombres de los campos (que deben comenzar con una letra y pueden contener cualquier
letra o número y el carácter de subrayado). Sin embargo, al acceder a las tablas de
Paradox y SQL, los nombres de campo de éstas pueden contener espacios y otros
caracteres no permitidos en los de dBASE. Para incluir los nombres de estos campos en
un comando de dBASE, encierre entre signos de dos puntos los nombres de campo que
no sean estándar.
SET FIELDS TO :Precio venta: && Los dos puntos encierran un campo que contiene espacios
Los nombres de campo también pueden delimitarse para acceder a campos de tablas de
dBASE que tienen identificadores distintos de controlador de idioma y que contienen
caracteres que no son válidos localmente. Por ejemplo, DRÜCKER es un nombre de
campo válido en alemán, pero “Ü” no es un carácter alfabético válido en el juego de
caracteres de Estados Unidos. Para más información, consulte el Apéndice C.

284 Guía del programador


recfld.dwp Page 285 Wednesday, August 30, 1995 2:20 PM

Adición de registros
Para añadir registros en una tabla, utilice los comandos APPEND e INSERT.
Normalmente, se utiliza el comando APPEND, y se deja que dBASE añada los registros
en la tabla (después del último registro de la tabla, si ésta tiene asignados números de
registro). INSERT apenas se utiliza, ya que precisa la reorganización de la tabla o de su
índice cada vez que se añade un registro.

Adición de registros
La ejecución del comando APPEND añade un nuevo registro y muestra una ventana en
que poder editar sus datos. APPEND BLANK añade un nuevo registro y sitúa el
puntero de registro en él, pero no lo muestra. Para editar los datos, utilice EDIT si desea
editarlo interactivamente o REPLACE para añadir datos en campos especificados.
USE EMPRESA
APPEND BLANK && Añade un registro al final de la tabla EMPRESA.DBF
&& y sitúa el puntero de registro en esa posición
REPLACE EMPRESA WITH "ABC " && Actualiza los campos en el registro añadido
&& con APPEND BLANK
Visual dBASE también permite el uso de del comando INSERT FROM de SQL para
añadir registros en una tabla. Para más información, consulte el Capítulo 9 de la
Referencia del lenguaje.

Adición de registros desde dentro de una ficha


En las aplicaciones de Visual dBASE, la mayor parte de la introducción de datos se
realizará mediante las fichas. Las fichas de Visual dBASE tienen propiedades y métodos
que hacen más directa la adición de registros. La Tabla 18.1 resume estas propiedades.
Tabla 18.1 Propiedades y métodos de ficha para añadir registros
Propiedad Descripción
AbandonRecord( ) No guarda datos en la tabla y borra el registro vacío añadido.
BeginAppend( ) Añade un registro vacío en la tabla para recibir datos de la ficha.
IsRecordChanged( ) Determina si los datos de los campos de la ficha han cambiado.
Devuelve .T. si los datos han cambiado, y .F. si no es así.
SaveRecord( ) Escribe los datos de la ficha en el registro vacío de la tabla que se
ha creado con AppendRecord( ).
View Contiene el nombre de la tabla o vista que se utiliza con esta
ficha. La tabla o vista se abre cuando se crea el objeto de ficha.

Para utilizar una ficha como sistema de entrada de datos, defina la propiedad View de la
ficha con el nombre de la tabla o vista en que desea añadir datos. Asegúrese de que los
campos en que desea añadir datos están en la ficha y de que sus propiedades Datalink
están definidas con los campos correctos. Para comenzar la introducción de datos, llame
al método BeginAppend( ) de la ficha para añadir un registro vacío en la tabla.
Introduzca los datos en los campos de la ficha.

Capítulo 18, Uso de los registros y campos 285


recfld.dwp Page 286 Wednesday, August 30, 1995 2:20 PM

Cuando todos los datos están introducidos en la ficha (lo cual suele indicarse pulsando
un botón Aceptar), llame al método IsRecordChanged( ) para comprobar la
introducción de los datos. Si IsRecordChanged( ) devuelve .T., llame a SaveRecord( )
para guardar los datos en el registro vacío. Si no se han introducido datos nuevos, o si
no deberían guardarse los cambios (es decir, se pulsó el botón Cancelar), llame al
método AbandonRecord( ) para cancelar la operación de adición y borrar el registro
vacío de la tabla. El código siguiente ilustra estas operaciones.
LOCAL F
F = NEW FICHADEPAR()
F.OPEN()
RETURN
CLASS FICHADEPAROF FORM
this.Text = "Nombre departamento"
this.Left = 43
this.Top = 6
this.ColorNormal = "/BTNFACE"
this.View = "DEPARTAM.DBF" && La ficha utiliza la tabla DEPT.DBF
this.Height = 5
this.Width = 35
* Añade un registro para los nuevos datos cuando se abre la ficha
this.OnOpen = {;this.BeginAppend()}
this.OnClose = CLASS::Proc_OnClose && realiza alguna limpieza al cerrar la ficha
this.ButtonPushed = .F.&& Indica si el botón se pulsó para salir
DEFINE TEXT TEXTO1 OF THIS;
PROPERTY;
Text "&Departamento",;
Left 7,;
Top 0.4688,;
ColorNormal "BtnText/BtnFace",;
PageNo 1,;
Border .F.,;
Height 0.7656,;
Width 11.666
DEFINE ENTRYFIELD CAMPOENT1 OF THIS;
PROPERTY;
DataLink "DEPARTAM->DEPARTAMENTO",; && el campo señala al campo DEPARTAMENTO de
DEPARTAM
Left 7,;
ColorHighLight "WindowText/Window",;
Top 1.4102,;
ColorNormal "WindowText/Window",;
PageNo 1,;
Border .T.,;
Height 1.001,;
Width 20.666

286 Guía del programador


recfld.dwp Page 287 Wednesday, August 30, 1995 2:20 PM

DEFINE OKBUTTON BOTONACEPTAR1 OF THIS;


PROPERTY;
Left 2,;
Top 2.8223,;
Height 1.5303,;
Width 14.166,;
Group .T.,;
OnClick CLASS::AceptarGuardar
DEFINE CANCELBUTTON BOTONCANCELAR1 OF THIS;
PROPERTY;
Left 19,;
Top 2.8223,;
Height 1.5303,;
Width 14.166,;
Group .T.,;
OnClick CLASS::CancelarGuardar

* Procedimiento Form.OnClose
PROCEDURE Proc_OnClose
* si el usuario no salió de la ficha pulsando el botón Aceptar o Cancelar
* es decir, el usuario pulsó Escape
IF .NOT. this.ButtonPushed
this.AbandonRecord() && abandona la operación de añadir y borra el registro vacío
ENDIF
RETURN

* Procedimiento OnClick del botón Aceptar


PROCEDURE AceptarGuardar
form.ButtonPushed = .T. && regista que el usuario pulsó un botón
IF form.IsRecordChanged() && comprueba si el usuario ha introducido datos
form.SaveRecord() && si el usuario introdujo datos, los guarda en
&& un registro vacío
ELSE && el usuario NO introdujo datos
form.AbandonRecord() && abandona la operación de añadir y borra el registro
ENDIF
form.Close()
RETURN

* Procedimiento OnClick del botón Cancelar


PROCEDURE CancelarGuardar
form.ButtonPushed = .T. && registra que se pulsó un botón
form.AbandonRecord() && abandona la operación de añadir y borra el registro vacío
form.Close()
RETURN

ENDCLASS

Capítulo 18, Uso de los registros y campos 287


recfld.dwp Page 288 Wednesday, August 30, 1995 2:20 PM

Inserción de registros
Los comandos INSERT e INSERT BLANK funcionan de forma similar a APPEND y
APPEND BLANK, excepto que los dos comandos INSERT insertan el nuevo registro
inmediatamente después del actual.
USE EMPRESA
INSERT BLANK && Inserta un registro después del actual
REPLACE EMPRESA WITH "Herramientas Acme" && Actualiza el registro insertado
INSERT también dispone de una opción BEFORE para insertar un nuevo registro antes
de la posición actual del puntero de registro.
Es posible añadir registros en las tablas de Paradox y SQL; sin embargo, dado que estas
tablas no tienen números de registro, el comportamiento de los comandos APPEND e
INSERT varía en algunas situaciones. Para más información sobre la adición (inserción)
de registros, consulte el Capítulo 23.

Adición de nuevos registros con índices activos


El comportamiento tanto de APPEND [BLANK] como de INSERT [BLANK] depende
de si hay un índice activo cuando se abre la tabla. Si utiliza un índice, los registros se
disponen en el orden del índice, no por número de registro. Cuando añade un registro y
edita sus datos, el registro se muestra en su posición correcta en el índice de la tabla.
De forma similar, cuando inserta un registro y edita los datos, el registro se muestra en
su posición correcta según el índice. (INSERT añade registros vacíos inicialmente al final
del archivo cuando se utiliza un archivo de índice.) En ambos casos, el puntero de
registro sigue al registro añadido. Para mantener el puntero en el mismo lugar después
de la edición de un registro mediante BROWSE, utilice la opción NOFOLLOW. Emplee
REINDEX para actualizar todos los archivos de índice abiertos, .NDX y .MDX, que no
estaban abiertos cuando los registros se añadieron o actualizaron en la tabla asociada.
Si especifica la opción INTEGRITY con SET RELATION para vincular tablas de dBASE,
los campos clave de los registros añadidos en las tablas secundarias se definen con
valores iguales a los de la tabla principal. Para más información, consulte la descripción
de SET RELATION en la Referencia del lenguaje.

Copia de valores de registros anteriores


Al utilizar los comandos APPEND e INSERT, también puede utilizar el comando
SET CARRY, que le permite especificar campos cuyos valores se copian (arrastran) del
registro anterior de la tabla.
USE EMPRESA
SET CARRY TO CIUDAD, PROVINCIA && Selecciona los campos cuyos valores se copiarán
APPEND && Añade un registro al final de la tabla EMPRESA

288 Guía del programador


recfld.dwp Page 289 Wednesday, August 30, 1995 2:20 PM

Modificación de los registros


Para modificar registros, utilice el comando REPLACE. Es posible especificar cualquier
expresión válida para sustituir el valor de un campo en uno o más registros. Si especifica
REPLACE sin ninguna opción, el comando actualiza los valores de los campos del
registro actual. Si especifica las opciones <ámbito>, FOR o WHILE, REPLACE actualiza
todos los registros que cumplen las condiciones especificadas.
USE EMPRESA ORDER EMPRESA
SEEK "Industrias Hernández" && Busca un registro específico
IF FOUND()
REPLACE EMPRESA WITH Nuevo_Nombre && Actualiza el campo Empresa con un nuevo valor
ENDIF
REPLACE ALL COD_EMP WITH Nueva_emp && Cambia el valor del campo COD_EMP en todos
&& los registros
También es posible utilizar el comando UPDATE de SQL para modificar datos en una
tabla.

Sustitución de campos desde una matriz


El comando REPLACE FROM ARRAY permite sustituir valores de campos por los
almacenados en los elementos de una matriz.
DECLARE MatrComp[2] && Declara y asigna valores a una matriz
MatrComp[1] = x1
MatrComp[2] = x2
USE EMPRESA ORDER Empresa
SEEK xEmpresa && Busca un registro específico
IF FOUND()
REPLACE FROM ARRAY MatrComp && Sustituye los 2 primeros campos por nuevos valores
ENDIF
En una matriz bidimensional, el segundo subíndice indica el número de columnas de la
matriz o el número de campos que pueden actualizarse. El primer subíndice indica el
número de filas de la matriz o el número de registros que pueden actualizarse.
Cuando utilice REPLACE FROM ARRAY, los datos se copian a partir de la primera fila
de la matriz en los campos correspondientes del registro actual de la tabla, los datos de
la segunda fila se copian en el segundo registro, y así sucesivamente.

Capítulo 18, Uso de los registros y campos 289


recfld.dwp Page 290 Wednesday, August 30, 1995 2:20 PM

Marcado y borrado de registros


El borrado de registros de una tabla de dBASE es un proceso de dos pasos:
1 Marque un registro para borrado mediante el comando DELETE.
2 Elimine permanentemente los registros marcados mediante PACK.
USE CLIENTES
DELETE FOR BAL_INIC < 750 && Marca los registros que cumplan la cláusula FOR
PACK && Borra físicamente los registros marcados
DELETE borra permanentemente los registros de las tablas de Paradox y SQL; no se
marcan los registros para su borrado ni se utiliza PACK para borrarlos de la tabla.
Además, no es posible restaurar los registros después de borrarlos. Sin embargo, es
posible utilizar transacciones para asegurarse de que las operaciones que realice se
completan satisfactoriamente antes de aceptar modificaciones (incluyendo borrados) en
la tabla.
La función DELETED() indica si el registro actual está marcado para borrado.
USE CLIENTES
DELETE FOR BAL_INIC < 750 && Marca los registros que cumplan la cláusula FOR
GOTO 23
? DELETED() && Devuelve True en los registros marcados
El comando RECALL desmarca los registros marcados para borrado (siempre que no se
hayan borrado permanentemente mediante PACK).
RECALL ALL && Quita la marca de borrado a todos los registros
&& marcados como tal
Al igual que con DELETE, es posible especificar opciones <ámbito>, FOR y WHILE con
el comando RECALL.
Si utiliza SET RELATION para vincular tablas de dBASE, puede especificar opciones
INTEGRITY para limitar el borrado de registros en la tabla principal cuando la tabla
secundaria aún contiene registros vinculados. Para más información, consulte la
descripción de SET RELATION en la Referencia del lenguaje.

Exclusión de registros marcados


SET DELETED ON hace que los registros que están marcados para borrado no se tengan
en cuenta en las operaciones de proceso realizadas por comandos que se ejecuten a
continuación; por lo contrario, si SET DELETED es OFF, los registros marcados se
incluyen en las operaciones.
USE CLIENTES
SET DELETED ON
Para borrar todos los registros de una tabla, también puede utilizar el comando ZAP.
Sin embargo, tenga cuidado al utilizar este comando pues todos los registros se borran
inmediatamente de la tabla sin posibilidad de restaurarlos.

290 Guía del programador


recfld.dwp Page 291 Wednesday, August 30, 1995 2:20 PM

Uso de las variables automem


El uso del Diseñador de fichas y de los nuevos métodos para crear fichas de
introducción y visualización de datos en el entorno Windows es preferible al uso de
métodos más tradicionales de programación en dBASE que emplean comandos
@...SAY...GET junto con APPEND y REPLACE.
Cuando diseñe fichas en que los usuarios puedan mostrar y actualizar datos, puede
definir campos de entrada en una ficha y especificar propiedades, como DataSource y
DataLink, que vinculen los campos de entrada con los campos de una tabla. También
puede especificar otras propiedades que controlen el desplazamiento por la tabla, el
proceso de los sucesos del teclado y del ratón y otras operaciones que pueden realizarse
en la ficha.
Sin embargo, tal vez necesite conservar programas existentes de dBASE DOS y
mantenerlos funcionando. Visual dBASE proporciona soporte para un nuevo tipo de
creación de variables de memoria, las variables automem, que crea variables de memoria
que corresponden a los campos de una tabla.

Creación de variables automem


Cree variables automem con USE...AUTOMEM, CLEAR AUTOMEM o STORE
AUTOMEM. Después de crearlas, puede actualizar sus valores como haría con
cualquier otra variable de memoria de dBASE. Cuando desee actualizar los datos en la
tabla correspondiente, puede ejecutar APPEND AUTOMEM, INSERT AUTOMEM o
REPLACE AUTOMEM.
USE CLIENTES AUTOMEM && Crea variables automem vacías cuando se abre la tabla
CLEAR AUTOMEM && Crea variables automem vacías que corresponden a los
&& campos de la tabla CLIENTES.DBF; si las variables ya
&& existieran, pierden su contenido actual y quedan vacías
STORE AUTOMEM && Guarda los valores del registro actual en las
&& variables automem. Si no existen, las crea
ƒ
REPLACE AUTOMEM && Actualiza el registro actual con los valores
&& guardados en las variables automem
El comando REPLACE AUTOMEM sólo actualiza el registro actual. Este comando no
incluye opciones <ámbito>, FOR o WHILE para actualizar todos los registros que
cumplan una condición especificada.
El comando APPEND AUTOMEM añade un nuevo registro y sustituye los valores de
los campos de la tabla por el contenido de las variables automem correspondientes.
APPEND AUTOMEM realiza las mismas operaciones que cuando se utiliza
APPEND BLANK seguido por REPLACE AUTOMEM. APPEND AUTOMEM también
añade datos en campos memo. Las variables automem pueden liberarse de la memoria
mediante el comando RELEASE AUTOMEM.

Capítulo 18, Uso de los registros y campos 291


recfld.dwp Page 292 Wednesday, August 30, 1995 2:20 PM

Uso de los campos memo, binarios y OLE


Las versiones anteriores de dBASE utilizaban los campos memo exclusivamente para
almacenar datos extensos en una tabla, como bloques grandes de texto, datos binarios,
gráficos y de sonido. Visual dBASE añade dos nuevos tipos de datos, binario y OLE,
especializados en el almacenamiento y soporte de otras aplicaciones Windows que
suministran datos.

Adición de datos en los campos memo


Visual dBASE permite las mismas operaciones con campos memo que en versiones
anteriores de dBASE. Utilice los comandos APPEND MEMO o REPLACE
MEMO...FROM para actualizar un campo memo del registro actual de una tabla con el
contenido de un archivo especificado. Utilice REPLACE MEMO...WITH para actualizar
un campo memo a partir del contenido de un elemento de una matriz.
USE CLIENTES
GOTO 1
APPEND MEMO HIST_CLI FROM ARCHM.TXT && Añade un memo con el contenido de ARCHM.TXT
DECLARE TempMemo[25] && Crea una matriz con elementos para cada
&& línea (hasta 25) que serán escritos en un
&& campo memo
ƒ
GOTO 10
REPLACE MEMO HIST_CLI WITH ARRAY TempMemo && Actualiza el campo memo. Cada elemento
&& de la matriz añade una línea al campo memo
? HIST_CLI && Muestra el contenido del campo memo
Cuando se muestran datos de los campos memo, SET MEMOWIDTH controla el
número de caracteres de cada línea. Para recuperar una línea especificada de un campo
memo, utilice la función MLINE() para especificar tanto el número de líneas como el
ancho de cada línea que se recupera de un campo memo.

292 Guía del programador


recfld.dwp Page 293 Wednesday, August 30, 1995 2:20 PM

Uso de los campos memo en las expresiones


En Visual dBASE, es posible utilizar un campo memo en cualquier lugar en que una
expresión o un campo de caracteres esté permitido (excepto que un índice no puede
crearse por un campo memo, binario u OLE). Por ejemplo, es posible buscar una cadena
de caracteres específica en un campo memo mediante la función AT() o utilizar
SUBSTR() para recuperar una subcadena de un campo memo. También puede utilizar
operadores como más (+) o menos (-) para añadir o restar el contenido de un campo
memo con otra cadena en la definición de una expresión de dBASE.
USE CLIENTES
? SUBSTR(Hist_Cli, 1, 80) && Devuelve los 80 primeros
&& caracteres del campo memo
&& Hist_Cli del registro actual
? SUBST(Hist_Cli, AT("Pedido devuelto",Hist_Cli),80) && Devuelve una cadena de caracteres
&& del campo memo si se encuentra
&& la cadena "Pedido devuelto"
? "Contenido del memo" + Hist_Cli && Añade una cadena de caracteres
&& al campo memo Hist_Cli
Otras funciones de gestión de cadenas que también puede utilizar con campos memo
son RAT(), STUFF(), TRANSFORM() y UPPER.

Adición de datos en los campos binarios y OLE


Los comandos REPLACE BINARY y REPLACE OLE actualizan los campos binarios y
OLE, respectivamente, desde un archivo binario (por ejemplo, .BMP, .PCX y .WAV) o
un documento OLE.
REPLACE BINARY DIBUJO FROM CEBRA.BMP && Guarda el contenido de CEBRA.BMP en un campo
&& binario
REPLACE OLE DOCWORD FROM BRIEF.DOC && Enlaza un campo OLE con un documento creado
&& por otra aplicación Windows
REPLACE BINARY también tiene una opción TYPE que especifica un tipo definido por
el usuario de archivo binario para la sustitución.

Capítulo 18, Uso de los registros y campos 293


recfld.dwp Page 294 Wednesday, August 30, 1995 2:20 PM

Visualización de datos en los campos binarios y OLE


Visual dBASE proporciona dos nuevos comandos para trabajar con datos de imagen y
de sonido que pueden almacenarse en campos del tipo de datos binarios. Para mostrar
el contenido de un campo binario en el registro actual de una tabla que contiene
imágenes (datos binarios .BMP y .PCX), utilice RESTORE IMAGE. Cuando se ejecuta
este comando, la imagen contenida en el campo binario especificado del registro actual
se muestra en una ventana. De forma similar, para reproducir datos de sonido (.WAV)
almacenados en un campo binario, utilice el comando PLAY SOUND.
Después de ejecutar BROWSE o EDIT, basta hacer doble clic en un campo binario de un
registro para ver las imágenes o reproducir el archivo de sonido que estén guardados en
el campo. Para mostrar imágenes en una ficha, puede definir un objeto de imagen y
utilizar DataSource para vincularlo a un campo binario de una tabla. Por ejemplo:
CLASS NuevoDib OF FORM
ƒ
DEFINE IMAGE Dibujo1 OF THIS;
PROPERTY;
DataSource "BINARY Animales->BMP", ;
ƒ
ENDCLASS
Las propiedades asignadas al objeto de imagen definen el tamaño y posición por defecto
de la ventana en que se muestran las imágenes.
Para abrir un documento OLE en una ficha, puede definir un objeto OLE y utilizar la
propiedad DataLink para vincular el objeto con un campo OLE de una tabla.

Copia de datos de campos binarios


Utilice COPY BINARY para copiar el contenido de un campo binario en un archivo.
COPY BINARY DIBUJO TO ARCHSAL.PCX && Escribe el contenido del campo binario DIBUJO en el
&& archivo ARCHSAL.PCX

294 Guía del programador


orderec.dwp Page 295 Wednesday, August 30, 1995 2:21 PM

Capítulo

Ordenación y relación de registros


Capítulo 19
19
Visual dBASE proporciona utilidades para organizar y ver los datos, de forma que
pueda obtener información útil a partir de los datos contenidos en las tablas.
Utilidades como las consultas, fichas e informes, emplean índices para buscar en las
tablas y organizar subconjuntos de los mismos datos en muchas formas diferentes de
presentación visual. Los registros de una tabla se presentan en secuencias diferentes,
dependiendo del índice maestro que utilice para controlar su orden de presentación.
Este capítulo trata los elementos y técnicas que toman parte en el uso de los índices al
programar en dBASE. Para una introducción a los conceptos básicos de la indexación,
consulte el Capítulo 2 de la Guía del usuario.
Este capítulo explica cómo:
• Utilizar archivos y etiquetas de índice
• Crear y borrar etiquetas de índice
• Definir relaciones y aplicar la integridad de datos

Acerca de la indexación y ordenación


La ordenación de registros es un proceso dinámico, independiente del orden físico en
que aparecen en las tablas. De hecho, el orden físico de los registros en una tabla carece
de importancia. Aunque comience con una tabla perfectamente ordenada, debido a que
los datos cambian constantemente, la posición física de los registros se desordena.
Los índices crean referencias ordenadas por campos clave que seleccione. Al usar un
índice, los datos aparecen ordenados según la expresión clave de índice seleccionada.
El orden físico de la tabla permanece inalterado y no se crea ninguna tabla duplicada.
La indexación suele ser preferible a la ordenación, que reorganiza los datos físicamente
según el campo de ordenación seleccionado y luego copia los datos en una nueva tabla.

Capítulo 19, Ordenación y relación de registros 295


orderec.dwp Page 296 Wednesday, August 30, 1995 2:21 PM

Ordenación de registros
A los nuevos usuarios de los programas de base de datos, la ordenación suele parecerles
una forma natural de organizar los datos, porque las bases de datos impresas y estáticas
que son habituales, como diccionarios, guías telefónicas y libros de referencia, están en
orden alfabético. Las tablas, sin embargo, tienen índices lógicos para ordenar y buscar
datos de forma dinámica, por lo que carece de importancia el orden físico de los
registros en una tabla. Por tanto, no es aconsejable el uso habitual del comando SORT.
Puede hacer un uso limitado del comando SORT para crear una nueva tabla para la
distribución de datos, en especial cuando está seguro de que los datos no van a cambiar
mucho durante algún tiempo. Un ejemplo podría ser un catálogo de artículos de
temporada, cuando sabe que no borrará ni añadirá elementos durante unos meses.
Para más información sobre el comando SORT, consulte la Referencia del lenguaje.

Concatenación de tablas
El comando JOIN une tablas para crear nuevas tablas que tienen sus campos dispuestos
en un orden especial. Puede utilizar JOIN para hacer lo siguiente:
• Crear una tabla temporal para un informe mediante SORT y JOIN, que extraiga un
subconjunto de datos organizados de una forma especial en una nueva tabla.
• Incorporar una tabla de referencia normalizada en la tabla principal, especialmente
cuando aquélla contiene datos estáticos.
• Situar campos muy relacionados uno junto a otro en una tabla para facilitar su
consulta y mejorar la velocidad del índice.
Nota El comando JOIN no debería utilizarse de forma rutinaria para crear nuevas tablas, ya
que puede hacerlas muy grandes, posiblemente con campos duplicados.
El Diseñador de consultas también puede utilizarse para crear nuevas tablas mediante
la combinación de campos de diferentes tablas. Para ello, guarde la vista resultante
seleccionando Consulta|Copiar resultado en nueva tabla. Seleccione los campos que
desea combinar con cuidado para evitar la duplicación de datos. Para conocer los
principios del diseño de tablas, consulte el Capítulo 1 de la Guía del usuario.

296 Guía del programador


orderec.dwp Page 297 Wednesday, August 30, 1995 2:21 PM

Uso de las etiquetas y archivos de índice


Las etiquetas de índice cambian el orden en que dBASE lee una tabla. Los registros se
organizan según la secuencia en que aparecen en el archivo de índice. Esta es una forma
rápida de cambiar cómo aparecen los datos y de buscar registros específicos.
Hay dos tipos: archivos de índice múltiple (.MDX) y de índice sencillo (.NDX).
Éstos últimos son comparables a una sola etiqueta de índice de un archivo .MDX.
La razón principal de la existencia de los archivos de índice sencillo es proporcionar
compatibilidad con versiones anteriores de dBASE. Sin embargo, un uso limitado de un
archivo de índice sencillo podría ser cuando se desea disponer de un índice temporal
para un solo uso. Para abrir un archivo de índice sencillo, siga estos pasos:
1 Abra una tabla en el Selector con el comando USE, por ejemplo, CLIENTES.DBF.
2 Indexe por un campo como Prefijo. En la ventana de comandos, teclee:
INDEX ON PREFIJO TO PREFIJO.NDX
La tabla Clientes se reorganiza según el orden de Prefijo. Un uso posible de este
índice podría ser extraer todos los clientes que tengan cierto prefijo para llamarlos
durante los periodos de tarifa favorable. Cuando termine de utilizarlo, puede borrar
el archivo .NDX.
Cuando crea una tabla, automáticamente se crea su correspondiente archivo de índice
de producción .MDX que tiene el mismo nombre que la tabla. El archivo de producción
.MDX se actualiza de forma automática cuando se modifican los datos de la tabla
correspondiente.

Conversión de archivos .NDX a .MDX


A menos que necesite mantener la compatibilidad con versiones anteriores de dBASE,
debería convertir los archivos de índice simple en etiquetas del archivo de producción
.MDX. Copie los archivos de índice sencillo en archivos de producción .MDX como
etiquetas de índice. Hay muchas ventajas en el uso de archivos de índice múltiple:
• Los archivos de producción .MDX se actualizan de forma automática, por lo que las
tablas y las etiquetas de índice permanecen sincronizadas.
• Los archivos .MDX proporcionan un mejor rendimiento que los de índice sencillo.
• Todas las etiquetas de un archivo .MDX abierto están disponibles siempre.
• En un archivo puede haber hasta 47 etiquetas por lo que hay menos archivos abiertos,
lo que a su vez mejora el rendimiento, en especial con tablas e índices grandes.
Para convertir archivos .NDX en etiquetas de .MDX, cópielos en un archivo .MDX. Si no
da el nombre de un .MDX específico como archivo de destino, dBASE copia la etiqueta
de índice sencillo al archivo de producción .MDX abierto. Es posible guardar varios
archivos .NDX como etiquetas, pero deben estar abiertos todos.

Capítulo 19, Ordenación y relación de registros 297


orderec.dwp Page 298 Wednesday, August 30, 1995 2:21 PM

Por ejemplo, para guardar dos archivos .NDX como etiquetas en el archivo de
producción .MDX de CLIENTES.DBF, puede utilizar la sintaxis siguiente:
USE CLIENTES EXCLUSIVE INDEX PREFIJO.NDX, EMPRESA.NDX && Abre los archivos necesarios

COPY INDEXES PREFIJO.NDX, EMPRESA.NDX && Copia los archivos de índices


&& como etiquetas del archivo MDX

USE && Cierra todos los archivos,


&& incluido el .MDX y los .NDX

Conversión de etiquetas de .MDX en archivos .NDX


También es posible crear un archivo de índice sencillo a partir de una etiqueta de índice
de un archivo .MDX. Esto puede ser útil cuando desee disponer de un índice que pueda
utilizar con dBASE III PLUS y con Visual dBASE. Por ejemplo:
USE CLIENTES && Abre la tabla y su archivo de índice MDX
COPY TAG Num_Cli TO NUM_CLI.NDX && Crea un archivo .NDX a partir de una etiqueta
SET INDEX TO NUM_CLI.NDX && Usa el nuevo archivo índice como índice maestro
Utilice el comando SEEK para buscar registros tomando Num_Cli como índice maestro.
Para más información sobre la búsqueda de registros, consulte el Capítulo 20.

Mantenimiento de los índices


Los índices precisan un mantenimiento periódico, en especial los índices que no son de
producción y que podría olvidar abrir con tablas grandes que utilizan cientos de
etiquetas de índice. Además, es posible que algunas etiquetas de los archivos .MDX no
sean útiles. Borre las etiquetas desfasadas de los archivos .MDX, en particular del
archivo de producción .MDX. Borre del disco también los archivos .NDX que no use.
Para borrar los archivos .NDX que no utilice, puede emplear la ventana de comandos.
Por ejemplo, borre el archivo de índice Prefijo con el comando siguiente:
DELETE FILE PREFIJO.NDX
Borre las etiquetas no usadas para mejorar las búsquedas en los índices. No es necesario
mencionar el nombre del archivo .MDX cuando está abierto en el área de trabajo actual.
Por ejemplo, si deja de controlar las fechas de envío, puede borrar esa etiqueta en la
ventana de comandos:
DELETE TAG FECH_ENVIO OF PEDIDOS.MDX
Si necesita una etiqueta de índice para una sola búsqueda, utilice la opción NOSAVE
con SET ORDER TO. La opción NOSAVE crea una etiqueta temporal en el archivo de
producción .MDX, o en cualquier otro archivo .MDX o .NDX que especifique. Cuando
cierre el índice, se borra la etiqueta. Por ejemplo,
SET ORDER TO FECH_ENVIO OF PEDIDOS.MDX NOSAVE

298 Guía del programador


orderec.dwp Page 299 Wednesday, August 30, 1995 2:21 PM

Después de utilizar el índice, cierre el índice .NDX maestro, o el .MDX que no es de


producción, con el comando siguiente:
SET ORDER TO
La etiqueta Fech_envio se borra del archivo .MDX, o se elimina el archivo .NDX.
Para utilizar la opción NOSAVE con el archivo de producción .MDX, cierre la tabla
abierta con CLOSE ALL, o teclee el comando USE sin un nombre de archivo.
Tenga cuidado de no eliminar etiquetas valiosas con esta opción; podría perder más
tiempo volviendo a crearlas que el que va a ahorrar con la opción NOSAVE.
Es posible que las etiquetas de índice no estén sincronizadas después de realizar muchas
actualizaciones de datos sin reindexar. Si sucede esto o si desea impedir que los índices
no estén sincronizados con los datos, reindexe de forma periódica las tablas grandes y
muy utilizadas. Para ello, siga estos pasos:
1 Abra cada tabla en un área de trabajo distinta.
2 Abra todos los índices, además de los archivos de producción .MDX, que necesite
actualizar. En un entorno multiusuario, es necesario abrir las tablas con EXCLUSIVE
para utilizar los comandos de indexación.
3 Actualice todas las etiquetas de índice de todos los archivos abiertos utilizando el
comando REINDEX. Por ejemplo:
USE CLIENTES IN 1 INDEX CLIENT2.MDX EXCLUSIVE && Abre una tabla en modo exclusivo junto
&& con su archivo de índice MDX y otro más
USE PEDIDOS IN 2 INDEX COD_CLI && Abre una segunda tabla y otro MDX
REINDEX && Actualiza todas las etiquetas de
&& índices en todos los archivos abiertos

Creación de etiquetas de índice


Es posible crear una clave de índice para una etiqueta basada en un solo campo o en una
expresión que incluya uno o más nombres de campo de una tabla de dBASE. No es
posible crear índices basados en campos memo, binarios ni OLE.
Al utilizar más de un nombre de campo, puede utilizar el operador de concatenación
más (+) para unir dos cadenas de caracteres o el operador menos (–) para desplazar los
espacios finales al final de la última cadena. Si hay claves numéricas en la tabla, puede
especificar una expresión de índice que emplee operadores matemáticos (–, *, /) para
resta, multiplicación y división, respectivamente. La expresión de índice debería
producir claves de índice con una longitud idéntica para todos los registros.
Visual dBASE puede utilizar índices creados para tablas de Paradox y SQL y permite
crear índices basados en estas tablas. Para más información sobre el uso de índices que
no son de dBASE, consulte el Capítulo 23.

Capítulo 19, Ordenación y relación de registros 299


orderec.dwp Page 300 Wednesday, August 30, 1995 2:21 PM

Conversión de datos de fecha


Para combinar datos de fecha con datos de otros tipos en una clave de índice, debe
convertirlos en datos de carácter. En primer lugar, dBASE convierte el formato de fecha
a AMD y muestra el siglo. Esta conversión muestra la fecha ordenada de izquierda a
derecha, del dígito más significativo al menos significativo. Por ejemplo:
FechaCar = DTOS(FECHAPAGO)
? FechaCar && Devuelve 19940327
Suponga que tiene una tabla que contiene los campos APELLIDOS, NOMBRE y
FECHAPAGO, entre otros. Utilice la función DTOS() para concatenar los datos de fecha
con los datos del nombre para ver qué clientes pagaron en la fecha convenida o antes:
INDEX ON APELLIDOS+NOMBRE+DTOS(FECHAPAGO) TAG Pagos OF CLIENTES.MDX;
FOR DTOS(FECHAPAGO) <= 19940301

Conversión de datos lógicos


Para utilizar campos lógicos en combinación con datos de otros tipos en una clave de
índice, debe convertirlos en datos de carácter. No hay una función de conversión directa
para transformar los datos lógicos en caracteres, pero puede utilizar la función IIF() para
alcanzar ese resultado. Suponga que la tabla CLIENTES tiene el campo lógico Llamado.
Puede crear un índice que muestre sólo los clientes a los que aún no ha llamado:
USE CLIENTES
INDEX ON IIF(LLAMADO, "Y", "N" ) TAG Llamar && Organiza los registros de forma
&& que los no llamados estén
&& primero y los llamados después
Este ejemplo crea una etiqueta de índice denominada Llamar que sitúa primero a los
clientes a los que aún no ha llamado. Puede obtener la misma información mediante una
consulta, si desea una búsqueda rápida. Sin embargo, el índice puede ser más útil si
trabaja con esta información continuamente.

Control de claves duplicadas


SET UNIQUE ON e INDEX ON <expresión clave> UNIQUE tienen la misma función:
procesan sólo el primer registro cuando hay varios que tienen el mismo valor de clave.
Si modifica un registro duplicado mientras hay abierto un índice maestro UNIQUE, sólo
se modifica o actualiza el primer caso de ese registro. Además, si reindexa con una
etiqueta de índice que se creó con UNIQUE, el índice permanece como UNIQUE
independientemente de que SET UNIQUE sea OFF u ON.
Nota UNIQUE oculta los registros duplicados de una tabla; no es posible verlos para
actualizarlos.
UNIQUE encuentra la primera aparición de un registro duplicado o excluye los campos
duplicados. Algunos campos, como los de nombres, podrían contener registros
duplicados que son inevitables. Utilice técnicas de validación de datos cuando acepte la
entrada del usuario en las tablas de datos.
La función KEYMATCH() puede comprobar en los índices si una expresión específica
ya está presente en un índice. Utilice esta función cuando añada registros en una tabla
para impedir la duplicación de registros existentes.

300 Guía del programador


orderec.dwp Page 301 Wednesday, August 30, 1995 2:21 PM

Apertura y cierre de los índices


Es posible abrir y cerrar archivos de índice, que no sean el de producción .MDX, cuando
sea necesario. Al abrir un archivo .MDX, también es posible indicar la etiqueta que se
desea como índice maestro.
Es posible abrir un índice .MDX y activar una etiqueta al mismo tiempo. Por ejemplo:
USE CLIENTE INDEX NOMBRES.MDX ORDER TAG Apellido OF NOMBRES.MDX
Es posible cambiar la etiqueta activa por otra del mismo o de otro archivo de índice
múltiple. Por ejemplo:
SET ORDER TO TAG Nombre
Es posible abrir varios archivos de índice a la vez. Por ejemplo:
USE CLIENTE INDEX NOMBRES.MDX, CLIENTE.MDX, SONIDOS.NDX
Es posible abrir varios archivos de índice simultáneamente y definir una etiqueta como
índice maestro. Por ejemplo:
SET INDEX TO NOMBRES.MDX, CLIENTE.MDX, SONIDOS.NDX ORDER Apellido OF CLIENTE
Para cerrar los índices, haga una de las siguientes acciones:
• SET INDEX TO y SET ORDER TO sin argumentos cierran el índice maestro, excepto
cuando está en el archivo de producción .MDX.
• CLOSE INDEX cierra todos los archivos de índice, excepto el archivo de producción
.MDX. Cierre la tabla para cerrar el archivo de producción .MDX.
• USE sin argumentos, o CLOSE TABLES, cierra todas las tablas abiertas y sus índices.

Control de la secuencia de los registros


El comando SET KEY TO RANGE limita los rangos superior e inferior de los registros
que se muestran empleando las etiquetas de índice existentes. Este método es más
rápido que la indexación condicional con FOR. El rango establecido por SET KEY es
efectivo de inmediato y tiene prioridad sobre el comando SET FILTER. Sin embargo, si
SET FILTER utiliza una clave de índice, los dos métodos operan con la misma velocidad.
Si utiliza tanto SET KEY como SET FILTER, sólo los registros que cumplan los criterios
de selección de ambos se seleccionan para su visualización o modificación.
SET KEY TO RANGE y SET FILTER TO pueden utilizarse con tablas de dBASE y no
dBASE. Utilice SET FILTER para lograr criterios de selección flexibles. Para más
información, consulte el Capítulo 23, “Uso de tablas que no son de dBASE”.

Capítulo 19, Ordenación y relación de registros 301


orderec.dwp Page 302 Wednesday, August 30, 1995 2:21 PM

Determinación del estado del índice


Las funciones de soporte de índice devuelven los nombres de los archivos de índice
activos o, datos lógicos sobre las etiquetas y archivos abiertos. La información devuelta
se utiliza en los programas para bifurcar, cambiar el índice maestro, validar datos y
otras tareas. Estos comandos y funciones se resumen en la Tabla 19.1 Para conocer los
parámetros que puede pasar a estas funciones, consulte la Referencia del lenguaje.
Tabla 19.1 Determinación del estado del índice
Función de índice Devuelve
DESCENDING() (.T.) si el índice maestro es descendente; (.F.) si es ascendente o es de un tipo de
índice que no permite los índices descendentes.
FOR() La condición FOR del índice maestro, o una cadena nula si hay un índice .NDX
abierto.
KEY() Clave actual del índice maestro.
KEYMATCH() (.T.) si una expresión se encuentra en el índice especificado, (.F.) si no se encuentra.
MDX() Nombre del archivo .MDX maestro, o una cadena nula si el índice maestro no es
un archivo .MDX.
NDX() Nombre del archivo .NDX maestro, o una cadena nula si el índice maestro no es
un archivo .MDX.
ORDER() Devuelve el nombre del índice maestro del área de trabajo actual.
TAG() Nombre de la etiqueta del índice maestro.
TAGCOUNT() Número total de índices del área de trabajo actual.
TAGNO() Posición del índice de control en el archivo .MDX.
UNIQUE() (.T.) si el índice de control utiliza UNIQUE, (.F.) si no es así.

Los siguientes son algunos ejemplos del uso de las funciones de índice:
USE CLIENTES EXCLUSIVE
INDEX ON NUM_CLI TAG NUM_CLI
INDEX ON EMPRESA TAG EMPRESA
? MDX() && Devuelve Clientes.mdx
? TAGCOUNT() && Devuelve 2
? TAGNO() && Devuelve 2
? ORDER() && Devuelve EMPRESA
SET ORDER TO OTRO_IND && Cambia el índice maestro
? ORDER() && Devuelve OTRO_IND
? TAG() && Devuelve OTRO_IND

302 Guía del programador


orderec.dwp Page 303 Wednesday, August 30, 1995 2:21 PM

Técnicas de indexación
Las tablas de dBASE contienen datos que suelen cambiar de forma habitual. En muchas
aplicaciones de base de datos, las tablas se actualizan desde programas que utilizan
comandos de dBASE, como BLANK, REPLACE, REPLACE FROM ARRAY o UPDATE.

Uso de REINDEX
En muchas aplicaciones, hay demasiados datos como para poder actualizarlos de forma
manual. Por contra, es posible que muchos registros se actualizasen varias veces al día
en una aplicación. Por ejemplo, los datos introducidos manualmente en las tablas por
personal de ventas o escáneres de punto de venta se utilizan con comandos como
BLANK, UPDATE, APPEND o REPLACE para actualizar tablas más grandes.
Todos estos comandos tienen REINDEX como palabra clave opcional. Utilícela con ellos
para lograr un mejor rendimiento cuando sustituya varios registros. Esta opción impide
que dBASE actualice los índices hasta que se hayan modificado todos los registros. Sin la
opción REINDEX, si sustituye un campo clave del índice maestro, dBASE podría mover
el puntero de registro si la posición de un registro cambia en el archivo de índice por
una actualización. Si se mueve el puntero, el ámbito de la sustitución global no puede
mantenerse y se producirán resultados imprevisibles.

Uso de SET KEY TO


El comando SET KEY TO RANGE especifica los límites inferior y superior de los
registros que se procesarán, o busca una coincidencia exacta cuando se utiliza un mismo
valor para ambos límites. Utilice este comando para aprovechar las etiquetas de índice
existentes sin necesidad de crear otras nuevas. SET KEY excluye registros como si
utilizara INDEX ON <clave> FOR <condición>.
Para actualizar los registros para saldos deudores entre 1 y 2000 dólares, utilice el código
siguiente para limitar los registros a los que cumplen el rango de la clave:
USE PEDIDOS
SET ORDER TO TAG Debe && Se supone que existe una etiqueta en el archivo
&& de índices para este campo
SET KEY TO RANGE 1.00, 2000 && Límite inferior y superior para el campo clave Debe
BROWSE && Sólo se mostrarán los registros que estén dentro
&& del rango
GO TOP
La indexación condicional mediante la condición FOR es el método más lento porque se
actualizan las etiquetas de índice existentes para seleccionar registros que cumplan
ciertas condiciones. Este proceso es prolongado porque dBASE podría evaluar muchos
registros para extraer un subconjunto. Por tanto, el comando SET KEY TO RANGE o las
condiciones de filtro que introduzca en el Diseñador de consultas son preferibles para la
ordenación de datos.

Capítulo 19, Ordenación y relación de registros 303


orderec.dwp Page 304 Wednesday, August 30, 1995 2:21 PM

Definición de relaciones entre tablas


El Diseñador de consultas le permite crear una consulta o vista con los datos de una o
más tablas subyacentes. Puede utilizar el Diseñador de consultas para:
• Abrir tablas
• Seleccionar los campos que se muestran
• Seleccionar los campos por los que se ordenan los registros
• Definir relaciones por los campos clave
Consulte la Guía del usuario para información sobre el uso del Diseñador de consultas.
El Diseñador de consultas genera código (sentencias de lenguaje dBASE que definen la
consulta) cuando se guarda la consulta en un archivo. Mediante el Editor de programas,
puede abrir los archivos creados por el Diseñador de consultas, y reutilizar este código
generado en sus aplicaciones.
También es posible escribir código directamente que defina relaciones entre las tablas
sin utilizar el Diseñador de consultas. Sin embargo, tal vez sea más sencillo utilizar
primero el Diseñador de consultas y modificar luego el código que produce.

Uso de SET RELATION


El comando SET RELATION define el vínculo entre las tablas de una consulta.
Las relaciones entre las tablas abiertas se definen en función de un campo clave común o
de una expresión. Cuando se define una relación, la tabla del área de trabajo actual se
denomina tabla principal, y una tabla vinculada con la principal por la clave especificada
se conoce como tabla secundaria. Puede definir más de una relación a partir de la misma
tabla principal si utiliza la opción ADDITIVE o si especifica varias relaciones con el
mismo comando SET RELATION.
Antes de definir una relación, debe abrir cada tabla en un área de trabajo distinta.
Además, indexe la tabla secundaria por el campo clave o expresión que utilizará para
vincular las tablas. La clave puede ser un solo campo o un grupo de campos
concatenados que estén presente en cada tabla. Si lo desea, puede utilizar SET EXACT
ON para especificar que son necesarias coincidencias exactas en los campos vinculados.
Por ejemplo, una tabla que contiene información de clientes puede vincularse con una
segunda tabla que contiene compras. Si vincula las dos tablas por el número de cliente,
puede ver información como el nombre y dirección de un cliente específico con los
detalles específicos de los pedidos de ese cliente.
SET EXACT ON
USE CLIENTE.DBF && Abre la primera tabla
SELECT 2 && Selecciona una segunda área de trabajo
USE PEDIDOS.DBF ORDER TAG Num_cli && Abre una segunda tabla seleccionando la
&& etiqueta Num_Cli
SELECT 1 && Vuelve a seleccionar el área de la tabla ppal
SET RELATION TO Num_Cli INTO PEDIDOS && Enlaza las tablas por el campo Num_Cli

304 Guía del programador


orderec.dwp Page 305 Wednesday, August 30, 1995 2:21 PM

Aplicación de la integridad de datos en tablas relacionadas


Las opciones CONSTRAIN e INTEGRITY de SET RELATION especifican normas de
integridad de los datos y determinan cómo afectan a las tablas principal y secundarias
las actualizaciones y borrados de registros. Estas normas de integridad sólo se aplican a
las tablas de dBASE. El Diseñador de consultas hace cumplir las normas de integridad
de datos mediante el uso de esas palabras clave en el código que genera. dBASE
comprueba y aplica la integridad de datos como sigue:
• SET RELATION hace que el campo clave de la tabla secundaria sea de sólo lectura
mientras dure la relación para impedir la creación de registros secundarios que no
tengan registros principales correspondientes.
• La palabra clave CONSTRAIN de SET RELATION limita los campos que se
muestran de la tabla secundaria a los que coincidan con la principal. Esta
característica suprime la necesidad de definir una condición de filtro en la tabla
secundaria para encontrar los registros iguales.
• La palabra clave INTEGRITY de SET RELATION aplica la integridad de registros
coincidentes entre las tablas principal y secundaria. Si activa la comprobación de
integridad entre dos tablas y crea una ficha que utiliza esas tablas, siempre que borre
registros en la tabla principal, se abre un cuadro de diálogo que le pregunta si desea
borrar también los registros coincidentes de la tabla secundaria (hacer el cambio en
cascada) o no borrar ningún registro. Esto impide la creación de registros huérfanos en
la tabla secundaria. La palabra clave INTEGRITY incluye dos opciones: CASCADE y
RESTRICTED.
• CASCADE borra automáticamente los registros secundarios cuando se borran los
registros equivalentes en la tabla principal. Sin embargo, no reproduce en cascada
el borrado hacia arriba, desde la tabla secundaria a la principal. Por el contrario, le
impide borrar registros secundarios relacionados con la opción RESTRICTED.
• RESTRICTED impide el borrado de registros de la tabla secundaria que tienen
registros correspondientes en la principal, y muestra un mensaje de error.
Si selecciona la opción Uno con muchos al utilizar el Diseñador de consultas, la palabra
clave CONSTRAIN se añade en el comando SET RELATION generado en el código.
El Diseñador de consultas también ejecuta SET SKIP para procesar todos los registros de
las tablas secundarias.
El segmento de código siguiente muestra el establecimiento de una relación entre
CLIENTE.DBF y PEDIDOS.DBF con código generado por el Diseñador de consultas.
Las tablas deben estar indexadas por el campo clave para que este código funcione.
CLOSE DATABASES && Nos aseguramos de que no existen tablas abiertas
SET EXACT ON && Comparación exacta
SELECT 1 && Selecciona un área de trabajo
USE CLIENTE.DBF && Abre la primera tabla
SELECT 2 && Selecciona una segunda área de trabajo
USE PEDIDOS.DBF ORDER TAG Num_Cli && Abre la segunda tabla seleccionando la etiqueta
&& Num_Cli
SELECT 1 && Volvemos a seleccionar el área de la tabla ppal

Capítulo 19, Ordenación y relación de registros 305


orderec.dwp Page 306 Wednesday, August 30, 1995 2:21 PM

SET RELATION TO Num_Cli;


INTO PEDIDOS CONSTRAIN INTEGRITY && Establece una relación por el campo Num_Cli
&& usando comandos que aseguren la coincidencia
&& entre los registros y la integridad de los datos
SET SKIP TO PEDIDOS && SKIP es asignado a la tabla secundaria
SET FILTER TO FOUND(2) && Condición de filtro para la tabla secundaria con
&& alias de área de trabajo 2
GO TOP && Va al principio para comenzar la búsqueda

Normas de integridad de referencias en tablas de Paradox


Una base de datos de Paradox puede tener normas de integridad de referencias con
nombre asociadas como parte de la estructura de la base de datos. Para definir dichas
normas para una base de datos de Paradox, utilice el cuadro de diálogo Nueva regla de
integridad referencial en el menú Archivo | Administración de base de datos. Para más
información sobre este cuadro de diálogo, consulte la Guía del usuario.

Filtrado de registros en tablas relacionadas


Puede utilizar SET KEY TO para establecer un rango, o SET FILTER TO <expresión>
cuando no sea posible establecer un rango.
Si filtra por campos indexados, SET FILTER TO <expresión> evalúa la expresión una vez
y crea el filtro, por lo que el rendimiento es igual que con SET KEY TO RANGE.
SET FILTER TO FOUND(<alias tabla secundaria>)
Si un registro de una tabla principal no tiene un registro correspondiente en la tabla
secundaria, la función FOUND() devuelve .F., y el registro principal se excluye de la
vista. Este proceso continúa hasta que el puntero de registro llega al final del archivo en
la tabla secundaria y EOF() es verdadero para el alias de la tabla secundaria.
Mediante el Diseñador de consultas, puede crear interactivamente condiciones de filtro
basadas en los campos de la tabla. Especifique las condiciones de filtro empleando
operadores relacionales y expresiones en el espacio situado bajo el nombre del campo en
las máscaras de archivo. O bien, puede utilizar el cuadro de condición del Diseñador de
consultas.

Inclusión de registros en tablas relacionadas


Por defecto, el uso de SET RELATION TO excluye todos los registros de la tabla
secundaria, excepto el primer registro que corresponda con uno de la tabla principal.
Para ver los registros de la tabla secundaria, puede seleccionar su área de trabajo, o
utilizar SET SKIP TO para ver todos los registros de la tabla secundaria que tengan
registros correspondientes en la tabla principal. Por ejemplo:
SET SKIP TO <alias tabla secundaria> [, <alias2 tabla secundaria>]...

306 Guía del programador


orderec.dwp Page 307 Wednesday, August 30, 1995 2:21 PM

Puede utilizar dos o más alias cuando haya varias tablas en la cadena de relación.
Si define SKIP para que incluya las áreas de trabajo en que están abiertas las tablas
secundarias, se procesan todos los registros de las tablas secundarias que tengan un
registro correspondiente en la tabla principal.
Cuando hay una lista de tablas secundarias, dBASE inicia el proceso desde la última
tabla de la lista.
No es necesario que incluya todas las tablas secundarias en un comando SET SKIP TO.
Incluya sólo las que tengan una relación uno con muchos con la tabla principal.
Por ejemplo, la tabla Elemento podría contener varios registros para cada cliente que
haya hecho más de una compra. Puede utilizar los comandos SET SKIP y SET FILTER
para procesar los artículos o elementos de cada pedido introducido para cada cliente.
Por ejemplo:
CLOSE DATABASES
SET EXACT ON
SELECT 1
USE PEDIDOS.DBF
SELECT 2
USE CLIENTE.DBF ORDER TAG Num_Cli
SELECT 3
USE ELEMENTO.DBF ORDER TAG Num_ped
SELECT 1
SET RELATION TO Num_Cli INTO CLIENTE, Num_Ped INTO ELEMENTO
SET SKIP TO CLIENTE
SET FILTER TO FOUND(2) .AND. FOUND(3) && Establece condiciones de filtro en tablas
&& secundarias
SELECT 3
SET FILTER TO Num_Ped = PEDIDOS->Num_Ped && Establece condición de filtro en la tabla
&& secundaria
SELECT 1
GO TOP

Capítulo 19, Ordenación y relación de registros 307


orderec.dwp Page 308 Wednesday, August 30, 1995 2:21 PM

308 Guía del programador


srchrec.dwp Page 309 Wednesday, August 30, 1995 2:22 PM

Capítulo

Búsqueda y resumen de registros


Capítulo 20
20
Para ver o cambiar datos específicos guardados en tablas, utilice comandos y funciones
de búsqueda. dBASE proporciona métodos para encontrar registros con o sin el uso de
índices; sin embargo, las búsquedas indexadas suelen ser más rápidas.
Este capítulo describe las técnicas de programación para buscar datos. Para una
explicación de los conceptos básicos de la búsqueda de registros y la sustitución de
datos, consulte el Capítulo 2 de la Guía del usuario.
Este capítulo trata los temas siguientes:
• Uso de LOCATE
• Búsqueda de registros indexados con FIND, SEEK y SEEK()
• Resumen de datos y realización de cálculos
• Uso de filtros y vistas

Capítulo 20, Búsqueda y resumen de registros 309


srchrec.dwp Page 310 Wednesday, August 30, 1995 2:22 PM

Búsqueda de registros
dBASE proporciona comandos y funciones para realizar búsquedas indexadas y
devolver valores. También pueden efectuarse búsquedas sin índices mediante el
comando LOCATE o la función LOOKUP(). En el caso de la función LOOKUP(), las
búsquedas indexadas son la única forma de obtener su funcionalidad mejorada.

Búsqueda con LOCATE


Utilice LOCATE para realizar búsquedas en la tabla registro por registro sin utilizar
índices. Éste es un método de búsqueda sencillo, flexible y eficaz para tablas pequeñas.
El ejemplo siguiente muestra el uso de LOCATE para encontrar el número de teléfono
de un cliente en una tabla.
USE CLIENTE
LOCATE FOR NOMBRE "Cesar " && Búsqueda de un cliente mediante una cadena parcial
IF FOUND()
DISPLAY telefono && Muestra el número de teléfono en la ventana de salida
ENDIF

Búsqueda con FIND y SEEK


FIND y SEEK utilizan el orden de la clave para buscar datos con rapidez en tablas
indexadas. SEEK es el comando más recomendable para las búsquedas en programas.
FIND proporciona compatibilidad con versiones anteriores de dBASE.
FIND tiene las características siguientes:
• Utiliza un índice de carácter, fecha o numérico para buscar el carácter literal, cadena o
número que se suministra.
• Las cadenas de búsqueda parcial deben comenzar con el carácter de la izquierda.
• No utiliza delimitadores alrededor de la cadena de caracteres.
• Puede utilizar claves de índice en que se han eliminado los espacios finales cuando la
etiqueta es del tipo de datos de carácter y SET EXACT es OFF. Las cadenas parciales
deben comenzar con el carácter situado más a la izquierda.
• No utiliza nombres de variables de memoria en las búsquedas, pero permite usar la
función & (macro).
• Diferencia mayúsculas y minúsculas. Si la etiqueta de índice contiene UPPER(), no es
posible encontrar cadenas de búsqueda en minúscula. Utilice las funciones de
conversión de cadenas, como UPPER() o LOWER(), para igualar el uso de
mayúsculas o minúsculas de la etiqueta de índice.

310 Guía del programador


srchrec.dwp Page 311 Wednesday, August 30, 1995 2:22 PM

SEEK tiene las características siguientes:


• Utiliza índices basados en expresiones que pueden incluir uno o más campos,
operadores, y combinar datos de carácter, numéricos o fecha.
• Utiliza delimitadores alrededor de las cadenas de búsqueda de carácter especificadas.
• Puede utilizar claves de índice en que se han eliminado los espacios finales cuando la
etiqueta es del tipo de datos de carácter y SET EXACT es OFF. Las cadenas parciales
deben comenzar con el carácter de la izquierda.
• Puede utilizar nombres de variables de memoria en las búsquedas.
• Diferencia mayúsculas y minúsculas. Si la etiqueta de índice contiene UPPER(), no es
posible encontrar cadenas de búsqueda en minúscula. Utilice las funciones de
conversión de cadenas, como UPPER() o LOWER(), para igualar el uso de
mayúsculas o minúsculas de la etiqueta de índice.
La función SEEK() tienen todas las características del comando SEEK y además
devuelve verdadero o falso como resultado de la búsqueda. Para un mejor rendimiento,
utilice la función SEEK() en lugar de FIND o SEEK.
Cuando no hay ningún índice abierto, la función LOOKUP() se comporta como
LOCATE y realiza una búsqueda registro por registro, pero éste no es un uso eficaz de
LOOKUP(). Utilice la función LOOKUP() con una tabla indexada para buscar una
expresión en un campo y recuperar los datos de otro campo.
Utilice SEEK y LOOKUP() en una búsqueda indexada para extraer información
relacionada del campo NUM_CLI. Por ejemplo:
USE CLIENTES
INDEX ON NUM_CLI TAG num_cli
SEEK 7845 && Busca un cliente por su código
Cantidad=LOOKUP(Bal_Inic,num_cli,NUM_CLI) && Obtiene el balance inicial de Num_cli
? Cantidad && Devuelve información del balance del cliente
Nombre= LOOKUP(Empresa num_cli, NUM_CLI) && Obtiene el nombre de la empresa de Num_cli
El comando de búsqueda más aconsejable, SEEK, y la función SEEK() también utilizan
las claves de índice en las búsquedas. La función SEEK() devuelve un valor lógico como
resultado de la búsqueda.
CLOSE ALL
USE CLIENTE
SET ORDER TO nombre && Usa un índice maestro
SEEK("Cesar") && Especifica una cadena de búsqueda
DISPLAY TELEFONO,NUM_CLI && Muestra campos del registro encontrado
La cadena de búsqueda debe coincidir exactamente, incluso en cuanto a mayúsculas y
minúsculas; de no ser así, SEEK() devuelve falso.
? SEEK("cesar") && Devuelve .F.

Capítulo 20, Búsqueda y resumen de registros 311


srchrec.dwp Page 312 Wednesday, August 30, 1995 2:22 PM

Interacciones de SET NEAR con las búsquedas


El parámetro del entorno, SET NEAR, cambia los resultados devueltos por las funciones
FOUND() y EOF() cuando una búsqueda no encuentra una coincidencia exacta.
La Tabla 20.1 resume esos valores devueltos.
Tabla 20.1 SET NEAR y los valores devueltos por las funciones en búsquedas fallidas
SET NEAR ON SET NEAR OFF
Posición del Registro siguiente a la Al final del archivo
puntero de coincidencia más
registro parecida
EOF() Falso Verdadero
FOUND() Falso Falso
SEEK() Falso Falso

El hecho de que EOF() es verdadero cuando SET NEAR está desactivado es útil para
determinar los resultados de las búsquedas. Sin embargo, podría haber búsquedas en
que EOF() puede ser verdadero con SET NEAR ON. Por ejemplo, si el último registro
del archivo es una “B” y se busca “C” el puntero de registro se sitúa en el registro que
sigue a la coincidencia más próxima y también en EOF().

Determinación del resultado de las búsquedas


Al final de una búsqueda con SEEK, compruebe el éxito de la búsqueda mediante los
valores devueltos por las funciones FOUND() y EOF(), o use sólo la función SEEK().
La función SEEK() puede devolver los mismos resultados que la combinación de IF
FOUND() y EOF(), ya que mueve la posición del puntero y devuelve un valor lógico
verdadero o falso. SEEK() es la función más aconsejable para determinar los resultados
de las búsquedas porque ahorra la línea adicional de código que sería necesaria con
FOUND() para comprobar el resultado lógico de la búsqueda. El ejemplo de programa
siguiente busca un registro existente y lo muestra cuando lo encuentra. Con SET NEAR
ON y en el caso de una búsqueda fallida, sitúa el puntero de registro en el registro más
parecido a la cadena de búsqueda y lo muestra.
Observe la conveniencia de la estructura IF e IF .NOT., que proporciona el valor que
devolvería FOUND().
IF SEEK("W8662")
DISPLAY && Muestra el registro completo en el panel de resultados
ENDIF
SET NEAR ON
IF .NOT. SEEK("A8330")
DISPLAY && Muestra el registro más parecido en el panel de resultados
ENDIF

312 Guía del programador


srchrec.dwp Page 313 Wednesday, August 30, 1995 2:22 PM

Resumen de los datos de los registros


Cuando realice cálculos, puede utilizar campos numéricos o definir una expresión (a la
que se denomina campo calculado) basada en uno o más campos. El valor de un campo
calculado no se almacena, sólo existe en memoria mientras las tablas están abiertas y
hay un cálculo activo. Dichos campos son de sólo lectura y no pueden editarse.
Suponga que tiene una tabla denominada MATERIAL.DBF que contiene los campos
Num_Mat, PrecUni y Cantidad, entre otros. Otra tabla denominada VENTAS.DBF
contiene Num_Mat y Nventa. El segmento de programa siguiente muestra cómo
pueden crearse campos calculados.
USE MATERIAL ORDER Num_Mat
USE VENTAS ORDER Nventa IN SELECT()
SET RELATION TO Num_Mat INTO VENTAS
SET FIELDS TO VENTAS->Nventa, Total=Cantidad*PrecUni
El campo calculado es Total. Sólo existe mientras estas tablas están abiertas y
relacionadas, o hasta que se desactiva SET FIELDS.

Cálculo de valores a partir de los campos


Aunque es posible usar comandos individuales como SUM o AVERAGE, para realizar
varios cálculos empleando el mismo comando repetidamente utilice el comando
CALCULATE, que proporciona un mejor rendimiento porque procesa los registros una
sola vez, independientemente de cuántos tipos diferentes de cálculos especifique.
Por ejemplo:
CALCULATE MEDIA(COST), TOTAL(COST) TO mcost_medio, mcost_total
El ejemplo siguiente cuenta el número de pedidos realizados por un cliente. En primer
lugar, la tabla Pedidos se indexa por el campo Num_cli. Después, la rutina toma del
usuario un número de cliente. Mediante SEEK, el programa busca el número de cliente
en el archivo y cuenta el número de pedidos (registros) realizados por este cliente.
También calcula la cantidad media pedida por este cliente.
mCli = SPACE(6) && Permite 6 espacios para
&& el código de cliente
INDEX ON NUM_CLI TAG Num_cli && Indexa la tabla
@ 10,10 SAY "Introduzca número de cliente" GET mCli && Muestra un mensaje y pide
&& un dato por teclado
READ && Lee el código de cliente
SET KEY TO mCli
SEEK mCli && Busca el código de cliente
CALCULATE CNT(), MEDIA(CANTIDAD) TO mcnt_Pedidos,mcnt_media;
WHILE mCli = NUM_CLI && Calcula el número de
&& pedidos y la cantidad
&& media que compra este
&& cliente

Capítulo 20, Búsqueda y resumen de registros 313


srchrec.dwp Page 314 Wednesday, August 30, 1995 2:22 PM

La Tabla 20.2 resume el uso de CALCULATE para determinar valores calculados a


partir de las tablas.
Tabla 20.2 Resumen de los datos de una tabla
Tarea Ejemplo
Contar cosas CALCULATE CNT() TO mCuenta
o bien
COUNT TO mcuenta
Sumar valores CALCULATE SUM(mprecio*mcantidad) TO mTotal
o bien
SUM mprecio*mcantidad TO mtotal
Promediar valores CALCULATE AVG(Precio) TO mPrec_med
o bien
AVERAGE Precio TO mPrec_med
Calcular la desviación CALCULATE STD(mPrecio) TO mPrec_tipico
típica y la varianza y
CALCULATE VAR(mPrecio) TO mVar_precio
Calcular el valor mínimo1 CALCULATE MIN(Precio) TO mPrecio_min
o bien
? MIN(mFecha1,mFecha2)
Calcular el valor máximo1 CALCULATE MAX(Fecha_trans) TO mFecha_max
o bien
? MAX(mContratos,mDespidos)

1. MIN() y MAX() con CALCULATE devuelven el valor menor o mayor que hay en el campo
mencionado. Por sí solas, estas funciones devuelven el menor o mayor de dos valores.

Filtrado de registros con el Diseñador de consultas


Puede utilizar el Diseñador de consultas para crear interactivamente condiciones de
filtro basadas en los campos de las tablas. El Diseñador de consultas genera un comando
SET FILTER que selecciona lo registros que se procesan en una tabla.
1 Abra el Diseñador de consultas. Haga doble clic en el icono Consultas o seleccione
Nuevo|Consulta en el menú Archivo.
2 Abra un archivo que desee utilizar en la condición de filtro. La máscara de archivo
aparece en el Diseñador de consultas. (Para ver datos de más de una tabla, utilice
Añadir tabla en el menú Consulta para mostrar máscaras de archivos adicionales.)
3 Especifique las condiciones de filtro empleando operadores relaciones y expresiones
en el espacio situado bajo el nombre de campo en las máscaras de archivo. O bien,
utilice el cuadro de condición del Diseñador de consultas.
4 Seleccione Resultado de la consulta para ver el resultado del filtro. La tabla muestra
los datos en el orden especificado.

314 Guía del programador


srchrec.dwp Page 315 Wednesday, August 30, 1995 2:22 PM

Puesto que el Diseñador de consultas crea código de programa en función de la consulta


que ha diseñado, puede incluirlo en un programa para lograr el mismo resultado.
El ejemplo siguiente muestra el código que genera el Diseñador de consultas para una
consulta típica de una sola tabla:
CLOSE DATABASES
SET EXACT ON
SELECT 1
USE EMPRESA.DBF
SET FILTER TO PROVINCIA = 'MA'
GO TOP
Para más información sobre el Diseñador de consultas, consulte la Guía del usuario y la
ayuda en línea.

Selección de campos
Utilice SET FIELDS TO <lista campos> para limitar los campos a los que son relevantes
para los datos que desea ver o para añadir campos de otras áreas de trabajo.
Mediante SET FIELDS ON y SET FIELDS OFF, puede conmutar entre la lista
personalizada de campos que cree y el valor por defecto de dBASE (todos los campos).
Cuando diseña una consulta en el Diseñador de consultas, éste también genera un
comando SET FIELDS TO con los campos incluidos en la consulta.
SET FIELDS TO sin opciones elimina la restricción de la lista de campos y vuelve a
mostrar todos los campos.

Capítulo 20, Búsqueda y resumen de registros 315


srchrec.dwp Page 316 Wednesday, August 30, 1995 2:22 PM

316 Guía del programador


shardat.dwp Page 317 Wednesday, August 30, 1995 2:23 PM

Capítulo

Programación para un entorno


Capítulo 21
21
compartido
Cuando se comparten datos, pueden surgir conflictos si más de un usuario o programa
intentan actualizar los mismos datos al mismo tiempo. dBASE proporciona
salvaguardias para impedir que se produzcan problemas en estas situaciones.
Al acceder a los datos, es posible elegir si los registros y tablas se bloquean automática o
explícitamente antes de actualizar los datos.
Este capítulo describe las técnicas básicas que puede utilizar al mostrar y actualizar
datos en un entorno compartido. También puede definir transacciones en que dBASE
verifica la realización satisfactoria de una actualización (de uno o más registros) antes de
aceptar los cambios en una tabla. (Para más información sobre las transacciones,
consulte el Capítulo 22.)
Este capítulo también describe las operaciones de dBASE que relacionan a diferentes
usuarios que comparten y acceden a datos en una red. Las mismas normas se aplican,
sin embargo, a las operaciones en que diferentes copias de Visual dBASE, o diferentes
programas ejecutados por la misma copia, comparten datos.
Visual dBASE proporciona las mismas operaciones para actualizar datos de tablas de
dBASE, Paradox o SQL. A menos que se indique lo contrario, los comandos y funciones
que se tratan en este capítulo funcionan de la misma forma para todos esos tipos de
tabla. (Para más información sobre diferencias específicas al usar comandos y funciones
de dBASE para acceder a tablas de Paradox y SQL, consulte el Capítulo 23.)

Capítulo 21, Programación para un entorno compartido 317


shardat.dwp Page 318 Wednesday, August 30, 1995 2:23 PM

Acceso a datos en un entorno compartido


En Visual dBASE, se opera en un entorno compartido cuando:
• Los datos se guardan en una unidad de red y los usuarios comparten estos datos.
• Se ejecutan varias copias de Visual dBASE en el ordenador y utilizan los mismos
datos.
• Se ejecuta una sola copia de Visual dBASE y se accede a los mismos datos desde
programas diferentes.
Para acceder a datos compartidos en el mismo ordenador:
• Defina la variable de entorno LOCALSHARE como ON en el archivo BDE.CFG.
• Ejecute el comando SHARE del DOS (no es necesario en Windows para Trabajo en
Grupo ni cuando se opera en una red).
• Para acceder a tablas de Paradox, defina también el directorio de archivos en red
mediante la utilidad de configuración BDE. (Tenga en cuenta que, al acceder a tablas
de Paradox, no están permitidos los bloqueos de lectura de Paradox.)
Cuando abre tablas con el comando USE, dBASE las abre automáticamente para su
acceso compartido. Para cambiar este comportamiento por defecto, active el parámetro
Exclusivo (para ello, elija Escritorio en el menú Propiedades). Para definir el modo
exclusivo archivo por archivo, utilice la opción EXCLUSIVE del comando USE, como se
describe en la sección siguiente.
Otro parámetro que afecta a la forma en que los datos de las tablas se comparten es
Sesiones. Una sesión de una sola copia de Visual dBASE es similar a una sesión de
usuario en un entorno multiusuario —cada sesión gestiona su propio conjunto de 255
áreas de trabajo, las operaciones de archivos de una sesión no tienen efecto sobre otras
sesiones y éstas no pueden acceder a un archivo abierto como exclusivo—.
El parámetro Sesiones está activado por defecto. Puede cambiarlo mediante el menú
Propiedades|Escritorio o utilizando el comando CREATE SESSION en un programa.
Para más información sobre las sesiones, consulte “Uso de las sesiones” más adelante en
este capítulo.

318 Guía del programador


shardat.dwp Page 319 Wednesday, August 30, 1995 2:23 PM

Apertura de una tabla para uso exclusivo


La especificación de USE con la palabra clave EXCLUSIVE abre una tabla en modo
exclusivo; es decir, nadie más puede acceder a la tabla hasta que la cierre con CLEAR
ALL, CLOSE ALL, CLOSE TABLES, USE o QUIT.
USE NOMBRES EXCLUSIVE && Abre la tabla en modo exclusivo
Si intenta abrir una tabla que ya está en uso exclusivo por otro usuario o programa o si
intenta abrir un archivo exclusivamente que ya está en uso exclusivo o compartido por
otro usuario o programa, dBASE devuelve un mensaje indicando que ya está en uso.
Varios comandos de dBASE precisan que abra las tablas para acceso exclusivo:
CONVERT
COPY INDEXES
DELETE TAG
INDEX ON...TAG
INSERT [BLANK]
MODIFY STRUCTURE
PACK
REINDEX
RESET
ZAP
Por ejemplo, dBASE precisa que abra una tabla en modo exclusivo antes de modificar su
estructura (mediante MODIFY STRUCTURE). Esto es necesario porque la modificación
de la estructura de una tabla y la adición o borrado de campos estaría en conflicto con
otros usuarios o programas que intenten mostrar o actualizar datos de la misma tabla.

Apertura de una tabla para acceso de sólo lectura


La especificación de USE con la opción NOUPDATE abre una tabla como de sólo
lectura; es decir, dBASE impide que añada, modifique o borre registros en la tabla.
USE VENTAS NOUPDATE && Abre la tabla para sólo lectura
Los privilegios de acceso establecidos para cada tabla y base de datos en una red
también afectan a las operaciones que pueden realizarse con ellas.

Capítulo 21, Programación para un entorno compartido 319


shardat.dwp Page 320 Wednesday, August 30, 1995 2:23 PM

Bloqueo de datos
Cuando más de un usuario o programa acceden a la misma tabla, pueden surgir
conflictos si intentan actualizar la misma información. dBASE gestiona estos conflictos
potenciales permitiendo que sólo un usuario o programa en cada momento realicen
operaciones que actualicen datos. Mientras un usuario actualiza un dato, esta tabla o
registro en uso está bloqueado y, aunque otros usuarios o programas pueden ver los
datos, no pueden actualizar ni bloquear la misma tabla o registro hasta que termine el
primer usuario.
dBASE proporciona dos tipos diferentes de operaciones de bloqueo: automático y
explícito. Con el bloqueo automático, dBASE bloquea automáticamente la tabla o registro
que se está actualizando. Con el explícito, puede bloquear toda una tabla o sólo los
registros específicos que desee actualizar.

Bloqueo automático
En dBASE, los registros de una tabla se bloquean de forma automática cuando utiliza
BROWSE o EDIT y pulsa cualquier tecla (o realiza una operación con el ratón) que
modifique un registro, excepto las utilizadas para desplazarse por la tabla o hacer una
selección de menú. (También es posible bloquear un registro eligiendo Tabla|Bloquear
registro seleccionado o pulsando Ctrl+L cuando se encuentre en un registro no
bloqueado.) dBASE intenta bloquear el registro que se está actualizando, y los registros
relacionados de otras tablas.
Visual dBASE utiliza los mismos tipos de bloqueo para las tablas de Paradox que los que
aplica a las de dBASE, es decir, intenta bloquear un registro, realiza la actualización y
libera el bloqueo. Sin embargo, al acceder a tablas de SQL, dBASE guarda primero sus
ediciones para un registro en particular y entonces comprueba si hay algún conflicto con
el registro guardado en el servidor de bases de datos en que se accede a la tabla SQL.
Si no hay ningún conflicto, actualiza el registro en el servidor de la base de datos.
(Para conocer el comportamiento específico de los bloqueos en su entorno, consulte la
documentación de SQL Link de Borland correspondiente a su servidor de base de
datos.)
Si logra el bloqueo de un registro o tabla, puede proceder a editar los datos. Si no,
Visual dBASE devuelve un mensaje indicando que el registro (o tabla) está en uso por
otro usuario. Si ha convertido la tabla empleando el comando CONVERT (consulte
“Obtención de información adicional sobre el bloqueo de registros”), Visual dBASE
también muestra el nombre del usuario que ha aplicado el bloqueo a esta tabla o
registro. En ambos casos, Visual dBASE continúa intentando obtener un bloqueo hasta
que lo logra o hasta que el usuario elige Cancelar.
Si obtiene un bloqueo en un registro, pero los datos han cambiado desde la última vez
que se mostraron, Visual dBASE actualiza los datos y devuelve el mensaje
Registro modificado por otro usuario. Después de elegir Aceptar, puede editar los
datos en el registro actualizado.

320 Guía del programador


shardat.dwp Page 321 Wednesday, August 30, 1995 2:23 PM

Cuando se ejecuta un comando que modifica toda la tabla, Visual dBASE intenta
bloquear la tabla de forma automática. La Tabla 21.1 enumera los comandos que
provocan un bloqueo automático (si no se ha aplicado ya un bloqueo explícito) y
muestra si bloquean toda la tabla o sólo los registros afectados. Si especifica un número
de registro como <ámbito> de DELETE, RECALL o REPLACE, se aplica un bloqueo
automático al registro, no a la tabla.
Tabla 21.1 Comandos que provocan un bloqueo automático
Comando Tipo de bloqueo
APPEND Registro
APPEND FROM Tabla
AVERAGE Tabla
BLANK Registro
BLANK <ámbito> Tabla
BROWSE1 Registro
CALCULATE Tabla
CHANGE1 Registro
COPY Tabla
COPY STRUCTURE Tabla
COUNT Tabla
DELETE Registro
DELETE <ámbito> Tabla
EDIT1 Registro
INDEX Tabla
JOIN Tabla
LABEL FORM Tabla
RECALL Registro
RECALL <ámbito> Tabla
REPLACE Registro
REPLACE <ámbito> Tabla
REPORT FORM Tabla
SORT Tabla
SUM Tabla
TOTAL Tabla
UPDATE Tabla

1. Visual dBASE intenta bloquear el registro cuando se


pulsa una tecla que modificaría los datos.

Después de realizar la actualización y pasar a otro registro, Visual dBASE libera la tabla
o registro de forma automática para que otros usuarios o programas puedan
actualizarlos. También puede liberar manualmente el bloqueo eligiendo
Tabla |Desbloquear registro seleccionado o pulsando Ctrl+L cuando se encuentre en un
registro bloqueado.

Capítulo 21, Programación para un entorno compartido 321


shardat.dwp Page 322 Wednesday, August 30, 1995 2:23 PM

También puede utilizar el comando UNLOCK para liberar los bloqueos de registros y
tablas en todas las áreas de trabajo o sólo en las especificadas. (Consulte “Bloqueo
explícito de tablas y registros” más adelante en este capítulo.)

Bloqueo automático de tablas relacionadas


Cuando edita datos que incluyen campos de más de una tabla (con SET RELATION o
SET VIEW), Visual dBASE bloquea de forma automática los registros relacionados
adecuados en cada tabla. Por ejemplo, podría establecer la siguiente relación entre dos
tablas, Pedidos y Elemento:
USE PEDIDOS IN SELECT() ALIAS Pedidos && Abre la tabla principal
USE ELEMENTO IN SELECT() ORDER Num_Ped ALIAS Elemento && Abre la tabla secundaria
SELECT Pedidos
SET RELATION TO Num_Ped INTO Elemento && Enlaza pedidos con clientes
SET SKIP TO Elemento && Muestra todos los elementos
&& de cada pedido
SET FIELDS TO PEDIDOS->NUM_PED, ELEMENTO->DESCRIPCION, ELEMENTO->CANTIDAD
BROWSE
Este ejemplo define una relación uno con muchos entre los pedidos facturados y los
artículos incluidos en cada pedido. Al desplazarse por cada pedido y comenzar a editar
los artículos individuales de un pedido en particular, Visual dBASE bloquea el registro
actual en la tabla secundaria. Cuando pase al siguiente pedido, Visual dBASE libera los
bloqueos. La Figura 21.1 ilustra esta operación:
Figura 21.1 Bloqueo de registros relacionados

PEDIDOS.DBF ELEMENTO.DBF

NUM_PED Relación uno con muchos NUM_PED ELEMENTO CAN


entre PEDIDOS.DBF y
00040 ELEMENTO.DBF 00040
00041 00041 Reloj 2
00042 00041 Silla 3
00043 00041 Mesa 1
00044 00042

Por ejemplo, si edita los campos de un registro del pedido que tiene el número 00041,
Visual dBASE bloquea ese registro en la tabla Pedidos y el registro actual en la tabla
Elemento. Entonces, cuando termine sus modificaciones y salga del registro,
Visual dBASE libera los bloqueos de ambas tablas.

322 Guía del programador


shardat.dwp Page 323 Wednesday, August 30, 1995 2:23 PM

Desactivación y activación del bloqueo automático


Dependiendo del número de usuarios o sesiones que compartan un archivo o de la
duración o complejidad de una operación en archivos, podría elegir activar o desactivar
el bloqueo automático. El bloqueo impide los conflictos con los datos, pero reduce el
rendimiento. Si actualiza una tabla que se utiliza con poca frecuencia, puede plantearse
la posibilidad de desactivar el bloqueo para acelerar la operación.
Para activar o desactivar el bloqueo automático, utilice SET LOCK. La Tabla 21.1
enumera los comandos que permiten desactivar el bloqueo automático.
Tabla 21.2 Comandos que permiten desactivar el bloqueo automático
Comandos
AVERAGE JOIN
CALCULATE LABEL FORM
COPY REPORT FORM
COPY STRUCTURE SORT
COUNT SUM
INDEX...TO <archivo .NDX> TOTAL

El valor por defecto es que el bloqueo esté activado; para desactivar el bloqueo
automático para estos comandos, utilice SET LOCK OFF antes de ejecutar el comando.
Advertencia Si se desactiva el bloqueo automático, la integridad de los datos no está garantizada.
SET LOCK OFF afecta a algunos de estos comandos sólo al leer los datos de una tabla,
no al escribirlos. Al escribir en una tabla, COPY, COPY STRUCTURE, INDEX, JOIN,
SORT y TOTAL abren la tabla de destino automáticamente en modo exclusivo.

Obtención de información adicional sobre el bloqueo de registros


El comando CONVERT añade un campo _dbaselock en las tablas existentes para
guardar información del estado de bloqueo y actualización de registros. La información
disponible en el campo depende del ancho del campo que especifique al utilizar
CONVERT, por ejemplo:
USE CLIENTE && Abre la tabla CLIENTE.DBF
CONVERT TO 24 && Crea un campo de 24 caracteres de longitud
Después de crear el campo _dbaselock, Visual dBASE almacena la siguiente información
para cada registro:
• La fecha y hora en que se bloqueó el registro por última vez.
• Si un registro se ha modificado desde que lo mostró por última vez.
• El identificador completo o parcial (dependiendo del ancho del campo CONVERT)
del último usuario que bloqueó o modificó el registro.

Capítulo 21, Programación para un entorno compartido 323


shardat.dwp Page 324 Wednesday, August 30, 1995 2:23 PM

Nota El comando CONVERT crea una copia de seguridad de la tabla original con la extensión
.CVT antes de añadir el nuevo campo _dbaselock. El tiempo y espacio necesarios para
crear la tabla convertida variará dependiendo del tamaño de la tabla.
Utilice LKSYS() para determinar la fecha y hora en que el registro se modificó por última
vez y, suponiendo que el ancho del campo sea mayor de 8 caracteres, el identificador del
último usuario que bloqueó el registro. CHANGE() devuelve .T. si se ha actualizado el
registro de la tabla desde la última vez que mostró los datos, y .F. si los datos no han
cambiado.
Cuando un registro ha cambiado, utilice REFRESH para actualizar los datos guardados
en la tabla. También puede utilizar SET REFRESH para especificar un intervalo de
tiempo para la actualización automática de la pantalla con los datos contenidos
actualmente en una tabla, por ejemplo:
USE CLIENTE && Abre la tabla CLIENTE.DBF
SET REFRESH TO 1000 && Especifica un intervalo de tiempo para la actualización
&& automática de la pantalla
IF CHANGE() && Comprueba que el registro actual ha sido actualizado
REFRESH && Si es así, actualiza
ELSE
REPLACE... && Si los datos no han cambiado, actualiza el valor del
&& registro
ENDIF

Bloqueo explícito de tablas y registros


Debido al bloqueo automático, es posible adaptar aplicaciones monousuario a un
entorno multiusuario o de datos compartidos con un mínimo de programación.
Sin embargo, quizá desee mejorar sus aplicaciones mediante la combinación de
operaciones de bloqueo automático y explícito de registros y tablas. Mediante el uso de
un bloqueo explícito de tablas y registros, puede reducir aún más las colisiones de datos,
que tienen lugar cuando dos o más usuarios intentar alterar los mismos datos
simultáneamente. Además, el uso del bloqueo explícito de registros suele reducir la
cantidad de tiempo que se mantienen los bloqueos, aumentando así el número de
usuarios que pueden acceder a la información de las tablas.
También puede personalizar la forma en que un programa interactúa con los usuarios y
gestionar cómo dos o más usuarios compiten por las tablas y registros ya bloqueados
por el otro. Pueden producirse puntos muertos, por ejemplo, cuando dos usuarios no
pueden acceder a las tablas o registros que ambos necesitan para completar una
transacción y continúan en un bucle infinito, cada uno esperando a que se libere el
bloqueo mantenido por el otro usuario. En lugar de intentar repetidamente el acceso a
datos que no están disponibles, puede cambiar el programa para que permita la
desaparición del punto muerto, de forma que un usuario pueda acceder a todas las
tablas y registros que sean necesarios para completar una transacción, después de lo
cual otro usuario puede continuar. (Para más información sobre las transacciones,
consulte el Capítulo 22.)

324 Guía del programador


shardat.dwp Page 325 Wednesday, August 30, 1995 2:23 PM

FLOCK() y RLOCK()
Utilice las funciones FLOCK() y RLOCK() para determinar si una tabla o registro está
bloqueado por otro usuario o programa y, si no lo está, para bloquearlo. Utilice
FLOCK() para bloquear toda una tabla y RLOCK() o LOCK() para bloquear el registro
actual y todos los registros (de tablas relacionadas) que dependan del registro actual.
Si la tabla o registro que desea bloquear no está bloqueado ya por otro usuario, FLOCK()
y RLOCK() bloquean la tabla o registro y devuelven .T. De lo contrario, si la tabla o
registro ya está bloqueado, o la operación de bloqueo falla por otra razón, devuelven .F.
Junto con estas dos funciones, SET REPROCESS le permite especificar el número de
veces que desea intentar aplicar un bloqueo si no lo logra la primera vez.
Utilice SET REPROCESS para definir tres tipos distintos de valores:
• Si está definido con un valor de –1, Visual dBASE continúa reintentando el bloqueo o
el intento de abrir una tabla (con el comando USE) hasta que otro usuario que tiene
actualmente un bloqueo sobre una tabla o registro libera el bloqueo. No se muestran
mensajes ni los usuarios pueden detener el proceso de reintento, por lo que podría
producirse una condición de punto muerto.
• Si está definido con un valor de 0, Visual dBASE intenta indefinidamente obtener un
bloqueo, pero aparece un cuadro de diálogo para que el usuario pueda pulsar Esc
para detener el intento de obtener el bloqueo.
• Si está definido con un entero positivo, Visual dBASE intenta lograr el bloqueo el
número especificado de veces antes de detenerse.
El ejemplo siguiente muestra cómo puede utilizar el bloqueo explícito junto con
SET REPROCESS en una aplicación:
SET REPROCESS TO 100 && Asigna 100 al contador de reintentos
IF RLOCK()
<realizar la actualización>
ƒ
ENDIF
En este ejemplo, Visual dBASE realiza 100 intentos para lograr un bloqueo antes de salir
del bloque IF...ENDIF. SET REPROCESS también puede utilizarse con rutinas
ON ERROR en un programa de aplicación. Si define una rutina ON ERROR, ésta no se
inicia hasta que termina el bucle de SET REPROCESS. Tenga en cuenta también que si
especifica un valor de –1 con SET REPROCESS, la rutina ON ERROR nunca se ejecuta.

Desbloqueo de una tabla o registro bloqueado explícitamente


Hay dos métodos de liberar una tabla o registro bloqueado.
• Utilice UNLOCK, que libera los bloqueos de registro y tabla en todas las áreas de
trabajo o en la especificada.
• Cierre una tabla con CLOSE ALL, CLOSE TABLES, USE, CLEAR ALL o QUIT.
Para más información sobre el uso de estos comandos, consulte la Referencia del lenguaje.

Capítulo 21, Programación para un entorno compartido 325


shardat.dwp Page 326 Wednesday, August 30, 1995 2:23 PM

Diseño de programas para entornos de red


Al diseñar un programa de aplicación, utilice NETWORK() para comprobar el entorno y
determinar si la aplicación se ejecuta en red. De esa forma, el programa puede
comportarse adecuadamente dependiendo de si accede a datos de una red o si se utiliza
en un entorno exclusivamente monousuario.
IF NETWORK( && Comprueba si el programa se está ejecutando en red
Lname = ID() && Devuelve el nombre de usuario en la red
ENDIF
Por ejemplo, en los entornos en que accede a datos en una red, probablemente desee
definir diferentes tipos de rutinas de gestión de errores (con el comando ON
NETERROR), además de proporcionar un mejor bloqueo de tablas y registros. Si accede
a tablas SQL, debe adaptar su aplicación e incluir rutinas de gestión de errores
específicas del servidor de base de datos que esté utilizando.

Uso de las sesiones


Las sesiones proporcionan una forma de asociar diferentes parámetros del entorno y
recursos con objetos de Visual dBASE, como fichas, programas, ventanas EDIT y
BROWSE abiertas dentro del entorno Windows. Cada sesión proporciona su propio
conjunto de áreas de trabajo y variables de entorno SET. Dentro de una sesión, puede
cambiar parámetros, definir una transacción, desplazarse por los registros y abrir tablas
e índices, todo ello sin afectar a otras sesiones abiertas al mismo tiempo.
Nota Las variables de memoria mantienen sus valores de una sesión a otra.
Sin embargo, sí afectan a las sesiones las actualizaciones y los bloqueos de tabla y
registro realizados dentro de otras sesiones abiertas. Las sesiones actúan como usuarios
distintos que acceden a datos en un entorno compartido. Es decir, si una sesión bloquea
una tabla o un registro, otra sesión no puede bloquear la misma tabla o registro. Cuando
las actualizaciones en una tabla se realizan en una sesión, los datos de otras sesiones que
accedan a la misma tabla se actualizan cuando sean la sesión actual de nuevo.

326 Guía del programador


shardat.dwp Page 327 Wednesday, August 30, 1995 2:23 PM

Creación de sesiones
Los parámetros y operaciones de una sesión se registran en función de las selecciones
vigentes cuando se crea o utiliza. La opción Sesiones del cuadro de diálogo
Propiedades |Escritorio le permite elegir si se utilizan sesiones al abrir tablas o
consultas mediante el Selector o el menú Archivo|Abrir. Los informes y etiquetas
siempre operan en su propia sesión.
Dentro de un programa, puede incluir el comando CREATE SESSION (normalmente al
principio del programa) para iniciar una nueva sesión. (Para más información sobre
CREATE SESSION y para una lista de los comandos, funciones y parámetros del
entorno controlados por las sesiones, consulte la Referencia del lenguaje.) Cuando cree
una sesión, todos los SET tienen sus valores iniciales (tomados de DBASEWIN.INI).
Aunque las tablas de otras sesiones activas permanecen abiertas, no hay tablas abiertas
en la nueva sesión y el área de trabajo actual es la 1.
CREATE SESSION && Comienza una nueva sesión
USE CLIENTE && Abre una tabla
BROWSE && Abre una ventana BROWSE
CREATE SESSION && Crea una segunda sesión
USE CLIENTE && Abre la tabla CLIENTE.DBF de nuevo
SKIP 1 && El desplazamiento en la segunda sesión no afecta
&& al puntero de registro de la primera
Si utiliza el Diseñador de fichas, necesita añadir el comando CREATE SESSION en el
código generado de una ficha si desea que funcione en su propia sesión.
Nota La ventana de comandos mantiene su propia sesión, por lo que las operaciones
realizadas en ella son independientes de la nuevas sesiones que cree, por ejemplo,
cuando abre un objeto como una consulta o una tabla usando el Selector.

Selección de sesiones
No hay ningún comando específico del lenguaje para seleccionar una sesión. Una sesión
se selecciona cuando se crea o cuando una ventana asociada a una sesión se convierte en
la ventana activa. Una sesión asociada a una ficha se selecciona siempre que se ejecuta
un método o controlador de sucesos para la ficha. Al seleccionar otra sesión abierta, se
restauran todos los parámetros del entorno definidos cuando se creó la sesión.

Cierre de las sesiones


Las sesiones permanecen abiertas mientras siga activo cualquier programa, ficha,
ventana BROWSE o EDIT que haga referencia a la sesión actual. Cuando no quedan
entidades que hagan referencia a una sesión, ésta se cierra automáticamente junto con
las tablas que sigan abiertas. Si hay una transacción en curso, Visual dBASE muestra un
cuadro de diálogo que le solicita que acepte, recupere o cancele la transacción.

Capítulo 21, Programación para un entorno compartido 327


shardat.dwp Page 328 Wednesday, August 30, 1995 2:23 PM

328 Guía del programador


trans.dwp Page 329 Wednesday, August 30, 1995 2:23 PM

Capítulo

Uso de las transacciones


Capítulo 22
22
En general, las aplicaciones utilizadas en entornos de red o de datos compartidos deben
ser más sofisticadas y gestionar situaciones más variadas que las que se ejecutan en un
entorno monousuario o monoprograma. Por ejemplo, cuando muchos usuarios
comparten el acceso a las mismas tablas, las operaciones pueden demorarse o
interrumpirse. Además, las aplicaciones que se ejecutan en una red suelen acceder a
tablas más grandes que contienen datos más importantes para la empresa en cuestión.
Este capítulo describe cómo puede utilizar las transacciones para mantener la integridad
de los datos. Mediante su uso, puede indicar a Visual dBASE que compruebe la
terminación satisfactoria de una actualización (de uno o más registros) antes de aceptar
los cambios en una tabla. Así puede proteger mejor la integridad de los datos e impedir
que las actualizaciones parciales o fallidas dejen los datos en un estado desconocido.
A menos que se indique lo contrario, las operaciones descritas en este capítulo se aplican
igualmente a las tablas de dBASE, Paradox o SQL. (Para más información sobre las
diferencias específicas al utilizar comandos y funciones de Visual dBASE para acceder a
las tablas de Paradox y SQL, consulte el Capítulo 23.)

Capítulo 22, Uso de las transacciones 329


trans.dwp Page 330 Wednesday, August 30, 1995 2:23 PM

Descripción general del proceso de una transacción


Cuando realiza actualizaciones en los datos, suele ser vital que sepa si una operación
puede completarse antes de efectuarla en realidad. Esto es importante en especial
cuando se realiza una operación que podría actualizar más de un registro o tabla. Las
transacciones agrupan un conjunto de actualizaciones como una sola operación, de
forma que o se realizan todas las modificaciones o, si hay algún problema para terminar
la transacción, no se realiza ningún cambio en las tablas.
Por ejemplo, suponga que va a actualizar registros en una ficha de entrada multitabla o
a realizar actualizaciones en un lote de registros con el comando REPLACE. En ese caso,
puede definir una transacción que englobe todas las actualizaciones en la misma
operación. Así, si no logra actualizar todos los registros en la transacción (por ejemplo, si
un registro o tabla que desea actualizar está bloqueado), puede restaurar los datos a su
estado original e intentar completar la transacción de nuevo más tarde.
Visual dBASE permite dos tipos de transacciones: locales y de servidor. Las transacciones
locales las realiza dBASE con tablas de dBASE o Paradox. Las actualizaciones o adiciones
en tablas de una transacción local no se escriben físicamente en las tablas hasta que se
acepten las modificaciones.
Las transacciones de servidor se someten al servidor para su proceso. Es posible designar
el nivel de aislamiento que debe utilizarse en una transacción de servidor.

Creación de transacciones
Visual dBASE dispone de un grupo de funciones, BEGINTRANS(), COMMIT() y
ROLLBACK(), que proporcionan el proceso de transacciones para la actualización de
tablas de dBASE, Paradox y SQL. Dependiendo de si una transacción se completa
satisfactoriamente, es posible o aceptar los cambios, o recuperar o restaurar las tablas a
su estado original.
BEGINTRANS() inicia una transacción. La función devuelve .T. si logra iniciarla. Todos
los bloqueos de registros y tablas se mantienen hasta terminar la transacción.
COMMIT() termina una transacción, haciendo que los cambios realizados en las tablas e
índices sean permanentes después de ejecutar COMMIT(). Esta función también libera
todos los bloqueos de registro y tabla y devuelve .T. si la transacción se acepta.
ROLLBACK() también termina una transacción, pero restaura todos los cambios
realizados en las tablas e índices abiertos antes de liberar todos los bloqueos de registro
y tabla. La función devuelve .T. si la transacción se logra recuperar.
ON ERROR ROLLBACK() && Deshace los cambios si se produce un error
USE EMPLEADO
IF BEGINTRANS() && Intenta comenzar la transacción.
REPLACE ALL Salario WITH Salario * 1.1 && Un cambio reversible con ROLLBACK()
IF .NOT. COMMIT() && Intenta escribir los cambios en el disco
* No se puede escribir. Incluya opciones para que
* el usuario pueda reintentar o cancelar
ƒ
ENDIF
ENDIF

330 Guía del programador


trans.dwp Page 331 Wednesday, August 30, 1995 2:23 PM

Proceso de transacciones con tablas de dBASE y Paradox


Cuando se accede a tablas de Paradox o de dBASE, Visual dBASE realiza un
seguimiento de toda la información que necesita para aceptar o recuperar los cambios.
En el caso de transacciones que implican tablas guardadas en un servidor de base de
datos, sin embargo, Visual dBASE envía los comandos de la transacción directamente al
servidor y éste es el que la procesa. Así, las reglas que se aplican pueden ser diferentes
de las utilizadas en una transacción local o de las transacciones de otro servidor.
Las transacciones que implican actualizaciones de tablas de Paradox y dBASE son
gestionadas por Visual dBASE.
Nota No es posible definir una transacción que incluya actualizaciones en una tabla de
dBASE o Paradox y en una tabla SQL, y no es posible anidar transacciones. Sin embargo,
al acceder a datos de SQL, puede definir transacciones distintas para cada base de datos
abierta, puesto que cada servidor de base de datos mantiene sus propias transacciones.
Para más información sobre la definición de transacciones para actualizaciones en tablas
SQL, consulte el Capítulo 23.

Uso de comandos y funciones de dBASE en las transacciones


Las transacciones de Visual dBASE contienen todos los comandos y funciones que
actualizan tablas. La tabla siguiente proporciona una lista de los comandos y funciones
de dBASE de las que se hace seguimiento en las transacciones:
Tabla 22.1 Comandos y funciones controlados en las transacciones de Visual dBASE
Comandos y funciones
APPEND [BLANK] BLANK
APPEND MEMO INSERT
REPLACE BROWSE
REPLACE MEMO EDIT
REPLACE BINARY @ GET...READ
REPLACE OLE RLOCK()
DELETE FLOCK()
RECALL

Las operaciones del comando APPEND FROM no se controlan en las transacciones de


Visual dBASE.
Los comandos que no están permitidos en las transacciones de Visual dBASE se
enumeran en la tabla siguiente:
Tabla 22.2 Comandos y funciones no permitidos en las transacciones de Visual dBASE
Comandos y funciones
BEGINTRANS() CREATE FROM
CLEAR ALL DELETE TAG
CLOSE ALL INSERT
CLOSE DATABASE MODIFY STRUCTURE

Capítulo 22, Uso de las transacciones 331


trans.dwp Page 332 Wednesday, August 30, 1995 2:23 PM

Tabla 22.2 Comandos y funciones no permitidos en las transacciones de Visual dBASE (continuación)
Comandos y funciones
CLOSE INDEX PACK
CLOSE TABLES ZAP
CONVERT

El comando UNLOCK no tiene ningún efecto hasta haber terminado la transacción de


Visual dBASE. Las transacciones anidadas no están permitidas en Visual dBASE.
Nota Debería utilizar PACK periódicamente para eliminar registros de las tablas de dBASE;
el “borrado” de los registros los marca para borrado, pero no los elimina en realidad de
la tabla.

Uso de las transacciones en las fichas


Mediante el Diseñador de fichas, puede crear fichas en que sus elementos, como campos
de entrada, están vinculados directamente a los datos de las tablas, por ejemplo,
mediante las propiedades DataSource y DataLink. Así, cuando se abre la ficha, los datos
se muestran automáticamente en los campos de entrada. Si edita los datos en la ficha,
Visual dBASE intenta bloquear el registro adecuado en la tabla subyacente y, si lo logra,
actualiza los datos del registro con los cambios del usuario.

Definición de puntos de transacción


Como con otros comandos de dBASE que actualizan datos, también puede definir
transacciones como parte de una ficha. La ficha CLIENTE.WFM, incluida con los
programas y archivos de ejemplo instalados con Visual dBASE, supone un ejemplo.
En esta ficha, una transacción se inicia cuando un usuario hace clic en el botón de
comando denominado BOTONVER_EDITAR. La propiedad OnClick define el
procedimiento que se llama cuando eso ocurre:
DEFINE PUSHBUTTON BOTONVER_EDITAR OF THIS;
PROPERTY;
ƒ
OnClick CLASS::VER_EDITAR ;
ƒ

PROCEDURE VER_EDITAR
IF form.ModoEdicion
form.Aceptar()
form.ModoEdicion = .F.
form.text = "Cliente -- Modo Ver"
this.text = "&Editar"
this.statusmessage = "En modo Ver. Pulse el botón Editar para editar los datos"
ELSE
BEGINTRANS()
form.ModoEdicion = .T.
this.text = "&Ver"
this.statusmessage = "En modo Edición. Pulse el botón Ver para volver al modo Ver"
ENDIF

332 Guía del programador


trans.dwp Page 333 Wednesday, August 30, 1995 2:23 PM

Este procedimiento proporciona código que cambia la ficha del modo de visualización
al de edición de datos, y viceversa. En la rama ELSE, en que la ficha se cambia para
permitir que el usuario edite los datos, Visual dBASE inicia una transacción con la
función BEGINTRANS().
La rama IF contiene código para cambiar la ficha de permitir ediciones a sólo visualizar.
En primer lugar, llama la función Aceptar() para averiguar si el usuario ha modificado
los datos.
FUNCTION Aceptar
IF form.ModoEdicion .AND. form.CambiosHechos
IF MessDlg("Confirmación", "¿Efectuar cambios?"...) = 6
COMMIT()
ELSE
ROLLBACK()
ENDIF
ELSE
ROLLBACK()
ENDIF
form.CambiosHechos = .F.
RETURN .T.
Si se han producido modificaciones, la ficha muestra un cuadro de diálogo que le
pregunta si desea aceptar, recuperar o cancelar los cambios realizados en los datos.
El cuadro de diálogo también aparece si sale de Visual dBASE mientras la transacción
está activa.
Visual dBASE incluye otras dos formas de evaluar los cambios asociados a una
transacción. Por ejemplo, puede utilizar la propiedad OnChange con elementos de
datos, como campos de entrada, cuadros de lista y cuadros combinados, y determinar si
se han realizado modificaciones en un campo.
DEFINE ENTRYFIELD ENTRNUMCLI OF THIS;
PROPERTY;
ƒ
DataLink "Num_cli", ;
OnChange CLASS::CAMBIOSHECHOS, ;
ƒ
Puede definir un método OnNavigate para determinar si un usuario ha salido de un
registro en particular y ha realizado modificaciones en el registro. Como se ha
mencionado antes, probablemente también deseará comprobar, al cerrar la ficha, si se
han efectuado cambios en una tabla.
CLASS CLIENTES OF FORM;
this.OnOpen=CLASS::FICHA_ABIERTA
this.OnClose=CLASS::FICHA_CERRADA
this.OnNavigate CLASS::CAMBIOSHECHOS
ƒ
También puede proporcionar proceso de transacciones en una ficha cuando utiliza los
comandos APPEND o REPLACE, en lugar de emplear objetos (como campos de
entrada) o métodos (como OnChange u OnNavigate), para añadir o actualizar un solo
registro o realizar operaciones por lotes que actualizan varios registros de una tabla.

Capítulo 22, Uso de las transacciones 333


trans.dwp Page 334 Wednesday, August 30, 1995 2:23 PM

Uso de niveles de aislamiento en las transacciones de servidor


Visual dBASE permite el uso de niveles de aislamiento en las transacciones de servidor.
Los niveles admitidos se enumeran en la Tabla 22.3:
Tabla 22.3 Niveles de aislamiento permitidos
Nivel de
aislamiento Descripción
0 Utiliza el nivel de aislamiento por
defecto
1 Dirty read (Se leen cambios no
aceptados)
2 Read committed (Cambios aceptados)
3 Repetable read (Repetibilidad de
lectura completa)

Los niveles de aislamiento se pasan como parámetro a la función BeginTrans( ). Por


ejemplo:
BEGINTRANS("LEDGERDB", 1)&& comienza la transacción en LedgerDB con nivel de aislamiento 1
La posibilidad de uso de los niveles de aislamiento depende del servidor de la base de
datos. La Tabla 22.4 muestra los niveles permitidos por diversos servidores:
Tabla 22.4 Niveles de aislamiento permitidos por los servidores de bases de datos
Nivel de
aislamiento Informix Interbase Sybase Oracle
0 (Por defecto) Sí No Sí No
1 (Dirty read) Sí No1 No1 No1
2 (Read committed) Sí Sí Sí Sï
3 (Repeteable read) Sí Sí No Sí2

1
Se trata comoread committed
2 Sólo lectura

Notas La versión anterior de dBASE tomaba el nivel de aislamiento 1 como nivel por defecto,
mientras que Visual dBASE toma el nivel 0.
Si el servidor los permite, los comandos SET TRANSACTION, COMMIT y ROLLBACK
de SQL interactiva pueden utilizarse en lugar de las funciones de dBASE.

334 Guía del programador


pdx_sql.dwp Page 335 Wednesday, August 30, 1995 2:24 PM

Capítulo

Uso de tablas que no son de dBASE


Capítulo 23
23
Visual dBASE incorpora el acceso estándar a las tablas de dBASE y Paradox mediante el
Borland Database Engine. Además, si ha adquirido e instalado SQL Links de Borland,
puede acceder a datos guardados en tablas SQL.
Para trabajar con las tablas de Paradox y SQL, puede utilizar los comandos y funciones
de dBASE. También puede ejecutar sentencias de SQL directamente para acceder a los
datos guardados en tablas de dBASE, Paradox o SQL.
Esta capítulo señala las diferencias en el comportamiento de Visual dBASE cuando se
accede a tablas de dBASE, Paradox y SQL y describe técnicas de programación para
acomodar los diferentes tipos de tablas.
Para una descripción general de cómo utiliza Visual dBASE el Borland Database Engine
para acceder a los diferentes tipos de tablas, consulte la Guía del usuario. Para
información adicional sobre el uso de tablas SQL con Visual dBASE, consulte la
documentación de SQL Links de Borland. Encontrara información sobre cómo
combinar operaciones de dBASE y SQL con datos de SQL en este capítulo.
Para acceder a otros tipos de datos, impórtelos en las tablas de Visual dBASE. Consulte
los comandos IMPORT y APPEND FROM en la Referencia del lenguaje.

Capítulo 23, Uso de tablas que no son de dBASE 335


pdx_sql.dwp Page 336 Wednesday, August 30, 1995 2:24 PM

Diferencias en las tablas de Paradox y SQL


Cuando se realizan operaciones en una tabla que no es de dBASE, Visual dBASE impone
las normas, como convenciones de nombres y construcción de claves de índice, que se
aplican a un tipo de tabla en particular. Por ejemplo, una clave de índice de dBASE
puede ser una expresión compleja que combine campos, funciones y operadores, por
ejemplo UPPER(Departamento+Cargo), pero la clave de una tabla de Paradox o SQL
sólo puede especificar un campo o una lista de campos. Deber estar familiarizado con
las normas aplicables a los tipos de tabla que utilice.
Las tablas de Paradox y SQL pueden contener campos de tipos diferentes a los
disponibles en las de dBASE. Visual dBASE puede leer y escribir en estos campos
cuando realiza operaciones mediante el Selector o cuando ejecuta comandos y funciones
de dBASE que acceden a datos. Para obtener más información sobre los tipos de datos
de Paradox, consulte la documentación de ese programa. Para obtener más información
sobre los tipos de datos utilizados en su base de datos de SQL, consulte la
documentación de su servidor SQL o la Guía del usuario de Vínculos SQL de Borland.
Cuando guarda datos de un tipo de campo que no es de dBASE en una variable de
memoria, Visual dBASE convierte los datos a un tipo de datos compatible. Por ejemplo,
si asigna un campo alfanumérico de Paradox a una variable de memoria, Visual dBASE
crea una variable de carácter.
De forma similar, cuando sustituye un tipo de campo que no es de dBASE por el
contenido de una variable de memoria, Visual dBASE convierte los datos al tipo que no
es de dBASE. Por ejemplo, puede sustituir un campo alfanumérico de Paradox por el
contenido de una variable de carácter.

Apertura de una base de datos


Para abrir una base de datos que no es de dBASE, primero debe definir un alias de BDE
para esa base de datos mediante la Utilidad de configuración BDE. (Para más
información, consulte la Guía del usuario.)
Una vez que haya definido un alias BDE, puede utilizarlo para conectarlo a la base de
datos y tener acceso a sus tablas. Si utiliza el comando OPEN DATABASE para acceder
a la base de datos, Visual dBASE muestra un cuadro de diálogo en que debe introducir
datos, como nombre y contraseña de conexión, que son necesarios para establecer una
conexión con esa base de datos. Si hay más de una base de datos abierta, utilice SET
DATABASE para definir la base de datos actual.
Visual dBASE también le permite especificar la posición de una tabla en una base de
datos anteponiendo al nombre de la tabla el de la base delimitado con signos de dos
puntos, por ejemplo:
USE :ContaDB:NUEVPEDS && Abre la tabla NUEVPEDS de la base de datos ContaDB

336 Guía del programador


pdx_sql.dwp Page 337 Wednesday, August 30, 1995 2:24 PM

Especificación del tipo de tabla


Cuando abre una tabla con USE, puede incluir una extensión .DBF o .DB en el nombre
de tabla para especificar la apertura de una tabla de dBASE o Paradox, respectivamente.
Si no especifica una extensión, Visual dBASE utiliza el valor de SET DBTYPE para
determinar el tipo de tabla que abre (por defecto es dBASE). Sustituya el valor actual de
SET DBTYPE especificando la opción TYPE con el comando USE.
Si accede a una base de datos, Visual dBASE intenta abrir el tipo de tabla almacenado en
ella, que podría ser una tabla de Paradox, SQL o dBASE.
USE CLIENTE && Abre una tabla dBASE: CLIENTE.DBF
USE PEDIDOS.DB && Abre una tabla Paradox: PEDIDOS.DB
USE ELEMENTO TYPE PARADOX && Abre una tabla Paradox: ELEMENTO.DB
SET DBTYPE TO PARADOX && Cambia el tipo de tabla por defecto a Paradox
USE PEDIDOS && Abre una tabla Paradox: PEDIDOS.DB
USE CLIENTE && Intenta abrir CLIENTE.DBF, pero no puede
USE CLIENTE TYPE DBASE && Abre una tabla dBASE: CLIENTE.DBF
COPY TO NUEVA && Copia a una tabla Paradox: NUEVA.DB
USE :ContaDB:NUEVCLI && Abre la tabla NUEVCLI de la base de datos ContaDB
SET DBTYPE también afecta al comando DIR.
DIR && Lista los archivos .DBF
SET DBTYPE TO PARADOX
DIR && Lista los archivos .DB

Delimitación de los nombres de campo


Los nombres de campo en Paradox y en otras tablas que no son de dBASE pueden
contener espacios y otros caracteres, como corchetes ([ ]), el signo # o paréntesis.
Esto afecta a cualquier expresión que utilice un nombre de campo. Cuando utilice
nombres de campo que no están permitidos normalmente en las tablas de dBASE, debe
delimitarlos con signos de dos puntos. No es necesario que delimite los nombres de
campo que cumplen con las normas de dBASE. Esos delimitadores funcionan con
cualquier comando o función en que puede especificar un campo o una lista de campos.
USE FACTURAS.DB TYPE PARADOX && Abre una tabla Paradox
DISPLAY :Apellido 1:, Telefono, :Cuenta#: && Delimita nombres de campo no válidos
BROWSE FIELDS :Apellido 1:, Telefono, :Cuenta#: && Delimita nombres de campo no válidos

Capítulo 23, Uso de tablas que no son de dBASE 337


pdx_sql.dwp Page 338 Wednesday, August 30, 1995 2:24 PM

Uso de indicadores para identificar registros


A diferencia de las tablas de dBASE, las de Paradox y SQL no tienen números de
registro fijos. La posición de un registro se modifica conforme cambia el orden de los
registros recuperados de una tabla. Por ejemplo, cuando abre una tabla de direcciones
empleando Paradox, Aaron Alcalá de Zaragoza puede ser el primer registro cuando el
índice maestro es Nombre y el último cuando el índice es Ciudad.
Para hacer referencia a los registros de Paradox y SQL, utilice indicadores. Puesto que los
registros no pueden identificarse por su posición en la tabla, los indicadores constituyen
identificadores (handles) de registros específicos de la tabla. El valor de un indicador
puede guardarse en una variable de memoria mediante BOOKMARK() o RECNO().
BOOKMARK() siempre devuelve un indicador; RECNO() devuelve un número de
registro para las tablas de dBASE y un valor de indicador para las de Paradox y SQL. El
uso de los indicadores es similar al de los números de registro.
USE FACTURAS.DB ORDER NumFac TYPE PARADOX && Abre una tabla Paradox
SEEK "1001" && Busca un registro
MkReg = BOOKMARK() && Guarda un indicador
GO TOP && Va al primer registro
GO MkReg && Vuelve a la factura número 1001
El valor del indicador es un tipo de datos especial no imprimible, similar a la variable de
referencia a un objeto; es posible utilizar comparadores relacionales para comparar los
indicadores, pero no puede consultarse ni manipularse su valor.
MkReg1 = BOOKMARK() && Guarda un indicador
SKIP 10 && Se desplaza a un nuevo registro
MkReg2 = BOOKMARK() && Guarda un segundo indicador
? MkReg2 > MkReg1 && Muestra .T.
? MkReg1 && Muestra "BookMark"
Tenga en cuenta también que las operaciones que reordenan registros, por ejemplo, la
modificación de los datos de un campo clave o la ejecución del comando SET ORDER,
invalidan los valores actuales de los indicadores.
La ausencia de números de registro fijos afecta a varios comandos y funciones. En las
tablas de Paradox y SQL:
• BROWSE, LIST y DISPLAY no muestran una columna de número de registro.
• GO y GOTO aceptan valores de indicador; GO y GOTO devuelven un error si
especifica un valor numérico.
• RECNO() devuelve un indicador.
• Para cualquier comando que tenga un <ámbito>, la palabra clave RECORD acepta un
valor de indicador.

338 Guía del programador


pdx_sql.dwp Page 339 Wednesday, August 30, 1995 2:24 PM

• LOCK() y RLOCK() aceptan una lista de indicadores. Tenga en cuenta que no es


posible aplicar bloqueos de archivo o registro en las tablas SQL. (Dentro de una
aplicación, utilice el comando ON ERROR y las funciones ERROR(), DBERROR() y
SQLERROR() para determinar si se han efecutado las actualizaciones.)
• REPLACE precisa un índice exclusivo en los servidores de base de datos que no
proporcionan direcciones de registro exclusivas.

Inserción de registros
Al añadir registros en las tablas de dBASE, puede hacerlo al final de la tabla (con
APPEND) o insertarlos en la posición del registro actual (con INSERT). Con tablas de
Paradox y SQL, sólo es posible añadir registros. En las de Paradox, INSERT con un
índice activo se comporta igual que APPEND con una tabla de dBASE. El registro se
“inserta” en el índice, pero se añade al final de la tabla.
En las tablas SQL, los índices no afectan a la forma en que se añaden los registros en las
tablas. Cuando inserta registros en una tabla SQL, el servidor de bases de datos al que
accede determina dónde y cómo se guardan los nuevos registros.

Borrado de registros
El borrado de registros individuales en una tabla de dBASE es un proceso de dos pasos.
En primer lugar, marca el registro para borrado con DELETE y lo elimina de la tabla con
PACK. (Debe abrir la tabla para uso exclusivo antes de ejecutar el comando.)
El comando RECALL desmarca los registros borrados, invirtiendo el efecto de DELETE.
Ni las tablas SQL ni las de Paradox mantienen una marca “borrado”, como tienen las de
dBASE. En consecuencia, el borrado se produce en un solo paso en esas tablas.
Cuando borra un registro con DELETE, se elimina permanentemente de la tabla, sin ser
posible recuperarlo.
En las tablas de Paradox y SQL:
• DELETE borra permanentemente un registro de una tabla y mueve el puntero al
siguiente registro. (En las tablas SQL, el puntero de registro pasa al siguiente registro
recuperado del servidor de bases de datos.)
• DELETED() siempre devuelve .F.
• RECALL y PACK devuelven errores.
• SET DELETED no tiene ningún efecto.
Mediante el comando ZAP, puede borrar todos los registros de las tablas de dBASE,
Paradox y SQL.

Capítulo 23, Uso de tablas que no son de dBASE 339


pdx_sql.dwp Page 340 Wednesday, August 30, 1995 2:24 PM

Validación de datos
En las tablas de Paradox y SQL, puede especificar controles de validación para cada
campo, que son normas que rigen los valores que puede introducir el usuario en el
campo. Por ejemplo, un control de validación puede especificar un valor mínimo,
máximo o por defecto para un campo.
Los controles de validación de Paradox y SQL son similares a las cláusulas de validación
que se especifican para los objetos de introducción del usuario (como los campos de
entrada) mediante la propiedad Valid, excepto que se asocian directamente a los
campos de una tabla.
Visual dBASE aplica los controles de validación existentes a las tablas de Paradox y SQL.
Cuando se editan datos en un campo de una tabla de Paradox o SQL que tiene un
control de validación, Visual dBASE evalúa la condición de validación y devuelve un
error si no se cumple. Sin embargo, no es posible crear ni modificar un control de
validación. Para ello, emplee Paradox o la utilidad adecuada disponible en el servidor
de bases de datos que contiene la tabla. Busque en la documentación de Paradox o del
servidor de bases de datos información sobre los tipos específicos de control de
validación que se proporcionan y cómo crearlos.
Los controles de validación de Paradox se almacenan en un archivo que tiene el mismo
nombre que la tabla y la extensión .VAL. Visual dBASE abre el archivo .VAL de forma
automática, si existe, al abrir una tabla de Paradox.

Uso de las transacciones


Visual dBASE proporciona una serie de funciones que permiten el proceso de
transacciones para las actualizaciones de las tablas de dBASE, Paradox y SQL.
• BEGINTRANS() inicia una transacción.
• COMMIT() acepta cambios realizados en la transacción actual.
• ROLLBACK() recupera los cambios y restaura una tabla a su estado original.
Para más información sobre la definición de transacciones en los programas de Visual
dBASE, consulte el Capítulo 21.
Cuando se accede a tablas de Paradox o de dBASE, Visual dBASE controla toda la
información que necesita para aceptar o recuperar los cambios. En el caso de
transacciones que implican a tablas guardadas en un servidor de bases de datos, sin
embargo, Visual dBASE envía los comandos de transacción directamente al servidor,
que se encarga del proceso de la transacción.
Nota No puede definirse una transacción que afecte a una tabla de dBASE o Paradox y a una
tabla SQL, ni pueden anidarse las transacciones. Sin embargo, al acceder a datos SQL, sí
puede definir transacciones distintas para cada base de datos abierta, puesto que cada
servidor mantiene sus propias transacciones. Al ejecutar BEGINTRANS(), COMMIT() o
ROLLBACK, especifique el nombre de la base de datos con la función de transacción
para determinar el servidor de bases de datos afectado.

340 Guía del programador


pdx_sql.dwp Page 341 Wednesday, August 30, 1995 2:24 PM

El ejemplo siguiente demuestra cómo podría definir una transacción para datos
guardados en un servidor de bases de datos:
ON ERROR DO ProcError && Indica una rutina de proceso de errores
OPEN DATABASE Conta && Abre una base de datos
USE :Conta:MITABLA && Abre una tabla de la base de datos
IF BEGINTRANS("Conta", 0) && Comienza una transacción de servidor
REPLACE ALL SALARIO WITH SALARIO * Inflacion && Intenta actualizar todos los registros
IF .NOT. COMMIT("Conta") && Acepta los cambios si no ocurre
&& ningún error
* Error al escribir
ƒ && Incorpora opciones para el caso en
&& que la transacción falle
ENDIF
ENDIF
ƒ
PROCEDURE ProcError && Define un procedimiento para
&& controlar los errores
IF .NOT. ROLLBACK("Conta") && Intenta deshacer los cambios
* Fallo al intentar deshacer los cambios
IF DBERROR() <> 0 && Comprueba si el error es del servidor
* Procesar error BDE
ƒ
IF SQLERROR() <> 0 && Comprueba si el error es del servidor SQL
* Procesar error SQL
ENDIF
ENDIF
ENDIF
RETURN

Capítulo 23, Uso de tablas que no son de dBASE 341


pdx_sql.dwp Page 342 Wednesday, August 30, 1995 2:24 PM

Uso de los índices


Visual dBASE mantiene índices para las tablas de Paradox en el formato adecuado al
tipo de la tabla. (Para las tablas SQL, los índices son mantenidos por el servidor de bases
de datos a través del cual se accede a una tabla.) Cuando se utilizan comandos y
funciones de indexación en una tabla de Paradox, Visual dBASE realiza las operaciones
utilizando los archivos de índice de Paradox.
Las tablas de Paradox utilizan estos tipos de archivos de índice:
• Los índices principales (.PX), que contienen la clave primaria, son los más comunes.
Una clave primaria es un identificador de registro exclusivo que opera de forma muy
similar a un número de registro de dBASE.
Los índices principales sólo pueden crearse por el primer o primeros campos de la
estructura de una tabla. Quizá tenga que reestructurar una tabla para mover los
campos a las posiciones correctas necesarias para crear un índice principal. Utilice la
palabra clave PRIMARY al crear un índice principal.
• Los índices secundarios monocampo (.Xnn e .Ynn) se basan en un solo campo. Al
crear un índice secundario por un solo campo, el nombre de etiqueta debe ser igual
que el del campo (para índices mantenidos con distinción de mayúsculas y
minúsculas). Así, puede omitir el nombre de la etiqueta al crear el índice.
• Los índices secundarios compuestos (.XGn e .YGn) se basan en dos o más campos.
Para más información sobre los índices de Paradox, consulte sus manuales.
Si no está familiarizado con la terminología de los índices (como índice de producción,
etiqueta de índice, índice maestro), consulte el Capítulo 2 de la Guía del usuario o el glosario
de la ayuda en línea.
Visual dBASE trata los índices de Paradox como etiquetas de índice de un archivo de
índice de producción de dBASE. Por ejemplo, todos los índices de Paradox se abren
automáticamente siempre que se abre la tabla y permanecen abiertos hasta que se cierra
la tabla. Si la tabla tiene un índice principal, se convierte en el índice maestro; de lo
contrario, los registros están en el orden natural.
Haga referencia a los índices secundarios de Paradox como nombres de etiqueta. Por
ejemplo, utilice SET ORDER TO (en lugar de SET INDEX TO) para especificar el índice
maestro. Para seleccionar el índice principal como índice maestro, utilice SET ORDER
TO sin especificar un nombre de índice.
En el ejemplo siguiente, la tabla Facturas tiene un índice principal (Num_Fac) y un
índice secundario (Cod_Elem):
USE FACTURAS TYPE PARADOX && La tabla está ordenada por Num_Fac
SET ORDER TO Cod_Elem && La tabla está ordenada por Cod_Elem
SET ORDER TO && La tabla está ordenada por Num_Fac
Al acceder a una tabla SQL, especifique el nombre de un índice (mediante SET ORDER
o USE...ORDER TAG) que controle el orden en que se recuperan los registros de la tabla.
Sin embargo, no es posible cerrar un índice de Paradox ni de SQL.

342 Guía del programador


pdx_sql.dwp Page 343 Wednesday, August 30, 1995 2:24 PM

Diferencias en las claves de índice


Los índices de Paradox pueden basarse en un solo campo o en una lista de campos. Una
clave que consiste en una lista de campos se denomina clave compuesta. Para especificar
una clave compuesta, separe los nombres de los campos con comas. Las expresiones no
están permitidas en las claves de Paradox.
USE CLIENTE.DB
INDEX ON CIUDAD PRIMARY && Crea un índice de clave simple
INDEX ON APELLIDOS, NOMBRE TAG Nombres && Crea un índice de clave compuesta
Para realizar búsquedas por índices de clave compuesta en las tablas de Paradox, utilice
sólo SEEK y SET KEY TO. Otros comandos y funciones de búsqueda, como FIND,
SEEK(), LOOKUP() y KEYMATCH(), no aceptan los argumentos de clave compuesta.
USE CLIENTE.DB ORDER Nombres && Abre la tabla con un índice compuesto
SEEK 'Pedro', 'José' && Busca con una clave compuesta de 2 campos
Otras diferencias entre los índices de dBASE y los definidos para las tablas de Paradox y
SQL son las siguientes:
• Los índices de Paradox no permiten las claves condicionales ni las descendentes. Las
opciones FOR y DESCENDING de INDEX y las funciones FOR() y DESCENDING()
devuelven errores para los índices de Paradox.
• En las tablas SQL, los tipos de índices que puede crear (utilizando el comando
INDEX de dBASE) dependen de las posibilidades del servidor de bases de datos
subyacente. Los servidores suelen permitir crear índices por un solo campo o por una
lista de campos, similar a como lo hace Paradox. La mayoría de servidores de bases
de datos disponibles hoy día permiten crear índices ascendentes y descendentes,
pero no índices condicionales.
• Los índices principales de Paradox imponen automáticamente valores de clave
exclusivos. (Los índices exclusivos de los servidores de bases de datos cumplen la
misma función.) Es decir, si ha creado un índice por Apellido y un registro contiene el
Apellido “Tapia”, no es posible añadir otro registro con “Tapia” en el campo
Apellido. La creación de una clave primaria compuesta (con más de un campo)
puede utilizarse para definir los criterios que desea que impongan los valores
exclusivos, por ejemplo, la indexación por Nombre y Apellido.
La imposición de una clave exclusiva también se aplica a los registros vacíos. Una
tabla indexada no puede contener más de un registro vacío porque provocaría un
valor de clave duplicado.
• Tanto los índices principales de Paradox como los exclusivos de SQL son diferentes
de los índices de dBASE creados con la opción UNIQUE. Esta opción simplemente
excluye los valores de clave duplicados de los índices de dBASE; no impide que se
añadan registros que provoquen valores de clave duplicados. La opción UNIQUE no
puede utilizarse con las tablas de Paradox. En el caso de las tablas SQL, la opción
INDEX...UNIQUE crea un índice exclusivo que sí impide la introducción de valores
de clave duplicados. Con las tablas de Paradox, UNIQUE() siempre devuelve .T. para
los índices principales y .F. para los secundarios. SET UNIQUE no tiene efecto alguno
sobre los índices de Paradox.

Capítulo 23, Uso de tablas que no son de dBASE 343


pdx_sql.dwp Page 344 Wednesday, August 30, 1995 2:24 PM

Los programas de dBASE suelen utilizar APPEND BLANK y REPLACE para añadir un
registro vacío en una tabla y rellenar los campos. Para evitar la creación de claves vacías
duplicadas en las tablas de Paradox o SQL, utilice APPEND AUTOMEM, que añade un
nuevo registro y simultáneamente rellena los campos con valores tomados de variables
automem creadas con anterioridad. Para más información sobre el uso de estos
comandos, consulte la Referencia del lenguaje.

Diferencias en seguridad
Visual dBASE permite el cifrado de tablas de dBASE mediante el sistema de seguridad
Protect. Protect también proporciona la seguridad por contraseña a niveles de
aplicación, de tabla y de campo. Con la excepción de la seguridad por contraseña de
aplicación, el sistema Protect se aplica sólo a las tablas de dBASE. Para más información
sobre el sistema de seguridad Protect de Visual dBASE, consulte la Guía del usuario.
Las tablas de Paradox incorporan un soporte de seguridad por contraseña.
Visual dBASE admite esta capacidad y requiere que se proporcione la contraseña
principal de una tabla de Paradox para poder tener acceso a los datos.
Las tablas SQL utilizan el sistema de seguridad del servidor de bases de datos.
Los nombres y las contraseñas de conexión necesarios deben proporcionarse al trabajar
con datos de un servidor SQL.

Ejecución de sentencias SQL


Acceda a una tabla SQL empleando los mismos comandos y funciones de dBASE que
utiliza para acceder a las tablas de dBASE y Paradox. Normalmente, elige una base de
datos y entonces abre una tabla con el comando USE antes de mostrar o realizar otras
operaciones en los datos.
Al abrir una tabla SQL con USE, Visual dBASE accede al servidor y, entonces, ejecuta
una sentencia SQL para recuperar registros. Los datos recuperados se guardan en una
memoria intermedia local mantenida por Visual dBASE que le permite mostrar y editar
los datos.
Una forma de mejorar el rendimiento, si no necesita editar los datos en el servidor, es
escribir sentencias SQL. Así, en lugar de abrir una tabla con USE, puede enviar
sentencias SQL directamente al servidor de bases de datos.
Nota También puede utilizar SQL para acceder a los datos de las tablas de dBASE y Paradox.
En ese caso, especifique sentencias SQL utilizando la sintaxis SQL de Visual dBASE, que
sigue las especificaciones ANSI.

344 Guía del programador


pdx_sql.dwp Page 345 Wednesday, August 30, 1995 2:24 PM

Enviando una sentencia SQL directamente al servidor, puede realizar cualquier


operación que permita ese servidor al que accede y consultar varias tablas o especificar
condiciones que limiten la cantidad de datos recuperados. Por ejemplo, podría ejecutar
las sentencias siguientes:
SET DATABASE TO Empresa
SELECT Empresa, Ciudad FROM Empresa WHERE Provincia = "M" '
LIST
En este ejemplo, la sentencia SELECT recupera datos de una tabla SQL y los almacena
en un área de trabajo de dBASE. A continuación, puede ver esta “instantánea” de datos
o utilizarla, por ejemplo, para crear un informe de Visual dBASE.

Resumen del soporte de tablas de Paradox y SQL


La Tabla 23.1 resume el soporte en Visual dBASE de las tablas de Paradox y SQL.
Tabla 23.1 Resumen de cómo gestiona Visual dBASE las tablas de Paradox y SQL
En tablas de
Paradox y SQL Esto afecta a Por tanto, Visual dBASE
Nombres Los nombres de Cualquier sentencia que utilice Proporciona los dos puntos ( : ) como
de campo campo pueden un nombre de campo en una delimitador para nombres de campos que no
contener espacios y expresión siguen las normas de nombres de dBASE.
otros caracteres no
permitidos en los
nombres de tabla de
dBASE.
Números Las tablas no tienen LIST / DISPLAY No muestra una columna de número de
de números de registro registro para las tablas de Paradox y SQL.
registro fijos. RECNO() Devuelve un número de registro para las
tablas de dBASE y un indicador para las tablas
de Paradox y SQL.
BOOKMARK() Devuelve un indicador para todos los tipos de
tabla.
GO / GOTO, LOCK(), RLOCK() Acepta indicadores para las tablas de Paradox
Palabra clave RECORD de y SQL; acepta números de registro o
comandos que tienen <ámbito> indicadores para las tablas de dBASE.
INSERT sin índice principal Para las tablas SQL, el comportamiento
depende del servidor.
INSERT con un índice principal Igual que APPEND. El registro se inserta en el
índice, pero se añada al final de la tabla.
Borrado Los registros DELETE Borra permanentemente registros de la tabla.
de borrados se eliminan DELETED() Devuelve siempre .F.
registros inmediatamente de
la tabla y no pueden RECALL, PACK Devuelve un error.
recuperarse. SET DELETED No tiene ningún efecto.

Capítulo 23, Uso de tablas que no son de dBASE 345


pdx_sql.dwp Page 346 Wednesday, August 30, 1995 2:24 PM

Tabla 23.1 Resumen de cómo gestiona Visual dBASE las tablas de Paradox y SQL (continuación)
En tablas de
Paradox y SQL Esto afecta a Por tanto, Visual dBASE
Archivos Todos los índices USE Abre automáticamente todos los índices
de índice asociados se abren al asociados (para Paradox, sea cual sea la
abrir la tabla y etiqueta “mantenida” de Paradox) y los
permanecen mantiene abiertos hasta que se cierra la tabla.
abiertos hasta que
se cierra la tabla.
Haga referencia a los SET ORDER TO Especifica el índice maestro de la misma forma
nombres del índice, USE...ORDER TAG que SET INDEX TO hace para una tabla de
no a los archivos de <nombre etiqueta> dBASE. Es necesario usar el nombre de índice
índice. como etiqueta; es decir, no es posible utilizar
SET ORDER TO <número índice>.
Opción PRIMARY de Proporciona la opción PRIMARY para hacer
INDEX referencia a los índices principales de las tablas
de Paradox. Haga referencia a los índices
secundarios por su nombre de índice.
SET INDEX TO Devuelve un error en cada caso. La referencia
Opciones INDEX y OF de USE a todos los índices de tablas de Paradox y SQL
se hace como etiquetas.
SET ORDER TO <número índice>
COPY TAG
COPY INDEXES
Opción OF de DELETE TAG
Claves de Las claves pueden INDEX Devuelve un error cuando una clave es una
índice ser un solo campo o Creación de etiquetas con expresión. Para índices compuestos, utilice
una lista de campos una coma para separar los nombres de los
(denominada clave CREATE campos.
compuesta). No se MODIFY STRUCTURE
permiten SEEK, SET KEY TO Acepta las listas de clave compuesta.
expresiones. FIND, SEEK(), LOOKUP(), No permite las listas de clave compuesta.
KEYMATCH() Utilice SEEK y SET KEY TO para buscar por
índices de clave compuesta.
No se permiten las Opciones FOR y DESCENDING Devuelve un error para las tablas que no
claves condicionales de INDEX permiten las claves condicionales o
y descendentes en descendentes.
las tablas de FOR(), DESCENDING() Devuelve una cadena vacía para FOR();
Paradox. (Los DESCENDING() devuelve .F. para las tablas
índices que no permiten los índices descendentes.
descendentes sí
suelen permitirse en
las tablas SQL.)
Los índices Opción UNIQUE de INDEX Devuelve un error para tablas de Paradox.
principales Para las tablas SQL, creará un índice exclusivo;
imponen los valores de clave exclusivos se imponen
automáticamente automáticamente.
los valores de clave UNIQUE() Devuelve siempre .T. para los índices
exclusivos. Los principales y .F. para los secundarios.
secundarios no
pueden imponer los SET UNIQUE No tiene ningún efecto.
valores de clave APPEND, EDIT, BROWSE Muestra una advertencia (APPEND, EDIT,
exclusivos. (Para las BROWSE) o un error (APPEND BLANK,
tablas SQL, los APPEND BLANK, REPLACE
REPLACE) y no añade registros si provocan
índices exclusivos un valor de clave duplicado. Esto incluye
imponen valores de registros vacíos; las tablas no pueden tener
clave exclusivos.) más de un registro vacío si hay un índice
principal o exclusivo.

346 Guía del programador


vdbsql.dwp Page 347 Wednesday, August 30, 1995 3:12 PM

Capítulo

24
Uso de SQL
Capítulo 24

Visual dBASE permite el uso de comandos de dBASE o SQL con tablas de servidores de
dBASE, Paradox o SQL. La sintaxis de SQL utilizada dentro de un programa dBASE se
denomina SQL incrustado. Cuando se utiliza SQL con tablas de dBASE o Paradox, se
denomina SQL local. Cuando se emplea con tablas de servidor, se denomina SQL
interactiva (los comandos de SQL se pasan de dBASE al servidor de base de datos, donde
realmente se ejecutan). Visual dBASE también permite la declaración y llamada de
procedimientos almacenados en servidores de base de datos.
Para obtener información sobre cómo trabajar con tablas de SQL, consulte el Capítulo
Capítulo 23. Para obtener una descripción detallada de la función SQLEXEC( ) y las
sentencias de SQL que puede utilizar en SQL local, consulte la Referencia del lenguaje.
Nota Para que pueda acceder a un servidor remoto de SQL es preciso que adquiera e instale
SQL Link de Borland y configure un alias de servidor remoto mediante el Database
Engine de Borland. Para más información, consulte la Guía del usuario de SQL Link.

Definición de SQL
SQL es un descendiente de SEQUEL (Structured English QUEry Language), que IBM
diseñó hace más de veinte años. SQL se creó como lenguaje para construir sistemas de
gestión de bases de datos relacionales (RDBMS) en cualquier plataforma de hardware. El
primer RDBMS comercial que utilizó SQL apareció en 1981, y SQL es ahora el lenguaje
estándar para consultas de red entre diferentes plataformas de hardware y software.
SQL es en realidad un sublenguaje diseñado para incrustarse en un lenguaje de
programación de aplicaciones. Es tan flexible que pueden emplearse como lenguaje de
definición de datos (DDL) y como lenguaje de manipulación de datos (DML).
Con frecuencia, una sola sentencia SQL (comando) puede sustituir a varios comandos de
definición, manipulación de datos e indexación de Visual dBASE.

Capítulo 24, Uso de SQL 347


vdbsql.dwp Page 348 Wednesday, August 30, 1995 3:12 PM

Nota Hay muchas fuentes externas para obtener información adicional sobre SQL y RDBMS.
Un ejemplo es An Introduction to Database Systems de C.J. Date (Addison-Wesley,
Reading, Massachusetts, 1983).

Terminología SQL
La tabla siguiente muestra los términos usados en dBASE y en SQL para describir
aspectos de las bases de datos relacionales.
Tabla 24.1 Terminología básica de SQL
SQL Visual dBASE Definición
Base de datos Base de datos Conjunto de información relacionada que se ha organizado para
un fin específico.
En dBASE, una base de datos reúne un conjunto de una o más
tablas que contienen y clasifican información, además de
archivos relacionados como índices y archivos memo.
En SQL, una base de datos agrupa un solo archivo grande que
alberga todos los datos de la base, y un catálogo del sistema que
contiene información descriptiva de la base de datos (a veces se
denomina diccionario de datos).
Tabla Tabla Estructura de filas (registros) y columnas (campos) que contiene
información.
Una base de datos SQL muestra datos en formato de tabla, igual
que un archivo de base de datos dBASE. Aunque no contiene
archivos de tabla individuales, designa ciertos datos como tablas,
y le permite darles un nombre, como si fueran tablas físicamente
aparte al estilo de dBASE.
Fila Registro Grupo de columnas (campos) de una tabla que contienen
(dirección horizontal) información relacionada acerca de un solo registro.
En dBASE (y otras bases de datos para PC), los registros están
numerados y tienen un orden explícito.
En las bases de datos SQL, no se numeran ni ordenan las filas.
Sin embargo, pueden agruparse, ordenarse y mostrarse como si
estuvieran numeradas (consulte “Uso de la sentencia SELECT de
SQL” en la página 355).
Columna Campo Categoría de información (columna) de una tabla que se incluye
(dirección vertical) para todos los registros de la tabla.
Vista Vista Una vista es un subconjunto de las filas y columnas de una o más
tablas. En ocasiones, se denomina tabla virtual porque en
realidad no contiene datos, sino que proporciona una ventana a
través de la cual pueden verse los datos de las tablas subyacentes
o de base en que se ha definido la vista. Una vista actualizable es
similar a una vista normal, excepto en que puede utilizarla para
actualizar e insertar datos en las tablas de base de la vista.

348 Guía del programador


vdbsql.dwp Page 349 Wednesday, August 30, 1995 3:12 PM

Acceso a datos orientado a conjuntos y no procedimental


Una sentencia de SQL sirve para especificar lo que se desea hacer con los datos de una
sola tabla, igual que se emplearía un comando de Visual dBASE para especificar una
operación sobre una sola base de datos. Sin embargo, las sentencias de SQL tienen otras
posibilidades:
• Las sentencias de SQL pueden dirigir operaciones sobre más de una tabla a la vez.
• Las sentencias de SQL son no procedimentales. Especifican la operación que se va a
realizar en los datos y no el procedimiento para acceder a ellos. El servidor de SQL es
el que determina el mejor procedimiento para realizar la operación.
• Las sentencias de SQL están orientadas a conjuntos. Normalmente operan sobre
conjuntos de registros, no sobre registros individuales. Mientras que en dBASE se
introducen comandos para abrir archivos de base de datos en áreas de trabajo y se
desplazan los punteros de registro dentro de cada archivo abierto para acceder a los
registros individuales, en SQL se introduce un solo comando para especificar el
conjunto de datos que se desea recuperar de una o más tablas. Para insertar,
actualizar y borrar datos, se utiliza un planteamiento similar orientado a conjuntos.
Este planteamiento orientado a conjuntos y no procedimental es diferente del de otros
lenguajes de base de datos (incluido Visual dBASE), que requieren el conocimiento de
cómo y dónde están almacenados los datos en el ordenador antes de poder utilizar
comandos para llegar a ellos, y sólo permiten el acceso a un registro cada vez.

Transacciones en SQL
Los servidores de bases de datos SQL gestionan las peticiones en unidades de trabajo
lógicas a las que se denomina transacciones. Una transacción es un grupo de operaciones
relacionadas que deben realizarse todas satisfactoriamente antes de que el RDBMS
finalice los cambios en la base de datos. El proceso de transacciones en los servidores de
bases de datos garantiza que las peticiones de proceso sean adecuadas al estado actual
de los datos.
En SQL, es posible terminar todas las transacciones explícitamente mediante una
sentencia que acepte o descarte los cambios. Una vez confirmado que no se han
producido errores durante la transacción, es posible terminarla con una sentencia
COMMIT. Entonces, la base de datos cambia para reflejar las operaciones que se acaban
de realizar. Si se produce un error, pueden abandonarse los cambios con una sentencia
ROLLBACK.

Capítulo 24, Uso de SQL 349


vdbsql.dwp Page 350 Wednesday, August 30, 1995 3:12 PM

SQL incrustado y SQLExec( )


Visual dBASE proporciona dos formas de usar SQL dentro de un programa de dBASE:
• Utilizar simplemente la sentencia de SQL dentro del programa como cualquier otra
sentencia de dBASE. Por ejemplo:
USE Animales && Comando de dBASE
SELECT * FROM ANIMALES WHERE NOMBRE = “BOA” && Comando de SQL
LIST && Comando de dBASE
CLEAR ALL && Comando de dBASE
• Utilizar la función SQLExec( ) para ejecutar un comando de SQL y devolver un
código de error. Por ejemplo:
USE Animales
nCodigoError = SQLExec(“SELECT * FROM ANIMALES WHERE NOMBRE = “BOA”)
IF nCodigoError = 0
LIST
ENDIF
CLEAR ALL
En general, el uso de comandos de SQL sin la función SQLExec( ) es más eficaz y debería
ser la sintaxis preferida. Hay dos situaciones en que una función SQLExec( ) es
necesaria:
• Cuando no es seguro el éxito del comando y se necesita comprobar un código de
error antes de continuar.
• Cuando se ejecuta un comando de SQL en un servidor de base de datos y se necesita
utilizar una sintaxis específica de ese servidor que no puede analizarse en dBASE.
Consulte la sintaxis permitida en la sección siguiente.

350 Guía del programador


vdbsql.dwp Page 351 Wednesday, August 30, 1995 3:12 PM

Sintaxis de SQL permitida


1
La Tabla 24.2 resume la sintaxis de SQL que está permitida en Visual dBASE. Esta
sintaxis, que es un subconjunto de SQL-92 (la norma SQL genérica de 1992), está
permitida para SQL interactiva y, con algunas excepciones, para SQL local.
Si el servidor de base de datos requiere otra sintaxis, o sintaxis adicional a la enumerada
en la Tabla 24.2, puede enviarla al servidor mediante la función SQLExec( ).
Para detalles sobre la sintaxis permitida por SQL local, busque “SQL local” en la ayuda
en línea o consulte el 2Capítulo 9 de la Referencia del lenguaje. Para detalles sobre la
sintaxis de SQL interactiva, consulte la documentación del servidor de base de datos.
Tabla 24.2 Sintaxis de SQL permitida por Visual dBASE
Sentencia Sintaxis
ALTER TABLE ALTER TABLE <nombre tabla>
ADD [COLUMN] <definición columna> [, <definición columna>...] |
DROP [COLUMN] <definición columna> [, <definición columna>...]
CREATE INDEX CREATE INDEX <nombre índice> ON <nombre tabla>
(<nombre columna> [,<nombre columna>...])
CREATE TABLE CREATE TABLE <nombre tabla> (<definición columna> [,<definición columna>...])
CREATE VIEW1 CREATE VIEW <nombre tabla> [(<lista columnas>)]
AS <sentencia SELECT> [WITH [CASCADED | LOCAL] CHECK OPTION]
DELETE FROM DELETE FROM <nombre tabla> [WHERE <condición búsqueda>]
DROP INDEX DROP INDEX <nombre índice>
DROP TABLE DROP TABLE <nombre tabla>
DROP VIEW DROP VIEW <nombre vista>
GRANT1 GRANT ALL [PRIVILEGES] | <lista privilegios> ON <lista tablas> TO PUBLIC |
<lista usuarios> [WITH GRANT OPTION]
INSERT INTO INSERT INTO <nombre tabla> [(<lista columnas>)]
VALUES (<lista valores>) | SELECT <comando>
REVOKE1 REVOKE <ALL [PRIVILEGES]> | <lista privilegios> ON <lista tablas>
FROM PUBLIC | <lista usuarios>
SELECT SELECT <cláusula columna seleccionada> FROM <lista tablas> [WHERE <condición
búsqueda>]
[GROUP BY <referencia a columna> [,<referencia a columna>...]
[HAVING <condición búsqueda>] [UNION <comando SELECT>]
[ORDER BY <lista columnas> | FOR UPDATE OF <lista columnas>]
[SAVE TO <nombrearchivo> | ALIAS <nombre alias>]

1. TOM L: This table reference needs to be soft coded. And there are a lot of hard-coded table references in
this section.
2. TOM L: Please make this a cross reference

Capítulo 24, Uso de SQL 351


vdbsql.dwp Page 352 Wednesday, August 30, 1995 3:12 PM

Tabla 24.2 Sintaxis de SQL permitida por Visual dBASE (continuación)


Sentencia Sintaxis
SET TRANSACTION2 SET TRANSACTION [NAME <transacción>]
[READ WRITE | READ ONLY]
[WAIT | NO WAIT]
[[ISOLATION LEVEL] {SNAPSHOT [TABLE STABILITY]
| READ COMMITTED [[NO] RECORD_VERSION]}]
[RESERVING <cláusula Reserving>
| USING <identificadordb> [, <identificadordb> ...]]
UPDATE UPDATE <nombre tabla>
SET <nombre columna> = <expresión> [,<nombre columna> = <expresión>]
[WHERE <condición búsqueda>]
1. No disponible en SQL local
2. Disponible sólo para InterBase

Añadidos, operadores y funciones


El comando SELECT de SQL permite varios añadidos, operadores y funciones, como se
muestra en la Tabla 24.2.
Tabla 24.3 Características adicionales de SELECT de SQL de Visual dBASE
Categoría Características permitidas
Añadidos SUM( ), AVG( ), MIN( ), MAX( ), COUNT( )
Operadores +, -, *, /, =, <>, IS NULL
Funciones de SUBSTRING( ), UPPER( ), LOWER( ),
cadena TRIM( )

352 Guía del programador


vdbsql.dwp Page 353 Wednesday, August 30, 1995 3:12 PM

Variables de memoria
Las variables de memoria de dBASE pueden utilizarse dentro de los comandos de SQL.
Incluya un signo de dos puntos (“:”) antes de los nombres de las variables de memoria,
como en el ejemplo siguiente:
x = "Roberto"
SELECT * FROM clientes WHERE nombre = :x

Diagnosis de errores SQL


Si un comando de SQL no produce el resultado correcto, hay varios lugares que puede
comprobar desde Visual dBASE para intentar determinar la causa del problema.
En primer lugar, si utiliza el comando de SQL por sí solo, intente ejecutar el mismo
comando con una función de SQLExec( ). Compruebe el código de error cuando vuelve
la función para ver si comunica algún error.
A continuación, pruebe a ejecutar alguna o todas las funciones enumeradas en la Tabla
24.3 para ver cuáles son los errores específicos que se obtienen del servidor y/o el BDE.
Tabla 24.4 Funciones de diagnóstico para comprobar errores del servidor o de BDE
Función Descripción
DBError( ) Devuelve el número del último error de BDE
DBMessage( ) Devuelve el último mensaje de error de BDE
SQLError( ) Devuelve el número del último error del
servidor de base de datos
SQLMessage( ) Devuelve el último mensaje de error del
servidor de base de datos

Capítulo 24, Uso de SQL 353


vdbsql.dwp Page 354 Wednesday, August 30, 1995 3:12 PM

SQL incrustado y servidores SQL


SQL incrustado (que se abrevia como ESQL) permite que los programadores en dBASE
optimicen el código de dBASE para su uso con datos de SQL accediendo a la potencia
del servidor de SQL para operaciones (como actualizaciones) que son más rápidas en el
mundo de SQL orientado a conjuntos.

Requisitos previos
Para cualquier operación de ESQL que accede a datos a través de un servidor de SQL,
asegúrese de:
• Abrir explícitamente la base de datos de destino. La conexión de base de datos SQL
Link supone que el destino de cualquier transacción SQL es la base de datos de SQL
especificada en el alias BDE. Si el objeto de la transacción ESQL es una base de datos
local de dBASE o Paradox, utilice OPEN DATABASE para abrirla explícitamente.
Nota • Si los comandos tendrán acceso a tablas de una sola base de datos abierta, puede
indicarla con SET DATABASE. Sin embargo, si los comandos harán referencia a
tablas de más de una base de datos, no utilice SET DATABASE; por el contrario,
cualifique los nombres de todas las tablas incluyendo el nombre de su base de datos
mediante el formato :NOMBREBASEDATOS:NOMBRETABLA (o :NOMBREALIAS:NOMBRETABLA). Si no
lo hace así, dBASE supone que todas las tablas mencionadas son del tipo especificado
en el alias de conexión de base de datos de SQL Link.

La cualificación de los nombres de tabla, según se ha descrito anteriormente, hace


posible la ejecución de uniones heterogéneas (uniones entre diferentes tipos de tablas).
• Definir SQLPASSTHRU MODE para una conexión de base de datos.
SQLPASSTHRU MODE, que se define mediante la Utilidad de configuración BDE,
especifica si la conexión debe compartirse o no, y si los comandos de SQL deben
aceptarse automáticamente o no. En general, es recomendable definir
SQLPASSTHRU MODE como "SHARED NOAUTOCOMMIT" para que el usuario
pueda controlar todas las operaciones de COMMIT y ROLLBACK. Para más
información, consulte la Guía del usuario de SQL Link o la ayuda en línea de la
Utilidad de configuración BDE.

354 Guía del programador


vdbsql.dwp Page 355 Wednesday, August 30, 1995 3:12 PM

Uso de la sentencia SELECT de SQL


La piedra angular de las operaciones de recuperación de datos en SQL es el comando
SELECT.
El comando SELECT se utiliza para recuperar datos en SQL interactivo e incrustado.
Con un solo comando SELECT, es posible recuperar cualquier conjunto de datos de una
o más tablas de SQL.
Esta sección describe cómo utilizar la sentencia SELECT. La mayoría de las cláusulas
descritas, pero no todas, están disponibles en SQL local. Para detalles sobre la sintaxis de
la sentencia SELECT para SQL local, busque “SQL local” en la ayuda en línea o consulte
el 3Capítulo 9 de la Referencia del lenguaje.

Sintaxis de SELECT
La sintaxis del comando SELECT, que se muestra a continuación, incluye diversas
cláusulas. Con la excepción de SELECT y FROM, la mayoría de las cláusulas son
opcionales. Cuando utilice más de una en un comando SELECT, combínelas en el orden
mostrado en la sintaxis.
SELECT <cláusula>
[INTO <cláusula>]
FROM <cláusula>
[WHERE <cláusula>]
[GROUP BY <cláusula>]
[HAVING <cláusula>]
[UNION subconsulta]...
[ORDER BY <cláusula>/FOR UPDATE OF <cláusula>]
[SAVE TO <cláusula>];

Consultas simples
La forma más simple del comando SELECT incluye sólo las cláusulas SELECT y FROM.
Por ejemplo:
SELECT Compania, Nombre, Apellido
FROM Client;
La cláusula SELECT especifica los nombres de las columnas que deben aparecer en el
resultado, y su orden. La cláusula FROM identifica la tabla que contiene las columnas.

3. TOM L: Please make this a soft cross reference.

Capítulo 24, Uso de SQL 355


vdbsql.dwp Page 356 Wednesday, August 30, 1995 3:12 PM

Se muestra el resultado siguiente:


COMPANIA NOMBRE APELLIDO
C. General de Servicios Clara Estévez
Muebles Rodríguez Mariano Rodríguez
Muebles El Detalle Alfonso Cuevas
La Oficina Esther Barahona
ƒ ƒ ƒ
Muebles Tersa Benito Encinas
Almacén General de Muebles Joaquín Carmona

Selección de todas las columnas


Utilice el símbolo de asterisco (*) para seleccionar todas las columnas de una tabla. Por
ejemplo, para mostrar la información de todas las columnas de la tabla Vender,
introduzca:
SELECT *
FROM Vender;

SELECT con DISTINCT


Puesto que cualquier artículo de un inventario puede estar inventariado en tres lugares
diferentes —Barcelona y Sevilla—, el resultado de SELECT * FROM Inventar podría contener
hasta tres filas para el mismo artículo. Para mostrar cada número de artículo una sola
vez, utilice la palabra clave DISTINCT.
Nota Las reglas siguientes se aplican al uso de la palabra clave DISTINCT en un comando
SELECT. Esto se aplica a la consulta externa y a las internas.
• DISTINCT puede utilizarse una sola vez en cualquier cláusula SELECT de un
comando SELECT.
• DISTINCT puede aplicarse sólo a la primera columna mencionada en una cláusula
SELECT.
• DISTINCT puede utilizarse en una cláusula HAVING que opere sobre los valores de
cualquier columna de la tabla (por ejemplo: SELECT Localidad, MAX(Salario) FROM
Vender GROUP BY Localidad HAVING MAX(DISTINCT Salario) > 500000;).
Por ejemplo, introduzca:
SELECT DISTINCT Num_Pieza, Descripc
FROM Inventar;

NUM_PIEZA DESCRIPC
001001 ESTACION T., OFICINA ELECTRON.
001002 CONJUNTO MOBILIARIO OFICINA
001005 CONJUNTO MOBILIARIO EJECUTIVO
001007 SOPORTE MADERA SIMPLE
ƒ ƒ
001033 SILLA REPOSABRAZOS
001038 LAMPARA MOVIL

356 Guía del programador


vdbsql.dwp Page 357 Wednesday, August 30, 1995 3:12 PM

Cláusula WHERE
Para consultar filas específicas, incluya una condición de búsqueda en una cláusula
WHERE.
Para mostrar todos los artículos del inventario de Barcelona, introduzca:
SELECT Num_Pieza, Descripc, Localidad, Disponible
FROM Inventar
WHERE Localidad = "BARCELONA";
La condición de búsqueda de WHERE (Localidad = "BARCELONA") compara el valor
de Localidad en cada fila con el valor "BARCELONA". Si la condición de búsqueda es
verdadera para una fila, ésta se incluye en la tabla resultante.
No es necesario que la tabla resultante incluya la columna utilizada al especificar la
condición WHERE. Por ejemplo:
SELECT Num_Pieza, Descripc, Disponible
FROM Inventar
WHERE Localidad = "BARCELONA";
El resultado incluye las mismas filas, pero omite la columna Localidad.

Capítulo 24, Uso de SQL 357


vdbsql.dwp Page 358 Wednesday, August 30, 1995 3:12 PM

Uso de los operadores de comparación


Los operadores de comparación enumerados en la Tabla 24.5 pueden utilizarse en las
condiciones de búsqueda de WHERE.
Tabla 24.5 Operadores de comparación
Operador Descripción
= Igual a
< Menor que
> Mayor que
<= Menor o igual a
>= Mayor o igual a
<> Distinto de (también está permitido # para compatibilidad con dBASE)
! Negación de los operadores <, > y = (por ejemplo, !< se evalúa como "no
menor que")

Los valores que se comparan deben ser de tipos de datos compatibles. Un valor
(o constante) de carácter debe compararse con otro de carácter, un valor numérico con
otro numérico, y así sucesivamente. Los tipos de datos SMALLINT, INTEGER,
DECIMAL, NUMERIC y FLOAT son compatibles entre sí.
Los valores del tipo carácter deben encerrarse entre comillas o entre corchetes ([]).
Además, se comparan usando su representación ASCII. Así, abc en minúsculas no es
igual que ABC en mayúsculas.
Una columna de tipo lógico puede especificarse en una cláusula WHERE con o sin un
operador de comparación. Por ejemplo, las consultas siguientes son equivalentes:
SELECT * FROM Pedidos WHERE ENVIADO = .T.;
SELECT * FROM Pedidos WHERE Enviado;
Las funciones de dBASE pueden utilizarse para convertir los valores de las condiciones
de búsqueda para su comparación. Por ejemplo, utilice la función UPPER( ) para
convertir los valores de una columna de tipo carácter y la función CTOD( ) para
convertir una cadena de fecha para su comparación con valores de columnas de fecha.

358 Guía del programador


vdbsql.dwp Page 359 Wednesday, August 30, 1995 3:12 PM

Combinación de condiciones
Es posible combinar condiciones de búsqueda en una cláusula WHERE mediante los
operadores lógicos de SQL. Introduzca:
SELECT Num_Pieza, Descripc, Disponible, Localidad
FROM Inventar
WHERE Localidad = "SEVILLA" AND Disponible < 20;

NUM_PIEZA DESCRIPC DISPONIBLE LOCALIDAD


001002 CONJUNTO MOBILIARIO OFICINA 2 SEVILLA
001001 ESTACION T., OFICINA ELECTRON. 3 SEVILLA
El resultado muestra los artículos inventariados en Sevilla que dispone de menos de 20
unidades. El operador lógico AND especifica que las filas mostradas satisfagan ambas
condiciones WHERE.
Los operadores lógicos de SQL se enumeran en la Tabla 24.6, en el orden que se evalúan
en una cláusula WHERE.
Tabla 24.6 Operadores lógicos de SQL
Operador Descripción
NOT Selecciona filas que no cumplen la condición de
búsqueda
AND Ambas condiciones deben ser verdaderas para
cualificar la fila
OR Una o las dos condiciones deben ser verdaderas

Cuando se especifica más de una condición WHERE, cada una se evalúa como
verdadero o falso para cada fila. Entonces, el operador lógico combina las filas paras las
que las condiciones de búsqueda se evalúen como verdaderas, y produce un resultado.
La prioridad de los operadores lógicos determina el orden en que se evalúan las
condiciones WHERE. Si utiliza el mismo operador lógico para combinar condiciones,
éstas se evalúan de izquierda a derecha.

Capítulo 24, Uso de SQL 359


vdbsql.dwp Page 360 Wednesday, August 30, 1995 3:12 PM

Es posible utilizar paréntesis para determinar el orden en que se evalúan las condiciones
de búsqueda. Por ejemplo, suponga que cambia el comando anterior para mostrar los
artículos inventariados en Sevilla o Madrid, excluyendo los que cuestan menos de
$1,000 y con reservas escasas. Para ello, introduzca:
SELECT Num_Pieza, Descripc, Localidad, Disponible, Coste_unit
FROM Inventar
WHERE Localidad = "SEVILLA" OR Localidad = "MADRID"
AND Disponible < 20 AND Coste_unit < 1000;

NUM_PIEZA DESCRIPC LOCALIDAD DISPONIBLE Coste_unit


001002 CONJUNTO MOBILIARIO OFICINA SEVILLA 2 1395.49
001008 ESTACION T., SOPORTE SEVILLA 22 275.66
001015 ESCRIBANIA, ROBLE MADRID 15 745.00
001022 MESA, CASTAÑO MADRID 5 414.95
ƒ ƒ ƒ ƒ ƒ
001032 ARCHIVADOR, 4 CAJONES SEVILLA 71 134.69
001001 ESTACION T., OFICINA ELECTRON. SEVILLA 3 1296.29
001029 ARCHIVADOR, 2 CAJONES SEVILLA 145 89.95
Claramente el resultado no es el esperado porque SQL evalúa primero Localidad =
"MADRID" AND Disponible < 20, luego AND Coste_unit < 1000 y, por último, Localidad = "SEVILLA".
Para obtener el resultado correcto, utilice paréntesis. Introduzca:
SELECT Num_Pieza, Descripc, Localidad, Disponible, Coste_unit
FROM Inventar
WHERE (Localidad = "SEVILLA" OR Localidad = "MADRID")
AND (Disponible < 20 AND Coste_unit < 1000);

NUM_PIEZA DESCRIPC LOCALIDAD DISPONIBLE COSTE_UNIT


001015 ESCRIBANIA, ROBLE MADRID 15 745.00
001022 MESA, CASTAÑO MADRID 5 414.95
En este caso, SQL evalúa primero la expresión Localidad = "SEVILLA" OR Localidad =
"MADRID", luego (Disponible < 20 AND Coste_unit < 1000) y , por último, combina las filas que
son verdaderas para ambos conjuntos de condiciones.

360 Guía del programador


vdbsql.dwp Page 361 Wednesday, August 30, 1995 3:12 PM

Definición y uso de las expresiones


Es posible utilizar expresiones en las cláusulas SELECT, WHERE y HAVING de un
comando SELECT. Una expresión es uno o más valores de datos, en ocasiones unidos
por operadores. Son similares a las utilizadas en los comandos de dBASE.
Por ejemplo, "Sevilla" es una expresión constante y Coste_unit*1000 es una expresión
variable. Cuando se evalúa, una expresión devuelve siempre un solo valor: una
condición (verdadero o falso), un valor numérico o una cadena de caracteres.
Una expresión puede contener los elementos siguientes:
• Nombres de columna
• Operadores aritméticos
• Constantes (numéricas, lógicas, de fecha y de carácter)
• Variables de memoria (consulte “Variables de memoria” en la página 353)
La Tabla 24.7 muestra los operadores aritméticos que pueden utilizarse con SQL.
Tabla 24.7 Operadores aritméticos
Operador Descripción
** y ^ Exponenciación
+y– Operador unario (+ es el operador por defecto; –
para valores negativos)
*y/ Multiplicación y división
+y– Suma y resta

Es posible utilizar todos estos operadores con valores numéricos. Sin embargo, sólo los
de suma y resta pueden utilizarse con cadenas de caracteres, para la concatenación.

Capítulo 24, Uso de SQL 361


vdbsql.dwp Page 362 Wednesday, August 30, 1995 3:12 PM

Expresiones en la cláusula SELECT


Las expresiones pueden utilizarse en una cláusula SELECT de las formas siguientes:
• Para definir una columna de resultado que describa información de una columna de
resultado adyacente
• Para definir una columna de resultado calculado
Por ejemplo, para mostrar los sueldos anuales a partir de los sueldos mensuales de la
tabla Vender, introduzca:
SELECT Num_vender, Nombre, Apellido, Salario*12, "(Salario anual)"
FROM Vender;

Vender_NO NOMBRE APELLIDO EXP1 EXP2


000001 Ricardo Zambrano 72000 (Salario anual)
000003 Chema Vidal 69360 (Salario anual)
000004 Santiago Colomán 74844 (Salario anual)
000006 Pedro Tomás 70500 (Salario anual)
000008 Daniel Milanés 57504 (Salario anual)
000011 Dolores Mínguez 59124 (Salario anual)
000012 Teodoro Chales 71340 (Salario anual)
000013 Marcos Marín 57624 (Salario anual)
000015 María Rodríguez 65916 (Salario anual)
000016 Nicolás López 62280 (Salario anual)
000019 Carlos Rodríguez 55032 (Salario anual)
000020 Catalina Santos 45396 (Salario anual)
EXP1, que contiene las cifras de los sueldos anuales, es una columna calculada. Esas
cifras se calculan a partir de las cifras de la columna Salario mensual de la tabla Vender
mediante la expresión Salario*12. La expresión "(Salario anual)" sirve para definir una
columna informativa (EXP2) que describe los datos de EXP1.
Observe que las columnas de resultados definidas mediante expresiones tienen
cabeceras EXP numeradas según su ubicación de izquierda a derecha en la cláusula
SELECT.

362 Guía del programador


vdbsql.dwp Page 363 Wednesday, August 30, 1995 3:12 PM

Expresiones en la cláusula WHERE


En una cláusula WHERE, las expresiones sirven para condiciones de búsqueda.
Por ejemplo, para mostrar información sobre los vendedores cuyo sueldo anual es
superior a 5.000000 pts., puede introducir:
SELECT Vender_no, Nombre, Apellido, Salario*12
FROM Vender
WHERE Salario*12 > 5.000.000;

VENDER_NO NOMBRE APELLIDO EXP1


000001 Ricardo Zambrano 72000
000003 Chema Vidal 69360
000004 Santiago Colomán 74844
000006 Pedro Tomás 70500
000008 Daniel Milanés 57504
000011 Dolores Mínguez 59124
000012 Teodoro Chales 71340
000013 Marcos Marín 57624
000015 María Rodríguez 65916
000016 Nicolás López 62280
000019 Carlos Rodríguez 55032
000020 Catalina Santos 45396
Toda la condición WHERE, Salario*12 > 5.000.000, es una expresión compuesta de dos
expresión, Salario*12 y 5.000.000.
Es posible emplear otra expresión, en la forma de variable de memoria, para sustituir la
expresión 50000. Esto permite que el comando SELECT proporcione resultados
diferentes dependiendo del valor asignado a la variable de memoria. Por ejemplo, introduzca:
mSalario = 5.000.000
5.000.000
SELECT Vender_no, Nombre, Apellido, Salario*12
FROM Vender
WHERE Salario*12 > mSalario;
El resultado es el mismo.
Nota La posibilidad de incluir variables de memoria en los comandos SELECT es útil en los
programas SQL.

Funciones agregadas en las expresiones


Es posible utilizar las siguientes funciones agregadas de SQL en las cláusulas SELECT:
• AVG( ) calcula la media de los valores de una columna numérica
• COUNT( ) cuenta el número de filas seleccionadas
• MAX( ) halla el valor máximo de una columna de tipo carácter, fecha o numérico
• MIN( ) halla el valor mínimo de una columna de tipo carácter, fecha o numérico
• SUM( ) suma los valores de una columna numérica

Capítulo 24, Uso de SQL 363


vdbsql.dwp Page 364 Wednesday, August 30, 1995 3:12 PM

Para columnas de tipo carácter, MIN( ) y MAX( ) devuelven los valores ASCII más bajos
(A a Z) y más altos (a a z), respectivamente. Los valores se comparan de izquierda a
derecha.
Nota Las funciones agregadas pueden emplearse sólo en expresiones que direccionen valores
de las columnas. No es posible realizar una operación agregada sobre un valor
constante, una variable de memoria o los valores de una columna de datos lógicos.

Expresiones agregadas en las cláusulas SELECT


En una cláusula SELECT, utilice expresiones agregadas en lugar de nombres de
columna. Las funciones agregadas operan sobre todos los valores seleccionados para
devolver un único valor de resumen. Si la cláusula SELECT contiene nombres de
columna y funciones agregadas, el comando debe incluir una cláusula GROUP BY.
Consulte la sección “Agrupamiento de filas” más adelante en este capítulo.
Dado que una función agregada opera sobre todos los valores de columna
seleccionados para producir un solo valor, no es posible utilizar una expresión agregada
en una cláusula SELECT que contenga una expresión de nombre de columna que
muestre todos los valores seleccionados.
Para contar el número de filas de la tabla Vender, introduzca:
SELECT COUNT(*)
FROM Vender;

COUNT1
12
El símbolo de asterisco (*) especifica que COUNT( ) opere sobre los valores de todas las
columnas de la tabla.
Los encabezados de la tabla resultante de las funciones agregadas utilizan el nombre de
la función más un número que indica su posición de izquierda a derecha en la cláusula
SELECT. Por ejemplo, para utilizar la función SUM( ) para calcular la nómina mensual
total de los vendedores, introduzca:
SELECT SUM(Salario)
FROM Vender;

SUM1
63410
Para averiguar los sueldos más alto, más bajo y medio de los vendedores, introduzca:
SELECT MAX(Salario), MIN(Salario), AVG(Salario)
FROM Vender;

MAX1 MIN2 AVG3


6237 3783 5284.17

364 Guía del programador


vdbsql.dwp Page 365 Wednesday, August 30, 1995 3:12 PM

Es posible utilizar una función agregada con la palabra clave DISTINCT para operar
sólo sobre valores de columna exclusivos. Por tanto, para contar el número de ciudades
en que hay clientes, introduzca:
SELECT COUNT(DISTINCT Ciudad)
FROM Client;

COUNT1
15

Expresiones agregadas cualificadas por una cláusula WHERE


En un comando SELECT que contiene una cláusula WHERE, una función agregada en
la cláusula SELECT se aplica sólo a los valores de columna que estén cualificados por la
condición WHERE.
Para hallar el número de clientes ubicados en Madrid, introduzca:
SELECT COUNT(*)
FROM Client
WHERE Provincia = "M";

COUNT1
5
El comando SELECT recupera las filas cuya columna Provincia contenga "M". Entonces,
la función COUNT( ) cuenta el número de filas recuperadas.

SELECT con predicados BETWEEN, IN y LIKE


Los predicados siguientes pueden utilizarse en una cláusula WHERE para cualificar filas
para su inclusión en un resultado:
• BETWEEN selecciona valores de columna situados dentro de un rango.
• IN selecciona valores de columna que coincidan con uno de una serie de valores.
• LIKE selecciona valores de columna que contengan una cadena de caracteres.
Se llama así a los predicados porque el resultado de SELECT se predica en su operación.

Ordenación del resultado


Hasta ahora, las filas del resultado de SELECT se han presentado en orden natural, aquél
en que se insertaron en la tabla de base. El resultado puede mostrarse por orden
alfabético o numérico de los valores de una o más columnas ; para ello, utilice la cláusula
ORDER BY del comando SELECT.

Ordenación por una sola columna


Empleando ORDER BY, puede presentar un resultado por orden ascendente (de menor
a mayor) o descendente (de mayor a menor).

Capítulo 24, Uso de SQL 365


vdbsql.dwp Page 366 Wednesday, August 30, 1995 3:12 PM

Para mostrar artículos de inventario por orden ascendente de la primera letra de sus
descripciones, introduzca:
SELECT Num_Pieza, Descripc, Localidad, Disponible
FROM Inventar
ORDER BY Descripc ASC;
Consejo Para acelerar la recuperación de los datos, cree índices por las columnas que utilice a
menudo para ordenar el resultado.

Ordenación por más de una columna


Ahora, orden el resultado anterior primero por localidad y luego por descripción. Para
ello, introduzca:
SELECT Num_Pieza, Descripc, Localidad, Disponible
FROM Inventar
ORDER BY Localidad, Descripc;

NUM_PIEZA DESCRIPC LOCALIDAD DISPONIBLE


001009 SILLA AJUSTABLE BARCELONA 124
001031 SILLON EJECUTIVO AJUSTABLE BARCELONA 44
001013 SILLA MODERNA, HIDRAULICA BARCELONA 115
ƒ ƒ ƒ ƒ
001031 SILLON EJECUTIVO AJUSTABLE SEVILLA 79
001013 SILLA MODERNA, HIDRAULICA SEVILLA 35
001025 ESCRITORIO EJECUTIVO, 2-M SEVILLA 63
ƒ ƒ ƒ ƒ
001001 ESTACION T., OFICINA ELECTRON. SEVILLA 3
001008 ESTACION T., SOPORTE SEVILLA 22
001031 SILLON EJECUTIVO AJUSTABLE MADRID 76
ƒ ƒ ƒ ƒ
001019 MESA DE REUNIONES MADRID 12
001022 MESA, CASTAÑO MADRID 5
001007 SOPORTE MADERA SIMPLE MADRID 29
Las filas de artículos de inventario se han ordenado en primer lugar por los valores de
Localidad. Dentro de cada grupo de localidades es, las filas se ordenaron por los valores
de Descripc. Las columnas se separan mediante comas en ORDER BY.

366 Guía del programador


vdbsql.dwp Page 367 Wednesday, August 30, 1995 3:12 PM

En una cláusula ORDER BY, puede hacer referencia a una columna por su posición en la
sentencia SELECT. Por ejemplo, para presentar los artículos por orden ascendente
(orden por defecto) de localidad y por orden descendente de número de artículos en
existencias, introduzca:
SELECT Num_Pieza, Descripc, Localidad, Disponible
FROM Inventar
ORDER BY 3, 4 DESC;

NUM_PIEZA DESCRIPC LOCALIDAD DISPONIBLE


001024 MESA, LAMPARA LATON BARCELONA 140
001009 SILLA AJUSTABLE BARCELONA 124
001013 SILLA MODERNA, HIDRAULICA BARCELONA 115
ƒ ƒ ƒ ƒ
001038 LAMPARA MOVIL SEVILLA 169
001029 ARCHIVADOR, 2 CAJONES SEVILLA 145
001031 SILLON EJECUTIVO AJUSTABLE SEVILLA 79
ƒ ƒ ƒ ƒ
001022 MESA, CASTAÑO MADRID 5
001021 CONJUNTO MOBILIARIO DIRECTOR MADRID 3
001005 CONJUNTO MOBILIARIO EJECUTIVO MADRID 0
Las columnas Localidad y Disponible se indican en la cláusula ORDER BY mediante los
números 3 y 4. Estos números representan las posiciones de izquierda a derecha de estas
columnas en la cláusula SELECT. Siempre que ordene por una columna que se derive
mediante una expresión, como Salario*12 o SUM(Salario), debe emplear un número
para representar la columna en ORDER BY.

ORDER BY con una cláusula WHERE


ORDER BY ordena un resultado cualificado por una condición WHERE.
Para limitar el resultado de Inventar a las filas cuyos artículos estén inventariados en
Barcelona, introduzca:
SELECT Num_Pieza, Descripc, Localidad, Disponible
FROM Inventar
WHERE Localidad = "BARCELONA"
ORDER BY Num_Pieza;

NUM_PIEZA DESCRIPC LOCALIDAD DISPONIBLE


001001 ESTACION T., OFICINA ELECTRON. BARCELONA 2
001005 CONJUNTO MOBILIARIO EJECUTIVO BARCELONA 1
001007 SOPORTE MADERA SIMPLE BARCELONA 35
001009 SILLA AJUSTABLE BARCELONA 124
001013 SILLA MODERNA, HIDRAULICA BARCELONA 115
001024 MESA, LAMPARA LATON BARCELONA 140
001027 ESCRITORIO EJECUTIVO, 3-M BARCELONA 20
001031 SILLON EJECUTIVO AJUSTABLE BARCELONA 44
001032 ARCHIVADOR, 4 CAJONES BARCELONA 15
001033 SILLA, REPOSABRAZOS BARCELONA 20
001038 LAMPARA MOVIL BARCELONA 89

Capítulo 24, Uso de SQL 367


vdbsql.dwp Page 368 Wednesday, August 30, 1995 3:12 PM

Agrupamiento de filas
Las cláusulas GROUP BY y HAVING permiten organizar las filas en grupos sobre los
que realizar operaciones de agregación. Mediante GROUP BY y HAVING, es posible
cambiar el ámbito de una operación agregada de todos los valores de una columna a
sólo los contenidos en el grupo.

Cláusula GROUP BY
La cláusula GROUP BY agrupa un resultado por valores comunes a una o más
columnas. Cada grupo se resume como una sola fila en la tabla de resultado, cuyos
encabezados identifican grupos (G_) o valores añadidos para cada grupo.
Cada columna especificada en la cláusula SELECT debe incluirse también en la cláusula
GROUP BY a menos que se utilice en una operación agregada. Lo contrario también es
cierto. Además, no es posible especificar una columna calculada en una cláusula
GROUP BY.
El ancho total de las columnas especificadas en una cláusula GROUP BY puede tener un
máximo de 100 bytes.
Por ejemplo, para obtener las existencias totales de cada artículo inventariado en todas
las localidades, introduzca:
SELECT Num_Pieza, Descripc, SUM(Disponible)
FROM Inventar
GROUP BY Num_Pieza, Descripc;

G_NUM_PIEZA G_DESCRIPC SUM1


001001 ESTACION T., OFICINA ELECTRON. 5
001002 CONJUNTO MOBILIARIO OFICINA 2
001005 CONJUNTO MOBILIARIO EJECUTIVO 1
ƒ ƒ ƒ
001032 ARCHIVADOR, 4 CAJONES 86
001033 SILLA, REPOSABRAZOS 20
001038 LAMPARA MOVIL 305
La información de Num_Pieza y Descripc se muestra para cada artículo, como si
hubiera introducido SELECT DISTINCT Num_Pieza, Descripc FROM Inventar;. Sin
embargo, GROUP BY le permite mostrar la cantidad inventariada de cada artículo para
las tres localidades. Observe que el resultado se ordena por los valores de Num_Pieza,
la primera columna especificada en la cláusula GROUP BY.
Para contar el número de vendedores en cada localidad, introduzca:
SELECT Localidad, COUNT(*)
FROM Vender
GROUP BY Localidad;

G_LOCALIDAD COUNT1
BARCELONA 3
SEVILLA 5
MADRID 4

368 Guía del programador


vdbsql.dwp Page 369 Wednesday, August 30, 1995 3:12 PM

Utilice una cláusula ORDER BY con una GROUP BY para ordenar un resultado de
forma diferente a lo indicado por el orden por defecto de GROUP BY. Por ejemplo, para
presentar los artículos por cantidad en existencias, en lugar de por número de artículo,
introduzca:
SELECT Num_Pieza, Descripc, SUM(Disponible)
FROM Inventar
GROUP BY Num_Pieza, Descripc
ORDER BY 3;

G_PART NO G_DESCRIPC SUM1


001005 CONJUNTO MOBILIARIO EJECUTIVO 1
001002 CONJUNTO MOBILIARIO OFICINA 2
001021 CONJUNTO MOBILIARIO DIRECTOR 3
001022 MESA, CASTAÑO 5
001001 ESTACION T., OFICINA ELECTRON. 5
001019 MESA DE REUNIONES 12
001015 ESCRIBANIA, ROBLE 15
001033 SILLA, REPOSABRAZOS 20
ƒ ƒ ƒ
Observe que el uso de su número de posición (3) en la cláusula SELECT para identificar
la columna de ORDER BY es obligatorio porque SUM(Disponible) es una columna
derivada mediante una expresión.

Cláusula HAVING
La cláusula HAVING sirve para identificar los grupos que aparecen en el resultado de
GROUP BY. HAVING cualifica el resultado de un grupo de la misma forma que
WHERE cualifica un resultado ordinario —especificando una condición que cada grupo
debe satisfacer—. La condición suele incluir la operación agregada que se especifica en
la cláusula SELECT.
Para mostrar los artículos que cuesten más de 50.000 pts. y cuyas existencias sean
superiores a 10 unidades, introduzca:
SELECT Descripc, Num_Pieza, SUM(Disponible), Coste_unit
FROM Inventar
WHERE Coste_unit > 500
GROUP BY Descripc, Num_Pieza, Coste_unit
HAVING SUM (Disponible) > 10;

G_DESCRIPC G_Num_Pieza SUM1 G_COSTE_UNIT


ESCRIBANIA, ROBLE 001015 15 745
ESCRITORIO EJECUTIVO, 2-M 001025 110 985
ESCRITORIO EJECUTIVO, 3-M 001027 76 1475
MESA DE REUNIONES 001019 12 4250
SOPORTE MADERA SIMPLE 001007 126 736
Nota La cláusula HAVING suele emplearse con la cláusula GROUP BY. Si utiliza HAVING
sin GROUP BY, HAVING opera sobre todo el resultado como si fuera un solo grupo.

Capítulo 24, Uso de SQL 369


vdbsql.dwp Page 370 Wednesday, August 30, 1995 3:12 PM

Cláusula UNION
La cláusula UNION sirve para combinar consultas. UNION combina las tablas de
resultado de dos o más comandos SELECT y elimina las filas duplicadas.
Para mostrar las localidades de ventas que también disponen de existencias, introduzca:
SELECT Localidad
FROM Vender
UNION
SELECT Localidad
FROM Inventar;

LOCALIDAD
BARCELONA
SEVILLA
MADRID
Las reglas siguientes rigen las cláusulas SELECT utilizadas para especificar una unión:
• Cada cláusula SELECT debe tener el mismo número de columnas, y los tipos de
datos de las columnas correspondientes deben coincidir. Sin embargo, no es
necesario que sean iguales los nombres de las columnas correspondientes.
• Los anchos de las columnas de carácter correspondientes deben coincidir.
• Las columnas numéricas correspondientes deben ser del mismo tipo de datos (fijo o
de coma flotante), contener el mismo número de dígitos y, si son decimales, tener el
mismo número de dígitos decimales.
• Las cláusulas SELECT no pueden contener una columna con un tipo de datos
LOGICAL, ni una función o variable de memoria de dBASE.
El resultado de UNION se ordena por los valores de todas las columnas especificadas en
cada cláusula SELECT. Si desea que el resultado se ordene por los valores de una
columna distinta, utilice ORDER BY como última cláusula del comando SELECT.
Utilice un número que indique la posición de izquierda a derecha de la columna
ORDER BY, por ejemplo, ORDER BY 2. Para mostrar los nombres de todas las ciudades
en que haya personal, inventario o clientes, introduzca:
SELECT Ciudad
FROM Client
UNION
SELECT Localidad
FROM Vender
UNION
SELECT Localidad
FROM Inventar;

CIUDAD
Alcorcón
BARCELONA
Badajoz
Barcelona
Cáceres

370 Guía del programador


vdbsql.dwp Page 371 Wednesday, August 30, 1995 3:12 PM

El Ronquillo
Leganés
MADRID
Madrid
Mairena
SEVILLA
Sevilla
Tomares
Torrejón
Torrente
Ultrera
Valencia
Zaragoza
Hay duplicados aparentes en este resultado (por ejemplo, BARCELONA y Barcelona),
lo cual refleja que hay valores de Ciudad (introducidos con mayúscula inicial y
minúsculas) y de Localidad (introducidos en mayúsculas) no tienen la misma
representación ASCII.

Uso de los procedimientos almacenados


La ejecución de procedimientos almacenados se gestiona igual que la de funciones
contenidas en bibliotecas de vínculo dinámico (DLL).

Requisitos previos
Antes de poder ejecutar un procedimiento, debe tener un prototipo declarado mediante
EXTERN.
Al igual que con EXTERN, para disponer de un argumento que el procedimiento pueda
cambiar, debe especificar tipos especiales de argumentos en el prototipo:
• CPTR especifica un argumento que se utilizar para entrada y para salida.
• CPTROUT especifica un argumento que es sólo para salida.
Los procedimientos almacenados están disponibles sólo mientras la base de datos está
abierta. Cualquier comando u operación que la cierre (como CLOSE DATABASE y
CLEAR ALL) también impide la ejecución de procedimientos almacenados.

Capítulo 24, Uso de SQL 371


vdbsql.dwp Page 372 Wednesday, August 30, 1995 3:12 PM

Sintaxis de declaración
EXTERN SQL [<tipo devuelto>] <nombre procedimiento> ( [<lista argumentos>])
[<nombre basedatos>] [ FROM <nombre pa> ]
<nombre procedimiento> es el nombre del procedimiento almacenado que se ejecuta en
dBASE.
<nombre pa> es el nombre real del procedimiento almacenado. Este parámetro es
necesario si el nombre del procedimiento debe cualificarse con un nombre de usuario,
nombres de paquete o direcciones de servidor remoto, debido a que el nombre del
procedimiento almacenado duplica el nombre de una función interna de dBASE.
<nombre basedatos> es el nombre de la base de datos en que reside el procedimiento
almacenado si es diferente de la configuración actual de SET DATABASE TO.
<lista argumentos> enumera los tipos de datos de los argumentos. Utilice CPTR o
CPTROUT para especificar un argumento cuyo valor modifica el procedimiento
almacenado.
<tipo devuelto> especifica el valor devuelto por las funciones almacenadas. Por ejemplo,
si la función almacenada devuelve un entero, el tipo devuelto sería CINT. (En general,
los procedimientos almacenados no devuelven un valor. Por defecto, el <tipo devuelto>
es CVOID.
Una vez declarado, el procedimiento puede ejecutarse por sí solo o igual que cualquier
otra función de dBASE.
Utilice SQLERROR( ) para determinar si se ejecuta con éxito la función/procedimiento
almacenado.

Sintaxis de ejecución
<nombre procedimiento> ( <lista argumentos> )

Ejemplos
El ejemplo siguiente ejecuta el procedimiento almacenado en servidor CalcMediana( ), que
toma el nombre de un cliente como argumento y almacena un valor numérico como segundo
argumento:

OPEN DATABASE remoto1


SET DATABASE TO remoto1
EXTERN SQL CalcMediana( CSTRING, CPTROUT )

mediana = 0
CalcMediana( "Roberto", mediana )
IF SQLERROR() != 0
? "Error del servidor:", SQLMESSAGE()
ELSE
? "Mediana=",mediana
ENDIF

372 Guía del programador


vdbsql.dwp Page 373 Wednesday, August 30, 1995 3:12 PM

El ejemplo siguiente muestra cómo quedaría la declaración si CalcMediana( ) perteneciera al


paquete Matemat:

OPEN DATABASE remoto1


EXTERN SQL CalcMediana( CSTRING,CPTROUT ) remoto1 FROM Matemat.CalcMediana

El ejemplo siguiente muestra cómo quedaría la declaración si CalcMediana( ) es una función


almacenada que toma un nombre de carácter y devuelve la mediana:

OPEN DATABASE remoto1


EXTERN SQL CINT CalcMediana(CSTRING) remoto1

median = CalcMediana("Roberto")

IF SQLERROR() != 0
? "Error del servidor:", SQLMESSAGE()
ELSE
? "Mediana=",mediana
ENDIF

Declaración automática de procedimientos almacenados


El comando OPEN DATABASE tiene una opción, AUTOEXTERN, que declara los
procedimientos que haya almacenados. Esto elimina la necesidad de que el usuario
declare (EXTERN) las llamadas de procedimientos almacenados. Una vez abierta la base
de datos, los procedimientos almacenados están disponibles de inmediato.
La sintaxis para utilizar OPEN DATABASE con AUTOEXTERN es:
OPEN DATABASE <nombre basedatos> [LOGIN ...] [WITH ...] [AUTOEXTERN]
Si se incluye AUTOEXTERN, Visual dBASE declara (EXTERN) internamente los
procedimientos almacenados que contenga el servidor especificado por
<nombre basedatos>.
Por ejemplo:
OPEN DATABASE remote1 AUTOEXTERN

BuscarCliente( "Roberto", codCli)


if SQLERROR() = 0
? "codCli:", codCli
ELSE
? "Ciente no existe"
ENDIF

Capítulo 24, Uso de SQL 373


vdbsql.dwp Page 374 Wednesday, August 30, 1995 3:12 PM

Limitaciones
• El soporte de los procedimientos almacenados depende por completo de la versión y
configuración del servidor de SQL. Por ejemplo, InterBase 4.0 permite los
procedimientos almacenados, pero InterBase 3.3 no; Oracle7 también los permite,
pero sólo si se activa la opción procedimental. Para más información, consulte al
administrador de bases de datos.
• Si el servidor permite los procedimientos sobrecargados, se declara automáticamente
un único procedimiento. (Oracle7 permite funciones o procedimientos
sobrecargados.)
• Si el servidor no proporciona acceso a las definiciones de procedimientos
almacenados, no permite el uso de AUTOEXTERN. Por ejemplo, Oracle7,
InterBase 4.0 y los controladores ODBC permiten AUTOEXTERN, pero Sybase no.

374 Guía del programador


p_wind.dwp Page 375 Wednesday, August 30, 1995 2:25 PM

Parte

V
Entorno Windows
Parte V

Esta parte trata los temas relacionados con la programación para el entorno Windows.
Contiene los capítulos siguientes:
• Capítulo 25, “Impresión”
• Capítulo 26, “Uso de las DLL y del API de Windows”
• Capítulo 27, “Expansión de dBASE mediante DDE/OLE”

Parte V, Entorno Windows 375


p_wind.dwp Page 376 Wednesday, August 30, 1995 2:25 PM

376 Guía del programador


printng.dwp Page 377 Wednesday, August 30, 1995 2:26 PM

Capítulo

25 Impresión
Capítulo 25

Visual dBASE incorpora un Diseñador de informes integrado y completamente


funcional. El Diseñador de informes genera un archivo que puede utilizar para imprimir
informes con el comando REPORT FORM. Para la mayoría de necesidades de
impresión, el Diseñador de informes es la mejor solución.
Este capítulo trata aspectos y técnicas de programación para la impresión empleando el
lenguaje de dBASE. Lea este capítulo para aprender sobre la impresión cuando no se
utiliza el Diseñador de informes.

Acerca de la impresión
Visual dBASE añade las capacidades de impresión del entorno Windows a las
capacidades de impresión que existían en versiones anteriores de dBASE. Visual dBASE
permite el uso de las variables de memoria del sistema para el control de la salida impresa.
Estas variables controlan todos los aspectos de la impresión, desde la selección del
controlador de impresora a la especificación de márgenes, interlineado y fuentes.

Capítulo 25, Impresión 377


printng.dwp Page 378 Wednesday, August 30, 1995 2:26 PM

Nuevas características de impresión de Visual dBASE


• El comando ? de dBASE puede utilizar fuentes TrueType.
• Las opciones de impresión pueden cambiarse desde los programas o mediante la
función CHOOSEPRINTER( ) que abre un cuadro de diálogo para que el usuario
seleccione la impresora.
• Es posible el espaciado fraccionario de los caracteres con _lmargin porque cada
carácter ocupa una celda separada con atributos individuales. El soporte de fuentes
proporcionales también permite un interlineado inteligente. El texto con fuentes
grandes en una línea no se superpondrá con la línea siguiente.
• Una variable de control de impresión, _porientation, proporciona el cambio de
orientación de la página desde un programa. Los cambios de orientación deben
producirse en los saltos de página; por ejemplo, no es posible cambiar la orientación
de una figura o tabla a mitad de la página. Además, cuando se cambia _porientation,
también debe reducir la variable _plength en el código. Si usa CHOOSEPRINTER( )
para permitir que el usuario cambie la orientación de la impresión, Visual dBASE
cambia la variable de memoria _plength de forma automática.

Especificación de los controladores de impresora


Un controlador de impresora es un programa que inserta códigos de impresión en la
salida de dBASE. Dichos códigos indican a la impresora, por ejemplo, que sangre un
párrafo, cambie de fuente o utilice negrita.
En el entorno Windows, los controladores de impresora se instalan (y las conexiones con
las impresoras se establecen) por medio del Panel de control de Windows. Además, el
Administrador de archivos sirve para establecer conexiones con las impresoras de una
red. Para más información, consulte la documentación de Windows.
Windows 3.1 permite que haya más de un controlador de impresora asignado al mismo
puerto. Por tanto puede asignar controladores de impresora diferentes a aplicaciones
distintas. Para cambiar entre los controladores instalados, utilice el parámetro _pdriver
desde un programa o permita que el usuario seleccione la impresora con la función
CHOOSEPRINTER( ).
Para asignar una impresora a la variable _pdriver, utilice una cadena delimitada que
contenga el nombre de archivo del controlador, una coma de separación y el nombre de
la impresora. La siguiente es una sentencia de ejemplo que asigna un controlador de
impresora Postscript a _pdriver:
_pdriver = "PSCRIPT, HP LaserJet IIISi Postscript"
En este caso, “PSCRIPT” es el nombre de archivo del controlador de impresora (se
supone la extensión por defecto .DRV), y “HP LaserJet IIISi Postscript” es el nombre y
tipo de la impresora. Es necesario el nombre exacto de la impresora porque un mismo
controlador de impresora Windows puede servir para más de un modelo de impresora.
Nota Con las impresoras Postscript, la selección importante es el nivel II o III de Postscript, no
la marca o el modelo de la impresora.

378 Guía del programador


printng.dwp Page 379 Wednesday, August 30, 1995 2:26 PM

Determinación del nombre de un controlador de impresora


Para determinar el nombre que Visual dBASE utiliza para un controlador de impresora
instalado en Windows 3.1 y después usarlo en una aplicación:
1 Seleccione esa impresora por defecto o en el Panel de control en Windows o en el
cuadro de diálogo de CHOOSEPRINTER( ) en Visual dBASE.
2 En la ventana de comandos, consulte a Visual dBASE el nombre del controlador.
Introduzca:
? _pdriver
Visual dBASE devuelve el nombre que utiliza para ese controlador de impresora en el
panel de resultados de la ventana de comandos.

Salida direccionable y direccionada


Muchos comandos de dBASE como ?, ??, DIR, DISPLAY y LIST producen salida
direccionable, mientras que otros, como EJECT y @...SAY, producen salida direccionada.
Cada tipo de salida precisa un tratamiento diferente en los programas que desarrolle, y
cada uno se envía a la impresora de una forma diferente.
Las siguientes son las diferencias entre la salida direccionable y la direccionada:
• La salida direccionable comienza en la posición de impresión actual, mientras que la
direccionada se dirige a una posición específica en la pantalla y en la página impresa.
Por ejemplo, si la posición de impresión actual es la fila 5, columna 7, ahí es donde va
por defecto el primer carácter de la salida direccionable. La salida direccionada va a
las coordenadas especificadas explícitamente en el comando de salida.
• Con la salida direccionable, un carácter siempre sigue a otro; por tanto, la posición de
cada carácter suele depender de la del carácter anterior. Con la salida direccionada, la
posición de un carácter no depende necesariamente de la de otro.
• La salida direccionable va a todos los dispositivos de salida disponibles, mientras que
la direccionada sólo va al dispositivo especificado. Por ejemplo, si dirige la salida
direccionable a la impresora, la salida va a la pantalla y a la impresora. Sin embargo,
cuando se dirige salida direccionada a la impresora, sólo va a la impresora.
• La salida direccionable puede formatearse mediante variables de memoria del
sistema, lo cual no es posible con la salida direccionada. Por tanto, el aspecto de la
salida direccionada depende de la fuente y estilo por defecto de la impresora usada.
Puesto que el aspecto de la salida direccionable puede controlarse con fuentes, estilos de
caracteres y formato de columnas, suele ser preferible a la salida direccionada. Sin
embargo, ésta última puede ser útil al imprimir en fichas o formularios en que los
caracteres deben insertarse en posiciones precisas sin necesidad de formatear el texto.

Capítulo 25, Impresión 379


printng.dwp Page 380 Wednesday, August 30, 1995 2:26 PM

Dirección de la salida
dBASE ofrece elementos del lenguaje y utilidades de menús asociadas que determinan
qué impresora recibe la salida. La forma en que se dirige la salida depende de si envía
salida direccionable o direccionada.
La impresión en Windows está orientada a documentos; es decir, Windows considera cada
operación de impresión como la apertura y cierre de un documento, al que
normalmente se denomina documento de impresión. Debe estar abierto antes de poder
generar la salida y debe cerrarse antes de que la salida pueda enviarse a la impresora.

Dirección de la salida direccionable


La salida direccionable se dirige abriendo y cerrando los documentos de impresión. Los
dos métodos son:
• SET PRINTER TO <dispositivo>, que designa la impresora de destino y abre un
documento para esa impresora. Si ya se ha abierto un documento, SET PRINTER TO
<dispositivo> lo cierra antes de abrir el nuevo documento de impresión.
• CHOOSEPRINTER( ), que proporciona una forma interactiva de seleccionar las
impresoras. Cuando el usuario selecciona una nueva impresora y elige Aceptar,
dBASE reinicializa todas las variables de memoria del sistema relevantes a los valores
adecuados. Además, ejecuta un SET PRINTER TO <dispositivo> implícito. Cuando el
usuario elige Aceptar para aplicar la nueva configuración, se cierra el cuadro de
diálogo Configuración de impresión y CHOOSEPRINTER( ) devuelve verdadero
(.T.). Entonces, CHOOSEPRINTER( ) reinicializa _pdriver.
Antes de poder enviar la salida, debe abrir un documento de impresión con SET
PRINTER TO <dispositivo> o con CHOOSEPRINTER( ). Para imprimir la salida después
de enviarla, debe cerrar el documento de impresión con CLOSE PRINTER, o cerrarlo y
abrir uno nuevo con SET PRINTER TO <dispositivo> o con CHOOSEPRINTER( ). Este
proceso se esboza en los pasos siguientes:
1 Para activar la impresión, abra el documento de impresión ejecutando SET PRINTER
TO <dispositivo> o CHOOSEPRINTER( ).
(También puede dirigir la salida a un archivo mediante SET PRINTER TO FILE
<nombre archivo>. La salida se imprime en un archivo en disco, que contiene la salida
y los códigos de control correspondientes a la impresora designada.)
2 Envíe la salida a la impresora ejecutando SET PRINTER ON.
3 Genere la salida ejecutando comandos como ?, ??, DIR, DISPLAY y LIST.
4 Para detener el envío de la salida a la impresora, ejecute SET PRINTER OFF.
5 Para cerrar el documento de impresión actual e imprimirlo, ejecute CLOSE PRINTER
o SET PRINTER TO.

380 Guía del programador


printng.dwp Page 381 Wednesday, August 30, 1995 2:26 PM

El ejemplo siguiente utiliza SET PRINTER TO para abrir un documento de impresión,


enviar la salida y cerrar el documento:
SET PRINTER TO LPT1 && Especifica un dispositivo de salida.
SET PRINTER ON && Dirige la salida a la impresora
DIR *.* && Envía al dispositivo de salida un listado del directorio
SET PRINTER OFF && Las próximas salidas ya no irán a la impresora
CLOSE PRINTER && Cierra el documento de impresión
El ejemplo siguiente utiliza CHOOSEPRINTER( ) para abrir un documento de
impresión en el cuadro de diálogo Configuración de impresión, enviar la salida y cerrar
el documento de impresión:
CHOOSEPRINTER() && Selecciona una impresora
SET PRINTER ON && Dirige la salida a la impresora
DIR *.* && Envía al dispositivo de salida un listado del directorio
SET PRINTER OFF && Las próximas salidas ya no irán a la impresora
CLOSE PRINTER && Cierra el documento de impresión

Dirección de la salida direccionada


Además de salida direccionable, SET PRINTER TO <dispositivo> dirige salida
direccionada; no obstante, también necesita SET DEVICE TO PRINTER para dirigir la
salida a la impresora en lugar de a la pantalla. Aunque no especifique un dispositivo con
SET PRINTER TO, la ejecución de SET DEVICE TO PRINTER dirige la salida
direccionada a la impresora. También puede utilizar SET DEVICE TO <nombre archivo>
para enviar la salida a un archivo en disco.
El programa siguiente dirige la salida a la impresora que esté conectada actualmente
mediante el Administrador de impresión en el puerto paralelo LPT2.
SET PRINTER TO LPT2 && Especifica un dispositivo de salida
SET DEVICE TO PRINTER && Salida directa a la impresora
@ 12, 1 SAY "Esto empieza en la fila 12 columna 1" && Mensaje de confirmación
SET DEVICE TO SCREEN && Redirecciona la salida a pantalla
CLOSE PRINTER && Cierra el documento de impresión
A diferencia de SET PRINTER ON/OFF, la salida no se muestra en pantalla porque SET
DEVICE envía la salida sólo a un dispositivo.

Capítulo 25, Impresión 381


printng.dwp Page 382 Wednesday, August 30, 1995 2:26 PM

Averiguación del estado de la impresora


La función PRINTSTATUS( ) permite detectar las siguientes condiciones de error:
• No hay suficiente memoria o espacio en disco para poner en cola el archivo de salida.
• La impresora está conectada en un puerto que no es el especificado con SET
PRINTER TO o CHOOSEPRINTER( ).
Cuando se produce una de estas condiciones, PRINTSTATUS( ) devuelve falso (.F.). El
Administrador de impresión gestiona directamente los demás errores (como ausencia
de papel).
Puede utilizar PRINTSTATUS( ) para detectar problemas potenciales en la impresora
antes de generar una condición de error para el Administrador de impresión. Por
ejemplo, el código siguiente comprueba PRINTSTATUS y muestra una advertencia si
no es adecuado:
IF .NOT. PRINTSTATUS() && Comprueba el estado de la impresora
WAIT "Aviso, problema con la impresora." && Mensaje de aviso
RETURN
ENDIF

Impresión de la salida en un archivo


Imprima la salida en un archivo cuando precise imprimirla directamente desde el DOS
o para crear un archivo que pueda imprimir sin utilizar el programa que lo creó.
Windows suele enviar la salida a un archivo temporal en disco antes de enviarlo a la
impresora. La salida se envía desde este archivo a la impresora y, después, se borra el
archivo. Si utiliza SET PRINTER TO FILE <nombre archivo> o SET DEVICE TO FILE
<nombre archivo>, no se envía nada a la impresora y la salida se escribe en el archivo
especificado. Puesto que este archivo contiene códigos de control de la impresora,
probablemente no sea un archivo de texto estándar que pueda leer. En este caso, puede
imprimirlo empleando el comando COPY del DOS con la opción /b. Para más
información, consulte el comando COPY en el manual del DOS.

382 Guía del programador


printng.dwp Page 383 Wednesday, August 30, 1995 2:26 PM

Posición de los caracteres de salida


Cuando la salida se dirige a la impresora, dBASE sitúa cada carácter según el plano de
coordenadas, una cuadrícula bidimensional imaginaria en que se sitúan los caracteres. El
plano de coordenadas está dividido en celdas de carácter. La anchura de las celdas de
carácter depende del valor de _ppitch, mientras que la altura de cada carácter siempre
es 1/6 de pulgada. Cuando el tamaño en puntos de la fuente actual supera esta altura,
cada línea de la salida ocupa más de una fila de celdas de carácter. (Consulte _ppitch en
la Referencia del lenguaje para ver una tabla de los valores de _ppitch.)

Impresión de la salida con coordenadas absolutas


El comando @...SAY puede enviar salida direccionada a posiciones específicas en
pantalla o a la impresora empleando el direccionamiento absoluto, que no depende de la
posición del carácter anterior.
El código de programa siguiente envía la salida a posiciones específicas de línea y
columna en la página impresa. La impresora debe estar situada al principio de la página
puesto que no hay forma de volver a una fila anterior en la salida impresa. Es la razón
de que sea más fácil utilizar coordenadas relativas para la salida impresa.
SET DEVICE TO PRINTER
@ 2, 1 SAY "Esto empieza en la fila 2, columna 1"
@ 3, 10 SAY "Esto empieza en la fila 3, columna 10"
CLOSE PRINTER
SET DEVICE TO SCREEN
La salida impresa tiene este aspecto:
Esto empieza en la fila 2 columna 1
Esto empieza en la fila 3, columna 10

Impresión de la salida con coordenadas relativas


Cuando se imprime salida direccionada, en ocasiones es mejor situar la salida en
relación con la posición de impresión actual de la impresora. Por ejemplo, los informes
que imprimen salida con diferentes anchos deben calcular una posición para cada nueva
corriente de datos en función de la posición de una corriente de datos anterior.
Utilice las funciones PROW( ) y PCOL( ) para situar la salida respecto a la posición
actual de la impresora. PROW( ) devuelve el número de fila y PCOL( ) devuelve el de
columna en que la impresora está situada para comenzar a imprimir.
Puesto que PROW( ) y PCOL( ) permiten averiguar la posición actual de la impresora,
puede utilizarlas para calcular las coordenadas de otras posiciones con respecto a la
actual. A esto se denomina direccionamiento relativo. Por ejemplo, el código de programa
siguiente imprime una cadena de caracteres cinco filas por debajo y cuatro columnas a
la derecha de la posición actual de la impresora en el plano de coordenadas:
SET DEVICE TO PRINTER
@ PROW() + 5, PCOL() + 4 SAY "Un ejemplo de direccionamiento relativo"
SET DEVICE TO SCREEN
CLOSE PRINTER

Capítulo 25, Impresión 383


printng.dwp Page 384 Wednesday, August 30, 1995 2:26 PM

Uso de coordenadas fraccionarias


dBASE puede utilizar valores fraccionarios o enteros con AT <expN>, una opción que
determina la colocación horizontal del primer carácter de una línea.
El efecto del número que especifique con AT depende de la definición actual de _ppitch.
Por ejemplo, si define _ppitch como “ELITE”, la densidad es de 12 caracteres por
pulgada. Si desea un sangrado de 1,85 pulgadas, multiplique 12 por 1,85, que es igual a
22,2; por tanto, especifique 22.2 en la cláusula AT:
SET PRINTER ON
_ppitch = "ELITE" && 12 caracteres por pulgada
? "Esta cadena comienza a 1,85 pulgadas del margen izquierdo" ;
AT 22.2 && 12 * 1,85 = 22,2
SET PRINTER OFF
CLOSE PRINTER

Definición de la posición vertical y horizontal de la impresora


SET PROW reinicializa la posición vertical de la impresora, es decir, la línea de salida
impresa, mientras que SET PCOL reinicializa su posición horizontal, o sea, el número de
columna. Las coordenadas de los siguientes comandos @...SAY se evalúan respecto a los
nuevos valores de PROW( ) y PCOL( ).
Por ejemplo, si la posición de impresión está en la fila 10 y se ejecuta SET PROW TO 0, el
comando siguiente imprime “¿Qué línea?” comenzando en la fila 7, que era la fila 17
antes de ejecutar el comando SET PROW:
@ 7, 20 SAY "¿Que línea?"

384 Guía del programador


printng.dwp Page 385 Wednesday, August 30, 1995 2:26 PM

Gestión de números de línea, saltos de página y número de copias


Un trabajo de impresión es una técnica que le permite definir o controlar los números de
línea, realizar saltos de página automáticos o imprimir varias copias de la salida. Estas
posibilidades se controlan mediante tres variables de memoria del sistema:
• _pcopies determina cuántas veces se imprime la salida cuando se ejecuta un trabajo
de impresión.
• _peject determina si la impresora debe expulsar una hoja de papel y cuándo lo hará.
• _plineno determina el número de línea actual de la salida impresa.
Estas variables sólo son efectivas durante un trabajo de impresión; cuando se genera la
salida de alguna otra forma, las variables no se tienen en cuenta. Para que una sección
de código de programa genere un trabajo de impresión, anteponga PRINTJOB (que abre
el trabajo de impresión) y termine con ENDPRINTJOB (que cierra el trabajo de
impresión). _pcopies, _peject y _plineno estarán activas durante esta parte del
programa.
El siguiente es un programa de ejemplo que ejecuta un trabajo de impresión:
SET TALK OFF
USE CLIENTES
_peject = "AFTER"
_pcopies = 2
SET PRINTER ON && Salida directa a la impresora.
PRINTJOB && Inicia un trabajo de impresión
* Al principio de cada hoja (pero no en la
* primera) imprime fecha y número de página.
ON PAGE AT LINE _plength - 1 DO SaltoPag && Indica en qué línea se hace el salto
SCAN && Recorre toda la tabla
?? Num_Cli, Empresa, Nombre && Imprime los campos
? && Inserta una línea en blanco
ENDSCAN
ENDPRINTJOB && Finaliza el trabajo de impresión
SET PRINTER OFF && La próxima salida irá a la pantalla
CLOSE PRINTER && Cierra el documento de impresión
RETURN

PROCEDURE SaltoPag
EJECT
* dBASE incrementará _pageno automáticamente
?? DATE() AT 1, "Página:" AT 67, _pageno PICTURE "999" AT 73 && Imprime la cabecera
&& de página
?
?
RETURN

Capítulo 25, Impresión 385


printng.dwp Page 386 Wednesday, August 30, 1995 2:26 PM

ON PAGE especifica un comando que se ejecuta cuando la salida impresa llega a un


número de línea específico. En este ejemplo, ON PAGE determina cuándo se ejecutan
los saltos de página; cada vez que el número de línea actual llega a la parte inferior de
una página, el procedimiento SaltoPag expulsa el papel e inicia la página siguiente
imprimiendo la fecha y el número de página al principio. El trabajo de impresión
imprime dos copias (porque _pcopies contiene 2) y expulsa una página en blanco
después de imprimir la última página (porque _peject contiene “AFTER”).

Formato de la salida impresa


Cuando llega el momento de dar un formato limpio y profesional a un documento
impreso, mucho de su formateo se realiza con variables de memoria del sistema.
Las variables de memoria del sistema pueden cambiarse utilizando la ventana de
comandos y desde dentro de los programas. LIST MEMORY muestra los valores
actuales de todas las variables de memoria del sistema. Los usuarios interactivos
pueden cambiar algunos valores de la impresora mediante el cuadro de diálogo
Configuración de impresora, al que se accede desde el menú Archivo. Pueden cambiar
la impresora por defecto, el tamaño del papel y la orientación. Independientemente de
que los cambios en las variables de memoria del sistema se realicen desde un programa
o desde el cuadro de diálogo, cada cambio sustituye a la definición anterior.
La salida también se formatea con las opciones AT, FUNCTION y STYLE de los
comandos de salida direccionable.

Ajuste de la longitud de la página


Utilice la variable _plength para definir el número de líneas por página. Por ejemplo, si
el número por defecto de líneas por pulgada en la impresora es 6, pero en modo
condensado imprimirá 8 líneas por pulgada, debería aumentar _plength de 66 (en el
caso de papel de 11 pulgadas de longitud) a 88.
Para _plength, puede especificar un número entero o fraccionario para acomodar
fuentes proporcionales o documentos con dimensiones poco habituales. Por ejemplo, los
cheques de las nóminas pueden tener dimensiones que entren en conflicto con las
características de interlineado de la fuente actual; en tales casos, puede utilizar _plength
para realizar los ajustes necesarios.
Si utiliza una impresora matricial que está conectada directamente al ordenador desde
el que imprime y puede detener una operación de impresión y volver a situar
manualmente el papel en dirección vertical, puede utilizar la variable de memoria del
sistema _plineno para compensar el cambio antes de continuar imprimiendo. Por
ejemplo, después de mover el papel, puede definir la variable _plineno con el número
de línea de la nueva posición del cabezal de impresión y, entonces, reanudar la
impresión. Con _plineno, es posible especificar un número entero o fraccionario para
definir con precisión las posición del cabezal.

386 Guía del programador


printng.dwp Page 387 Wednesday, August 30, 1995 2:26 PM

Especificación del modo apaisado o vertical


Si la impresora que utiliza lo permite, puede imprimir en modo vertical o apaisado con
la variable _porientation. Especifique el modo de impresión mediante
_porientation=“portrait” o _porientation=“landscape”. No olvide cambiar la variable
_plength de 55 o 60 líneas para una página de 11 pulgadas a 51 para una página
completa o a 45 líneas para dejar un margen en la parte superior e inferior de 3 líneas en
una página de 8,5 pulgadas.

Selección de las opciones de impresión por parte del usuario


La configuración de la impresora puede cambiarse desde el cuadro de diálogo
Configuración de impresión. Para mostrarlo con la función CHOOSEPRINTER( ), use:
? CHOOSEPRINTER()
Si realiza cambios en este cuadro de diálogo y elige Aceptar para aplicar los cambios,
CHOOSEPRINTER( ) devuelve verdadero (.T.). dBASE evalúa las opciones
seleccionadas y reinicializa las variables de memoria del sistema modificadas. Si elige
Cancelar, la función devuelve falso (.F.) y no se cambia ninguna variable del sistema.
Los cambios que realice en el cuadro de diálogo Configuración de impresión afectan al
valor de SET PRINTER TO y cambia el contenido de tres variables de memoria del
sistema: _pdriver, _plength y _porientation.

Especificación de márgenes y sangrado


Hay varias variables de memoria del sistema que controlan los márgenes y el sangrado
de la salida impresa. Estas variables contienen valores numéricos que representan el
ancho, en columnas, del margen o el sangrado. Están permitidos números enteros o
fraccionarios, por lo que puede especificar márgenes y sangrados con un alto grado de
precisión.
Tabla 25.1 Variables de memoria del sistema que afectan a los márgenes y al sangrado
Variable Propósito
_ploffset Determina la distancia desde el borde izquierdo del papel hasta el margen izquierdo del
área imprimible. En general se define como 0. Puede utilizarse para dejar espacio para los
orificios del papel continuo.
_lmargin Define el margen izquierdo de la salida impresa desde el principio del área imprimible.
_rmargin Define el margen derecho de la salida impresa desde el final del área imprimible.
_pcolno Especifica la columna en que comienza la impresión de la primera línea de una corriente
de salida.
_indent Especifica un sangrado adicional para la primera línea de un párrafo.

Los valores que especifique son aditivos; por ejemplo, la distancia total desde el borde
izquierdo del papel hasta el primer carácter de un párrafo es el valor de _ploffset más el
número de columnas especificadas con _indent y _lmargin.

Capítulo 25, Impresión 387


printng.dwp Page 388 Wednesday, August 30, 1995 2:26 PM

Especificación de la alineación horizontal


Utilice la variable _alignment para alinear a la izquierda o a la derecha o centrar la salida
entre los márgenes definidos con _lmargin y _rmargin o con _ploffset y _rmargin. Para
que _alignment, _lmargin o _rmargin tengan efecto, defina _wrap como .T.

Ajuste del interlineado y posiciones de tabulación


La variable _pspacing controla el interlineado. Por ejemplo, si define _pspacing igual a
2, la salida impresa es a doble espacio, y si la define igual a 3, es a triple espacio. Con
_pspacing, es posible especificar un número entero o fraccionario, para que pueda
ajustar el espaciado vertical con precisión.
_pspacing = 2
_pspacing = 1.5
La variable _tabs controla la longitud de las posiciones de tabulación y acepta sólo
números enteros como expresiones de carácter delimitadas:
_tabs = "5,10,15,20"
Si no define tabuladores, dBASE utiliza el valor por defecto del DOS de un tabulador
cada 8 espacios.

Avances de hoja y paginación


Los avances de hoja y la paginación pueden controlarse en la salida impresa cuando
envíe varias páginas de salida a la impresora.

Avances de hoja y avances de línea


Utilice la variable _padvance para determinar si la impresora avanza el papel con un
avance de hoja o con avances de línea.
Si especifica el valor por defecto “formfeed”, el papel avanza una página cada vez,
pasando al principio de la página siguiente según el valor de longitud de página por
defecto de la impresora.
Con una impresora matricial, puede especificar una longitud de página más corta
mediante el valor “linefeeds”. Por ejemplo, puede imprimir páginas cortas, como
cheques, que tengan 20 líneas de longitud. Defina _plength con la longitud de la salida
(20 en este ejemplo) y defina _padvance como “linefeeds”.
Para imprimir cheques en una impresora láser, debe componer una página con tres
cheques y utilizar un avance de hoja para expulsar el papel.

388 Guía del programador


printng.dwp Page 389 Wednesday, August 30, 1995 2:26 PM

Números de página y rangos de página


Cuando genera una salida de varias páginas, las variables _pageno, _pbpage y _pepage
le permiten especificar la impresión de un rango de páginas. _pageno contiene el
número de la página actual, _pbpage especifica la primera página que se imprime y
_pepage especifica la última página que se imprime; las páginas con valores _pageno
menores que _pbpage o mayores que _pepage no se imprimen.
Conforme dBASE genera la salida, comprueba constantemente el número de página
actual (_pageno) e imprime páginas sólo cuando _pageno es mayor o igual a _pbpage.
Cuando _pageno es menor que _pbpage o mayor que _pepage, dBASE procesa la salida
internamente pero sin imprimirla.
Para cambiar el número de la página actual, en cualquier momento cambie el valor de
_pageno.

Modo de calidad y modo de borrador


La mayoría de impresoras matriciales pueden imprimir en modo de calidad o en modo
de borrador. El primero produce una copia impresa con la mayor calidad (mejor
resolución) que el modo de borrador; sin embargo, con la mayoría de las impresoras, el
modo de borrador imprime con mayor velocidad que el de calidad. El modo se
especifica con la variable _pquality; cuando _pquality es verdadero (.T.), el modo es
calidad; falso (.F.) equivale al modo de borrador.
Puesto que las impresoras láser no diferencian entre borrador y calidad, el uso de
_pquality=.T. produce un mensaje de error.

Especificación de las fuentes y estilos de texto


El aspecto de la salida direccionable depende de la fuente y el estilo de texto que elija.
Una fuente (también denominada tipo de letra) es un conjunto de letras, números,
símbolos y signos de puntuación que tienen una forma o aspecto común. Un estilo de
texto es un atributo, como negrita, cursiva o subrayado. dBASE puede utilizar las
fuentes que se instalan por medio del Panel de control de Windows.
Los estilos de texto que puede utilizar dependen de la fuente que seleccione; algunas
fuentes sólo permiten unos pocos estilos (o ninguno), mientras que otras permiten
muchos estilos. Cuando envíe salida direccionable a la impresora con ? o ??, puede
especificar la fuente y el estilo con la opción STYLE.

Capítulo 25, Impresión 389


printng.dwp Page 390 Wednesday, August 30, 1995 2:26 PM

Uso de las fuentes de Windows con Visual dBASE


Antes de poder usar una fuente en dBASE, es necesario asignarle un número de
identificación. Edite la sección [Fonts] del archivo DBASEWIN.INI para asignar un
número de identificación a las fuentes que desea utilizar. Para más información sobre
los parámetros de DBASEWIN.INI, consulte el Apéndice C de la Guía del usuario. Unas
entradas típicas serían las siguientes:
[Fonts]
1=Times New Roman, 12, ROMAN
2=Arial, 10, SWISS
Cada una de las entradas de este ejemplo consta de cuatro partes. Los componentes de
la primera entrada son:
• El identificador numérico (1) es el número que identifica a la fuente en la expresión
STYLE de un comando de salida direccionable, como ? o ??. Puede ser cualquier
número entre 1 y 32767. En la realidad, probablemente no tendrá instaladas más de
una docena de fuentes; piense en la posibilidad de agrupar los identificadores de
fuentes por decenas. La serie Times Roman podría ir del 1 al 10, para los diversos
tamaños en puntos. La serie Arial podría ir del 11 al 20 etc. Esta convención puede
facilitarle recordar las fuentes de los grupos al codificarlas.
• El nombre de la fuente (Times New Roman). Las fuentes que desee utilizar deben
estar instaladas en el sistema operativo Windows. (Compruebe las fuentes que hay
instaladas en el cuadro de diálogo Fuentes del Panel de control de Windows.)
• El tamaño en puntos (12). Este valor determina la altura y anchura de los caracteres.
• La familia de fuentes (ROMAN). Una fuente puede pertenecer a una de las siguientes
familias: ROMAN, SWISS, MODERN, SCRIPT o DECORATIVE. Si la fuente que
especifique no está disponible en el dispositivo de salida actual, el Administrador de
impresión de Windows elige automáticamente otra fuente de la misma familia.

Uso de una fuente


Utilice el número que asignó a una fuente en DBASEWIN.INI con los comandos de
salida direccionable para imprimir la salida con esa fuente.
En la sección [Fonts] del ejemplo anterior, se especificaron dos fuentes. El comando
siguiente genera la cadena “Hola” con la fuente Times New Roman y un tamaño de 12
puntos:
? "Hola" STYLE 1
De la misma forma, el comando siguiente genera la cadena “Adiós” con la fuente Arial y
un tamaño de 10 puntos:
? "Adiós" STYLE 2

390 Guía del programador


printng.dwp Page 391 Wednesday, August 30, 1995 2:26 PM

Estilos de texto
La opción STYLE de dBASE ofrece cinco estilos de texto, que se muestran en la Tabla
6.3. Estos estilos están disponibles para todas las fuentes instaladas en Windows.
• B especifica el estilo negrita.
• I especifica el estilo cursiva.
• U especifica el estilo subrayado.
• R especifica el estilo superíndice (elevado).
• L especifica el estilo subíndice (descendido).
Para utilizar un estilo de texto, especifique su letra en la opción STYLE. También es
posible combinar los estilos de texto:
Tabla 25.2 Ejemplos de los estilos de texto
Comando Resultado
? "Esta es una frase en negrita" STYLE "B" Esta es una frase en negrita
? "Esta es una frase inclinada "STYLE "I" Esta es una frase inclinada
? "Fuente Times New Roman subrayada" STYLE "1U" Fuente Times New Roman subrayada
? "Fuente Arial, tamaño 10 puntos y cursiva"; Fuente Arial, tamaño 10 puntos y
STYLE "2I" cursiva

Capítulo 25, Impresión 391


printng.dwp Page 392 Wednesday, August 30, 1995 2:26 PM

Justificación de las cadenas de texto


Con frecuencia, es deseable que una cadena de texto larga se “justifique” dividiéndola
en dos o más líneas y apilando éstas en vertical, como cuando se desea impedir que una
cadena cubra ciertas partes de una página de salida. El proceso de justificar las cadenas
se denomina ajuste vertical.
dBASE le permite utilizar valores fraccionarios o enteros para especificar el ancho de las
líneas justificadas.

Especificación del ancho de línea con ajuste vertical


El ajuste vertical se aplica con el símbolo de función V, en el que se especifica el ancho
del campo en caracteres. Si desea calcular el ancho en pulgadas, debe tener en cuenta el
número de caracteres por pulgada. La salida direccionable utiliza PICA, ELITE y
CONDENSED, que corresponden respectivamente a 10, 12 y 17,16 caracteres por
pulgada. Esto implica que un margen de 2 pulgadas incluirá 20 caracteres en modo
PICA, 24 en ELITE y 34 en CONDENSED. El número actual de caracteres por pulgada
se guarda en la variable del sistema _ppitch.
La rutina siguiente imprime una cadena de texto con un ancho de justificación de 2,88
pulgadas:
SET PRINTER ON
_ppitch = "ELITE" && Densidad de 12 caracteres por pulgada
? "Esta es una cadena muy larga que se ajustará a la columna definida.";
FUNCTION "V34.56" && 2,88 * 12 = 34,56
SET PRINTER OFF
CLOSE PRINTER

Especificación de la alineación horizontal con ajuste vertical


dBASE permite especificar la alineación horizontal con valores fraccionarios y enteros.
La salida impresa con valores variables de número de caracteres por pulgada puede
alinearse mediante los símbolos de función I y J. El símbolo I centra cada línea justificada
con respecto a las demás, mientras que el símbolo J alinea a la derecha cada línea
justificada.
La rutina siguiente imprime una cadena de texto con un ancho de justificación de 2,88
pulgadas y centra las líneas justificadas, unas respecto a otras:
SET PRINTER ON
_ppitch = "ELITE" && Densidad de 12 caracteres por pulgada.
? "Esta es una cadena muy larga que se ajustará a la columna definida.";
FUNCTION "V34.56I" && 2,88 * 12 = 34,56. La "I" centra las líneas.
SET PRINTER OFF
CLOSE PRINTER

392 Guía del programador


dll&api.dwp Page 393 Wednesday, August 30, 1995 2:27 PM

Capítulo

Uso de las DLL y del API de Windows


Capítulo26
26
Las bibliotecas de vínculo dinámico (DLL) son archivos que contienen código de
programa compilado que las aplicaciones Windows cargan y ejecutan en tiempo de
ejecución. El código de programa puede contener subrutinas y recursos, como mapas de
bits e iconos. Mediante las DLL, puede ejecutar funciones que no son de dBASE en el
código de dBASE.
El API (interfaz de programación de aplicaciones) de Windows es una biblioteca
incorporada en el entorno Windows que contiene cientos de útiles funciones C
guardadas en las DLL. El soporte de DLL en Visual dBASE permite a los programadores
de dBASE el acceso al API de Windows.
Este capítulo presenta el vínculo dinámico y muestra cómo ejecutar funciones que no
son de dBASE desde las DLL y el API de Windows. Para los programadores que están
habituados a llamar funciones externas, Visual dBASE facilita el acceso a DLL. Sin
embargo, la ejecución de las funciones DLL es una técnica avanzada; le sería útil algún
conocimiento de otro lenguaje de programación, en especial de los tipos de datos
utilizados.

Acerca de las bibliotecas y los vínculos


Los términos “biblioteca” y “vínculo” significan cosas distintas para programadores
diferentes. Los programadores de dBASE mantienen bibliotecas de rutinas utilizadas
con frecuencia y las incorporan en las aplicaciones sin necesidad de vínculos. Los
usuarios de lenguajes de programación compilados tradicionales utilizan montadores
para vincular programas compilados y rutinas de biblioteca en un archivo ejecutable.
El vínculo estático ocurre una sola vez en el momento de crear la aplicación. Por el
contrario, las aplicaciones Windows realizan un vínculo dinámico llamando a rutinas y
recursos durante su ejecución. El vínculo dinámico puede ocurrir muchas veces
mientras se ejecuta una aplicación.

Capítulo 26, Uso de las DLL y del API de Windows 393


dll&api.dwp Page 394 Wednesday, August 30, 1995 2:27 PM

La Figura 26.1 ilustra la diferencia entre el vínculo estático (a la izquierda) y el dinámico


(a la derecha).
Figura 26.1 Comparación de vínculo estático y vínculo dinámico
Vínculo estático Vínculo dinámico

LibFunc() LibFunc()

.PRG .PRG Biblioteca .PRG .PRG .PRG .DLL .PRG


vínculo
Compilación y

vínculo
Compilación y

Compilación

Compilación
LibFunc() LibFunc() LibFunc()

.EXE .EXE .PRO .DLL .PRO

Llamada durante la ejecución

Si mantiene rutinas compiladas en distintos archivos y las vincula a las aplicaciones


durante la ejecución:
• Puede compartir la misma biblioteca entre muchas aplicaciones, incluso entre dos
que se ejecuten simultáneamente en el modo extendido 386 de Windows.
• Ahorra memoria, puesto que la biblioteca se carga en memoria una sola vez.
• No tiene que recompilar o revincular la biblioteca cada vez que compile y genere la
aplicación.
• Crea archivos ejecutables más pequeños.
Puede llamar cualquier función de una DLL siempre que sepa los parámetros que la
función espera, el valor que devuelve y la convención de llamada que utiliza.
La mayoría de archivos de programa asociados a las aplicaciones Windows y el sistema
operativo Windows en sí son módulos de programa (.EXE) o DLL. Puesto que las DLL
son parte de las aplicaciones, deben estar presentes en el disco cuando un programa
llama una de sus rutinas o recursos.

394 Guía del programador


dll&api.dwp Page 395 Wednesday, August 30, 1995 2:27 PM

Los archivos DLL pueden tener cualquier extensión, como .DLL, .DRV, .FON o .EXE,
aunque la mayoría tienen la extensión .DLL. Los ocho primeros caracteres de cualquier
DLL cargado en memoria por una aplicación deben ser exclusivos, sea cual sea su
extensión. Es decir, si ha cargado SCRIPT.FON, no podrá cargar después SCRIPT.DLL.
El comando que intente cargar SCRIPT.DLL (ya sea EXTERN o LOAD DLL) no podrá
hacerlo ni comunicará ningún error.

Uso de las DLL con dBASE


Las DLL son un puente con C, Pascal, lenguaje ensamblador y cualquier otra función
que no sea de dBASE. Los programadores familiarizados con otro lenguaje pueden
escribir sus funciones y compilarlas con un compilador Windows. Además, muchos
proveedores suministran bibliotecas de funciones para su uso en aplicaciones Windows.
Puesto que Windows está escrito en C, la mayoría de funciones DLL que utiliza están
escritas en C o C++.
Los proveedores de las DLL deben suministrar información sobre el uso de las
funciones de la biblioteca. Para utilizar una función de una DLL, debe conocer:
• El nombre de la función.
• El nombre del archivo DLL que contiene la función.
• Los tipos de datos de los parámetros y del valor que devuelve, si devuelve alguno.
• La convención de llamada. (La mayoría de funciones Windows utilizan la
convención de llamada de Pascal, que precisa un número fijo de parámetros; la
convención de C permite un número variable de parámetros.)

Llamada de funciones DLL


Una función DLL puede llamarse desde el código de un programa o desde la ventana de
comandos. Siga estos pasos para llamar a una función DLL:
1 Cree un prototipo de la función no dBASE con el comando EXTERN. Un prototipo
especifica los tipos de datos de los parámetros de la función y del valor que devuelve.
2 Llame la función no dBASE directamente en el código de dBASE.
Sea cual sea el lenguaje en que están escritas, las funciones que no son de dBASE no
distinguen entre mayúsculas y minúsculas cuando se llaman en Visual dBASE.

Capítulo 26, Uso de las DLL y del API de Windows 395


dll&api.dwp Page 396 Wednesday, August 30, 1995 2:27 PM

Creación de prototipos y conversión de los tipos de datos


Visual dBASE utiliza prototipos para convertir automáticamente los parámetros y los
valores devueltos entre los tipos de datos de dBASE y no dBASE. Utilice EXTERN para
declarar un prototipo para cada función no dBASE antes de llamar la función. Para una
descripción completa de EXTERN, consulte la Referencia del lenguaje; éste es un ejemplo
de una sentencia EXTERN:
EXTERN CWORD MiFuncC( CWORD, CSTRING ) MiBibC.DLL FROM "NombreFuncC"

Palabra clave que Nombre que Palabras clave que Nombre del archivo Nombre de la
representa el tipo de asigne a la representan los tipos de DLL que contiene la función en la DLL
datos del valor función datos de los parámetros función
devuelto por la función de la función

La Tabla 26.1 muestra las palabras clave de EXTERN para cada tipo de datos:
Tabla 26.1 Palabras clave de EXTERN para parámetros y valores devueltos
Palabra Tipo de Tipo de datos Tipo de
clave datos dBASE Tipo de datos C Pascal datos ASM
Parámetros CDOUBLE Numérico long double (80 bits) Double N/D
o valores CHANDLE Numérico Handles, como Handles, como dw
devueltos HANDLE, HWND, HWND, HFont,
HFONT, HDC HDC
CINT Numérico int Integer dw (16 bits)
CLOGICAL Lógico short Int Integer dw (16 bits)
CLONG Numérico long int (32 bits) Long Int dd (32 bits)
CSTRING Carácter char far * PChar dw (16 bits)
(terminado en cero)
CVOID N/D void Procedure N/D
CWORD Numérico short int (16 bits) WORD dw (16 bits)
Sólo CPTR N/D void * Pointer dd (32 bits)
parámetros

La necesidad de prototipos se hace más evidente cuando se piensa en el problema de


pasar parámetros a funciones de lenguajes diferentes. Por ejemplo, una función C
podría esperar un entero de 32 bits sin signo como parámetro. Cuando llame la función
en el código dBASE, puede pasar una variable numérica de dBASE. Visual dBASE
convierte automáticamente el parámetro del tipo de datos de dBASE al de C. De igual
forma, convierte los valores devueltos de C a dBASE.

396 Guía del programador


dll&api.dwp Page 397 Wednesday, August 30, 1995 2:27 PM

Ejemplos de llamada de funciones DLL


Como con las funciones internas de dBASE, puede llamar las funciones que no son de
dBASE por sí solas o como parte de una expresión de dBASE. El ejemplo siguiente
demuestra los dos métodos.
EXTERN CWORD Func_C(CWORD, CSTRING) MiBIB_C.DLL && Prototipo de la función
nParam = 10
cParam = "texto"
nResult = Func_C(nParam,cParam) && Ejecuta Func_C()
Func_C(nParam,cParam) && Ejecuta la función sin devolver
&& ningún valor
También puede realizar una llamada de una función interna de dBASE o de una función
no dBASE dentro de una llamada de una función no dBASE. El ejemplo siguiente llama
las funciones CDOW(), DTOC() y DATE() de dBASE dentro de la función MessageBox
del API de Windows.
EXTERN CWORD MessageBox(CHANDLE,CSTRING,CSTRING,CWORD) USER.EXE
* parámetro 1 - CHANDLE - Identificador de la ventana padre
* parámetro 2 - CSTRING - Mensaje en el cuadro de diálogo
* parámetro 3 - CSTRING - Título del cuadro de diálogo
* parámetro 4 - CWORD - Estilo del cuadro de diálogo
MessageBox(0,"Hoy es: "+CDOW(DATE()),DTOC(DATE()),65)
El ejemplo siguiente llama GetFocus desde dentro de MessageBox.
EXTERN CWORD MessageBox(CHANDLE,CSTRING,CSTRING,CWORD) USER.EXE
EXTERN CHANDLE GetFocus(CVOID) user.exe && Devuelve el identificador de la ventana que
&& tiene el foco
? MessageBox(GetFocus(),"¡Hola! ", "MessageBox() llamada desde dBASE",17)
Si lo desea, puede declarar sus propios nombres para las funciones no dBASE en la
sentencia EXTERN. Esto es útil si la función no dBASE tiene el mismo nombre que una
función interna de dBASE o una UDF que haya creado. Para declarar el nuevo nombre,
introdúzcalo en la sentencia EXTERN y utilice la opción FROM para especificar el
nombre auténtico de la función en el archivo DLL. El ejemplo siguiente declara un
cuadro de alerta personalizado basado en MessageBox.
EXTERN CWORD MiMensaje(CHANDLE,CSTRING,CSTRING,CWORD) USER.EXE FROM "MessageBox"
? MiMensaje(0,"Para tu información, !Esto es MiMensaje!","MiMensaje",65)
Algunas funciones C aceptan identificadores (handles) de ventana como parámetros o
los devuelven como resultado. Los identificadores son números que el entorno
operativo Windows asigna a cada ventana. Guarde esos identificadores de ventana en
dBASE como variables numéricas y utilice CHANDLE para representarlas en el
comando EXTERN.
Algunas funciones C aceptan un puntero a una cadena (CSTRING) que la función
rellena con el valor devuelto. En este caso, necesita pasar una variable de caracteres que
sea lo suficientemente grande como para guardar el resultado. Algunas funciones
también precisan que pase la longitud máxima de la cadena como argumento. El
ejemplo siguiente llama GetSystemDirectory desde el API de Windows para recuperar
el nombre del directorio del sistema de Windows.

Capítulo 26, Uso de las DLL y del API de Windows 397


dll&api.dwp Page 398 Wednesday, August 30, 1995 2:27 PM

EXTERN CWORD GetSystemDirectory(CSTRING,CWORD) krnl.exe && Prototipo


DirSys = SPACE(144) && Inicializa el parámetro
GetSystemDirectory(DirSys,144) && Pasa el parámetro a la función
? "El directorio del sistema es: "+DirSys
El ejemplo siguiente muestra el código dBASE para llamar una función C, así como el
código C que crea la función:
***********************************************************************
* Programa dBASE (ejemplC2.PRG)
* StrRevC():
* En este ejemplo la variable MiCadena se pasa por referencia a la
* función StrRevC() de la Dll, la cual modifica su contenido. La cadena
* modificada se muestra en dBASE.
***********************************************************************
EXTERN CVOID StrRevC(CSTRING) ejemplC2.dll && Prototipo
MiCadena = "AbCdE"
? "Cadena original: ",MiCadena
StrRevC(MiCadena)
? "Después de pasar por StrRevC: ",MiCadena
Este es el código fuente C para la DLL que contiene StrRevC():
/****************************************************************
* Código fuente de la DLL (ejemplC2.C)
*****************************************************************/
#include <windows.h>
#include <string.h>
#pragma argsused
int FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg,
WORD wHeapSize, LPSTR lpszCmdLine){
if (wHeapSize > 0)
UnlockData (0) ;
return 1;}
#pragma argsused
int FAR PASCAL WEP(int wParameter) {
return 1;}
/* Función StrRevC() */
#pragma argsused
void FAR PASCAL StrRevC(LPSTR lpstrString) {
strrev(lpstrString);}
Este es el archivo .DEF para el DLL:
;***********************************************************
;* Archivo DEF para la dll en C (ejemplC2.DEF)
;***********************************************************
LIBRARY EJEMPLC2
DESCRIPTION 'Ejemplo de DLL para dBASE para Windows'
EXETYPE WINDOWS
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD SINGLE
HEAPSIZE 1400
EXPORTS
WEP @1
REVCADC @2

398 Guía del programador


dll&api.dwp Page 399 Wednesday, August 30, 1995 2:27 PM

Carga y liberación de las DLL


Un archivo DLL debe cargarse en memoria antes de que pueda llamar una de sus
funciones. Una vez cargada una DLL, dBASE puede llamar cualquier función para la
que ya haya creado un prototipo. La carga de una DLL utiliza una pequeña cantidad de
memoria, sea cual sea el tamaño del archivo DLL.
El entorno Windows gestiona la memoria de forma automática. Windows realiza un
seguimiento de cada archivo DLL que hay en memoria incrementando un contador de
referencias cada vez que una nueva aplicación carga una DLL en particular. Dentro de
Visual dBASE, si varios programas llaman funciones de una DLL en particular, el
contador de referencias se incrementa una sola vez, y el archivo se carga sólo si no se ha
cargado ya.
Visual dBASE proporciona tres formas de cargar archivos DLL:
• Un archivo DLL se carga automáticamente la primera vez que se crea un prototipo de
una función del DLL con el comando EXTERN. Es el método más común.
EXTERN CWORD MiFuncC2() MiLibC2.DLL && Prototipo e inicialización de la función
nResult = MiFuncC() && Llamada a la función
• El comando LOAD DLL permite cargar un archivo DLL antes de llamar alguna de
sus funciones. Utilice LOAD DLL para una DLL sólo de recursos antes de utilizar
uno de sus recursos. También puede utilizar LOAD DLL en otros casos especiales,
por ejemplo, cuando desee comprobar si hay errores de carga.
LOAD DLL MisGrafs.DLL && Inicialización.
DEFINE IMAGE MiGraf OF MiFicha PROPERTY; && Objeto imagen para una ficha creada
DataSource "RESOURCE #1001 MisGrafs.DLL" && previamente. Se especifica el nombre de la
&& DLL y su número de recurso
• Introduzca el nombre de la DLL en el archivo de configuración de Visual dBASE,
DBASEWIN.INI. Al arrancarse, Visual dBASE carga las DLL especificadas en
DBASEWIN.INI. (Para más información sobre las definiciones de DBASEWIN.INI,
consulte el Apéndice C de la Guía del usuario.)
[LoadDLL] && Comienza la sección de DLL en DBASEWIN.INI
DLL0 = MiLibC2.DLL && Especifica el archivo DLL a cargar en la
&& inicialización
No es necesario liberar explícitamente las DLL; se liberan automáticamente al salir de
Visual dBASE.

Capítulo 26, Uso de las DLL y del API de Windows 399


dll&api.dwp Page 400 Wednesday, August 30, 1995 2:27 PM

Llamada de las funciones del API de Windows


Las funciones del API de Windows se llaman de la misma forma que cualquier otra
función de una DLL, con estas diferencias:
• Las DLL que constituyen el API de Windows se inicializan de forma automática
cuando se inicia Windows. Por tanto, nunca necesita utilizar LOAD DLL para cargar
una DLL del API de Windows. Sin embargo, especifique el nombre de la DLL que
contiene la función API cuando cree su prototipo con EXTERN.
• La mayoría de las funciones API de Windows utilizan la convención de llamada de
Pascal. Por tanto, no utilice la opción CDECL de EXTERN.
El Kit de desarrollo de software (SDK) de Windows y libros de otros proveedores
explican las funciones API de Windows en detalle y enumeran las funciones contenidas
en cada DLL de Windows. Para facilitarle las llamadas al API de Windows, Visual
dBASE proporciona un archivo include, WINAPI.H, que contiene prototipos EXTERN y
definiciones de constantes para las funciones API de Windows que le pueden ser más
útiles.
Si lo desea, puede cortar y pegar prototipos y constantes de WINAPI.H en su programa.
O bien, si llama muchas funciones API de Windows, puede utilizar la directiva de
preprocesador #include para incluir WINAPI.H en su programa. (Para una explicación
de las directivas de preprocesador, consulte el Capítulo 7.)
Los ejemplos siguientes utilizan funciones del API de Windows.
EXTERN CLONG GetFreeSpace( CINT ) krnl.exe && Devuelve el número de bytes
MemLibre=LTRIM(STR(GetFreeSpace(0)/1024))+"K" && disponibles en la memoria del
? "Memoria libre: "+MemLibre && sistema

EXTERN CWORD GetFreeSystemResources( CINT ) user.exe && Devuelve el porcentaje de


RecLib=LTRIM(STR(GetFreeSystemResources(1)))+'%' && recursos disponibles en el
? "Recursos libres: "+RecLib && sistema

EXTERN CWORD GetNumTasks() krnl.exe && Devuelve el número de tareas que


NumTareas = LTRIM(STR(GetNumTasks())) && están en ejecución bajo Windows
? "Tareas ejecutándose: "+NumTareas
La función MessageBox() del API de Windows está contenida en USER.EXE. El ejemplo
siguiente muestra un cuadro de diálogo titulado “Desde el API de Windows” que
contiene el mensaje “¡Hola a todos!”, el icono de la señal de stop y dos botones, Aceptar
y Cancelar. El valor devuelto es 1 si se elige Aceptar o 2 si se elige Cancelar.
EXTERN CWORD MessageBox(CHANDLE,CSTRING,CSTRING,CWORD) user.exe
nRespuesta = MessageBox(0, "¡Hola a todos!", "Desde el API de Windows" , 17)

400 Guía del programador


dll&api.dwp Page 401 Wednesday, August 30, 1995 2:27 PM

Uso de las constantes del API de Windows


El API de Windows utiliza muchas constantes para representar controles comunes,
como iconos y cursores. Algunas se definen como enteros, otras como números
hexadecimales. Por ejemplo, puede utilizar la constante, IDC_ARROW (definida como
32512), para especificar el cursor con forma de flecha en el parámetro de una función.
El ejemplo siguiente utiliza las constantes ANCHURA_PANTALLA y
ALTURA_PANTALLA para obtener la anchura y altura de la pantalla:
#define ANCHURA_PANTALLA 0
#define ALTURA_PANTALLA 1
EXTERN CINT GetSystemMetrics(CINT) user.exe
? "Anchura de la pantalla = ", GetSystemMetrics(ANCHURA_PANTALLA)
? "Altura de la pantalla = ", GetSystemMetrics(ALTURA_PANTALLA)
Al utilizar constantes, quizá tenga que convertir entre valores enteros y hexadecimales.
Visual dBASE dispone de estas funciones de conversión:
• HTOI() convierte un número hexadecimal guardado como cadena de texto en entero.
• ITOH() convierte un entero en un número hexadecimal guardado como cadena de
texto.
El ejemplo siguiente utiliza HTOI() con la constante ICONO_PREGUNTA para mostrar
un icono de pregunta en un cuadro de mensaje:
#define ICONO_PREGUNTA HTOI("0020")
EXTERN CWORD MessageBox(CHANDLE,CSTRING,CSTRING,CWORD) user.exe
MessageBox(0,"Mensaje","Desde Windows",ICONO_PREGUNTA)

Gestión a nivel de bits de los parámetros y los valores devueltos


Algunas funciones del API de Windows interpretan los bits individuales de un
argumento entero o valor devuelto. Los programadores avanzados puede realizar una
manipulación a nivel de bits de los valores enteros, mediante estas funciones:
• BITAND() realiza una comparación AND lógica de dos valores de bits.
• BITOR() realiza una comparación OR lógica de dos valores de bits.
• BITXOR() realiza una comparación EXCLUSIVE OR lógica de dos valores de bits.
• BITLSHIFT() desplaza los bits de un entero n posiciones a la izquierda.
• BITRSHIFT() desplaza los bits de un entero n posiciones a la derecha.
• BITSET() devuelve el valor de un bit (.T. o .F.).
Los bits que constituyen un entero se numeran desde el bit menos significativo (el
situado más a la derecha) al más significativo (el de la izquierda). La numeración de los
bits comienza en 0 con el bit menos significativo. En un entero de 16 bits, los bits 0 a 7
constituyen la palabra inferior, mientras que los bits 8 a 15 son la palabra superior.

Capítulo 26, Uso de las DLL y del API de Windows 401


dll&api.dwp Page 402 Wednesday, August 30, 1995 2:27 PM

Las funciones del API de Windows devuelven, en ocasiones, dos valores en un solo
entero: uno en la palabra inferior y otro en la superior. Mediante las funciones de bits,
puede extraer y manipular estos valores. La Figura 26.2 ilustra la división en bits del
número 36783, un entero sin signo de 16 bits:
Figura 26.2 Numeración de los bits

Palabra superior Palabra inferior

Valores de
los bits 1 0 0 0 1 1 1 1 1 0 1 0 1 1 1 1

Números de bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

Más significativo Menos significativo

El ejemplo siguiente utiliza funciones de bits y la directiva de preprocesador #define


para extraer las versiones de DOS y Windows del valor devuelto por la función
GetVersion() del API de Windows:
FUNCTION Versiones
#define HiWord(x) (BITRSHIFT(BITAND(x,4294901760),16))
#define LoWord(x) (BITAND(x,65535))
#define HiByte(x) (LTRIM(STR(BITRSHIFT(BITAND(x,65280),8))))
#define LoByte(x) (LTRIM(STR(BITAND(x,255))))
EXTERN CLONG GetVersion( ) krnl386.exe
z=GetVersion( )
RETURN "Versión DOS : "+HiByte(HiWord(z))+"."+LoByte(HiWord(z))+ ;
", Versión Windows : "+LoByte(LoWord(z))+"."+HiByte(LoWord(z))
Para más información sobre el uso de #define, consulte el Capítulo 7.

402 Guía del programador


dde.dwp Page 403 Wednesday, August 30, 1995 2:27 PM

Capítulo

Expansión de dBASE mediante


Capítulo 27
27
DDE/OLE
El Intercambio dinámico de datos (DDE) es una característica de Windows que permite
que dos aplicaciones compartan e intercambien datos. Para ello, DDE establece un
vínculo que sirve como canal de comunicaciones entre las aplicaciones. Las aplicaciones
vinculadas no necesitan ser similares en función o propósito; por ejemplo, puede utilizar
DDE para intercambiar datos entre Visual dBASE (un sistema de gestión de bases de
datos) y Quattro Pro para Windows (una aplicación de hoja de cálculo). El único
requisito es que ambas aplicaciones tengan la capacidad de DDE.
Vínculo e incrustación de objetos (OLE) le permite utilizar aplicaciones externas, o
partes de ellas, directamente desde una tabla o ficha de dBASE. De hecho, con OLE, es
posible controlar una aplicación externa desde dentro de Visual dBASE.

Acerca de DDE
El Intercambio dinámico de datos le permite intercambiar instrucciones e información
entre Visual dBASE y otra aplicación Windows mediante un canal denominado vínculo
DDE. Esta comunicación de dos vías funciona de forma parecida a una conexión
telegráfica, con dBASE en un extremo y otra aplicación Windows en el otro.
Cada aplicación del vínculo juega un papel diferente, dependiendo de la dirección en
que fluyen las instrucciones por la conexión. Una aplicación de un vínculo DDE es el
cliente, y la otra es el servidor. El cliente solicita datos al servidor, envía datos al servidor o
envía comandos para que los ejecute el servidor. El servidor juega un papel más pasivo;
es invocado por el cliente, y recibe datos, produce datos o realiza tareas según las
instrucciones del cliente. dBASE puede ser cliente, servidor o ambos al mismo tiempo;
es decir, puede invocar y dar instrucciones a otra aplicación, o ser invocado y recibirlas
de otra aplicación.
dBASE proporciona dos clases DDE, DDELink y DDETopic. Los objetos DDELink se
crean cuando dBASE es cliente y los DDETopic se crean con dBASE como servidor.

Capítulo 27, Expansión de dBASE mediante DDE/OLE 403


dde.dwp Page 404 Wednesday, August 30, 1995 2:27 PM

Creación de aplicaciones cliente


Un objeto de la clase DDELink inicia una sesión en la aplicación servidor y establece el
vínculo DDE entre dBASE y el servidor. Una vez establecido el vínculo, use los métodos
y propiedades del objeto DDELink para comunicarse con la aplicación servidor.
Cree un objeto DDELink con el operador NEW, como en el ejemplo siguiente:
PUBLIC MiObjVinculo && MiObjVinculo está disponible para otras rutinas
MiObjVinculo = NEW DDELINK() && Crea un objeto DDELink
También puede crear un objeto DDELink con el comando DEFINE:
DEFINE DDELINK MiObjVinculo && Crea un nuevo objeto DDELink
La Tabla 27.1 resume las propiedades de la clase DDELink.

Tabla 27.1 Métodos y propiedades de DDELink


Propiedad Descripción
Advise( ) Pide al servidor que notifique al cliente cuándo un elemento cambia en el
documento del servidor. (Crea un vínculo dinámico.)
ClassName Identifica la clase de objeto DDELink.
Execute( ) Envía instrucciones al servidor en su propio lenguaje.
Initiate( ) Inicia una conversación con una aplicación servidor DDE.
OnNewValue Ejecuta una subrutina cuando un elemento vinculado dinámicamente cambia en
la aplicación servidor.
Peek( ) Recupera un elemento guardado en un documento del servidor.
Poke( ) Inserta un elemento en un documento del servidor.
Reconnect( ) Restaura un vínculo DDE que se terminó con Terminate( ).
Release( ) Elimina de la memoria la definición del objeto DDELink.
Server Contiene el nombre de la aplicación servidor DDE después de un Initiate()
satisfactorio.
TimeOut Determina el periodo de tiempo que dBASE espera una transacción antes de
devolver un error.
Topic Contiene el nombre del tema que especificó con el método Initiate( ), si es
satisfactorio.
Unadvise( ) Pide al servidor que detenga la notificación al objeto DDELink cuando un
elemento cambia en el documento del servidor. (Termina un vínculo dinámico.)

404 Guía del programador


dde.dwp Page 405 Wednesday, August 30, 1995 2:27 PM

Establecimiento y terminación de un vínculo DDE


Utilice el objeto DDELink para conectar dBASE (el cliente) a otra aplicación (el servidor).
Antes de que esto sea posible, el servidor debe estar ejecutándose. Utilice Initiate( ), un
método de la clase de objeto DDELink, para abrir la sesión (si es necesario) y establecer
el vínculo.
PUBLIC MiObjVinculo && Así está disponible en
&& todo el programa
MiObjVinculo = NEW DDELink() && Crea un objeto DDELink
IF MiObjVinculo.Initiate("QPW","C:\QPW\EJEMPLOS\PAGOCRED.WB1") && Comienza una sesión de
&& servidor DDE
? "Iniciada conexión con Quattro."
ELSE
? "No se ha conseguido conectar con Quattro"
ENDIF
Este código de programa intenta iniciar una sesión de servidor en Quattro Pro para
Windows (si es necesario), abrir un archivo de hoja de cálculo denominado
PAGOCRED.WB1 y establecer un vínculo DDE entre dBASE y Quattro Pro.
La sentencia IF...ELSE...ENDIF que ejecuta Initiate( ) imprime un mensaje en el panel de
resultados, indicando al usuario el éxito o fracaso del intento.
Cuando termine con el vínculo DDE, puede cerrarlo con el método Terminate().
MiObjVinculo.Terminate() && Cierra el vínculo DDE.

Intercambio de datos con el servidor


Una vez establecido satisfactoriamente el vínculo entre dBASE y la aplicación servidor,
puede solicitar datos del documento servidor con el método Peek( ) o enviar datos al
documento servidor con el método Poke( ).
PUBLIC MiObjVinculo && Disponible en toda la
&& aplicación
MiObjVinculo = NEW DDELINK() && Crea un objeto DDELink
MiObjVinculo.initiate("QPW","C:\QPW\EJEMPLOS\MIHOJA.WB1") && Comienza una sesión de
&& servidor DDE
xValor = MiObjVinculo.Peek("A:A1") && Mira en la página A celda A1
MiObjVinculo.Poke("A:A1", "12345") && Coloca "12345" en la
&& página A celda A1
Este código de programa abre MIHOJA.WB1, copia el valor de la celda A:A1 en la
variable de memoria xValor con el método Peek( ) e inserta la cadena de caracteres
“12345” en la celda con el método Poke( ). (A continuación, se tratan las posiciones de
celda en Quattro Pro, como A:A1.)

Capítulo 27, Expansión de dBASE mediante DDE/OLE 405


dde.dwp Page 406 Wednesday, August 30, 1995 2:27 PM

Desplazamiento por regiones


En el ejemplo anterior, se envió una cadena de texto a la celda A:A1 de una hoja de
cálculo de Quattro Pro. Sin embargo, la transferencia de una sola unidad de información
suele ser inadecuada. Por ejemplo, para transferir el contenido de varios campos y
registros a una hoja de cálculo, es necesario escribir datos en muchas celdas. Lo cual se
hace con una técnica denominada desplazamiento por regiones.
El desplazamiento por regiones es el proceso de pasar de elemento en elemento en un
documento servidor y leer o escribir datos en cada elemento. Controla el movimiento
logrando crear cadenas de texto que identifiquen a los elementos. El siguiente es un
ejemplo de programa.
EnviaDatos = NEW DDELINK() && Crea un nuevo objeto DDE
EnviaDatos.initiate("QPW", "C:\QPW\EJEMPLOS\MIARCH.WB1") && Comienza una sesión de
&& servidor
SELECT 1
USE CLIENTES
ContFila = 1 && Comienza en las celdas de
&& la fila 1
DO WHILE RECNO() <= 10 && Trata los 10 primeros
&& registros.
xCampo = 1 && Comienza en el primer campo
ContCar = ASC("D") && Comienza en la celda de la
&& columna 10
DO WHILE xCampo <= FLDCOUNT("CLIENTES") && Se mueve de campo en campo
NombreCampo = FIELD(xCampo) && Obtiene el nombre del
&& campo actual
NombreCelda = "A:"+CHR(ContCar)+LTRIM(STR(ContFila)) && Crea un identificador de
&& celda
EnviaDatos.Poke(NombreCelda, &NombreCampo) && Inserta el contenido del
&& campo en la celda
xCampo = xCampo + 1 && Va al siguiente campo
ContCar = ContCar + 1 && Va a la siguiente celda
ENDDO
SKIP && Pasa al siguiente registro
ContFila = ContFila + 1 && Pasa a la siguiente fila
ENDDO
Este programa utiliza la variable de carácter NombreCelda como identificador de las
celdas individuales de una hoja de cálculo. Consta de tres partes:
• Identificador de página: la cadena literal “A:” identifica la primera página del archivo
de hoja de cálculo. (Los archivos de hoja de cálculo de Quattro Pro constan de varias
capas y contienen páginas, como un cuaderno.)
• Letra de columna: como en otras aplicaciones del mismo tipo, una hoja de cálculo de
Quattro Pro utiliza letras (A, B, C, etc.) para identificar las columnas de celdas.
El programa crea cada letra de fila sucesiva con la llamada de la función siguiente:
CHR(ContCar)

406 Guía del programador


dde.dwp Page 407 Wednesday, August 30, 1995 2:27 PM

ContCar es una variable numérica que se incrementa una vez por cada fila sucesiva.
La función CHR( ) convierte este número a su equivalente de letra ASCII. El
programa inicializó ContCar con el comando siguiente:
ContCar = ASC("D")
Esto inicializa ContCar como 68; cuando se pasa a la función CHR( ), ésta devuelve
“D”, que es la letra de la primera columna.
• Número de fila: Quattro Pro utiliza números para identificar las filas de celdas.
Cada número de fila se guarda en ContFila, que se convierte al tipo carácter y se
eliminan los espacios iniciales con la siguiente llamada de función:
LTRIM(STR(ContFila))
El programa concatena estos tres elementos en la variable NombreCelda. El comando
siguiente utiliza NombreCelda como primer argumento del método Poke( ) para apuntar
a la celda e insertar el valor. El primer valor de NombreCelda es “A:D1”, que se traduce
como “Página A, Columna D, Fila 1”. El siguiente valor de NombreCelda es “A:E1”, es
decir “Página A, Columna E, Fila 1”. El proceso continúa hasta que todos los campos del
primer registro se escriben en sus celdas respectivas.
El bucle interior termina y la fila (controlada por la variable ContFila) se incrementa en 1.
ContCar se vuelve a definir como 68, y el registro siguiente se transfiere a las celdas de la
fila 2. La celda que recibirá el primer campo del segundo registro es A:D2.
El proceso continúa hasta que el último registro se escribe en la hoja de cálculo.

Envío de comandos al servidor


DDE ofrece más que un intercambio de datos entre cliente y servidor. También es
posible controlar la aplicación servidor mediante el envío de instrucciones en su propio
lenguaje con el método Execute( ).
PUBLIC MiObjVinculo && Disponible en toda la aplicación
MiObjVinculo = NEW DDELINK() && Crea un nuevo objeto DDE
MiObjVinculo.initiate
("QPW","C:\QPW\EJEMPLO\MIHOJA.WB1") && Comienza una sesión de
&& servidor DDE

MiObjVinculo.Execute("{OPEN "C:\DATOS.TXT", W}") && Crea el archivo de texto DATOS.TXT


MiObjVinculo.Execute("{WRITELN +A:A1}") && Envía la celda A:A1 a DATOS.TXT
MiObjVinculo.Execute("{WRITELN +A:A2}") && Envía la celda A:A2 a DATOS.TXT.
MiObjVinculo.Execute("{WRITELN +A:A3}") && Envía la celda A:A3 a DATOS.TXT.
MiObjVinculo.Execute("{CLOSE}") && Cierra DATOS.TXT

Capítulo 27, Expansión de dBASE mediante DDE/OLE 407


dde.dwp Page 408 Wednesday, August 30, 1995 2:27 PM

Este código de programa comienza una sesión en Quattro Pro, abre un archivo de hoja
de cálculo denominado MIHOJA.WB1 y establece un vínculo DDE entre la sesión de
Quattro Pro y la sesión actual de dBASE. El programa utiliza el método Execute( ) para
hacer lo siguiente:
1 Crea un nuevo archivo de texto denominado DATOS.TXT (comando {OPEN} de
Quattro Pro).
2 Envía el contenido de las celdas A1, A2 y A3 a DATOS.TXT (con el comando
{WRITELN} de Quattro Pro).
3 Cierre DATOS.TXT (con el comando {CLOSE} de Quattro Pro).

Creación de vínculos dinámicos


Un vínculo dinámico es un vínculo DDE especial que notifica al cliente que un elemento
especificado de un documento servidor sufre un cambio. Cree un vínculo dinámico con
el método Advise( ) y asigne un bloque de código o una subrutina a la propiedad
OnNewValue. dBASE ejecuta el bloque de código o subrutina cuando cambia el valor
del elemento especificado.
MiObjVinculo = NEW DDELINK() && Crea un nuevo objeto DDE
MiObjVinculo.OnNewValue = IdentifValor && Una subrutina dBASE
MiObjVinculo.Initiate("QPW","C:\QPW\EJEMPLOS\MIHOJA.WB1") && Comienza una sesión de
&& servidor DDE
MiObjVinculo.Advise("A:A1") && Se pide notificación de
&& cambios
* ... resto del programa...

FUNCTION IdentifValor && Rutina OnNewValue


PARAMETER Elem,Valor && Pasados automáticamente
? Elem," ha cambiado su valor a ",Valor && Genera una salida
RETURN .T.
Este código de programa utiliza la propiedad Advise para establecer un vínculo
dinámico con la celda A:A1 del archivo de cuaderno de Quattro Pro MIHOJA.WB1.
Mientras este programa se ejecuta, MiObjVinculo recibe notificaciones de los nuevos
valores que se introducen en la celda desde el servidor (Quattro Pro). La subrutina o
bloque de código que especifique para OnNewValue (en este caso, IdentifValor) se
ejecuta cada vez que cambia el valor de la celda A:A1.
Cuando el vínculo dinámico ya no es útil, puede cerrarlo con el método Unadvise( ).
MiObjVinculo.Unadvise("A:A1")

408 Guía del programador


dde.dwp Page 409 Wednesday, August 30, 1995 2:27 PM

Creación de aplicaciones servidor


Igual que dBASE puede utilizar una aplicación externa como servidor DDE, una
aplicación cliente externa puede utilizar dBASE como servidor DDE. La aplicación
cliente puede leer y escribir datos en un documento de dBASE (normalmente un archivo
de tabla o una QBE) y enviar directivas a la sesión de dBASE.
Cuando dBASE es la aplicación servidor, se utiliza un objeto DDETopic para realizar
acciones cuando se producen los sucesos de intercambio de datos. El objeto DDETopic
determina lo que sucede cuando dBASE recibe datos, instrucciones o peticiones de
datos.
Cree un objeto DDETopic con el operador NEW:
PUBLIC MiObjServ
MiObjServ = NEW DDETOPIC("MiTema") && La variable es MiObjServ, el tema es MiTema
También puede utilizar el comando DEFINE:
DEFINE DDETOPIC MiObjServ && Variable y tema son ambos MiObjServ
Tenga en cuenta que, al crear la variable de referencia a objeto con el operador NEW,
especifica el nombre del tema explícitamente, como con MiTema en el ejemplo anterior.
Cuando se utiliza el comando DEFINE, el tema recibe el mismo nombre que la variable
de referencia a objeto.
La Tabla 27.2 resume las propiedades de la clase DDETopic.
Tabla 27.2 Propiedades de DDETopic
Propiedad Descripción
ClassName Identifica la clase de objeto DDETopic
Notify( ) Notifica a una aplicación cliente que ha cambiado un elemento de dBASE
OnAdvise Ejecuta una subrutina cuando una aplicación externa solicita un vínculo dinámico
con el elemento de dBASE
OnExecute Ejecuta una subrutina cuando una aplicación cliente envía una directiva a dBASE
OnPeek Ejecuta una subrutina cuando una aplicación cliente solicita leer un valor de
dBASE
OnPoke Ejecuta una subrutina cuando una aplicación cliente solicita insertar un valor en
un elemento de datos de dBASE
OnUnadvise Ejecuta una subrutina cuando el cliente solicita eliminar un vínculo dinámico
Release( ) Elimina de la memoria la definición del objeto DDETopic
Topic Tema del objeto DDELink

Capítulo 27, Expansión de dBASE mediante DDE/OLE 409


dde.dwp Page 410 Wednesday, August 30, 1995 2:27 PM

Arranque de Visual dBASE desde una aplicación cliente


Antes de poder utilizar un DDETopic, debe residir en memoria, por lo que dBASE (la
aplicación servidor) debe ejecutar un programa de arranque antes de establecer el
vínculo DDE. La secuencia de sucesos es la siguiente:
1 El cliente inicia una sesión de dBASE (si no hay una disponible actualmente).
2 El cliente indica a dBASE que ejecute el programa de arranque. Este programa
contiene una rutina de controlador de inicialización, que se describe en la sección
siguiente.
3 El cliente inicia el vínculo DDE con un documento de dBASE.
Por ejemplo, si desea llamar dBASE desde Quattro Pro para Windows y ejecutar una
aplicación de arranque en dBASE, debería crear una rutina de macro en Quattro Pro
para Windows, como la siguiente:
{EXEC "DBASEWIN INICSES.PRG", 1}
{INITIATE "DBASEWIN", "MiTema", CHANNEL}
• INICSES.PRG es el nombre del programa dBASE que contiene la rutina de
controlador de inicialización.
• MiTema es el tema invocado por Quattro Pro. El cliente puede enviar cualquier cosa
como tema, pero con frecuencia contiene el nombre de una tabla o una QBE.
• CHANNEL es el nombre que ha asignado a una celda de una hoja de cálculo de
Quattro Pro. Esta celda contiene un número de identificación generado de forma
automática que diferencia el vínculo DDE de otros vínculos DDE que puedan existir.
La primera línea del código de la macro utiliza el comando {EXEC} de Quattro Pro para
arrancar una sesión de dBASE. El parámetro INICSES.PRG indica a dBASE que ejecute
INICSES.PRG inmediatamente después de iniciar la sesión de dBASE.
La segunda línea del código de la macro utiliza el comando {INITIATE} para establecer
un vínculo DDE entre Quattro Pro y la sesión de dBASE.

410 Guía del programador


dde.dwp Page 411 Wednesday, August 30, 1995 2:27 PM

Escritura de rutinas de controlador de inicialización


Al iniciar dBASE, se crea una copia de un objeto de aplicación de dBASE. Para hacer
referencia al objeto de aplicación, se utiliza la variable de memoria del sistema _app, que
contiene una referencia a un objeto.
Cuando un cliente ejecuta una solicitud Initiate (como hizo Quattro Pro con el comando
{INITIATE} en el ejemplo anterior), dBASE ejecuta la subrutina que asigne a OnInitiate,
una propiedad de control de sucesos del objeto de aplicación. Utilice la subrutina
OnInitiate para crear un objeto DDETopic, que sirve para controlar los sucesos del
servidor DDE.
El programa siguiente (INICSES.PRG del ejemplo anterior) hace que el objeto _app
responda al suceso OnInitiate creando una clase personalizada denominada
EXISTENDDETopic. También abre una tabla (EXISTEN.DBF), que contiene el número
de acciones que se poseen para varias corporaciones.
* INICSES.PRG
SET PROCEDURE TO PROGRAM(1) ADDITIVE
PUBLIC Avisar
_app.DdeServiceName = "SERVIDOREXISTEN" && Da un nombre a dBASE para su
&& registro con Windows.
_app.OnInitiate = ContrInic && Controlador de inicialización
SET EXACT OFF && Búsqueda no exacta
USE EXISTEN ORDER TAG NOMBRECOMP && Contiene el campo ACCIONES
SEEK "MiEmpresa" && Busca"MiEmpresa".

CLASS EXISTENDDETOPIC(tema) OF DDETOPIC(Tema) && Crea una clase


FUNCTION OnPeek (Elem) && Responde a una solicitud PEEK
IF Elem = "ACCIONES"
? "Número de acciones: ", ACCIONES && Mensaje de confirmación
? "Enviando número de acciones al cliente." && Mensaje de confirmación
RETURN ACCIONES && Envía el número al cliente
ELSE
? "No se envía nada al cliente." && Mensaje de confirmación
RETURN .F. && Envía FALSE al cliente
ENDIF

FUNCTION OnPoke (Elem, Val) && Respuesta a una solicitud POKE


? "POKE: ", Elem, Val && Mensaje de confirmación
REPLACE ACCIONES WITH Val && Hace el cambio
IF (Elem = Avisar) && ¿Es necesario avisar al cambiar
&& el valor de este elemento?
? "Advirtiendo al cliente del cambio." && Mensaje de confirmación
This.Notify(Avisar) && Notificación al cliente
ENDIF
RETURN .T.

FUNCTION OnAdvise (Elem) && Responde a una solicitud de aviso


? "Advertir al cliente cuando "
, Elem, " cambie." && Mensaje de confirmación
Advertir= Elem && Guarda el nombre del elemento a
&& controlar
RETURN .T.

Capítulo 27, Expansión de dBASE mediante DDE/OLE 411


dde.dwp Page 412 Wednesday, August 30, 1995 2:27 PM

FUNCTION OnUnadvise (Elem) && Responde a una solicitud de


&& terminar aviso
IF (Avisar = Elem) && ¿Es el elemento a controlar?
? "No advertir cuando ",Elem," cambie." && Mensaje de confirmación
Avisar = .F. && Se deja de controlar el elemento
ENDIF
RETURN .T.

FUNCTION OnExecute (Cmd) && Responde a instrucciones del


&& cliente
? "El comando solicitado es: ",Cmd && Mensaje de confirmación
DO CASE
CASE Cmd = "VENDER"
REPLACE ACCIONES WITH ACCIONES - 10 && Vende 10 acciones
CASE Cmd = "COMPRAR"
REPLACE ACCIONES WITH ACCIONES + 10 && Compra 10 acciones
OTHERWISE
? "No entiendo el comando recibido" && Mensaje de aviso
ENDCASE
? "Notificando al cliente el cambio de un
valor" && Mensaje de confirmación
This.Notify(Avisar) && Se comunica el cambio al cliente
? "El valor de salida es ", ACCIONES && Mensaje de confirmación
RETURN .T.
ENDCLASS

FUNCTION ContrInic (Tema) && Crea un objeto EXISTENDDETopic


Tema = UPPER(Tema)
xContrl = NEW EXISTENDDETopic(Tema) && Instancia de EXISTENDDETopic.
? "¡Servidor inicializado!" && Mensaje de confirmación
? "Mi tema es ", xHandler.Tema && Mensaje de confirmación
RETURN xContrl && Referencia al objeto creado
Este programa especifica subrutinas para las propiedades OnPeek, OnPoke, OnAdvise,
OnUnadvise y OnExecute del objeto DDETopic. Por ejemplo, cada vez que Quattro Pro
escribe un valor en un elemento de dBASE (con el comando {POKE} de Quattro Pro), la
subrutina OnPoke utiliza el comando ? para mostrar mensajes de confirmación y utiliza
la propiedad Notify( ) para indicar a Quattro Pro que el elemento ha cambiado.
Tenga en cuenta que la subrutina OnIntitiate (FUNCTION CONTRINIC) recibe un
parámetro, Tema. Este parámetro contiene el nombre del tema DDE que ha especificado
la aplicación cliente (Quattro Pro para Windows).
La propiedad DdeServiceName designa un nombre para el servidor dBASE. Esto
permite identificar un servidor dBASE en particular cuando existen varios servidores
dBASE. Por ejemplo, la definición de DdeServiceName como “SERVIDOREXISTEN” en
el ejemplo anterior permite que Quattro Pro inicie un vínculo DDE con el servidor
dBASE adecuado:
_app.DdeServiceName = "SERVIDOREXISTEN"

412 Guía del programador


dde.dwp Page 413 Wednesday, August 30, 1995 2:27 PM

La aplicación cliente utiliza este nombre (en lugar de “DBASEWIN”) para ejecutar una
solicitud Initiate. Por ejemplo, para crear un vínculo DDE con la sesión
SERVIDOREXISTEN (en lugar de con otra sesión), Quattro Pro podría ejecutar la
siguiente solicitud {INITIATE}:
{INITIATE "SERVIDOREXISTEN", "MiTema", CHANNEL}
Para crear un vínculo DDE con la otra sesión (cuya propiedad DdeServiceName aún
contiene el valor por defecto “DBASEWIN”), Quattro Pro podría ejecutar la siguiente
solicitud {INITIATE}:
{INITIATE "DBASEWIN", "MiTema", CHANNEL}

Acerca de OLE
Vínculo e incrustación de objetos (OLE) es una tecnología de Microsoft que no sólo
permite que las aplicaciones intercambien datos, sino también que compartan código
según ciertas normas específicas. Por ejemplo, observe el archivo IMAGENES.WFM en
el directorio de ejemplos de Visual dBASE. Cuando se ejecuta IMAGENES.WFM,
muestra imágenes gráficas de mapa de bits. El campo de imagen de la tabla es un campo
OLE que está "vinculado" con la aplicación Paintbrush de Windows. Cuando se hace clic
en la imagen gráfica, se ejecuta Paintbrush desde dentro de Visual dBASE para permitir
la edición de la imagen (consulte la Figura 27.1).
Observe cómo el título y los menús de Paintbrush reflejan su vínculo con la ficha de
Visual dBASE.
Figura 27.1 Paintbrush iniciado desde dentro de dBASE mediante OLE.

Capítulo 27, Expansión de dBASE mediante DDE/OLE 413


dde.dwp Page 414 Wednesday, August 30, 1995 2:27 PM

Uso de la automatización OLE


Las aplicaciones que permiten la automatización OLE, como Microsoft Office y las
aplicaciones de PerfectOffice de Novell, pueden iniciarse y controlarse desde Visual
dBASE mediante el uso de la clase OLEAutoClient. Por ejemplo, para crear un objeto
con acceso a todos los métodos de Word BASIC de Microsoft Word 6.0 , use el comando
siguiente:
oWordControl = NEW OLEAutoClient("Word.BASIC")
Este comando ejecuta Microsoft Word y crea un objeto de automatización OLE con
acceso a los métodos de Word BASIC. Es posible ver y utilizar los métodos desde
dBASE igual que desde Word. Si inspecciona el objeto, puede ver los métodos
disponibles, como en la Figura 27.2.
Figura 27.2 Métodos de Microsoft Word BASIC en un objeto OLEAutoClient

Los objetos OLEAutoClient no tienen que residir dentro de las fichas. También pueden
utilizarse dentro de los programas de dBASE y desde la ventana de comandos.
Las propiedades, sucesos y métodos de un objeto OLEAutoClient se determinan por la
aplicación servidor. Las aplicaciones OLE2 varían considerablemente en su
implementación, por lo que debería consultar la documentación de las aplicaciones
servidor para conocer detalles específicos. El programa siguiente demuestra cómo crear
un gráfico y situarlo en una ficha usando Excel.
* abre objetos
unaHoja = NEW OLEAutoClient("Excel.Sheet")
unaHoja.Application.Visible = .T.
USE animales
* crea rejilla
celda = unaHoja.Cells( 2, 1 )
celda.Value = "Tamaño"
celda = unaHoja.Cells( 3, 1 )
celda.value = "Peso"
SCAN
celda = unaHoja.celdas( 1, RECNO() + 1)
celda.value = animales->nombre
celda = unaHoja.celdas( 2, RECNO() + 1)
celda.value = animales->tamano
celda = unaHoja.celdas( 3, RECNO() + 1)
celda.value = animales->peso
ENDSCAN

414 Guía del programador


dde.dwp Page 415 Wednesday, August 30, 1995 2:27 PM

* crea gráfico
unGrafico = unaHoja.ChartObjects.Add(100, 100, 200, 200)
series = unGrafico.chart.SeriesCollection
series.Add("Hoja1!R1C1:R2C7",.T.)
series.Add("Hoja1!R3C1:R3C7",.T.)
* copia gráfico a dBASE
unGrafico.Copy()
unaHoja.application.quit()
* crea tabla temporal que contendrá el gráfico
temp1DBF = FUNIQUE()
CREATE (temp1DBF) STRUCTURE EXTENDED
APPEND BLANK
REPLACE field_name WITH "OLE", field_type WITH "G"
temp2DBF = FUNIQUE()
CREATE (temp2DBF) FROM (temp1DBF)
CLOSE TABLES
DELETE TABLE (temp1DBF)
* crea ficha con control OLE
USE (temp2DBF) NOSAVE
APPEND BLANK
f1 = new form()
ole = new ole(f1)
ole.width = f1.width
ole.height = f1.height
ole.datalink = "OLE"
f1.OnOpen = { ; KEYBOARD "{Ctrl+V}" CLEAR }
f1.OnClose = { ; CLOSE TABLES }
f1.open()
* fin de ejemplo de Excel

Capítulo 27, Expansión de dBASE mediante DDE/OLE 415


dde.dwp Page 416 Wednesday, August 30, 1995 2:27 PM

416 Guía del programador


p_apndx.dwp Page 417 Wednesday, August 30, 1995 2:28 PM

Parte

VI
Apéndices
ParteVI

• Apéndice A, “Ejecución de aplicaciones de dBASE para DOS”


• Apéndice B, “Mejora de las aplicaciones de dBASE para DOS”
• Apéndice C, “Uso de los juegos de caracteres y controladores de idioma”

Parte VI, Apéndices 417


p_apndx.dwp Page 418 Wednesday, August 30, 1995 2:28 PM

418 Guía del programador


dosprog.dwp Page 419 Wednesday, August 30, 1995 2:29 PM

Apéndice

Ejecución de aplicaciones
ApéndiceA
A
de dBASE para DOS
Visual dBASE es compatible con dBASE III PLUS, dBASE IV y dBASE 5.0 para DOS, y la
mayoría de aplicaciones escritas para dBASE DOS se ejecutan con pocos o ningún
cambio en el código fuente. Para comenzar, simplemente ejecute la aplicación y observe
cómo funciona.
Nota En este apéndice, dBASE III PLUS, dBASE IV y dBASE 5.0 para DOS se mencionan
como dBASE DOS cuando lo que se trata se aplica a todos ellos.
Debido a las diferencias inherentes entre el entorno basado en caracteres de DOS y el
entorno gráfico de Windows, las aplicaciones complejas pueden precisar algunos
cambios para que se ejecuten correctamente. Además, algunos comandos y funciones de
dBASE DOS no son adecuados para el entorno Windows y tienen unos equivalentes
nuevos.
Este apéndice proporciona consejos generales para ejecutar aplicaciones dBASE DOS.
Cuando esté preparado para mejorar la aplicación con las características de Visual
dBASE, consulte el Apéndice B.

Apéndice A, Ejecución de aplicaciones de dBASE para DOS 419


dosprog.dwp Page 420 Wednesday, August 30, 1995 2:29 PM

Escritorio de Visual dBASE y aplicaciones DOS


Cuando se ejecutan en dBASE IV o dBASE III PLUS, las aplicaciones ocupan toda la
pantalla. En Visual dBASE, se ejecutan dentro de ventanas. Por defecto, las aplicaciones
de dBASE III+/IV se ejecutan dentro del panel de resultados de la ventana de
comandos.
La Tabla 27.3 muestra una aplicación de ejemplo de dBASE IV, GESTION.PRG,
ejecutándose en Visual dBASE.
Tabla 27.3 Aplicación de ejemplo de dBASE IV ejecutándose en dBASE para Windows

Tenga en cuenta los puntos siguientes:


• Por defecto, la ventana de comandos muestra la salida con una fuente fija (no
proporcional). Los objetos de pantalla se alinean igual que cuando se ejecutan en
dBASE III+/IV.
• El nombre del programa principal se convierte en el título del panel de resultados de
la ventana de comandos.
• El soporte del ratón es automático.
• Si necesita cambiar el tamaño del panel de resultados de la ventana de comandos
para que coincida con el número de líneas y columnas bajo el DOS, utilice SET
DISPLAY en el programa o cámbielo en DBASEWIN.INI. Para más información
sobre DBASEWIN.INI, consulte el Apéndice C de la Guía del usuario.

420 Guía del programador


dosprog.dwp Page 421 Wednesday, August 30, 1995 2:29 PM

Pasos básicos
A continuación se resumen los pasos que debe seguir para ejecutar las aplicaciones de
dBASE DOS. Cada paso se explica en profundidad en las secciones siguientes.
1 Tipos de archivos permitidos. Copie los archivos DOS necesarios en un directorio que
esté en la vía de acceso o añada el directorio en que se hallan los archivos en la vía de
acceso actual de Visual dBASE.
2 Entorno de configuración de Windows. Si es necesario, cambie los valores por defecto
definidos en el entorno DOS a las definiciones correspondientes de Windows.
Asegúrese de que la aplicación no precisa el uso de ciertas pulsaciones que tienen un
significado diferente en el entorno Windows.
3 Mejoras en el lenguaje. Algunos de los comandos permitidos funcionan en Visual
dBASE de forma distinta a como lo hacen en dBASE DOS. Podrían tener valores por
defecto distintos, valores máximo y mínimo diferentes o nuevas opciones. Revise esta
sección para comprobar si algunos de estos cambios afectan a sus aplicaciones.
4 Diferencias en los códigos de error. Muchos códigos de error en Visual dBASE son los
mismos que en dBASE IV y dBASE 5.0 para DOS, aunque algunos han cambiado. Si
una aplicación utiliza comandos ON ERROR, lea esta sección para averiguar si debe
actualizar algún código de error en la aplicación.

Tipos de archivos permitidos


Visual dBASE lee las tablas y sus archivos asociados, como los índices, y los archivos de
código fuente de dBASE DOS. Los archivos compilados de código fuente (como .DBO) y
otros archivos generados por los diseñadores de dBASE IV (como .SCR) no están
permitidos.
La Tabla A.1 resume los tipos de archivos de dBASE DOS que puede utilizar Visual
dBASE. El Apéndice A de la Guía del usuario describe todos los tipos de archivos de
dBASE DOS que están permitidos. Además, la ayuda en línea contiene una lista
completa de los tipos de archivos utilizados por Visual dBASE.
Tabla A.1 Resumen del soporte de los tipos de archivos de dBASE DOS
Tipo de archivo Extensiones
Programa .PRG (cualquier archivo de código fuente),
.MEM
Datos .DBF, .DBT, .MDX, .NDX
Pantalla o formato .FMT
de pantalla
Consulta .QBE, .VUE, (.QRY sólo para dBASE III PLUS)
Informe .FRG, (.FRM sólo para dBASE III)
Etiqueta .LBG, (.LBL sólo para dBASE III)
Catálogo .CAT

Copie los archivos que necesite a un directorio que esté incluido en la vía de acceso de
Visual dBASE o añada su posición actual en la vía de acceso.

Apéndice A, Ejecución de aplicaciones de dBASE para DOS 421


dosprog.dwp Page 422 Wednesday, August 30, 1995 2:29 PM

Entorno de configuración de Windows


El entorno Windows proporciona recursos estándar, como controladores de impresora
y pantalla, que las aplicaciones Windows pueden compartir. Visual dBASE utiliza estos
controladores compartidos, facilitando la escritura de aplicaciones independientes de
los dispositivos. Además, ciertas pulsaciones, como Alt+F4, están reservadas en
Windows y no debería utilizarlas en sus aplicaciones. Para más información sobre las
pulsaciones reservadas, consulte la documentación de Windows.

DBASEWIN.INI
Algunas aplicaciones de dBASE DOS dependen de definiciones específicas guardadas
en el archivo de configuración CONFIG.DB, pero Visual dBASE no lo lee, sino que
utiliza un archivo .INI, DBASEWIN.INI, para la configuración. Los archivos .INI son
archivos de texto que las aplicaciones Windows emplean para almacenar las
definiciones de configuración. El propio Windows utiliza WIN.INI y SYSTEM.INI (entre
otros) para configurar su entorno.
Algunas definiciones de CONFIG.DB tienen equivalentes en DBASEWIN.INI. Otras se
toman de Windows y no son necesarias en DBASEWIN.INI. Si una aplicación depende
de los parámetros de CONFIG.DB, debe ajustar el código o añadir definiciones
equivalentes en el archivo DBASEWIN.INI. El Apéndice C de la Guía del usuario describe
todos los parámetros de DBASEWIN.INI.

Referencias a controladores de impresora


Si un programa hace referencia a archivos de controlador de impresora (.PR2) de dBASE
IV o dBASE 5.0 para DOS definiendo la variable de memoria del sistema _pdriver, debe
actualizar la sintaxis con el controlador de impresora correspondiente en Windows. La
entrada de _pdriver en la Referencia del lenguaje describe la nueva sintaxis. El ejemplo
siguiente muestra cómo actualizar la sintaxis de _pdriver para una popular impresora
láser.
Cambie la primera línea por la segunda:
_pdriver = "HPLAS260.PR2"

_pdriver = "HPPCL, HP LaserJet Series II"


La variable _pdriver es la única variable de memoria del sistema relacionada con la
impresión que necesita cambiar. Otras, como _lmargin y _alignment, se asignan de
forma automática al valor correspondiente del controlador de impresora de Windows.
Para una explicación completa de la impresión en las aplicaciones de Visual dBASE,
consulte el Capítulo 25.

422 Guía del programador


dosprog.dwp Page 423 Wednesday, August 30, 1995 2:29 PM

Formatos por defecto de los datos


dBASE DOS tiene sus propios valores por defecto para el formato de las fechas, horas,
monedas y números. Visual dBASE no incorpora valores por defecto para esos
parámetros, sino que utiliza los definidos por la opción Internacional del Panel de
control de Windows. El Panel de control es una utilidad que personaliza y configura el
entorno Windows. La mayoría de aplicaciones desarrolladas para este entorno permiten
que los usuarios especifiquen el formato en el Panel de control, en lugar de incluir
valores por defecto propios.
Si nunca ha utilizado el Panel de control, busque su icono en el grupo Principal. Para
información sobre su uso, consulte la documentación de Windows.
Si su aplicación precisa un cierto formato por defecto, puede sustituir el valor actual del
Panel de control de dos formas:
• Puede especificar parámetros en el archivo de configuración de Visual dBASE,
DBASEWIN.INI. El Apéndice C de la Guía del usuario describe todos los parámetros
de DBASEWIN.INI.
• Puede ejecutar los comandos SET adecuados en su programa. Estos comandos SET
prevalecen sobre los valores por defecto del Panel del control:
• SET CENTURY formatea la parte de año de las fechas.
• SET CURRENCY LEFT|RIGHT sitúa los símbolos monetarios a la izquierda o a la
derecha de un valor monetario.
• SET CURRENCY TO especifica el carácter o caracteres que se utilizan como
símbolo monetario.
• SET DATE formatea las fechas.
• SET MARK especifica el carácter usado para separar el mes, día y año en las
fechas.
• SET POINT especifica el carácter que separa la parte decimal de la parte entera en
los valores numéricos.
• SET SEPARATOR especifica el carácter que separa cada grupo de tres cifras en la
parte entera de los valores numéricos iguales o mayores que 1000.

Apéndice A, Ejecución de aplicaciones de dBASE para DOS 423


dosprog.dwp Page 424 Wednesday, August 30, 1995 2:29 PM

La Figura A.1 muestra cómo corresponden estos comandos SET a los parámetros de la
opción Internacional.
Figura A.1 Comandos que anulan los parámetros de la opción Internacional del Panel de control

SET CENTURY SET CURRENCY LEFT|RIGHT


SET DATE SET CURRENCY TO
SET MARK
SET POINT
SET SEPARATOR

Conflictos con las pulsaciones de Windows


Ciertas combinaciones de pulsaciones son comunes a todas las aplicaciones Windows.
Por ejemplo, Alt+F4 cierra la ventana o cuadro de diálogo activo. Su aplicación no debería
asignar otro comportamiento a estas combinaciones de teclas reservadas.

Mejoras en el lenguaje
Algunos comandos de dBASE DOS son innecesarios en el entorno Windows y se han
suprimido. Otros se han sustituido por nuevos comandos o funciones. El Apéndice A de
la Referencia del lenguaje enumera todos los comandos de dBASE III+/IV que Visual
dBASE ya no utiliza.
Los comandos para los que no hay soporte en Visual dBASE no producen errores
durante la compilación ni durante la ejecución, ni tienen efecto alguno en el programa.
Visual dBASE simplemente no los tiene en cuenta.
Aunque una aplicación se ejecute bajo Visual dBASE sin cambios, debería repasar la lista
de comandos no permitidos de la Referencia del lenguaje. Si hay una parte concreta de la
aplicación que dependa de uno de estos comandos, deberá modificar el código
ligeramente para que refleje el nuevo entorno en que se ejecuta la aplicación.
Por ejemplo, si la aplicación utiliza bases de datos SQL, habrá utilizado los comandos
BEGIN TRANSACTION y END TRANSACTION para controlar cuándo se escriben los
cambios en un registro. Puesto que Visual dBASE está diseñado para programación
controlada por sucesos, estos comandos se han sustituido por funciones denominadas
BEGINTRANS(), COMMIT() y ROLLBACK(). Los comandos antiguos no devuelven un
error, pero no tienen el mismo efecto que tenían en dBASE IV o dBASE 5.0 para DOS.

424 Guía del programador


dosprog.dwp Page 425 Wednesday, August 30, 1995 2:29 PM

Evitar conflictos de funciones


Los programadores suelen escribir sus propias funciones, denominadas funciones
definidas por el usuario o UDF, para realizar tareas que no son estándar en el lenguaje
dBASE. Puesto que Visual dBASE proporciona muchas funciones nuevas que no se
encontraban en dBASE DOS, debe asegurarse de que las UDF que ha creado no tengan
el mismo nombre que una función estándar de Visual dBASE. El Apéndice A de la
Referencia del lenguaje enumera las funciones añadidas en dBASE después de dBASE IV.

Cambios específicos del lenguaje


Cada entrada de la Referencia del lenguaje tiene una sección Compatibilidad que indica
las diferencias que hay entre Visual dBASE y dBASE III+/IV.
Además, el Apéndice A de la Referencia del lenguaje enumera todos los comandos de
dBASE que se han mejorado con nuevas opciones después de dBASE III+/IV. Consulte
esta lista para familiarizarse con algunas de las nuevas posibilidades que se han
incorporado en comandos que posiblemente ya ha utilizado. Estas opciones nuevas
podrían permitirle escribir código con mejor eficacia en el futuro.

Diferencias en los códigos de error


La mayoría de los códigos de error, incluidos los errores multiusuario, son los mismos
que en dBASE IV y dBASE 5.0 para DOS, aunque algunos han cambiado. Estos cambios
son inevitables en la transición al entorno Windows. Si su programa capta ciertos
códigos de error en tiempo de ejecución, quizá deba actualizarlos.
Para ver una lista de los códigos de error en tiempo de ejecución de Visual dBASE,
consulte la ayuda en línea.

Apéndice A, Ejecución de aplicaciones de dBASE para DOS 425


dosprog.dwp Page 426 Wednesday, August 30, 1995 2:29 PM

426 Guía del programador


dos2win.dwp Page 427 Wednesday, August 30, 1995 2:30 PM

Apéndice

Mejora de las aplicaciones de


Apéndice B
B
dBASE para DOS
El Apéndice A describe cómo ejecutar aplicaciones de dBASE para DOS. Después de
conseguir que un programa se ejecute, probablemente desee mejorarlo para que tenga el
aspecto y comportamiento de una aplicación Windows.
Cada capítulo de este manual describe las características de programación de Visual
dBASE y proporciona consejos para los programadores de dBASE DOS. Para ayudarle a
comenzar, este apéndice muestra cómo mejorar una aplicación típica de dBASE IV con
una breve presentación de las características de Visual dBASE. Para una explicación más
detallada de los temas planteados aquí, consulte el capítulo correspondiente de este
manual.
Nota Este apéndice utiliza dBASE DOS para hacer referencia a las aplicaciones de dBASE III
PLUS, dBASE IV y dBASE 5.0 para DOS.
Consulte el Apéndice A de la Referencia del lenguaje para ver una lista de:
• Nuevos elementos del lenguaje
• Elementos del lenguaje mejorados
• Elementos del lenguaje que ya no se utilizan

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 427


dos2win.dwp Page 428 Wednesday, August 30, 1995 2:30 PM

El Generador de componentes
El Generador de componentes es una utilidad que traduce el código generado por las
utilidades de diseño de dBASE IV al formato utilizado por las de Visual dBASE.
Aunque estos archivos se ejecutan tal cual, si los procesa con el Generador de
componentes, podrá mejorarlos con los controles estándar en Windows, como botones
y cuadros de lista.
Mediante el Generador de componentes, puede convertir con facilidad los archivos de
formato, informe y etiqueta de dBASE IV al entorno Windows. Después de ejecutar el
Generador de componentes, cargue el código generado en las utilidades de diseño de
Visual dBASE como el Diseñador de fichas y el Diseñador de menús.
El Generador de componentes es un programa de Visual dBASE instalado en el
subdirectorio UTIL. Ejecútelo con el archivo de programa CB.PRO. Para instrucciones
sobre el uso del Generador de componentes, consulte su ayuda en línea.

Estrategias de mejora
Los bloques en que se basan las aplicaciones de Visual dBASE son las fichas, que son
ventanas que contienen objetos con los que interactúa el usuario. Su principal labor en la
mejora de una aplicación es crear las fichas que constituyen el interface de usuario.
Para detalles sobre la creación de fichas con el Diseñador de fichas, consulte la Guía del
usuario. Para una explicación del código utilizado por el Diseñador de fichas, consulte
los Capítulos 13 a 16 de este manual.
Los pasos que siga dependen principalmente del tipo de interface de usuario que desee
desarrollar. Como se describe en el Capítulo 13, hay tres planteamientos básicos:
• Un interface modal es como el de una aplicación de dBASE III PLUS o dBASE IV.
El usuario interactúa con una ficha cada vez, y cada ficha tiene el control exclusivo
del escritorio. Es muy fácil mejorar una aplicación en un interface modal.
• Un interface no modal está completamente controlado por sucesos. El usuario puede
interactuar con más de una ficha, o ejecutar más de un programa, simultáneamente.
La conversión de su aplicación a un interface no modal podría precisar alguna
reestructuración del código para hacerla completamente controlada por sucesos.
• Un interface no modal orientado a objetos también está controlado por sucesos. La
conversión de su aplicación a un interface orientado a objetos implica la traducción
del código a una serie de declaraciones de clase, que son muy reutilizables.
El Diseñador de fichas emplea este planteamiento, generando una declaración de
clase para cada ficha que cree.
Este apéndice trata las cuestiones que afrontará al convertir una aplicación a un interface
modal que utiliza fichas. Comienza mostrando una aplicación simple primero en
dBASE IV y luego mejorada en Visual dBASE. A continuación, describe las diferencias
principales entre las dos versiones. Para mejorar aún más la aplicación y reprogramarla
para un interface no modal u orientado a objetos, consulte la comparación de los tres
planteamientos en el Capítulo 13.

428 Guía del programador


dos2win.dwp Page 429 Wednesday, August 30, 1995 2:30 PM

Programa de dBASE IV de ejemplo


El programa de dBASE IV de ejemplo, CLIdB4.PRG, muestra una ventana que solicita al
usuario un nombre para su búsqueda. Si el usuario no introduce nada, desaparece la
ventana y termina el programa. Si el usuario introduce un nombre, el programa lo busca
en la tabla de clientes. Si se encuentra el nombre, otra ventana muestra el nombre y
dirección para su edición.
La Figura B.1 muestra el programa ejecutándose en la ventana de comandos de Visual
dBASE.
Figura B.1 Ejecución de CLIdB4.PRG en Visual dBASE

El siguiente es el código fuente de CLIDB4.PRG:


* ClidB4.prg - Agenda en dBASE IV
SET TALK OFF
CLEAR
SET SCOREBOARD OFF
SET STATUS OFF
PUBLIC nLongNombre= 30

SET VIEW TO EDITCLI.QBE


DEFINE WINDOW EDITCLI FROM 1, 1 TO 17, 48
DEFINE WINDOW BUSCACLI FROM 5, 1 TO 11, 48
ACTIVATE WINDOW BUSCACLI
BuscaMas= .T.
DO WHILE BuscaMas
cNombre = SPACE(nLongNombre)
@ 2, 1 SAY "Introduzca un nombre:"
@ 2, 15 GET cNombre
COLOR ,N/GB
READ
@ 0, 5 CLEAR TO 0, 40
BuscaMas = BuscaNombre()
ENDDO
DEACTIVATE WINDOW BUSCACLI

*---------------- Declaración de procedimientos y funciones-*

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 429


dos2win.dwp Page 430 Wednesday, August 30, 1995 2:30 PM

FUNCTION BuscaNombre
IF LEN(TRIM(cNombre) > 0
SEEK UPPER(TRIM(cNombre))
IF .NOT. FOUND()
? CHR(7)
@ 0, 11 SAY "Nombre no encontrado. Inténtelo de nuevo"
ELSE
DO EDITA
ENDIF
BuscaMas = .T.
ELSE
BuscaMas = .F.
ENDIF
RETURN BuscaMas

PROCEDURE EDITA
ACTIVATE WINDOW EDITCLI
@ 2, 2 SAY "Nombre"
@ 2, 14 GET CLIENTE->NOMBRE;
COLOR ,N/GB
@ 4, 2 SAY "Calle"
@ 4, 14 GET CLIENTE->CALLE;
COLOR ,N/GB
@ 6, 2 SAY "Ciudad"
@ 6, 14 GET CLIENTE->CIUDAD
COLOR ,N/GB
@ 8, 2 SAY "Provincia"
@ 8, 14 GET CLIENTE->PROVINCIA;
COLOR ,N/GB
@ 10,2 SAY "C. Postal"
@ 10,14 GET CLIENTE->COD_POST;
PICTURE "!!!!!!!!!!";
COLOR ,N/GB
@ 12,2 SAY "Teléfono"
@ 12,14 GET CLIENTE->TELEFONO;
PICTURE "999-999-9999";
COLOR ,N/GB
READ
DEACTIVATE WINDOW EDITCLI
RETURN

430 Guía del programador


dos2win.dwp Page 431 Wednesday, August 30, 1995 2:30 PM

Programa de dBASE IV de ejemplo mejorado


CLIDBW.PRG es una versión mejorada de CLIDB4.PRG diseñada para Visual dBASE.
La Figura B.2 muestra el programa ejecutándose en el escritorio de Visual dBASE:
Figura B.2 Ejecución de CLIDBW.PRG en Visual dBASE

El siguiente es el código fuente de CLIDBW.PRG:


* ClidBW.prg - Agenda en dBASE IV adaptada a dBASE para Windows
SET CUAENTER OFF
#define ColorEntrada "N/GB"
#define nLongNombre 30
*-------------- Crea la ficha BuscaCli--------------*
DEFINE FORM BuscaCli FROM 5,5 TO 15,80;
PROPERTY EscExit .T.,;
Text "Buscar un cliente",;
MDI .F.

DEFINE TEXT TEXT1 OF BuscaCli AT 1,2;


PROPERTY ColorNormal "N/W",;
Text "Introduzca un nombre",;
Width 30

DEFINE ENTRYFIELD ENTRADA1 OF BuscaCli AT 1,34;


PROPERTY Value SPACE(nLongNombre),;
PROPERTY Text "Aceptar",;
ColorNormal EntryColor

*---------------- Fin de la ficha BuscaCli-------*

*---------------- Crea la ficha EditCli-- ------------*


DEFINE FORM EDITCLI FROM 12.5, 24.5 TO 31.5, 70.38;
PROPERTY EscExit .T.,;
View "EDITCLI.QBE",;
Text "Editar clientes",;
MDI .F.

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 431


dos2win.dwp Page 432 Wednesday, August 30, 1995 2:30 PM

DEFINE TEXT TEXT1 OF EditCli AT 1,2;


PROPERTY Text "Nombre"

DEFINE ENTRYFIELD ENTRADA1 OF EditCli AT 1,13;


PROPERTY DataLink "CLIENTE->NOMBRE",;
ColorNormal ColorEntrada

DEFINE TEXT TEXT2 OF EditCli AT 3,2;


PROPERTY Text "Calle"

DEFINE ENTRYFIELD ENTRADA2 OF EditCli AT 3,13;


PROPERTY DataLink "CLIENTE->CALLE",;
ColorNormal ColorEntrada

DEFINE TEXT TEXT3 OF EditCli AT 5,2;


PROPERTY Text "Ciudad"

DEFINE ENTRYFIELD ENTRADA3 OF EditCli AT 5,13;


PROPERTY DataLink "CLIENTE->CIUDAD",;
ColorNormal ColorEntrada

DEFINE TEXT TEXT4 OF EditCli AT 7,2;


PROPERTY Text "Provincia"

DEFINE ENTRYFIELD ENTRADA4 OF EditCli AT 7,13;


PROPERTY DataLink "CLIENTE->PROVINCIA",;
ColorNormal ColorEntrada

DEFINE TEXT TEXT5 OF EditCli AT 9,2;


PROPERTY Text "C. Postal"

DEFINE ENTRYFIELD ENTRADA5 OF EditCli AT 9,13;


PROPERTY DataLink "CLIENTE->COD_POST",;
ColorNormal ColorEntrada,;
Picture "!!!!!!!!!!"

DEFINE TEXT TEXT6 OF EditCli AT 11,2;


PROPERTY Text "Teléfono"

DEFINE ENTRYFIELD ENTRADA6 OF EditCli AT 11,13;


PROPERTY DataLink "CLIENTE->TELEFONO",;
ColorNormal ColorEntrada,;
Picture "999-999-9999"

DEFINE PUSHBUTTON PULSADOBUSCAR OF EditCli FROM 15,17 TO 16,27;


PROPERTY Text "Buscar",;
OnClick PULSADOBUSCAR_ONCLICK
*---------------- Fin de la ficha EditCli------*

432 Guía del programador


dos2win.dwp Page 433 Wednesday, August 30, 1995 2:30 PM

BuscaCli.ReadModal()

*---------------- Declaración de procedimientos------*


PROCEDURE PulsadoBuscar
EditCli.Entrada1.SetFocus()
EditCli.READMODAL()
RETURN

PROCEDURE PulsadoOK
IF .NOT. EMPTY(form.Entrada1.Value)
SEEK(UPPER(TRIM(form.Entrada1.Value)))
IF .NOT. FOUND()
? CHR(7)
form.StatusMessage = "Nombre no encontrado. Inténtelo otra vez"
form.Entrada1.SetFocus()
ELSE
form.StatusMessage = ""
form.Entrada1.Value = SPACE(nLongNombre)
CLOSE FORM BuscaCli
ENDIF
ELSE
form.StatusMessage = ""
CLOSE FORM BuscaCli
ENDIF
RETURN

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 433


dos2win.dwp Page 434 Wednesday, August 30, 1995 2:30 PM

Explicación de las diferencias en el código


Las secciones siguientes extraen varios segmentos de código de los programas de
dBASE IV y Visual dBASE listados antes y los compara, señalando las diferencias en el
código y dónde puede encontrar más información en este manual.

Inicialización
La mayoría de programas de dBASE comienzan con comandos que inicializan variables
públicas y configuran el entorno. Las aplicaciones de Visual dBASE utilizan pocas
variables globales, y muchas definiciones globales se especifican mediante propiedades
de objetos o simplemente no son operativas.
La aplicación de dBASE IV comienza con varios comandos SET y la declaración de una
variable pública. La aplicación de Visual dBASE comienza con directivas de
preprocesador y un solo comando SET.
Figura B.3 Comparación del código de inicialización

dBASE IV dBASE para Windows

SET TALK OFF SET CUAENTER OFF


CLEAR #define ColorEntrada "N/GB"
SET SCOREBOARD OFF #define nLongNombre 30
SET STATUS OFF
PUBLIC nLongNombre
nLongNombre= 30

• En dBASE IV, SET TALK OFF desactiva la salida de comandos y CLEAR borra la
pantalla. En Visual dBASE, la salida de comandos sólo aparece en el panel de
resultados de la ventana de comandos. Puesto que las fichas aparecen en el escritorio,
estos comandos no son necesarios.
• En la aplicación de dBASE IV, SET SCOREBOARD OFF y SET STATUS OFF
desactivan la visualización de la información de estado, que aparece en el área
direccionable de la pantalla y puede entrar en conflicto con la salida en pantalla.
En Visual dBASE, esta información aparece en la línea de estado, área no
direccionable del entorno. Puesto que no puede haber conflicto con la salida en
pantalla, estos comandos no son operativos.

434 Guía del programador


dos2win.dwp Page 435 Wednesday, August 30, 1995 2:30 PM

• En las aplicaciones Windows, la tecla Intro suele aceptar fichas, de forma similar a
Ctrl+W en dBASE III PLUS o Ctrl+Fin en dBASE IV. El nuevo comando de Visual
dBASE, SET CUAENTER OFF, hace que la tecla Intro se comporte como en
dBASE DOS, moviendo el foco al siguiente control disponible.
En general, debería dejar SET CUAENTER con su valor por defecto ON; sin
embargo, para facilitar la transición a las pulsaciones de Windows, puede definirlo
como OFF al principio de los programas convertidos. Para ver una lista de las
pulsaciones empleadas en Visual dBASE, consulte el Apéndice B de la Guía del
usuario.
• La aplicación de dBASE IV inicializa nLongNombre como variable pública. En Visual
dBASE, las constantes globales suelen definirse mediante #define, una directiva del
preprocesador. Para más información sobre estas directivas, consulte el Capítulo 7.

Creación de la ventana
En ocasiones, las aplicaciones de dBASE IV crean ventanas para mostrar datos.
En Visual dBASE, toda la interacción del usuario se produce dentro de las fichas, que
son ventanas del tipo Windows que contienen objetos del interface de usuario.
Para más información sobre la programación con diferentes tipos de ventanas de ficha,
consulte el Capítulo 13.
La aplicación de dBASE IV crea una ventana para editar clientes; la de Visual dBASE
hace lo mismo con una ficha. Ambas aplicaciones utilizan EDITCLI.QBE para
configurar el entorno de la tabla.
Figura B.4 Comparación de la creación de la ventana

dBASE IV dBASE para Windows

SET VIEW TO EDITCLI.QBE DEFINE FORM EDITCLI;


DEFINE WINDOW EDITCLI; FROM 12.5, 24.5 TO 31.5, 70.38;
PROPERTY EscExit .T.,;
FROM 1, 1 TO 17, 48
View "EDITCLI.QBE",;
Text "Editar clientes",;
MDI .F.

• Las ventanas creadas con DEFINE WINDOW aparecen en el panel de resultados de


la ventana de comandos. Las ventanas de ficha creadas con DEFINE FORM aparecen
en el escritorio.

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 435


dos2win.dwp Page 436 Wednesday, August 30, 1995 2:30 PM

• En dBASE IV, DEFINE WINDOW simplemente crea una ventana donde aparece la
salida, como los comandos @...SAY. En Visual dBASE, DEFINE FORM crea un objeto
de ficha con muchas propiedades que puede definir. Este ejemplo define las
siguientes propiedades de ficha:
• La propiedad EscExit, definida como .T., permite salir de la ficha pulsando Esc.
• La propiedad Text muestra un mensaje en la barra de título de la ventana.
• La propiedad MDI debe definirse como .F. para las ventanas no modales.
El Capítulo 13 describe las características de las ventanas MDI.
• En dBASE IV, el comando SET VIEW configura el entorno de la tabla en un archivo
.QBE. En Visual dBASE, el mismo archivo .QBE se vincula con la ficha mediante la
propiedad View de la ficha.

SAY/GET y objetos de datos


Las aplicaciones de dBASE IV utilizan comandos @...GET para mostrar campos y
comandos @...SAY para rotularlos. Las aplicaciones de Visual dBASE utilizan campos de
entrada y otros objetos para mostrar campos, y objetos de texto para rotularlos.
Para más información sobre la colocación de objetos en las fichas, consulte los capítulos
que tratan del Diseñador de fichas en la Guía del usuario.
Ambas aplicaciones muestran campos de la tabla de clientes. La aplicación de dBASE IV
los muestra en la ventana EditCli y la de Visual dBASE los muestra en la ficha EditCli.

436 Guía del programador


dos2win.dwp Page 437 Wednesday, August 30, 1995 2:30 PM

Figura B.5 Comparación de SAY/GET y objetos de datos

dBASE IV dBASE para Windows


ACTIVATE WINDOW EDITCLI DEFINE TEXT TEXTO1 OF EditCli AT 2,2;
@ 2, 2 SAY "Nombre" PROPERTY Text "Nombre"
@ 2, 14 GET CLIENTE->NOMBRE; DEFINE ENTRYFIELD ENTRADA1 OF EditCli AT 2,14;
COLOR N/GB PROPERTY DataLink "CLIENTE->NOMBRE",;
ColorNormal ColorEntrada
@ 4, 2 SAY "Calle"
DEFINE TEXT TEXTO2 OF EditCli AT 2,2;
@ 4, 14 GET CLIENTE->CALLE; PROPERTY Text "Calle"
COLOR N/GB
DEFINE ENTRYFIELD ENTRADA2 OF EditCli AT 2,14;
@ 6, 2 SAY "Ciudad" PROPERTY DataLink "CLIENTE->CALLE",;
@ 6, 14 GET CLIENTE->CIUDAD; ColorNormal ColorEntrada
COLOR N/GB DEFINE TEXT TEXTO3 OF EditCli AT 2,2;
@ 8, 2 SAY "Provincia" PROPERTY Text "Ciudad"
@ 8, 14 GET CLIENTE->PROVINCIA; DEFINE ENTRYFIELD ENTRADA3 OF EditCli AT 2,14;
COLOR N/GB PROPERTY DataLink "CLIENTE->CIUDAD",;
ColorNormal ColorEntrada
@ 10, 2 SAY "C. Postal"
DEFINE TEXT TEXTO4 OF EditCli AT 2,2;
@ 10, 14 GET CLIENTE->COD_POST; PROPERTY Text "Provincia"
PICTURE "!!!!!";
COLOR N/GB DEFINE ENTRYFIELD ENTRADA4 OF EditCli AT 2,14;
PROPERTY DataLink "CLIENTE->`PROVINCIA",;
@ 12, 2 SAY "Teléfono" ColorNormal ColorEntrada
@ 12, 14 GET CLIENTE->TELEFONO; DEFINE TEXT TEXTO5 OF EditCli AT 2,2;
PICTURE "999-99-99"; PROPERTY Text "C. Postal"
COLOR N/GB
DEFINE ENTRYFIELD ENTRADA5 OF EditCli AT 2,14;
PROPERTY DataLink "CLIENTE->COD_POST",;
ColorNormal ColorEntrada,;
Picture "!!!!!"
DEFINE TEXT TEXTO6 OF EditCli AT 2,2;
PROPERTY Text "Teléfono"
DEFINE ENTRYFIELD ENTRADA6 OF EditCli AT 2,14;
PROPERTY DataLink "CLIENTE->TELEFONO",;
ColorNormal ColorEntrada,;
Picture "999-99-99"

• En dBASE IV, los comandos @...SAY/GET aparecen en la ventana activa, si la hay.


La aplicación de dBASE IV activa la ventana EditCli. En Visual dBASE, el campo de
entrada, el texto y los demás objetos del interface de usuario tienen que aparecer en
una ficha. Utilice la opción OF del comando DEFINE para especificar la ficha que
aparece el objeto. La aplicación de Visual dBASE especifica la ficha EditCli.
• En dBASE IV, el nombre del campo se especifica después de GET. En Visual dBASE,
un campo de entrada se vincula con un campo de una tabla mediante la propiedad
DataLink.
• En dBASE IV, las coordenadas aparecen después del signo @. En Visual dBASE, se
sitúan después de la opción AT. Como alternativa a la opción AT, puede especificar
coordenadas con las propiedades Top y Left.
• Las opciones PICTURE y COLOR de dBASE IV corresponden a las propiedades
Picture y ColorNormal de Visual dBASE.

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 437


dos2win.dwp Page 438 Wednesday, August 30, 1995 2:30 PM

Variables de memoria
El uso de variables de memoria se mejora significativamente en Visual dBASE. Para la
introducción y visualización de datos, se utiliza la propiedad Value de los objetos en
lugar de crear variables de memoria globales. Si utiliza variables de memoria
tradicionales, puede gestionarlas con mayor eficacia mediante los dos nuevos ámbitos:
local y estático. El Capítulo 5 describe en detalle los ámbitos de las variables de
memoria.
Ambas aplicaciones piden al usuario un nombre que buscar.
Figura B.6 Comparación de las variables de memoria

dBASE IV dBASE para Windows


cNombre = SPACE(nLongNombre) DEFINE TEXT TEXTO1 OF BuscaCli AT 1,2;
PROPERTY Text "Introduzca un nombre"
@ 2, 1 SAY "Introduzca un nombre:"
DEFINE ENTRYFIELD ENTRADA1 OF BuscaCli AT
@ 2, 15 GET cNombre;
COLOR N/GB 1,14;
PROPERTY Value SPACE(nLongNombre),;
ColorNormal ColorEntrada

La aplicación de dBASE IV inicializa una variable de memoria y la muestra con un


comando @...GET. La aplicación de Visual dBASE no crea variable de memoria alguna,
sino que utiliza la propiedad Value del objeto de campo de entrada. El valor que
introduce el usuario se almacena automáticamente en la propiedad Value.
El uso de la propiedad Value, en lugar de una variable de memoria tradicional,
encapsula los datos con la ficha y facilita evitar los conflictos de nombres entre las
variables. Para más información sobre las ventajas de la orientación a objetos, consulte
los Capítulos 9 a 12.

Procedimientos y funciones
El código contenido en los procedimientos y funciones —el núcleo de la mayoría de
programas de dBASE— es muy similar en dBASE IV y en Visual dBASE. Se diferencian
principalmente en la forma en que se llaman y en cómo hacen referencia a los datos.
Dado que las aplicaciones Visual dBASE son controladas por sucesos, los
procedimientos y funciones suelen ejecutarse cuando se producen los sucesos, no
cuando un programa los llama explícitamente. Además, como se ha mencionado
anteriormente, Visual dBASE almacena muchos elementos de datos en forma de
propiedades de objetos. Las subrutinas utilizan la “notación de punto” para hacer
referencia a las propiedades.
El Capítulo 2 presenta la programación controlada por sucesos y el Capítulo 14 describe
cómo escribir controladores de sucesos. Para una introducción al trabajo con objetos y
propiedades, consulte el Capítulo 9.
Ambas aplicaciones utilizan una subrutina para buscar el nombre.

438 Guía del programador


dos2win.dwp Page 439 Wednesday, August 30, 1995 2:30 PM

Figura B.7 Comparación de llamada y declaración de una subrutina

dBASE IV dBASE para Windows


BuscarOtraVez = .T. DEFINE PUSHBUTTON BOTONOK OF BuscarCli;
DO WHILE BuscarOtraVez FROM 5,18 TO 6,28;
... PROPERTY Text "OK",;
BuscarOtraVez = BuscarNombre() OnClick BuscarNombre
ENDDO

FUNCTION BuscarNombre PROCEDURE BuscarNombre


IF .NOT. ISBLANK(cNombre) IF .NOT. ISBLANK(form.Entrada1.Value)
SEEK UPPER(TRIM(cNombre)) SEEK(UPPER(TRIM(form.Entrada1.Value) ))
IF .NOT. FOUND() IF .NOT. FOUND()
? CHR(7) ? CHR(7)
@ 0, 11 SAY ; form.StatusMessage = ;
"Nombre no encontrado." "Nombre no encontrado."
ELSE form.Entrada1.SetFocus()
DO EDITAR ELSE
ENDIF form.StatusMessage = ""
BuscarOtraVez = .T. form.Entrada1.Value = ;
ELSE SPACE(nLongNombre)
BuscarOtraVez = .F. form.Close()
ENDIF ENDIF
RETURN BuscarOtraVez ELSE
form.StatusMessage = ""
form.Close()
ENDIF
RETURN

• La aplicación de dBASE IV llama la función BuscarNombre() explícitamente en el


código. La de Visual dBASE convierte BuscarNombre en un controlador de sucesos y
lo asigna a la propiedad OnClick de un botón. El usuario ejecuta el controlador de
sucesos haciendo clic en el botón.
• Ambas aplicaciones utilizan SEEK para buscar el nombre. La aplicación de dBASE IV
busca la variable cNombre; la de Visual dBASE busca la propiedad Value del campo
de entrada Entrada1. La expresión form.Entrada1.Value hace referencia a la propiedad
Value del objeto denominado Entrada1 de la ficha actual.
• Si SEEK no encuentra el nombre, ambas aplicaciones muestran un mensaje. La de
dBASE IV utiliza el comando @...SAY; la aplicación de Visual dBASE cambia la
propiedad StatusMessage de la ficha, que muestra el mensaje en la barra de estado.
• Si SEEK encuentra el nombre, ambas aplicaciones muestran la ventana de edición.
La aplicación de dBASE IV muestra la ventana EditCli llamando explícitamente el
procedimiento Editar. En la de Visual dBASE, la ficha EditCli ya está abierta;
cerrando la ficha actual (con form.Close()) el foco vuelve a EditCli.

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 439


dos2win.dwp Page 440 Wednesday, August 30, 1995 2:30 PM

Transición a los menús de Visual dBASE


Visual dBASE proporciona una nueva y versátil forma de crear menús Windows
estándar para las aplicaciones. Los comandos de menú de dBASE IV están permitidos,
aunque no en las fichas. Para crear menús, emplee el Diseñador de menús, utilidad
disponible en el Diseñador de fichas y que genera un archivo .MNU que contiene el
código de dBASE para crear el menú. Los archivos .MNU se vinculan con las fichas
mediante la propiedad MenuFile de la ficha.
Para convertir el código de menús de dBASE IV en código de menús de Visual dBASE,
utilice el Generador de componentes, que crea un archivo .MNU.
La Tabla B.1 enumera los comandos de menú de dBASE IV y sus equivalentes en Visual
dBASE. Para más información sobre la creación de menús, consulte el Capítulo 16.
Tabla B.1 Comparación de dos formas de crear menús en dBASE
dBASE IV
Tarea Menú de línea Menú de ventana dBASE para Windows
Definición DEFINE MENU DEFINE POPUP Crea objetos para todos los tipos de menús a
DEFINE PAD DEFINE BAR partir de la clase MENU.
Asignación ON MENU ON POPUP No hay un equivalente directo en Visual
de acciones ON PAD ON BAR dBASE. Para asignar acciones, vincule
controladores de sucesos al suceso OnClick
ON SELECTION ON SELECTION de cada menú, o controle todas las
MENU POPUP selecciones mediante un controlador de
ON SELECTION ON SELECTION sucesos vinculado con el suceso OnSelection
PAD BAR de la ficha.
ON EXIT MENU ON EXIT POPUP
ON EXIT PAD ON EXIT BAR
Activación o ACTIVATE ACTIVATE POPUP La apertura de la ficha activa
visualización MENU SHOW POPUP automáticamente el menú de la ficha.
SHOW MENU
Desactivación DEACTIVATE DEACTIVATE Todo el menú permanece activo mientras la
MENU POPUP ficha está abierta. Para desactivar una opción
RELEASE RELEASE POPUPS de menú individual, defina su propiedad
MENUS Enabled.
CLEAR POPUPS
CLEAR MENUS

La Figura B.8 compara el código de menú de dBASE IV con uno similar de Visual
dBASE. El código de dBASE IV, extraído de la aplicación de ejemplo GESTION.PRG,
crea un menú de ventana; el código de Visual dBASE, extraído de la aplicación
GESTION.PRG mejorada, crea una jerarquía de objetos de menú.

440 Guía del programador


dos2win.dwp Page 441 Wednesday, August 30, 1995 2:30 PM

Figura B.8 Comparación del código de menú

dBASE IV dBASE para Windows

DEFINE POPUP Principal FROM 7,27 TO 22,50 DEFINE MENU Ver OF f.Main;
PROPERTY Text "&Ver"
DEFINE BAR 1 OF Principal;
PROMPT "===== MENU PRINCIPAL =====" SKIP DEFINE MENU Empleados OF f.Main.Ver;
PROPERTY Text "&Empleados",;
DEFINE BAR 2 OF Principal ; OnClick VerEmpleados
PROMPT " Tablas:" SKIP
DEFINE MENU Clientes OF f.Main.ver;
DEFINE BAR 3 OF Principal; PROPERTY Text "&Clientes",;
PROMPT "EMPLEADOS" OnClick VerClientes
DEFINE BAR 4 OF Principal; DEFINE MENU Vendedores OF f.Main.ver;
PROMPT "CLIENTES" PROPERTY Text "&Vendedores",;
DEFINE BAR 5 OF Principal; OnClick VerVendedores
PROMPT "VENDEDORES" DEFINE MENU Inventario OF f.Main.ver;
DEFINE BAR 6 OF Principal; PROPERTY Text "&Inventario",;
PROMPT "INVENTARIO" OnClick VerInventario
DEFINE BAR 7 OF Principal; DEFINE MENU Cuentas Of f.Main.Ver;
PROMPT "PEDIDOS" PROPERTY Text "C&uentas",;
DEFINE BAR 8 OF Principal; OnClick VerCuentas
PROMPT "CUENTAS" DEFINE MENU Prefijos OF f.Main.ver;
DEFINE BAR 9 OF Principal; PROPERTY Text "&Prefijos",;
PROMP "PREFIJOS" OnClick VerPrefijos

• El código de dBASE IV utiliza la opción OF para especificar el menú de ventana a que


pertenece cada línea de menú. El de Visual dBASE la utiliza para especificar el lugar
del objeto de menú en la jerarquía de menús. La expresión f.Main.Ver añade el nuevo
objeto en el menú Ver del menú Principal de la ficha actual.
• El código de dBASE IV precisa un comando ON BAR u ON SELECTION BAR
distinto para especificar que se ejecute un procedimiento. El de Visual dBASE
especifica el procedimiento que se ejecuta con la propiedad OnClick de cada menú.
• El código de dBASE IV especifica el texto del menú con la opción PROMPT. El de
Visual dBASE hace lo mismo con la propiedad Text del objeto de menú.
• El código de Visual dBASE utiliza el ampersand (&) para especificar un carácter que
el usuario puede pulsar con Alt+carácter para seleccionar el menú.

Apéndice B, Mejora de las aplicaciones de dBASE para DOS 441


dos2win.dwp Page 442 Wednesday, August 30, 1995 2:30 PM

Mejoras adicionales
El Capítulo 1 enumera las características nuevas más importantes del lenguaje en Visual
dBASE. Consulte ese capítulo para obtener ideas. Estos son algunos ejemplos de las
mejoras que quizá desee aplicar:
• Para mostrar botones con iconos personalizados para tareas comunes como Aceptar,
Cancelar o Ayuda, utilice los controles de BOTONES.CC en el subdirectorio
EJEMPLOS. El Capítulo 15 describe cómo crear y utilizar controles personalizados de
dBASE y VBX.
• Las siguientes funciones nuevas muestran cuadros de diálogo para recibir la entrada
del usuario:
• CHOOSEPRINTER() muestra un cuadro de diálogo para seleccionar una
impresora y devuelve la impresora elegida.
• GETCOLOR() muestra un cuadro de diálogo donde puede definir un color
personalizado o seleccionar un color en la paleta de colores.
• GETDIRECTORY() muestra un cuadro de diálogo donde puede seleccionar un
directorio para su uso con los comandos siguientes.
• GETEXPR() muestra el Generador de expresiones y devuelve la expresión creada.
• GETFILE() y PUTFILE() muestra cuadros de diálogo en que el usuario puede
elegir o introducir un nombre de archivo existente, o uno nuevo, respectivamente.
• GETFONT() muestra un cuadro de diálogo en que seleccionar una fuente.
Devuelve una cadena que contiene el nombre de la fuente, el tamaño en puntos,
el estilo (si elige otro que no sea Normal) y la familia.
• Si la aplicación utiliza archivos .PRS que contienen sentencias de SQL, emplee la
función SQLEXEC(). Para más información, consulte el Capítulo 23.

442 Guía del programador


ld&cs.dwp Page 443 Wednesday, August 30, 1995 2:31 PM

Apéndice

Uso de los juegos de caracteres y


Apéndice C
C
controladores de idioma
dBASE es una utilidad de software internacional y puede adaptarse a código escrito en
muchos idiomas. Cada idioma o categoría de idiomas utiliza su propio juego de
caracteres y cada uno tiene su propia forma de ordenar y relacionar sus caracteres.
dBASE proporciona un amplio soporte de idiomas y juegos de caracteres mediante
múltiples controladores de idioma y la conversión automática OEM–ANSI.
Los usuarios que no conocían el entorno Windows deben comprender la diferencia
entre los juegos de caracteres OEM y ANSI. Quienes intercambian datos entre fronteras
nacionales o lingüísticas necesitan comprender cómo utiliza dBASE los controladores de
idioma para procesar los datos de diferentes idiomas.
Este apéndice explica cómo utiliza Visual dBASE los juegos de caracteres OEM y ANSI y
describe las técnicas para trabajar con controladores de idioma.

Apéndice C, Uso de los juegos de caracteres y controladores de idioma 443


ld&cs.dwp Page 444 Wednesday, August 30, 1995 2:31 PM

Acerca de los juegos de caracteres


A principios de la década de los 80, el equipo de desarrollo del IBM PC creó una lista
ordenada de símbolos conocidos como juego de caracteres extendidos de IBM. Esta lista
contenía los habituales caracteres ASCII de 7 bits, junto con varios símbolos
matemáticos, caracteres de dibujo de líneas y recuadros y algunas vocales acentuadas.
Aunque esto era adecuado para ciertos países angloparlantes, era insuficiente para otros
muchos países. Por ejemplo, hay caracteres acentuados en diversos idiomas europeos
no incluidos en el juego de caracteres extendido de IBM. Por tanto, se desarrollaron
algunos otros juegos de caracteres. Cada uno de ellos, incluido el original de IBM, está
contenido en una página de códigos. Cada página de códigos está diseñada para un país o
grupo de países en particular, y se identifica mediante un número de tres cifras.
Algunos ejemplos de las páginas de códigos permitidas en MS-DOS son:
• 437 Inglés y algunos idiomas de Europa Occidental
• 850 Muchos idiomas de Europa Occidental
• 852 Muchos idiomas de Europa Oriental
• 860 Portugués
• 863 Francés canadiense
• 865 Idiomas nórdicos
Éstas son las denominadas páginas de códigos OEM (Original Equipment Manufacturer).
El clásico juego de caracteres extendidos IBM está contenido en la página de códigos
OEM 437 y es la página por defecto para Estados Unidos. El sistema DOS considera la
página de códigos 850 como la página por defecto para la mayoría de países europeos.
Esta página contiene todas las letras (pero no todos los símbolos) de las páginas de
código 437, 860, 863 y 865; en consecuencia, la página 850 omite muchos caracteres de
dibujo de recuadros y líneas contenidos en esas páginas para dejar sitio para los
caracteres acentuados.
Cada carácter de una página de códigos se identifica mediante un número (que puede
ser decimal o hexadecimal) que se denomina punto de código. Por ejemplo, el punto de
código del carácter numérico “4” es 52 (decimal) o 34 (hexadecimal) en las páginas de
códigos 437 y 850.
El entorno Windows utiliza su propio juego de caracteres, que generalmente se conoce
como juego de caracteres ANSI. Aunque este juego comparte muchos caracteres con las
páginas de códigos OEM, omite la mayoría de caracteres de dibujo de líneas y símbolos
matemáticos que ofrecen estas páginas de códigos. Además, incluso los caracteres que
existen tanto en una página de códigos OEM como en el juego de caracteres ANSI
suelen tener números de punto de código diferentes.

444 Guía del programador


ld&cs.dwp Page 445 Wednesday, August 30, 1995 2:31 PM

Para maximizar la compatibilidad con versiones anteriores, Visual dBASE utiliza


internamente los juegos de caracteres OEM. Cuando se leen o escriben datos en un
disco, no se convierte el juego de caracteres. Sin embargo, dado que dBASE es una
aplicación Windows, el juego de caracteres ANSI se utiliza en otras operaciones de
entrada y salida. La Figura 27.3 resume cómo gestiona dBASE la conversión de OEM y
ANSI:
Figura 27.3 Resumen de la conversión de juegos de caracteres OEM y ANSI

Depende
de la fuente
ANSI ANSI

Unidad dBASE para


de disco Windows DDE/OLE
OEM Sin conversión

Nota Cuando dBASE envía la salida a la pantalla, el juego de caracteres depende de la fuente
utilizada. Por ejemplo, las áreas siguientes de la pantalla utilizan por defecto el juego de
caracteres OEM:
• Editor de textos (incluidos los archivos memo)
• Campos de entrada
• Ventana de comandos
Por contra, las áreas siguientes de la pantalla utilizan por defecto una fuente ANSI:
• Ventana de registros de la tabla
• Rótulos en los botones
• Selector de archivos

Apéndice C, Uso de los juegos de caracteres y controladores de idioma 445


ld&cs.dwp Page 446 Wednesday, August 30, 1995 2:31 PM

Acerca de los controladores de idioma


dBASE utiliza controladores de idioma para especificar qué juego de caracteres se
emplea y qué normas idiomáticas se aplican a ese juego de caracteres. Cada juego de
caracteres es idéntico a uno contenido en una de las páginas de códigos OEM; por
ejemplo, el controlador de idioma del francés canadiense utiliza un juego de caracteres
que es idéntico a la página de códigos 863, mientras que el controlador por defecto de
Estados Unidos utiliza un juego de caracteres que es idéntico a la página de códigos 437.
Es importante comprender que Visual dBASE utiliza estas páginas de códigos internas,
no las suministradas por el DOS.
Los controladores de idioma de dBASE contienen tablas que definen o controlan lo
siguiente para un juego de caracteres en particular:
• Caracteres alfabéticos
• Reglas de mayúsculas y minúsculas
• Secuencia utilizada en la ordenación o indexación
• Comparaciones de cadenas (=, <, >, <=, >=)
• Valores de código fonético (valores que representan similitudes fonéticas cuando no
se conoce la ortografía exacta de una palabra)
• Reglas para la conversión entre los juegos de caracteres OEM y ANSI
dBASE identifica cada controlador mediante una cadena de caracteres conocida como
nombre interno. Por ejemplo, el nombre interno del controlador de alemán para la página
de códigos 850 es DB850DE0 y el del finlandés es DB437FI0. La Tabla C.1 lista los
controladores de idiomas europeos disponibles en dBASE.
Tabla C.1 Controladores de idiomas europeos
Página Nombre
Idioma o país códigos interno
Alemán 437 DB437DE0
Alemán 850 DB850DE0
Danés 865 DB865DA0
Español 437 DB437ES1
Español 850 DB850ES0
Finlandés 437 DB437FI0
Francés 437 DB437FR0
Francés 850 DB850FR0
Francés/Canadá 850 DB850CF0
Francés/Canadá 863 DB863CF1
Holandés 437 DB437NL0
Holandés 850 DB850NL0
Inglés/Reino Unido 437 DB437UK0
Inglés/Reino Unido 850 DB850UK0
Inglés/Estados Unidos 437 DB437US0

446 Guía del programador


ld&cs.dwp Page 447 Wednesday, August 30, 1995 2:31 PM

Tabla C.1 Controladores de idiomas europeos (continuación)


Página Nombre
Idioma o país códigos interno
Inglés/Estados Unidos 850 DB850US0
Italiano 437 DB437IT0
Italiano 850 DB850IT1
Noruego 865 DB865NO0
Portugués/Brasil 850 DB850PT0
Portugués/Portugal 860 DB860PT0
Sueco 437 DB437SV0
Sueco 850 DB850SV1

Cuando dBASE convierte datos de OEM a ANSI, y viceversa, la mayoría de caracteres


alfabéticos existen tanto en una página de códigos OEM como en el juego de caracteres
ANSI y se convierten sin problemas. La mayoría de los símbolos gráficos extendidos de
una página de códigos OEM no pueden representarse en el juego de caracteres ANSI.
Cuando existe una discrepancia así, dBASE, como otras aplicaciones Windows
estándar, calcula el carácter más parecido, pero pueden producirse pérdidas de datos.
Por esta razón, si utiliza archivos creados en dBASE IV o dBASE 5.0 para DOS durante
una sesión de dBASE, asegúrese de que el controlador de idioma actual coincide con la
tabla de idioma usada en dBASE IV o dBASE 5.0 para DOS cuando se crearon los
archivos. De hecho, los controladores de idioma deberían coincidir siempre que sea
posible, incluso cuando ambas tablas se creen en sesiones de dBASE.

Apéndice C, Uso de los juegos de caracteres y controladores de idioma 447


ld&cs.dwp Page 448 Wednesday, August 30, 1995 2:31 PM

Coincidencias exactas e inexactas


Cuando dBASE compara dos caracteres, puede realizar una coincidencia exacta
(también denominada coincidencia principal) o una inexacta (coincidencia secundaria).
Se realiza una coincidencia exacta si la condición EXACT está ON, y una inexacta si
está OFF. Para interacciones de SET NEAR y SET EXACT con las búsquedas, consulte
también el Capítulo 20.
Una coincidencia exacta precisa que los caracteres sean exactamente los mismos. Por
ejemplo, aunque los caracteres A y Á son similares, no cumplen el requisito de una
coincidencia exacta, y una expresión SEEK o FIND los trata como caracteres diferentes.
Por contra, una coincidencia inexacta sólo precisa que los caracteres pertenezcan a la
misma categoría general. Por ejemplo, dado que los caracteres A y Á son similares en
varios idiomas, satisfacen el requisito de una coincidencia inexacta con muchos
controladores de idioma; una expresión SEEK o FIND los trata como caracteres
idénticos.
Las coincidencias exactas e inexactas se realizan en función de ponderaciones primarias y
secundarias, que un controlador de idioma asigna a cada carácter. Las coincidencias
exactas utilizan las ponderaciones primarias y secundarias, mientras que las inexactas
utilizan sólo las primarias y no tienen en cuenta las secundarias. Por ejemplo, el
comando SET EXACT controla si los caracteres con diéresis se consideran igual que los
mismos caracteres sin diéresis. Los comandos siguientes abren una tabla denominada
VOLK.DBF y buscan un registro con un valor de clave que cumple un criterio de
coincidencia exacta:
SET EXACT ON
USE VOLK ORDER TAG NAMEN
SEEK "KONIG" && No encontrará "KÖNIG".
EXACT es ON y las ponderaciones secundarias de O y Ö son diferentes, por lo que se
evalúan como caracteres diferentes. Los comandos siguientes abren VOLK.DBF y
buscan un registro con un valor de clave que cumple un criterio de coincidencia
inexacta:
SET EXACT OFF
USE VOLK ORDER TAG NAMEN
SEEK "KONIG" && Sí puede encontrar "KÖNIG".
EXACT es OFF y las ponderaciones principales de O y Ö son las mismas, por lo que se
evalúan como caracteres idénticos.

448 Guía del programador


ld&cs.dwp Page 449 Wednesday, August 30, 1995 2:31 PM

Uso de controladores de idioma globales


Cada vez que se activa una sesión de dBASE, automáticamente se activa un controlador
de idioma. Es el denominado controlador de idioma global y se aplica a la mayor parte de la
actividad siguiente en la sesión. Por ejemplo, ese controlador rige la forma en que se
evalúan muchas expresiones FOR y WHILE.
Visual dBASE elige el controlador de idioma global entre una de estas definiciones:
• La definición de Tabla de idioma de dBASE en el cuadro de diálogo Opciones del
programa Utilidad IDAPI. Para información sobre cómo especificar un controlador
de idioma para IDAPI, consulte el Capítulo 1 de la Guía del usuario.
• Una entrada de la sección [CommandSettings] del archivo DBASEWIN.INI (consulte
la sección siguiente).
Cuando no hay ninguna entrada en DBASEWIN.INI para el controlador de idioma, la
definición de la Utilidad IDAPI determina el controlador de idioma global. Cuando se
incluye una entrada de controlador válida en DBASEWIN.INI, prevalece sobre la
definición de la Utilidad IDAPI.
Para especificar un controlador de idioma global en DBASEWIN.INI, realice los pasos
siguientes:
1 Cierre la sesión actual de dBASE (si hay una abierta).
2 Abra el archivo DBASEWIN.INI (en el subdirectorio BIN del directorio de
instalación) e incluya la entrada siguiente en la sección [CommandSettings]:
ldriver = <nombre interno del controlador>
Por ejemplo, el nombre interno de un controlador de español europeo para la página
de códigos 437 es DB437ES1; para instalar este controlador, inserte la definición
siguiente:
ldriver = DB437ES1
3 Guarde los cambios y comience otra sesión en dBASE.
Es muy recomendable que la página de códigos subyacente del DOS coincida con la
página de códigos interna almacenada en el controlador de idioma activo. Por ejemplo,
el controlador de idioma español mencionado antes utiliza una página de códigos
interna que coincide con la 437; por tanto, también debe definir la página de códigos 437
en el DOS. Hay varias razones para ello, la más importante de las cuales es el nombre de
los archivos; si guarda un archivo desde una sesión de dBASE con un nombre de
archivo que contiene caracteres que no existen en la página de códigos actual del DOS,
es posible que se corrompa el nombre del archivo.
Para instrucciones sobre cómo cambiar las páginas de códigos DOS y Windows,
consulte sus respectivos manuales.

Apéndice C, Uso de los juegos de caracteres y controladores de idioma 449


ld&cs.dwp Page 450 Wednesday, August 30, 1995 2:31 PM

Uso de controladores de idioma de tablas


dBASE asigna un controlador de idioma a una tabla de forma automática cuando se
crea. Esta asignación se registra en el LDID, un identificador de 1 byte en el área de
cabecera del archivo. Cuando se crea una tabla de cero, dBASE siempre asigna el
controlador de idioma global actual al LDID. Cuando se crea un archivo de tabla a partir
de otro archivo de tabla, o el controlador de idioma actual o el de la tabla original se
asigna al LDID de la nueva tabla. El idioma que se asigna depende del comando que
emplee para crear el archivo, como se muestra en la Tabla C.2.
Tabla C.2 Comandos que utilizan controladores de idioma globales o de una tabla
Asigna el controlador global al LDID Asigna el controlador de la tabla original al
de la nueva tabla LDID de la nueva tabla
CREATE COPY FILE
CREATE...FROM COPY STRUCTURE
CREATE...STRUCTURE EXTENDED COPY...STRUCTURE EXTENDED
COPY TABLE
COPY TO
IMPORT
JOIN
MODIFY STRUCTURE
RENAME TABLE
SORT
TOTAL

Por ejemplo, los comandos siguientes abren un archivo de tabla y crean otro con el
LDID definido como el controlador de idioma global actual:
USE CLIENTES && LDID especifica un controlador de idioma distinto del global
COPY TO CLIENTE && El LDID de CLIENTE.DBF especifica el controlador de idioma de
&& CLIENTES.DBF.
Los comandos siguientes abren un archivo de tabla y crean un archivo de tabla
ordenada con un LDID definido como el controlador de idioma de la tabla original:
USE CLIENTES && LDID especifica un controlador de idioma distinto del
&& global
SORT ON APELLIDOS TO CLIENTE && El LDID de CLIENTE.DBF es el mismo que el de
&& CLIENTES.DBF

450 Guía del programador


ld&cs.dwp Page 451 Wednesday, August 30, 1995 2:31 PM

Identificación del controlador de idioma y página de códigos


de una tabla
Dado que algunos comandos se comportan de forma diferente que otros cuando el
controlador de idioma de una tabla es distinto del global, suele ser necesario detectar
qué controlador de idioma se asigna al área LDID del archivo de tabla o averiguar qué
página de códigos utiliza el controlador de idioma. Por ejemplo, los nombres de archivo
y campo pueden ser válidos en un idioma pero no en otro, o un campo clave puede
tener caracteres que no son compartidos entre las páginas de códigos de los
controladores de idioma.
Cuando se utiliza un comando que abre una tabla, y ésta tiene un controlador de idioma
distinto al global, dBASE muestra un cuadro de diálogo de advertencia. Esta condición
de error no activa una rutina ON ERROR, como en dBASE IV o dBASE 5.0 para DOS.
Si no desea que aparezca el cuadro de diálogo, desactívelo ejecutando SET LDCHECK
OFF.
Para determinar qué controlador de idioma se graba en el área LDID de una tabla y qué
página de códigos emplea el controlador, utilice las funciones LDRIVER( ) y
CHARSET( ):
SET LDCHECK OFF && Desactiva la opción de comprobar la compatibilidad entre
&& controladores de idioma
USE CLIENTES EXCLUSIVE
INDEX ON Empresa TAG Empresa

IF LDRIVER() = "DB437DE0" && Si el controlador de idioma corresponde al idioma alemán


SEEK "Shönberg Systems" && La búsqueda es correcta
ELSE
IF CHARSET() = "DOS:437" && Si el controlador utiliza la página de códigos 437
Warn1.Open() && Abre una ventana de advertencia
ELSE && Si el controlador no utiliza la página de códigos 437
Warn2.Open() && Abre una ventana de advertencia diferente.
ENDIF
ENDIF

Apéndice C, Uso de los juegos de caracteres y controladores de idioma 451


ld&cs.dwp Page 452 Wednesday, August 30, 1995 2:31 PM

Uso de los controladores de idioma de la tabla frente a los globales


Cuando el controlador de idioma de una tabla es distinto al global actual, el de la tabla
se carga en memoria automáticamente al abrirla. A continuación, el controlador de
idioma de la tabla es respetado por algunos comandos, mientras que el global es
respetado por otros.
Todos los comandos que no tienen relación con las tablas utilizan los controladores de
idioma globales. La Tabla C.3 muestra las normas generales cuando se realizan
operaciones con los datos de una tabla y el controlador de idioma de ésta es distinto del
global.
Tabla C.3 Contextos para los controladores de idioma de una tabla y globales
Controlador de tabla Controlador global
Expresiones del comando INDEX ON Expresión del comando SET FILTER
Expresión de ámbito FOR del comando INDEX Expresiones FOR y WHILE de todos los comandos
ON excepto INDEX ON
Expresión de comprobación de rango SET KEY Expresión SET RELATION TO
Expresiones del comando SORT
Coincidencias secundarias para las expresiones
LOOKUP( ), FIND, SEEK y SEEK( ) con EXACT
definido como OFF
Coincidencias secundarias para la expresión SET
RELATION TO con EXACT definido como OFF
(usa el controlador de la tabla secundaria)

Por ejemplo, cuando se crea un archivo de tabla con el controlador de idioma de alemán,
se escribe un identificador LDID en el área de cabecera del archivo. Cuando se abren los
archivos en una sesión de Visual dBASE en que el controlador de idioma global es de
inglés, existe una discrepancia entre los juegos de caracteres. Si crea un índice con
INDEX ON, el orden lógico del índice obedece al controlador de idioma de la tabla:
USE VOLK && Creado con el controlador de idioma de alemán
INDEX ON NAMEN TAG DIENAMEN && Ordena los registros según el orden alfabético alemán
Por contraste, si crea un filtro con SET FILTER, la condición de filtro obedece al
controlador de idioma global:
USE VOLK
SET FILTER TO NAMEN = "KONIG" && Excluye los registros con "KÖNIG" en NAMEN

452 Guía del programador


ld&cs.dwp Page 453 Wednesday, August 30, 1995 2:31 PM

Incompatibilidades de caracteres en los nombres de campo


Cuando se utiliza un archivo de tabla que se creó con un controlador de idioma que no
es el controlador global actual, es posible que no se reconozcan algunos caracteres de la
tabla, lo cual puede crear problemas.
Por ejemplo, el controlador de idioma de alemán DB437DE0 tiene la misma página de
códigos y juego de caracteres que el controlador de idioma de inglés DB437US0.
Sin embargo, el controlador de alemán reconoce como alfabéticos los caracteres
extendidos Ö y Ü, mientras que el de inglés no los reconoce. En consecuencia, cuando el
nombre de un campo contiene caracteres así (como ÜNAMEN en el ejemplo siguiente),
se producen problemas al intentar hacer referencia al campo en una sesión de Visual
dBASE que utiliza el controlador de idioma de inglés.
Cuando existen las condiciones descritas, este comando genera una condición de error:
REPLACE ÜNAMEN WITH "Schiller"
Puede solucionar el problema rodeando el nombre del campo con el delimitador :, que
considera el nombre del campo como identificador sean cuales sean las normas
contenidas en el controlador de idioma actual:
REPLACE :ÜNAMEN: WITH "Schiller"
Cuando utiliza este comando, cada elemento del nombre de campo ÜNAMEN, incluida
la Ü, se trata como identificador y el comando se ejecuta correctamente.
Visual dBASE tiene una característica que convierte automáticamente los datos creados
con un controlador de idioma para que sean compatibles con el actual. Esta
característica debería eliminar muchos problemas como el descrito antes. Si desea
desactivar esta característica, ejecute el comando SET LDCONVERT OFF.

Apéndice C, Uso de los juegos de caracteres y controladores de idioma 453


ld&cs.dwp Page 454 Wednesday, August 30, 1995 2:31 PM

454 Guía del programador


pg_dbw.ix Page 455 Wednesday, August 30, 1995 2:32 PM

Indice
Símbolos alineación
horizontal 392
aplicaciones
orientadas a objetos,
# Vea directivas de números a la derecha 78 diseño 179-180
preprocesador números a la izquierda 78 uso compartido de datos 403
& (operador de macro) 64-65 salida impresa 388, 392 archivos
( ) (operador de llamada) 39 almacenamiento apertura en el Depurador 100
* (asterisco) en consultas cambios 279 ayuda 233
SQL 356 configuración del biblioteca 46
+ (operador de Depurador 124 borrado 275
concatenación) 72 ámbito cobertura 32
– (operador de archivos de memoria 66 impresión en 380, 382
concatenación) 72 Depurador 121 índice múltiple 297
. (operador de punto) 143 identificadores 90 índice simple 297
: (delimitador) objetos 152 memoria 66
nombres de campo 284, 337 opciones de edición 281 ámbito 66
para controladores de variables de memoria 54-59, objeto 26
idioma 453 177 procedimiento 44, 46
:: (operador de resolución de ampersand (&), operador de programa 44
macro 64-65 extensiones por defecto 26
ámbito) 169
Añadir punto de evaluación, registro de Paradox 340
@...SAY, comandos 291 texto, visualización en el
[ ] (operador de índice) 144 cuadro de diálogo 116
Añadir punto de ruptura, cuadro Depurador 125
de diálogo 109 vinculación 28
A análisis de cobertura 30-33 archivos de covertura 32
actualización finalización 31 áreas de trabajo 272
campos memo 292 inicio 30 alias 272
datos #pragma 96 campos 283
indexación 303 análisis de covertura no actual 272
transacciones 330 definido 13 AreUSure( ) 165
índices 288, 303 ancho máximo de línea en el Argumentos de parámetros,
información de bloqueos de editor de textos 25 cuadro de diálogo 113
registros 324 animación de programas aritméticos, operadores de
variables automem 291 (Depurador) 104 consultas SQL 361
actualización de datos definición de velocidad 123 ASCII, juego de caracteres 444
datos ANSI, juego de caracteres 444 associative arrays
transacciones 334 anulación defined 13
adición miembros heredados 168 asterisco (*) en consultas
características de Windows en tipos de tabla 337 SQL 356
menús 258-260 apaisado, modo (impresión) 387 AUTO 27
elementos en matrices 61 apertura automem, variables 291
puntos de evaluación archivos en el Depurador 100 liberación 291
(Depurador) 116 bases de datos 270 sustitución 291
puntos de ruptura 107, 110 varias 336 avance
registros 285 documentos OLE 294 hoja 388
agrupamiento de datos en fichas páginas 388
consultas SQL 368-369 modales/no modales 197 ayuda, provisión 233
aislamiento secuencia de sucesos 220
niveles índices 301
transacciones 334 tablas 269
ajuste vertical 392 exclusivamente 319
alias de tabla 271 sólo lectura 319
aplicación, objeto 132

Indice 455
pg_dbw.ix Page 456 Wednesday, August 30, 1995 2:32 PM

B búsqueda OLE 293


cadenas selección 315
Barra rápida del Depurador 100 diferencia de mayúsculas/ sustitución desde
bases de datos minúsculas 310 matrices 289
Vea también tablas datos 310-312 variables automem 291
alias 271 parámetros del entorno 311 cancelación de cambios 279
apertura 270 resultado 312 carácter de continuación 25
BDE 10, 354 SET NEAR 312 caracteres
BEGINTRANS( ) 11 tablas 310-312 coincidencia 448
biblioteca, archivos 46 indexadas 310 literales 70
bibliotecas de vínculo dinámico sin indexar 310 repetición 75
Vea DLL carga de archivos en el
binarios, campos Vea campos C Depurador 100
binarios cascada, menús 258
C.J. Date 348
binarios, tipos de datos 292 CASCADE 305
cadena, datos Vea datos carácter
BITAND( ) 235 centrado de números 78
cadenas
bits ceros 78
búsqueda
funciones de diferencia de mayúsculas/ iniciales 78
manipulación 235 minúsculas 310 CHOOSEPRINTER( ) 8
manipulación en en el Depurador 103 cierre
parámetros 401-402 concatenación en SQL 361 fichas
BITSET( ) 235 inspección de variables 121 secuencia de sucesos 221
bloqueo vacías 78 índices 301
automático 320 cálculos sesiones 327
comandos 321 campos 313 tablas 269
desactivación 323 financieros 79 vínculos dinámicos DDE 408
tablas relacionadas 322 múltiples 313 cifrado de tablas 344
explícito 324 trigonométricos 79 clases 159-174
liberación 325 cambio asociación de métodos 161
funciones 325 Vea también modificación código reutilizable 178
número de intentos 325 controlador de idioma 449 declaración 159, 165
bloques de código foco en fichas 232 código del Diseñador de
ejemplos 47 nombre de tablas 273 fichas 252
expresión 36 separadores 77 en función de otra 167
frente a punteros de frente a definición de
símbolo monetario 77
función 49 código 240
campo de entrada de
parámetros 47 definición 136
contraseña 241 jerarquías 166
sentencia 36
campos 277 creación 173
sintaxis 47
binarios llamada de métodos 171
bloques lógicos al analizar sustitución de datos 293
programas 32 parámetros 163
visualización del pase 172
BOOKMARK( ) 12 contenido 294
booleanos, datos Vea datos personalizadas
calculados 313 programación 206
lógicos creación de índices 299 referencia a métodos 169
borrado dBASE IV 436 relación con objetos 136
archivos 275 definición interactiva 268
elementos de matrices 61 clases, palabra clave 169
devolución de valores 284 claves compuestas
permanente de registros 290 lista 283
puntos de ruptura 108 (Paradox) 343
lógicos
registros 290 indexación 300 claves primarias (Paradox) 342
Paradox/SQL 339 memo cliente DDE 403
tablas 275 actualización 292 cobertura
variables de memoria 52 en expresiones 293 análisis 30-33
botones, ejemplo 241 nombres archivos 32
BROWSE, comando delimitación 284, 337 codigo
edición 278 diferentes tipos de constructor 159
tablas 337

456 Guía del programador


pg_dbw.ix Page 457 Wednesday, August 30, 1995 2:32 PM

código concatenación de cadenas de controles


constructor 159 caracteres 72 desplazamiento del foco 232
orden de ejecución 167 condicional, compilación 93 instalación 238
variables de memoria 161 CONFIG.DB (dBASE III+/ personalizados 237-243
dBASE IV 434 IV) 422 definición 237
adaptación 428 Configuración de impresión, ejemplos 241
función/ cuadro de diálogo 387 VBX
procedimiento 438 configuración del instalación 239
depuración 114 convenciones
Depurador 123
Diseñador de fichas 252-263 programación 24-25
modificación 253 constantes
API de Windows 401 convenciones tipográficas 3
Diseñador de menús 255-260 conversión
Diseñador de menús de definición 89
CONSTRAIN sustituye a aplicaciones dBASE III+/
ventana 261 IV 420-425
reutilizable 178 FOUND() 306
constructor code 159 archivos de índice 297
salida 219-221 código de dBASE IV 428
coincidencia consulta de propiedades de
funciones 401
caracteres 448 clase 167 juegos de caracteres 445
principal 448 consultas notaciones de números 401
secundaria 448 filtrado de registros 314 sentencias define en
colisiones 324 consultas SQL declaraciones de clase 240
comandos agrupamiento de tipos de datos 86
bloqueo automático 321 datos 368-369 automática 336
desactivación 323 creación 355-371 coordenadas de impresión
en transacciones 331 diferencia de mayúsculas/ absolutas 383
formato 70 minúsculas 358 fraccionarias 384
menú de dBASE IV 440 ordenación 365-367 relativas 383
para compilar 27 varias tablas 370 copia
que precisan uso contenedores datos de campos binarios 294
exclusivo 319 herencia 167 estructura de tabla 268
COMMIT 349 contraseña, campo de tablas 274
COMMIT( ) 11 entrada 241 valores 288
comparación control copias, programación 130
datos registros duplicados 300 Count( ) 157
carácter 71 vista de datos 303 creación
fecha 81 control de versiones con archivos de covertura 32
lógicos 84 directivas de preprocesador 93 archivos de índice 298
fecha de modificación 28 controladores de idioma 446-452 archivos de programa 23
operadores caracteres incompatibles 453 copias de clases 130
consultas SQL 358 coincidencia 447 ejecutables 28
valores de indicadores 338 europeos 446 etiquetas de índice a partir de
valores de SQL 358 global y de tabla 452 campos 299
compatibilidad globales 449 jerarquía de objetos de
Vea también dBASE III+/IV identificación 451 menú 257
comandos @...SAY 291 nombre interno 446 jerarquías de clases 173
juegos de caracteres 445 tabla 450 matrices asociativas 157, 158
lenguaje de dBASE III+/ controladores de impresora 378 matrices dispersas 155-157
IV 425 dBASE IV 422 menús de ventana 261
compilación controladores de sucesos 20, menús en dBASE para
automática 28 228-231 Windows y en dBASE
comandos 27 cambio de foco 217 IV 440
condicional 93 definición 211 objetos 139-141
opciones 88 depuración 114 definidos por el
preproceso 88 ejecución secuencial 217 usuario 150
comprobación de entorno programación 211-236 dentro de objetos 148
multiusuario 326 ratón 235 objetos DDELink 404
concatenación vínculo 213-217 objetos de matriz 153
cadenas SQL 361

Indice 457
pg_dbw.ix Page 458 Wednesday, August 30, 1995 2:32 PM

propiedades y métodos datos numéricos 76-77 declaración


personalizados 145 funciones 76 clases 159, 165, 240
sesiones 327 tipos 76 basadas en otra 167
sistemas de menús 255-260 dBASE matrices 60
tablas 268 cambios en el lenguaje 425 parámetros
transacciones 330 caracteristicas de para clases 163
variables automem 291 programación 12 procedimientos 37
ventanas de dBASE IV 435 ejecución desde aplicaciones procedimientos
vínculos dinámicos DDE 408 clientes 410 almacenados 372
CUA, normas para sintaxis de SQL automáticamente 373
interfaces 209 permitida 351-353 prototipos con EXTERN 396
cuadros de diálogo Vea fichas, dBASE III+/IV variables de memoria 54
modales aplicaciones 427-442 #define 91
cuadros de incremento conversión 420-425 DEFINE, comando
desplazamiento de mandatos de depuración creación de objetos 139
registros 241 desfasados 98 y operador NEW 148
cursiva 391 tipos de archivos definición de constantes 89
cursor como punto de permitidos 421 delimitadores
ruptura 112 dBASE IV caracteres literales 70
campos 436 dos puntos (:) 284, 337
D código 434 depuración
función/ compilación condicional 93
DataSource, propiedad 294 procedimiento 438 programas 97-124
datos comandos de menú 440 sucesos 114
actualización 303 programa de ejemplo 429-433 Depurador
transacciones 330, 334 variables de memoria 438 ámbito 121
agrupamiento en consultas ventanas 435 animación de programas 104
SQL 368-369 __dbasewin__ 94 definición de
aleatorios, generación 29 DBError( ) 353 velocidad 123
búsqueda 310-312 DBMessage( ) 353 apertura de archivos 100
control de vista 303 DDE 403 Barra rápida 100
copia de campos binarios 294 Vea también vínculos DDE búsqueda de cadenas de
edición 278 cliente 403 texto 103
encapsulados en objetos 177 ejecución de dBASE 410 búsqueda de procedimientos
formatos de dBASE III+/ controlador de externos 103
IV 423 inicialización 411-412 cambio de directorio 125
integridad desplazamiento por cambio de foco entre
sesiones 326 regiones 406 ventanas 99
intercambio con el nombre de sesiones de carga de subrutinas 101
servidor 405 dBASE 412 configuración 123
ocultación 57 objetos 403 desplazamiento 102
protección 276 servidor 403 ejecución de programas 112
sustitución 293 aplicación de control 407 control 104
uso compartido 317-327 dBASE 409 detención 113
entre aplicaciones 403 vínculos dinámicos 408 restablecimiento 113
validación 227 DDELink, clase 404 errores lógicos 98
tablas no dBASE 340 decimales evaluación de
datos carácter 70-75 redondeo 80 expresiones 122
formato 391 separadores 77 fuentes de visualización 123
datos de cadena Vea datos historial de búsquedas 103
carácter inicio 99
datos de hora 83 modificación de valores 120
fraccionarios 83 pase de parámetros 113
validación 227 paso a paso 106
datos lógicos 84 posición de archivos
datos nulos 85 fuente 124
puntos de evaluación 116

458 Guía del programador


pg_dbw.ix Page 459 Wednesday, August 30, 1995 2:32 PM

puntos de ruptura Vea dos puntos, delimitador (:) espacios, eliminación en


puntos de ruptura nombres de campos 284, 337 cadenas 75
rangos de inspección 120 para controladores de estáticas, variables de
recuperación de valores por idioma 453 memoria 57, 177
defecto 124 dos vías, utilidades 251 estilo, convenciones de
seguimiento 105 DOS, soporte de páginas de programación 24-25
varias copias 125 códigos 444 evaluación
ventana Pila 115 duplicación expresiones 361
ventana Visor 116 registros 300 Evaluar/Modificar, cuadro de
visualización de archivos de diálogo 122
texto 125
vuelta al procedimiento de
E exclusión de registros
marcados 290
llamada 104 edición exponenciación 361
desactivación de bloqueos Vea también modificación
exponencial, notación 78
automáticos 323 código del Diseñador de
fichas 253 exportación de datos de campos
desbloqueo de registros y
datos 278 binarios 294
tablas 325
almacenamiento de expresiones
descarga de programas del búsqueda de tipos de datos
Depurador 113 cambios 279
opciones de ámbito 281 mixtos 311
desplazamiento cambio de valores 118
en el Depurador 102 puntos de evaluación 117
puntos de ruptura 108 campos memo 293
foco en fichas 232 como nombres de tablas 270
por regiones (DDE) 406 EditCopyMenu 258
EditCutMenu 258 consultas SQL 361-365
puntero de registro 280 evaluación 122, 361
símbolo monetario 77 EditPasteMenu 258
inspección 119
desviación típica y varianza 314 EditUndoMenu 258
visualización 116
determinación del resultado de ejecución
extendido, juego de
la búsqueda 312 código constructor 167
programas caracteres 444
directivas de extensión de objetos 131
preprocesador 87-96 desde el Depurador 112
modalidad 198-199 extracción de subcadenas 73
archivos include 95
condicional 93 secuencia en controles de una
identificadores 89 ficha 232 F
resumen (tabla) 88 sentencias SQL 344
ejecutables, creación 28 fallidas, búsquedas 312
Diseñador de fichas fechas 81-82
código 252-263 elementos de matriz Vea
indexación 300
edición 253 matrices, elementos
eliminación de marca de borrado fichas
secciones 252-255 apertura
escritura de controladores de de registros 290 modal/no modal 197
sucesos 213-217 en línea, funciones 92 secuencia de sucesos 220
Diseñador de menús 255-260 encapsulado de código y cambio de foco entre 232
Diseñador de menús de ventana datos 177 cierre
código 261 entorno secuencia de sucesos 221
diseño multiusuario controles Vea controles
aplicaciones orientadas a comprobación 326 creación de menús 255-260
objetos 179-180 uso compartido de creación de menús de
modular, planteamientos 176 datos 318 ventana 261
orientado a objetos 175, 191 parámetros inicialización 219-221
ejemplo 180 búsqueda 311 modales
DLL 393-402 inicio de sesiones 327 programación 200
carga 399 errores visualización 197
convenciones de llamada 395 lógicos 98 no modales
nombres 395 SQL 353 programación 203
DO 27 tiempo de ejecución 98 visualización 197
espaciado de líneas números en 78
(impresión) 388 personalizadas 237, 244-250
plano de coordenadas 262

Indice 459
pg_dbw.ix Page 460 Wednesday, August 30, 1995 2:32 PM

programación 200-208 H índices


sucesos de movimiento 234 actualización 288, 303
transacciones 333 herencia apertura y cierre 301
filtrado código reutilizable 178 archivos
registros 314 contenedores 167 creación de uno
en vistas 315 subclases 166 simple 298
tablas subordinadas 305 hexadecimales, valores de determinación del estado 302
finalización variables de cadena 121 etiquetas 297
programas 113 hijos, objetos 134 creación a partir de
vínculos DDE 405 hojas, avance 388 campos 299
finanzas, funciones 79 horizontal, impresión 387 creación de archivos de
FirstIndex 158 huérfanos, registros 305 índice 298
FLOCK() 325 exclusivos 339
funciones 302
foco, sucesos de cambio 231
form, referencia a objeto 143, 160
I mantenimiento 298
formato identificación del controlador de no dBASE
datos de dBASE III+/IV 423 idioma 451 comparación con
sustitución 423 identificadores dBASE 343-344
página (impresión) 386-391 ámbito 90 Paradox 342
referencia 342
formato, comandos y creación 89
secundarios (Paradox) 342
funciones 70 ventanas 397
inicialización
fracción de datos de hora 83 idiomas Vea controladores de
código de dBASE IV 434
fuentes 389 idioma controlador (DDE) 411-412
funciones #if 93 fichas 219-221
bloqueo 325 #ifdef 93 matrices 60
conversión 401 #ifndef 93 variables de memoria de
creación de consultas impresión 377-392 dBASE IV 438
SQL 363-365 dirección de salida 380 inicio
devolución de valores de en un archivo 382 análisis de cobertura 30
campos 284 formato de datos 386-391 Depurador 99
DLL 395-399 formato de página 386-391 inserción
en línea 92 orientación de página 387 archivos include 95
en transacciones 331 varias páginas 389 parámetros con #define 91
financieras 79 impresoras registros 288, 339
formato 70 averiguación del estado 382 inspección
frente a procedimientos 36 controladores expresiones 119
numéricas 76 dBASE IV 422 propiedades de objetos y
trigonométricas 79 definición de elementos de matrices 121
coordenadas 384 variables de cadena 121
G elección 380 ventana (Depurador) 120
modo de calidad y instalación de controles 238
generación borrador 389
código integridad de referencias 305
#include 95
Diseñador de INTEGRITY CASCADE 11
include, archivos 95
fichas 252-263 INTEGRITY RESTRICTED 11
usos (tabla) 96
Diseñador de intercambio de datos con el
incrustación
menús 255-260 servidor 405
SQL 354
Diseñador de menús de Intercambio dinámico de datos
indexación 295-304
ventana 261 Vea DDE
datos aleatorios 29 actualización de datos 303
frente a ordenación 295 interfaces de usuario
Generador de componentes 428 dBASE III+/IV 428
indicadores 338
GetAuthorization( ) 165 normas CUA 209
comparación de valores 338
GetSalary( ) 165 interlineado (impresión) 388
índice, operador ([ ]) 144
GetSSN( ) 165 introducción de un programa 24
globales, variables de IsIndex( ) 157, 158
memoria 56

460 Guía del programador


pg_dbw.ix Page 461 Wednesday, August 30, 1995 2:32 PM

J M memo, campos Vea campos


memo
jerarquías macro, operador (&) 64-65 menos (–), operador 72
clases 166 macro, terminador (.) 64 MenuFile, propiedad de
creación 173 macros de preprocesador 92 fichas 255
objetos de menú 257 mantenimiento de índices 298 menús
juegos de caracteres 443-453 marcado de registros para adición de características de
ANSI 444 borrado 290 Windows 258-260
ASCII 444 marcas de comentario 24 menús de ficha
caracteres incompatibles 453 marcas, parámetro 235 creación 255-260
conversión 445 márgenes de página propiedades 258
extendido 444 (impresión) 387 métodos
pantalla 445 más (+), operador 72 asociación a clases 161
justificación de cadenas de matrices 60-63 declaración 159
texto 392 asociativas para otras clases 162
justificación Vea alineación creación 158 definición 133
definición 157 llamada desde clases 171
L como objetos 153 objetos estándar 134
declaración 60 ocultación 164
lenguaje SQL, soporte 344 dimensiones 60 personalizados 145
liberación dispersas 155 protección 164-165
bloqueos 325 creación 155-157 redeclaración 168
objetos 152 definidas 13 referencia 169
variables automem 291 elementos 60 miembros
variables de memoria 52 adición o borrado 61 heredados, anulación 168
líneas inspección 121 operadores de acceso 143-144
ancho 392 subíndices 60 índice ([ ]) 144
avances 388 funciones 61-62 punto (.) 143
comando como puntos de inicialización 60 miles, separador 77
ruptura 111 pase como parámetros 41 minúscula, cadenas en 74
por página (impresión) 386 matrices asociativas modal, interface de dBASE III+/
lista de campos 283 creación 157 IV 428
llamada matrices dispersas modales, ventanas 195
funciones de DLL 395-399 definidas 13 modalidad y ejecución de un
no dBASE 397 matriz 13 programa 198-199
funciones del API de asociativas modificación
Windows 400 creación 157 código del Diseñador de
método desde clases 171 matriz, métodos de la clase 154 fichas 253
operador (paréntesis) 39 mayúscula, cadenas en 74 estructura de menús de una
locales, variables de mayúsculas/minúsculas ficha 257
memoria 57, 177 consultas SQL 358 estructura de tablas 275
lógicos mayúsculas/minúsculas, mayúsculas/minúsculas 74
operadores modificación 74 posición de símbolo
consultas SQL 359 MDI, ventanas 196 monetario 77
.MDX, archivos 297 registros 289
.MDX, conversión de etiquetas a modular, programación 35
archivos .NDX 298 Módulo, ventana
.MEM, archivos Vea archivos, (Depurador) 102
memoria

Indice 461
pg_dbw.ix Page 462 Wednesday, August 30, 1995 2:32 PM

N O ocultación
métodos 164
.NDX, archivos 297 objeto, archivos propiedades 164
conversión a .MDX 297 vinculación 28 ocultación de datos 57
negrita 391 objetos 158 OEM, páginas de códigos 444
NEW, operador clases 138 OLE 413-415
creación de objetos 139 creación 139-141 OLE, campos Vea campos OLE
y comando DEFINE 148 definidos por el OLE, tipos de datos 292
NextIndex( ) 157, 158 usuario 150 OnChange, controlador de
no dBASE, campos dentro de objetos 148 sucesos 228-230
conversión 336 diseño orientado a 175-191 OnClick, controlador de
no dBASE, funciones 397 ejemplo 180 sucesos 222
no dBASE, tablas 335-345, 347 en otros objetos 134
OnGotFocus, controlador de
acceso 335 explicación 133
sucesos 232
delimitación de nombres de extensión 131
interfaces orientados a OnHelp, controlador de
campo 284 sucesos 233
posición en bases de objetos y dBASE III+/
IV 428 OnInitMenu( ) 258
datos 336 OnLostFocus, controlador de
transacciones 340 jerarquía de menús 257
liberación 152 sucesos 232
no dBASE, tipos de datos 85 OnMove, controlador de
no modal, interface comparado matrices como 153
matriz sucesos 234
con dBASE III+/IV 428 OnNavigate 230
creación 153
no modales, ventanas 195 OnOpen 261
padre/hijo 134
nombre propio, formato 74 programación de fichas OnSize, controlador de
nombres orientada a 206 sucesos 235
campos propiedades operadores
delimitación 337 inspección 121 acceso a miembro 143-144
sesiones de dBASE referencia 149 concatenación (+ y –) 72
(DDE) 412 referencia consultas SQL
tablas 270 interna 152 aritméticos 361
notación de punto 143 miembros 142 comparación 358
referencia a métodos 169 múltiples 149 lógicos 359
NULL 85 notación de llamada (paréntesis) 39
número de líneas en el punto 143, 149 lógicos
Depurador 102 padre e hijo 149 .NOT. 84
números relación padre-hijo 135 macro (&) 64-65
alineación 78 variables resolución de ámbito (::) 169
asignación a fuentes tipos de datos 134 ordenación
(impresión) 390 variables de referencia 142 frente a indexación 295
en fichas 78 como parámetros 146, 151 registros 295, 296
form 143, 160 ordenación de consultas
múltiples 148 SQL 365-367
this 142, 160
y variables orientación a objetos 129
tradicionales 146 programación de fichas 206

462 Guía del programador


pg_dbw.ix Page 463 Wednesday, August 30, 1995 2:32 PM

P posición
por defecto de pantalla
programas
análisis 30-33
padres, objetos 134 (aplicaciones de dBASE animación (Depurador) 104
páginas III+/IV) 420 archivos 44
avance 388 puntero de registro 280 cabecera en las fichas 252
impresión de rangos 389 salida impresa 383 control de ejecución
páginas de códigos 444 tablas en bases de datos 336 (Depurador) 104
coincidencia 449 tabulación (impresión) 388 depuración 97-124
identificación 451 #pragma 96 detención de la ejecución 113
internas y DOS 446 análisis de cobertura 31, 32 ejecución desde el
palabras clave preprocesador Vea directivas de Depurador 112
con EXTERN 396 preprocesador ejecución y
en declaraciones de clase 169 privadas, variables de modalidad 198-199
PICTURE 78 memoria 56 restablecimiento 113
pantalla, juego de caracteres 445 procedimientos 36 visualización en ventana
Paradox, tablas Vea tablas de almacenados Pila 115
Paradox declaración 372 propiedades
parámetros declaración inspección de objetos 121
bloques de código 47 automática 373 menús de una ficha 258
clases locales 163 limitaciones 374 ocultación 164
declaraciones de clase 163 almacenados (SQL) 371-374 personalizadas 145
#define 91 archivos 44, 46 programación 129
número 41 búsqueda en el protección 164-165
pase 13, 37-42 Depurador 103 reinicialización 168
en el Depurador 113 código del Diseñador de protección
referencias form 212 fichas 252 datos 276
subclases a declaración 37 métodos 164-165
superclases 172 disponibilidad 44 propiedades 164-165
tipo de datos 40 ejemplos 37 protección de variables de
pasadas, número 111 frente a funciones 36 memoria 43
pase de parámetros 13, 37-42 pase de parámetros a 39-41 prototipos
en el Depurador 113 uno solo 45 declaración 396
referencias form 212 varios 45 para pasar parámetros 396
paso a paso en programas PROCEDURE 38 pseudofunciones 92
(Depurador) 106 proceso públicas, variables de
patrón de trabajo transacciones 330 memoria 56
objeto-acción 19 producción, archivos .MDX 297 pulsaciones
PCOUNT( ) 41 programa, archivos parámetro de marcas 235
personalización vinculación 28 reservadas (advertencia) 424
controles 237-243 programación punteros de función frente a
definición 237 carácter de continuación 25 bloques de código 49
ejemplos 241 caracteristicas de 12 punteros de registro 280
instalación 238 controlada por sucesos y determinación de la
fichas 237, 244-250 tradicional 211 posición 282
propiedades y métodos 145 controladores de punto de código 444
PICTURE, palabra clave 78 sucesos 211-236 punto, notación 143
Pila, ventana (Depurador) 115 convenciones puntos de evaluación 116
plano de coordenadas estilísticas 24-25 modificación de valores de
fichas 262 copias 130 expresiones 118
impresión 383 estrategias 200-208 puntos de ruptura 107-112
polimorfismo marcas de comentario 24 acción 111
definición 179 mayúsculas y minúsculas 25 borrado 108
ejemplo 181 modular 35 definición 107, 110
orientada a objetos 129-138 ejecución de programas
propiedades 129 hasta 112
sangrado del código 24 globales o específicos 109
visual 17 número de pasadas 111

Indice 463
pg_dbw.ix Page 464 Wednesday, August 30, 1995 2:32 PM

R relación
INTEGRITY 305
SET NEAR y búsquedas 312
SET PROCEDURE 27
rangos de valores de tablas 304 SetSalary( ) 165
inspección 120 RemoveAll( ) 157, 158 SetSSN( ) 165
ratón RemoveKey( ) 157, 158 signo # Vea directivas de
control de sucesos 235 reordenación de registros e preprocesador
técnicas 2 indicadores 338 símbolo monetario 77
RDBMS, definición 347 repetición de caracteres 75 sintaxis
recuento de registros 282 REPLACE, comando 289 #define 91
recuperación de configuración resolución de ámbito, operador errores 98
del Depurador 124 (::) 169 sistema, variables de memoria
recuperación de registros 290 restablecimiento de Vea impresión
redeclaración de métodos 168 programas 113 sólo lectura, tablas 319
redondeo de valores RESTRICTED 305 soporte
decimales 80 RETURN, puntos de ruptura en sintaxis de SQL 351-353
referencia el comando 113 tablas de Paradox y
genéricas RLOCK() 325 SQL 345-346
programación 212 ROLLBACK 349 tipos de archivos de dBASE
índices no dBASE 342 ROLLBACK( ) 11 III+/IV 421
integridad 305 SOUNDEX, indexación con 300
métodos 169
miembros de un objeto 142
S SQL 10, 347-374
comandos 349
objetos salida consultas
interna 152 código 219-221 agrupamiento de
notación de punto 143 direccionable 379 datos 368-369
padre e hijo 149 dirección 380 creación 355-371
por notación de punto 149 direccionada 379 diferencia de mayúsculas/
propiedades de objetos 149 dirección 381 minúsculas 358
Refresh( ) 231 seguimiento de programas operadores
registros 277 (Depurador) 105 aritméticos 361
adición 285 seguridad 344 operadores de
bloqueo SELECT de SQL 355-371 comparación 358
automático 321 expresiones 362 operadores lógicos 359
información de estado 323 opción DISTINCT 356, 365 ordenación 365-367
sesiones 326 varias tablas 370
opción FROM 355
borrado Paradox/SQL 339 definición 347
opción GROUP BY 368-369
búsqueda 310-312 errores 353
opción HAVING 369
control de duplicaciones 300 incrustado 354
opción ORDER BY 365, 366,
cuadro de incremento de lectura recomendada 348
367
desplazamiento 241 procedimientos
opción UNION 370
edición 279 almacenados 371-374
opción WHERE 357, 363, 365
filtrado 314 sentencias
separadores numéricos 77 COMMIT 349
en vistas 315 SEQUEL 347
huérfanos 305 ROLLBACK 349
servidor SELECT 355-371
indicadores como
aplicación de control servidores 354
números 338
(DDE) 407 sintaxis permitida 351-353
inserción 339
dBASE 409 tablas
marcado para borrado 290
DDE 403 compatibilidad de tipos
ordenación 295, 296
transacciones 330 de datos 358
recuento 282 niveles de aislamiento 334
secuencia 301 términos 348
sesiones 326-327 usos 347
sin numerar, sustitución 339
cierre 327 SQL, tablas Vea tablas SQL
sustitución de valores 289
creación 327 SQLError( ) 353
reinicialización de
integridad de datos 326 SQLExec( ) 350
propiedades 168 múltiples 318 SQLMessage( ) 353
SET CUAENTER 209 subcadenas, extracción 73
SET EXACT y búsquedas 312

464 Guía del programador


pg_dbw.ix Page 465 Wednesday, August 30, 1995 2:32 PM

subclases 166 varias Translate, propiedad 242


herencia 166 áreas de trabajo 272 trigonometría, funciones 79
subíndice 391 inclusión en lista de
subíndices de elementos de campos 283 U
matrices 60 virtuales 348
subrayado 391 tablas de Paradox UDF, búsqueda de definiciones
subrutinas borrado de registros 339 en el Depurador 103
carga en el Depurador 101 controles de validación 340 #undef 88
seguimiento 105 creación 268 unión
sucesos índices 342 tablas 296
Vea también controladores de referencia 342 uso compartido de
sucesos soporte 345-346 datos 317-327
cambio de foco 231 transacciones 331 tablas de Paradox 318
definición 134 uso compartido de datos 318 utilidades de dos vías 251
programación tradicional y y dBASE 336
tablas no dBASE
controlada por 211
Vea también tablas de Paradox;
V
super, palabra clave 169
superíndice 391 tablas SQL V, función (impresión) 392
sustitución tablas SQL 344 vacías, cadenas 78
datos 293 borrado de registros 339 validación de datos 227
por valores de controles de validación 340 en tablas no dBASE 340
matrices 289 creación 268 valores
registros sin numerar 339 referencia a índices 342 cálculo con campos 313
tipos de tabla 337 soporte 345-346 cálculo de máximos 314
variables automem 291 y dBASE 336 cálculo de mínimos 314
tabulación, posiciones comparación 358
(impresión) 388
T terminación
copia 288
enteros
tablas 12, 267-275 programas 113 conversión en
alias 271 transacciones 349 hexadecimales 401
apertura 269 vínculos DDE 405 hexadecimales
exclusivamente 319 texto, estilos 389 conversión en enteros 401
bloqueo automático 321 this, referencia a objeto 142, 160 lógicos como puntos de
borrado 275 tipos de campo, nuevos 11 ruptura 110
búsqueda tipos de datos 68-86 valores literales
indexadas 310 cálculos mixtos 86 código del Diseñador de
sin indexar 310 conversión 86 fichas 253
cambio de nombre 273 no dBASE 85 como constantes 89
cierre 269 nuevos 292 variables de memoria
cifradas 344 nulo 85 almacenamiento en
controladores de idioma parámetros 40 archivo 66
y globales 452 tablas de SQL 358 ámbito 54-59, 177
copia 274 tipos de letra Vea fuentes ejemplos 58-59
creación 268 trabajos de impresión 385-389 asignación de nombre 53
definición 267 transacciones 329-333, 334 asignación de valores 52
definidas 10 comandos y funciones automem 291
especificación de tipo 337 permitidos 331 borrado 52
estructura creación 330 búsqueda 311
cambio 275 definición 349 como constantes 89
copia 268 en fichas 333 como propiedades de
no dBASE 335-345, 347 locales 330 objeto 133
nombre 270 proceso 330, 331 dBASE IV 438
operaciones SQL en dBASE III+/IV 424 declaración 54
varias 349 servidor 330 definición 51
consultas 370 niveles de aislamiento 334 en código constructor 161
relacionadas 304 tablas no dBASE 340 en sesiones 326
unión 296 terminación 349 encapsuladas en objetos 177
uso exclusivo 319

Indice 465
pg_dbw.ix Page 466 Wednesday, August 30, 1995 2:32 PM

estáticas 57 W
disponibilidad 58
globales 56 WindowMenu 258
liberación 52 Windows
locales 57 adición de sus características
nombre 25 en menús 258-260
privadas 56 API 393-402
protección de valores 43 constantes 401
públicas 56 controladores de
restauración 66 impresora 378
sistema Vea impresión creación de archivos de
tipos 54 ayuda 233
tradicionales y referencia a Windows environment 9
objeto 146
uso 51
VBStream, propiedad 242
Z
VBX, controles 242 ZAP, comando 290
instalación 239
Velocidad de animación, cuadro
de diálogo 123
ventanas
dBASE IV 435
identificadores 397
MDI 196
modales/no modales 195-199
vertical, modo (impresión) 387
vinculación
archivos 28
vinculación de controladores de
sucesos 213-217
vínculo e incrustación de objetos
Vea OLE
vínculos DDE 403-413
Vea también DDE
dinámicos 408
establecimiento 405
finalización 405
Peek() y Poke() 405
Visor, ventana (Depurador) 116
Visual dBASE Compiler 28
visualización
archivos de texto en el
Depurador 125
expresiones en el
Depurador 116
flujo del programa 115

466 Guía del programador


title.dwp Page i Wednesday, August 30, 1995 1:55 PM

Guía del programador

®
VERSIÓN 5.5 dBASE para Windows
Borland International, Inc., 100 Borland Way
P.O. Box 660001, Scotts Valley, CA 95067-0001
title.dwp Page ii Wednesday, August 30, 1995 1:55 PM

Borland puede tener patentes y/o aplicaciones pendientes de patente que cubren el contenido de este documento.
La posesión de este documento no concede al usuario licencia para esas patentes.
COPYRIGHT © 1984, 1994 Borland International. Todos los derechos reservados. Todos los productos de Borland
son marcas comerciales o marcas comerciales registradas de Borland International, Inc. Otras marcas y nombres
de productos son marcas comerciales o marcas comerciales registradas de sus propietarios respectivos.
pg_dbw.toc Page i Wednesday, August 30, 1995 1:56 PM

Contenido
Introducción 1 Parte I
Organización de este manual . . . . . . . . . . . . 2 Conceptos básicos de
Uso del ratón . . . . . . . . . . . . . . . . . . . . .2
Uso del teclado . . . . . . . . . . . . . . . . . . . .3
programación 15
Referencias a dBASE para DOS . . . . . . . . . .3
Capítulo 2
Capítulo 1 Principios de la programación
Introducción a la programación en dBASE 17
en dBASE 5 Codificación manual frente a programación
Extensiones orientadas a objetos . . . . . . . . . . 5 visual . . . . . . . . . . . . . . . . . . . . . . . . 17
Objetos y clases . . . . . . . . . . . . . . . . . . . .5 Razones para el uso de programas
Ámbito de las variables de memoria . . . . . . .6 controlados por sucesos . . . . . . . . . . . . . 18
Bloques de código y punteros de función. . . . .6 Funcionamiento de los programas
Fichas . . . . . . . . . . . . . . . . . . . . . . . . . . 7 controlados por sucesos . . . . . . . . . . . . . 20
Ventanas de ficha . . . . . . . . . . . . . . . . . . .7 Desarrollo de programas controlados
Objetos del interface de usuario . . . . . . . . . .7 por sucesos . . . . . . . . . . . . . . . . . . . . . 22
Controladores de sucesos . . . . . . . . . . . . . .7
Utilidades de dos vías . . . . . . . . . . . . . . . .7 Capítulo 3
Sesiones . . . . . . . . . . . . . . . . . . . . . . . .8 Creación, compilación y
Entorno Windows . . . . . . . . . . . . . . . . . . 8
Controladores de impresora Windows . . . . . .8
comprobación de programas 23
Fuentes de Windows. . . . . . . . . . . . . . . . .8 Creación de un archivo de programa . . . . . . 23
Llamadas a DLL y API de Windows . . . . . . .9 Uso de convenciones estilísticas . . . . . . . . . 24
Multimedia: gráficos y sonido . . . . . . . . . . .9 Compilación de programas . . . . . . . . . . . . 26
DDE y OLE . . . . . . . . . . . . . . . . . . . . . .9 Comparación de versiones de los
Tablas . . . . . . . . . . . . . . . . . . . . . . . . . 10 archivos fuente y objeto . . . . . . . . . . . . . 28
El Database Engine de Borland (BDE). . . . . . 10 Creación de ejecutables. . . . . . . . . . . . . . . 28
Soporte SQL . . . . . . . . . . . . . . . . . . . . . 10 Comprobación de programas . . . . . . . . . . . 29
Fichas escalables . . . . . . . . . . . . . . . . . . 10 Generación de registros aleatorios . . . . . . . . 29
Integridad referencial . . . . . . . . . . . . . . . 11 Uso del análisis de cobertura . . . . . . . . . . . 30
Seguridad . . . . . . . . . . . . . . . . . . . . . . 11 Inicio del análisis de cobertura. . . . . . . . . . 30
Proceso de transacciones controladas Finalización del análisis de cobertura. . . . . . 31
por sucesos. . . . . . . . . . . . . . . . . . . . . 11
Creación frente a actualización de los
Tipos de campo especiales: binario y OLE . . . 11 archivos de cobertura . . . . . . . . . . . . . 32
Indicadores . . . . . . . . . . . . . . . . . . . . . 12 Bloques lógicos . . . . . . . . . . . . . . . . . . 32
Expresiones de campos memo . . . . . . . . . . 12 Visualización de la información del
Programación general . . . . . . . . . . . . . . . 12 archivo de cobertura. . . . . . . . . . . . . . . 33
Preprocesador . . . . . . . . . . . . . . . . . . . . 12
Depurador . . . . . . . . . . . . . . . . . . . . . . 12
Matrices versátiles . . . . . . . . . . . . . . . . . 13
Transmisión mejorada de parámetros. . . . . . 13
Análisis de cobertura. . . . . . . . . . . . . . . . 13
Generación de datos aleatorios para
comprobación . . . . . . . . . . . . . . . . . . . 14
Amplias capacidades. . . . . . . . . . . . . . . . 14

i
pg_dbw.toc Page ii Wednesday, August 30, 1995 1:56 PM

Capítulo 4 Capítulo 6
Uso de los procedimientos y Uso de los tipos de datos 67
bloques de código 35 Acerca de los tipos de datos . . . . . . . . . . . . 67
Acerca de los procedimientos y los bloques de Formato por defecto de los datos . . . . . . . . 69
código. . . . . . . . . . . . . . . . . . . . . . . . 36 Uso de los datos de carácter . . . . . . . . . . . . 70
Procedimientos frente a funciones . . . . . . . . 36 Comparación de cadenas de caracteres. . . . . 71
Declaración de procedimientos. . . . . . . . . . 37 Comparación fonética . . . . . . . . . . . . . . 71
Declaración de parámetros . . . . . . . . . . . . 37 Concatenación de cadenas de caracteres . . . . 72
Llamada de procedimientos con parámetros. . 39 Extracción de parte de una cadena
de caracteres . . . . . . . . . . . . . . . . . . . 73
Variación del número de parámetros . . . . . . 41
Modificación o determinación del uso de
Protección de los valores de las variables de mayúsculas y minúsculas. . . . . . . . . . . . 74
memoria . . . . . . . . . . . . . . . . . . . . . . 43 Eliminación de espacios. . . . . . . . . . . . . . 75
Uso de los archivos y bibliotecas de Repetición de caracteres . . . . . . . . . . . . . 75
procedimientos . . . . . . . . . . . . . . . . . . 44 Uso de los datos numéricos . . . . . . . . . . . . 76
Almacenamiento de procedimientos en los Especificación de valores globales de
archivos de programa . . . . . . . . . . . . . . 44 visualización de los números. . . . . . . . . . 77
Archivos de programa que contienen un solo Visualización de los números en las fichas. . . 78
procedimiento . . . . . . . . . . . . . . . . . 45
Archivos de programa que contienen varios
Realización de cálculos financieros . . . . . . . 79
procedimientos . . . . . . . . . . . . . . . . . 45 Realización de cálculos trigonométricos . . . . 79
Almacenamiento de procedimientos Truncado y redondeo de números . . . . . . . 80
en archivos de procedimiento. . . . . . . . . . 46 Uso de los datos de fecha . . . . . . . . . . . . . 81
Almacenamiento de procedimientos Formato y visualización de las fechas . . . . . 82
en archivos de biblioteca. . . . . . . . . . . . . 46 Uso de los datos de hora . . . . . . . . . . . . . . 83
Uso de los punteros de función y bloques de Uso de los datos lógicos . . . . . . . . . . . . . . 84
código. . . . . . . . . . . . . . . . . . . . . . . . 47 Uso de los tipos de datos nulos . . . . . . . . . . 85
Capítulo 5 Uso de los tipos de datos que no son
de dBASE . . . . . . . . . . . . . . . . . . . . . . 85
Uso de las variables de memoria 51 Conversión entre tipos de datos . . . . . . . . . 86
Acerca de las variables de memoria . . . . . . . 52
Nombres de las variables de memoria . . . . . 53 Capítulo 7
Declaración de las variables de memoria . . . . 54 Uso de las directivas de
Explicación de los ámbitos de las variables preprocesador 87
de memoria . . . . . . . . . . . . . . . . . . . . 54
Públicas . . . . . . . . . . . . . . . . . . . . . . 56 Acerca del preproceso . . . . . . . . . . . . . . . 88
Privadas . . . . . . . . . . . . . . . . . . . . . . 56 Definición de constantes . . . . . . . . . . . . . . 89
Locales . . . . . . . . . . . . . . . . . . . . . . . 57 Uso de literales como constantes . . . . . . . . 89
Estáticas . . . . . . . . . . . . . . . . . . . . . . 57 Uso de variables de memoria
Uso de las matrices . . . . . . . . . . . . . . . . . 60 como constantes . . . . . . . . . . . . . . . . . 89
Manipulación de las matrices y los Uso de identificadores para representar
valores que contienen . . . . . . . . . . . . . . 61 constantes . . . . . . . . . . . . . . . . . . . . . 90
Expansión de las variables de tipo carácter. . . 64 Expansión de expresiones . . . . . . . . . . . . . 91
Almacenamiento de las variables Transmisión de parámetros: identificadores
de memoria en archivos . . . . . . . . . . . . . 66 frente a procedimientos . . . . . . . . . . . . . 92
Compilación condicional. . . . . . . . . . . . . . 93
Compilación condicional para diferentes
versiones de dBASE . . . . . . . . . . . . . . . 94
Inserción de archivos include . . . . . . . . . . . 95
Definición de la opción de cobertura. . . . . . . 96

ii
pg_dbw.toc Page iii Wednesday, August 30, 1995 1:56 PM

Capítulo 8 Evaluación de las expresiones. . . . . . . . . . 116


Adición de puntos de evaluación . . . . . . . .116
Depuración de programas 97 Edición de los puntos de evaluación . . . . . .117
Acerca del Depurador . . . . . . . . . . . . . . . 97 Cambio de los valores de los puntos de
Tipos de errores . . . . . . . . . . . . . . . . . . . 97 evaluación. . . . . . . . . . . . . . . . . . . . .118
Errores de compilación o sintaxis . . . . . . . . 98 Inspección de expresiones . . . . . . . . . . . . 119
Errores en tiempo de ejecución . . . . . . . . . . 98 Establecimiento de inspecciones. . . . . . . . .119
Errores lógicos. . . . . . . . . . . . . . . . . . . . 98 Ventana de inspección . . . . . . . . . . . . . .120
Inicio del Depurador . . . . . . . . . . . . . . . . 99 Definición de un rango de valores . . . . . . .120
Carga de un archivo de programa . . . . . . . 100 Cambio del valor del elemento que está
Carga de subrutinas . . . . . . . . . . . . . . . 101 inspeccionando . . . . . . . . . . . . . . . . .120
Desplazamiento en la ventana Módulo . . . . 102 Inspección de las propiedades de un
objeto o de los elementos de una matriz . .121
Vuelta al editor de textos . . . . . . . . . . . . 102 Visualización de los valores
Desplazamiento a un número de línea . . . . 102 hexadecimales de las cadenas . . . . . . . .121
Desplazamiento del cursor a su posición Inspección de otra expresión . . . . . . . . . .121
anterior . . . . . . . . . . . . . . . . . . . . . . 102 Evaluación, inspección y ámbito . . . . . . . .121
Búsqueda de texto . . . . . . . . . . . . . . . . 103 Evaluación y modificación de datos . . . . . . 122
Desplazamiento a un procedimiento . . . . . 103
Configuración del Depurador. . . . . . . . . . 123
Vuelta al procedimiento que ha realizado
la llamada (origen) . . . . . . . . . . . . . . . 104 Opciones por defecto de configuración. . . . .123
Definición de la velocidad de animación. . . .123
Control de la ejecución del programa . . . . . 104
Definición de fuentes . . . . . . . . . . . . . . .123
Animación . . . . . . . . . . . . . . . . . . . . . 104
Definición de la vía de acceso por defecto
Seguimiento . . . . . . . . . . . . . . . . . . . . 105 a los archivos fuente . . . . . . . . . . . . . . .124
Paso a paso . . . . . . . . . . . . . . . . . . . . 106 Almacenamiento de las opciones de
Uso de los puntos de ruptura . . . . . . . . . . 107 configuración en archivos .CFG . . . . . . . .124
Definición de puntos de ruptura. . . . . . . . 107 Restauración de opciones de configuración . .124
Borrado de puntos de ruptura . . . . . . . . . 108
Edición de puntos de ruptura . . . . . . . . . 108
Asociación del Depurador a copias de
Definición de puntos de ruptura globales o
Visual dBASE. . . . . . . . . . . . . . . . . . . 125
específicos de un programa. . . . . . . . . . 109 Modificación del directorio actual . . . . . . . 125
Ruptura en una expresión . . . . . . . . . . . 110 Visualización de archivos de programa . . . . 125
Ruptura en un número de línea específico . . 111
Especificación de un número de pasadas. . . 111
Realización de una acción cuando se
produce un punto de ruptura . . . . . . . . 111
Ejecución de un programa a toda velocidad
en el Depurador. . . . . . . . . . . . . . . . . 112
Ejecución hasta el cursor . . . . . . . . . . . . 112
Ejecución hasta una sentencia RETURN . . . 113
Especificación de parámetros para el
programa . . . . . . . . . . . . . . . . . . . . 113
Detención de la ejecución del programa . . . 113
Restablecimiento de programas . . . . . . . . 113
Terminación y borrado de programas de la
memoria . . . . . . . . . . . . . . . . . . . . . 113
Depuración de controladores de sucesos . . . 114
Visualización de la pila . . . . . . . . . . . . . 115

iii
pg_dbw.toc Page iv Wednesday, August 30, 1995 1:56 PM

Parte II Capítulo 12
Objetos y clases 127 Diseño orientado a objetos 175
Cuándo utilizar el diseño orientado
Capítulo 9 a objetos . . . . . . . . . . . . . . . . . . . . . . 175
Introducción a la programación Transición del diseño procedural al
orientado a objetos . . . . . . . . . . . . . . . 176
orientada a objetos 129 Fundamentos procedurales . . . . . . . . . . .176
Breve recorrido por la orientación a objetos . 129 Problemas de ámbito de las variables de
Acerca de los objetos . . . . . . . . . . . . . . . 133 memoria . . . . . . . . . . . . . . . . . . . . . .177
Contenedores de objetos. . . . . . . . . . . . . 134 Problemas para lograr la reutilización . . . . .178
Acerca de las clases . . . . . . . . . . . . . . . . 136 Fases de diseño . . . . . . . . . . . . . . . . . . 179
Resumen de la orientación a objetos . . . . . . 138 Un ejemplo . . . . . . . . . . . . . . . . . . . . . 180
Cómo aprender más . . . . . . . . . . . . . . . 191
Capítulo 10
Uso de los objetos 139 Parte III
Creación de objetos . . . . . . . . . . . . . . . . 139 Fichas 193
Referencia a los miembros de un objeto. . . . 142
Uso del operador de punto . . . . . . . . . . . 143 Capítulo 13
Uso del operador de índice . . . . . . . . . . . 144 Creación de aplicaciones
Adición de propiedades y métodos con fichas 195
personalizados. . . . . . . . . . . . . . . . . . 145
Uso de las variables de referencia a objeto . . 146 Acerca de las fichas modales y no modales. . 195
Creación de objetos dentro de otros objetos . 148 Apertura de fichas modales y no modales . . 197
Referencia a objetos principales y Modalidad y ejecución de un programa. . . . 198
derivados. . . . . . . . . . . . . . . . . . . . . 149 Estrategias de programación . . . . . . . . . . 200
Creación de objetos personalizados . . . . . . 150 Interface modal. . . . . . . . . . . . . . . . . . .200
Transmisión de referencias a objeto Interface no modal. . . . . . . . . . . . . . . . .203
como valores Interface no modal orientado a objetos . . . . .206
devueltos y parámetros . . . . . . . . . . . . 151 Definición del comportamiento de la
Liberación de objetos . . . . . . . . . . . . . . . 152 tecla Intro . . . . . . . . . . . . . . . . . . . . . 209
Uso de las matrices como objetos . . . . . . . 153 Cómo aprender más . . . . . . . . . . . . . . . 209
Creación de matrices dispersas . . . . . . . . . 155
Creación de matrices asociativas . . . . . . . . 157 Capítulo 14
Escritura de controladores
Capítulo 11 de sucesos 211
Uso de las clases 159 Acerca de los controladores de sucesos . . . . 211
Declaración de clases . . . . . . . . . . . . . . . 159 Escritura de controladores de sucesos con el
Referencias a miembros en una Diseñador de fichas . . . . . . . . . . . . . . . 213
declaración de clase. . . . . . . . . . . . . . . 160 Averiguación de cuándo tienen lugar
Asociación de métodos a una clase . . . . . . 161 los sucesos . . . . . . . . . . . . . . . . . . . . 217
Declaración de los parámetros de una clase . 163
Protección de propiedades y métodos. . . . . 164
Escritura de código de inicialización y
salida de las fichas. . . . . . . . . . . . . . . . 219
Acerca de las jerarquías de clases . . . . . . . 166 Ejecución de sucesos al abrir una ficha . . . . .220
Declaración de una clase basada en Ejecución de sucesos al cerrar una ficha . . . .221
otra clase . . . . . . . . . . . . . . . . . . . . . 167 Ejecución de procedimientos . . . . . . . . . . 222
Anulación de los miembros heredados . . . . 168
Referencia a métodos desde una clase. . . . . 169
Transmisión de parámetros a las
superclases. . . . . . . . . . . . . . . . . . . . 172
Creación de una jerarquía de clases . . . . . . 173

iv
pg_dbw.toc Page v Wednesday, August 30, 1995 1:56 PM

Trabajo con más de una ficha . . . . . . . . . . 225 Parte IV


Uso de sesiones para crear entornos de
datos independientes. . . . . . . . . . . . . . 225 Tablas 265
Uso compartido del entorno de datos
entre las fichas. . . . . . . . . . . . . . . . . . 226 Capítulo 17
Validación de datos. . . . . . . . . . . . . . . . 227 Uso de las tablas 267
Respuesta a los cambios en los datos . . . . . 228 Creación de tablas. . . . . . . . . . . . . . . . . 268
Gestión de los sucesos de cambio de foco. . . 231 Apertura y cierre de tablas. . . . . . . . . . . . 269
Secuencia de ejecución al desplazarse entre
controles . . . . . . . . . . . . . . . . . . . . . 232 Uso de las bases de datos . . . . . . . . . . . . .270
Especificación de nombres y tipos
Secuencia de ejecución al desplazarse entre
fichas . . . . . . . . . . . . . . . . . . . . . . . 232 de tablas . . . . . . . . . . . . . . . . . . . . . .270
Referencias a tablas: alias . . . . . . . . . . . . .271
Provisión de ayuda en línea. . . . . . . . . . . 233
Uso de varias tablas: áreas de trabajo . . . . . 272
Gestión de los sucesos de desplazamiento
de las fichas . . . . . . . . . . . . . . . . . . . 234 Cambio de nombre de las tablas . . . . . . . . 273
Gestión de los sucesos del ratón . . . . . . . . 235 Copia de tablas . . . . . . . . . . . . . . . . . . 274
Modificación de la estructura de una tabla . . 275
Capítulo 15 Borrado de tablas . . . . . . . . . . . . . . . . . 275
Creación de controles y fichas Protección de datos . . . . . . . . . . . . . . . . 276
personalizados 237 Capítulo 18
Acerca de los controles personalizados . . . . 237 Uso de los registros y campos 277
Instalación de controles personalizados. . . . 238
Acerca del trabajo con registros. . . . . . . . . 277
Declaración de clases de controles
Uso del comando BROWSE . . . . . . . . . . . 278
personalizados. . . . . . . . . . . . . . . . . . 240
Opciones del comando BROWSE . . . . . . . .278
Ejemplos de controles personalizados Edición de datos registro por registro . . . . .279
de dBASE. . . . . . . . . . . . . . . . . . . . . 241 Almacenamiento de los cambios . . . . . . . .279
Uso de los controles VBX . . . . . . . . . . . . 242 Desplazamiento por los registros. . . . . . . . 280
Acerca de las fichas personalizadas . . . . . . 244 Uso de los números de registro e
Controles personalizados no dBASE indicadores . . . . . . . . . . . . . . . . . . . .280
mediante PaintBox . . . . . . . . . . . . . . . 250 Desplazamiento a un registro específico . . . .280
Uso de las opciones de ámbito. . . . . . . . . .281
Capítulo 16 Desplazamiento hacia adelante y
Uso del código generado 251 hacia atrás . . . . . . . . . . . . . . . . . . . . .281
Determinación de la posición del
Acerca del código del Diseñador de fichas . . 252 puntero de registro . . . . . . . . . . . . . . .282
Acerca del código de los menús . . . . . . . . 255 Recuento de los registros . . . . . . . . . . . . .282
Adición de características comunes de Especificación y delimitación de campos . . . 283
Windows en una barra Especificación de una lista de campos . . . . .283
de menús. . . . . . . . . . . . . . . . . . . . . 258
Uso de campos de más de un área
Uso del código de los menús de ventana . . . 261 de trabajo . . . . . . . . . . . . . . . . . . . . .283
Plano de coordenadas . . . . . . . . . . . . . . 262 Uso de información sobre los campos . . . . .284
Direcciones relativas . . . . . . . . . . . . . . . 263 Delimitación de los nombres de campo . . . .284

v
pg_dbw.toc Page vi Wednesday, August 30, 1995 1:56 PM

Adición de registros . . . . . . . . . . . . . . . 285 Definición de relaciones entre tablas . . . . . . 304


Adición de registros . . . . . . . . . . . . . . . 285 Uso de SET RELATION . . . . . . . . . . . . .304
Adición de registros desde dentro de Aplicación de la integridad de datos
una ficha . . . . . . . . . . . . . . . . . . . . . 285 en tablas relacionadas . . . . . . . . . . . . . .305
Inserción de registros . . . . . . . . . . . . . . 288 Normas de integridad de referencias
Adición de nuevos registros con índices en tablas de Paradox. . . . . . . . . . . . . . .306
activos . . . . . . . . . . . . . . . . . . . . . . 288 Filtrado de registros en tablas
Copia de valores de registros anteriores . . . 288 relacionadas. . . . . . . . . . . . . . . . . . . .306
Modificación de los registros . . . . . . . . . . 289 Inclusión de registros en tablas
Sustitución de campos desde una matriz . . . 289 relacionadas. . . . . . . . . . . . . . . . . . . .306
Marcado y borrado de registros . . . . . . . . 290 Capítulo 20
Exclusión de registros marcados . . . . . . . . 290
Uso de las variables automem . . . . . . . . . 291 Búsqueda y resumen de registros 309
Creación de variables automem . . . . . . . . 291 Búsqueda de registros . . . . . . . . . . . . . . 310
Uso de los campos memo, binarios y OLE . . 292 Búsqueda con LOCATE . . . . . . . . . . . . .310
Adición de datos en los campos memo . . . . 292 Búsqueda con FIND y SEEK . . . . . . . . . .310
Uso de los campos memo en las Interacciones de SET NEAR con las
expresiones . . . . . . . . . . . . . . . . . . . 293 búsquedas . . . . . . . . . . . . . . . . . . . .312
Adición de datos en los campos binarios Determinación del resultado de las
y OLE . . . . . . . . . . . . . . . . . . . . . . . 293 búsquedas. . . . . . . . . . . . . . . . . . . . .312
Visualización de datos en los campos Resumen de los datos de los registros . . . . . 313
binarios y OLE . . . . . . . . . . . . . . . . . 294 Cálculo de valores a partir de los campos . . .313
Copia de datos de campos binarios . . . . . . 294 Filtrado de registros con el Diseñador de
consultas. . . . . . . . . . . . . . . . . . . . . .314
Capítulo 19 Selección de campos. . . . . . . . . . . . . . . .315
Ordenación y relación de registros 295 Capítulo 21
Acerca de la indexación y ordenación . . . . . 295 Programación para un entorno
Ordenación de registros . . . . . . . . . . . . . 296
Concatenación de tablas . . . . . . . . . . . . . 296 compartido 317
Uso de las etiquetas y archivos de índice . . . 297 Acceso a datos en un entorno compartido . . 318
Conversión de archivos .NDX a .MDX . . . . 297 Apertura de una tabla para uso exclusivo . . .319
Conversión de etiquetas de .MDX en Apertura de una tabla para acceso de
archivos .NDX. . . . . . . . . . . . . . . . . . 298 sólo lectura . . . . . . . . . . . . . . . . . . . .319
Mantenimiento de los índices. . . . . . . . . . 298 Bloqueo de datos . . . . . . . . . . . . . . . . . 320
Creación de etiquetas de índice. . . . . . . . . 299 Bloqueo automático . . . . . . . . . . . . . . . .320
Conversión de datos de fecha . . . . . . . . . 300 Bloqueo automático de tablas relacionadas . .322
Conversión de datos lógicos . . . . . . . . . . 300 Desactivación y activación del bloqueo
Control de claves duplicadas. . . . . . . . . . 300 automático . . . . . . . . . . . . . . . . . . . .323
Apertura y cierre de los índices. . . . . . . . . 301 Obtención de información adicional sobre el
Control de la secuencia de los registros . . . . 301 bloqueo de registros. . . . . . . . . . . . . . . 323
Determinación del estado del índice. . . . . . 302 Bloqueo explícito de tablas y registros . . . . . 324
Técnicas de indexación. . . . . . . . . . . . . . 303 FLOCK() y RLOCK() . . . . . . . . . . . . . . .325
Uso de REINDEX . . . . . . . . . . . . . . . . . 303 Desbloqueo de una tabla o registro
Uso de SET KEY TO . . . . . . . . . . . . . . . 303 bloqueado explícitamente . . . . . . . . . . .325
Diseño de programas para entornos de red. . 326
Uso de las sesiones . . . . . . . . . . . . . . . . 326
Creación de sesiones. . . . . . . . . . . . . . . .327
Selección de sesiones . . . . . . . . . . . . . . .327
Cierre de las sesiones . . . . . . . . . . . . . . .327

vi
pg_dbw.toc Page vii Wednesday, August 30, 1995 1:56 PM

Capítulo 22 Capítulo 24
Uso de las transacciones 329 Uso de SQL 347
Descripción general del proceso de una Definición de SQL. . . . . . . . . . . . . . . . . 347
transacción . . . . . . . . . . . . . . . . . . . . 330 Terminología SQL . . . . . . . . . . . . . . . . .348
Creación de transacciones . . . . . . . . . . . . 330 Acceso a datos orientado a conjuntos y
Proceso de transacciones con tablas no procedimental . . . . . . . . . . . . . . . .349
de dBASE y Paradox . . . . . . . . . . . . . . 331 Transacciones en SQL . . . . . . . . . . . . . . .349
Uso de comandos y funciones de SQL incrustado y SQLExec( ) . . . . . . . . . . 350
dBASE en las transacciones . . . . . . . . . . 331 Sintaxis de SQL permitida . . . . . . . . . . . . 351
Uso de las transacciones en las fichas . . . . . 332 Añadidos, operadores y funciones . . . . . . .352
Definición de puntos de transacción. . . . . . 332 Variables de memoria . . . . . . . . . . . . . . .353
Uso de niveles de aislamiento en las Diagnosis de errores SQL . . . . . . . . . . . . 353
transacciones de servidor . . . . . . . . . . . 334 SQL incrustado y servidores SQL . . . . . . . 354
Requisitos previos . . . . . . . . . . . . . . . . .354
Capítulo 23 Uso de la sentencia SELECT de SQL . . . . . . 355
Uso de tablas que no son Sintaxis de SELECT . . . . . . . . . . . . . . . .355
de dBASE 335 Consultas simples . . . . . . . . . . . . . . . . .355
Selección de todas las columnas . . . . . . . . .356
Diferencias en las tablas de Paradox y SQL. . 336
SELECT con DISTINCT. . . . . . . . . . . . . .356
Apertura de una base de datos . . . . . . . . . 336 Cláusula WHERE . . . . . . . . . . . . . . . . .357
Especificación del tipo de tabla . . . . . . . . . 337 Uso de los operadores de comparación . . . .358
Delimitación de los nombres de campo. . . . 337 Combinación de condiciones. . . . . . . . . . .359
Uso de indicadores para identificar Definición y uso de las expresiones . . . . . . .361
registros. . . . . . . . . . . . . . . . . . . . . . 338 Expresiones en la cláusula SELECT . . . . . .362
Inserción de registros. . . . . . . . . . . . . . . 339 Expresiones en la cláusula WHERE . . . . . .363
Borrado de registros . . . . . . . . . . . . . . . 339 Funciones agregadas en las expresiones . . .363
Expresiones agregadas en las cláusulas
Validación de datos. . . . . . . . . . . . . . . . 340 SELECT . . . . . . . . . . . . . . . . . . . . .364
Uso de las transacciones . . . . . . . . . . . . . 340 Expresiones agregadas cualificadas por
Uso de los índices . . . . . . . . . . . . . . . . . 342 una cláusula WHERE . . . . . . . . . . . . .365
Diferencias en las claves de índice . . . . . . . 343 SELECT con predicados BETWEEN,
IN y LIKE . . . . . . . . . . . . . . . . . . . .365
Diferencias en seguridad . . . . . . . . . . . . 344
Ordenación del resultado . . . . . . . . . . . .365
Ejecución de sentencias SQL . . . . . . . . . . 344 Ordenación por una sola columna . . . . .365
Resumen del soporte de tablas de Paradox Ordenación por más de una columna . . .366
y SQL . . . . . . . . . . . . . . . . . . . . . . . 345 ORDER BY con una cláusula WHERE . . .367
Agrupamiento de filas . . . . . . . . . . . . . .368
Cláusula GROUP BY . . . . . . . . . . . . .368
Cláusula HAVING . . . . . . . . . . . . . .369
Cláusula UNION . . . . . . . . . . . . . . . . .370
Uso de los procedimientos almacenados . . . 371
Requisitos previos . . . . . . . . . . . . . . . . .371
Sintaxis de declaración . . . . . . . . . . . . . .372
Sintaxis de ejecución. . . . . . . . . . . . . . . .372
Ejemplos. . . . . . . . . . . . . . . . . . . . . . .372
Declaración automática de procedimientos
almacenados . . . . . . . . . . . . . . . . . . .373
Limitaciones . . . . . . . . . . . . . . . . . . . .374

vii
pg_dbw.toc Page viii Wednesday, August 30, 1995 1:56 PM

Parte V Capítulo 26
Entorno Windows 375 Uso de las DLL y del API
de Windows 393
Capítulo 25 Acerca de las bibliotecas y los vínculos . . . . 393
Impresión 377 Uso de las DLL con dBASE . . . . . . . . . . . 395
Acerca de la impresión. . . . . . . . . . . . . . 377 Llamada de funciones DLL . . . . . . . . . . . 395
Nuevas características de impresión de Creación de prototipos y conversión de
Visual dBASE . . . . . . . . . . . . . . . . . . 378 los tipos de datos. . . . . . . . . . . . . . . . .396
Especificación de los controladores Ejemplos de llamada de funciones DLL . . . .397
de impresora. . . . . . . . . . . . . . . . . . . 378 Carga y liberación de las DLL . . . . . . . . . .399
Determinación del nombre de un Llamada de las funciones del API de
controlador de impresora . . . . . . . . . . . 379 Windows . . . . . . . . . . . . . . . . . . . . . 400
Salida direccionable y direccionada . . . . . . 379 Uso de las constantes del API
Dirección de la salida. . . . . . . . . . . . . . . 380 de Windows . . . . . . . . . . . . . . . . . . .401
Dirección de la salida direccionable . . . . . . 380 Gestión a nivel de bits de los parámetros
Dirección de la salida direccionada . . . . . . 381 y los valores devueltos . . . . . . . . . . . . .401
Averiguación del estado de la impresora . . . 382
Impresión de la salida en un archivo . . . . . 382 Capítulo 27
Posición de los caracteres de salida . . . . . . 383 Expansión de dBASE mediante
Impresión de la salida con coordenadas
absolutas . . . . . . . . . . . . . . . . . . . . . 383
DDE/OLE 403
Impresión de la salida con coordenadas Acerca de DDE . . . . . . . . . . . . . . . . . . 403
relativas . . . . . . . . . . . . . . . . . . . . . 383 Creación de aplicaciones cliente . . . . . . . . 404
Uso de coordenadas fraccionarias . . . . . . . 384 Establecimiento y terminación de un
Definición de la posición vertical y vínculo DDE . . . . . . . . . . . . . . . . . . .405
horizontal de la impresora . . . . . . . . . . 384 Intercambio de datos con el servidor . . . . . .405
Gestión de números de línea, saltos de Desplazamiento por regiones . . . . . . . . . .406
página y número de copias . . . . . . . . . . 385 Envío de comandos al servidor . . . . . . . . .407
Formato de la salida impresa . . . . . . . . . . 386 Creación de vínculos dinámicos . . . . . . . . .408
Ajuste de la longitud de la página . . . . . . . 386 Creación de aplicaciones servidor . . . . . . . 409
Especificación del modo apaisado Arranque de Visual dBASE desde una
o vertical . . . . . . . . . . . . . . . . . . . . . 387 aplicación cliente . . . . . . . . . . . . . . . . .410
Selección de las opciones de impresión Escritura de rutinas de controlador de
por parte del usuario . . . . . . . . . . . . . . 387 inicialización . . . . . . . . . . . . . . . . . . .411
Especificación de márgenes y sangrado. . . . 387 Acerca de OLE. . . . . . . . . . . . . . . . . . . 413
Especificación de la alineación horizontal . . 388 Uso de la automatización OLE. . . . . . . . . .414
Ajuste del interlineado y posiciones de
tabulación . . . . . . . . . . . . . . . . . . . . 388
Avances de hoja y paginación . . . . . . . . . 388
Avances de hoja y avances de línea . . . . . . 388
Números de página y rangos de página . . . 389
Modo de calidad y modo de borrador. . . . . 389
Especificación de las fuentes y estilos
de texto . . . . . . . . . . . . . . . . . . . . . . 389
Uso de las fuentes de Windows con
Visual dBASE . . . . . . . . . . . . . . . . . . 390
Uso de una fuente . . . . . . . . . . . . . . . . 390
Estilos de texto . . . . . . . . . . . . . . . . . . 391
Justificación de las cadenas de texto . . . . . . 392
Especificación del ancho de línea con
ajuste vertical . . . . . . . . . . . . . . . . . . 392
Especificación de la alineación horizontal
con ajuste vertical . . . . . . . . . . . . . . . . 392

viii
pg_dbw.toc Page ix Wednesday, August 30, 1995 1:56 PM

Parte VI Apéndice C
Apéndices 417 Uso de los juegos de caracteres y
controladores de idioma 443
Apéndice A Acerca de los juegos de caracteres . . . . . . . 444
Ejecución de aplicaciones Acerca de los controladores de idioma . . . . 446
de dBASE para DOS 419 Coincidencias exactas e inexactas. . . . . . . . 448
Escritorio de Visual dBASE y Uso de controladores de idioma globales . . . 449
aplicaciones DOS . . . . . . . . . . . . . . . . 420 Uso de controladores de idioma de tablas . . 450
Pasos básicos. . . . . . . . . . . . . . . . . . . . 421 Identificación del controlador de idioma
y página de códigos de una tabla . . . . . . .451
Tipos de archivos permitidos . . . . . . . . . . 421
Uso de los controladores de idioma de
Entorno de configuración de Windows . . . . 422 la tabla frente a los globales . . . . . . . . . .452
DBASEWIN.INI. . . . . . . . . . . . . . . . . . 422 Incompatibilidades de caracteres en los
Referencias a controladores de impresora . . 422 nombres de campo . . . . . . . . . . . . . . . 453
Formatos por defecto de los datos . . . . . . . 423
Conflictos con las pulsaciones de Windows . 424 Indice 455
Mejoras en el lenguaje . . . . . . . . . . . . . . 424
Evitar conflictos de funciones. . . . . . . . . . 425
Cambios específicos del lenguaje. . . . . . . . 425
Diferencias en los códigos de error. . . . . . . 425

Apéndice B
Mejora de las aplicaciones de
dBASE para DOS 427
El Generador de componentes . . . . . . . . . 428
Estrategias de mejora. . . . . . . . . . . . . . . 428
Programa de dBASE IV de ejemplo . . . . . . 429
Programa de dBASE IV de ejemplo
mejorado . . . . . . . . . . . . . . . . . . . . . 431
Explicación de las diferencias en el código . . 434
Inicialización . . . . . . . . . . . . . . . . . . . 434
Creación de la ventana. . . . . . . . . . . . . . 435
SAY/GET y objetos de datos . . . . . . . . . . 436
Variables de memoria . . . . . . . . . . . . . . 438
Procedimientos y funciones. . . . . . . . . . . 438
Transición a los menús de Visual dBASE . . . 440
Mejoras adicionales. . . . . . . . . . . . . . . . 442

ix
pg_dbw.toc Page x Wednesday, August 30, 1995 1:56 PM

Anda mungkin juga menyukai