0% encontró este documento útil (0 votos)
40 vistas65 páginas

Tutorial de Visual FoxPro y ODBC

Este documento presenta un tutorial sobre Visual FoxPro. Cubre temas como programación orientada a objetos, uso de la API de Windows, y capacidades cliente-servidor como conexiones ODBC a bases de datos remotas y creación de vistas sobre tablas remotas. El documento también proporciona ejemplos de código para ejecutar consultas SQL y actualizar datos en un servidor Oracle a través de funciones como SQLCONNECT, SQLEXEC y SQLDISCONNECT.

Cargado por

Ricardo Suaste
Derechos de autor
© Attribution Non-Commercial (BY-NC)
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
40 vistas65 páginas

Tutorial de Visual FoxPro y ODBC

Este documento presenta un tutorial sobre Visual FoxPro. Cubre temas como programación orientada a objetos, uso de la API de Windows, y capacidades cliente-servidor como conexiones ODBC a bases de datos remotas y creación de vistas sobre tablas remotas. El documento también proporciona ejemplos de código para ejecutar consultas SQL y actualizar datos en un servidor Oracle a través de funciones como SQLCONNECT, SQLEXEC y SQLDISCONNECT.

Cargado por

Ricardo Suaste
Derechos de autor
© Attribution Non-Commercial (BY-NC)
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd

Tutorial sobre

Visual FoxPro

Introducción a las capacidades Cliente-Servidor

Introducción a la POO en Visual FoxPro (I)


Introducción a la POO en Visual FoxPro (y II)

Uso del API de Windows : Introducción (I)


Uso del API de Windows : Funciones para ficheros .INI (II)
Uso del API de Windows : Funciones para ficheros .INI (III)
Uso del API de Windows : Introducción a las ventanas (IV)
Uso del API de Windows : Introducción a las ventanas (V)
Uso del API de Windows : Manejo del Registro (y VI)

Introducción a las capacidades Cliente-Servidor 


Visual FoxPro 3.0 es un gran salto en el desarrollo de aplicaciones xBase. Dentro de todas las novedades
que trae esta nueva versión está la ampliación de sus capacidades en el desarrollo de aplicaciones Cliente-
Servidor. Ahora es mucho más sencillo realizar conexiones a bases de datos remotas por medio de ODBC,
pues Visual FoxPro nos va a permitir no sólo realizar llamadas explícitas a funciones de ODBC, sino crear
vistas locales sobre tablas remotas, lo que supone que podemos trabajar con tablas de SQL-Server u
Oracle práticamente como si fueran tablas del propio Visual FoxPro.

FoxPro 2.5/2.6 ya incorporaba la posibilidad de realizar aplicaciones con acceso a ficheros de otros
formatos y bases de datos remotas por medio de ODBC, pero ahora esta posibilidad se potencia y
simplifica, aportando nuevas y más fáciles formas de desarrollar en este entorno.

Cuando aparezca el producto definitivo, ahora en fase de betatest, las nuevas capacidades en el desarrollo
Cliente-Servidor serán una de las mejoras más importantes del producto junto a la introducción de los
diccionarios o bases de datos (DBC), la programación orientada a objeto y los nuevos diseñadores de
pantallas.

ODBC

ODBC se ha convertido de hecho en un estándar en el acceso a datos, tanto locales en otros formatos,
como remotos en servidores SQL. Visual FoxPro utiliza toda la potencia de ODBC 2.0 integrando en su
versión estándar todas las funcionalidades para el acceso a estos drivers.

El producto definitivo incluirá un conjunto de drivers ODBC 2.0 para leer ficheros Access, dBase, Btrieve,
Excel, Paradox y TXT, así como para conectarse a servidores SQL Server y Oracle. No son los más rápidos
y mejores drivers que se pueden encontrar, existe un mercado con más de 150 fabricantes de drivers
ODBC que ofrecen, en muchos casos, drivers más optimizados y que permiten el acceso a más de 50 tipos
diferentes de formatos y servidores. Pero, al fin y al cabo Microsoft sólo nos regala unos pocos drivers
para que empecemos a trabajar.

Integración de las capacidades Cliente-Servidor


Hasta ahora, si queríamos incluir capacidades de conexión con bases de datos remotas, debíamos adquirir
FoxPro Versión Profesional, que incluye el Conectivity Kit, y cargar la librería [Link]. Con esto
obteníamos una serie de funciones para acceder por medio de ODBC a bases de datos de otros formatos y
a bases de datos de servidores SQL.

Con Visual FoxPro 3.0 la posibilidad de interactuar con ODBC está implícita al producto en su Versión
Estándar, es decir, se incorporan al lenguaje de Visual FoxPro todas las funciones necesarias de estos
drivers, sin necesidad de adquirir o cargar ningún otro producto adicional.

Además se ha incluido toda una serie de diseñadores y asistentes para hacer más fácil e intuitivo el
trabajo con Visual FoxPro y ODBC. Este conjunto de herramientas nos van a permitir de forma interactiva
definir y realizar conexiones a servidores SQL sin necesidad de teclear largas listas de comandos.

Esta integración de las capacidades Cliente-Servidor en Visual FoxPro, facilitará, sin duda, la extensión del
desarrollo de aplicaciones en modelo Cliente-Servidor a un mayor número de usuarios.

Funciones SQL : El SQL Pass-through

Con este término nos referimos a las funciones para la ejecución explícita de órdenes SQL por medio de
los drivers ODBC, similares a las que obteníamos con el Connectivity Kit de la Versión Profesional de
FoxPro. En Visual FoxPro estas funciones comienzan con las letras "SQL", a diferencia de la versión
anterior que comenzaban con "DB", y son las que se señalan en el cuadro 1.

Con todas estas funciones somos capaces de realizar llamadas explícitas a los drivers de ODBC sin que
estos drivers realicen la interpretación del SQL, dejando a los servidores de bases de datos realizar esta
labor.

Para ejemplificar este sistema de trabajo vamos a ver un caso muy simple, pero no elemental : la
obtención de los datos de tres tablas residentes en Oracle y la actualización de algunos de estos datos.
Las sentencias SQL pueden ser mucho más sencillas o mucho más complejas, en esta ocasión hemos
querido presentar un caso intermedio.

****************************************
* Programa de ejemplo *
* con SQL pass-through *
* *
* Conexión al servidor Oracle con un *
* Data Source de ODBC *
****************************************
nConexion = SQLCONNECT ;
( "ORADESA", "PALMUN", "PASSWORD" )
IF nConexion < 1
ERROR "Conexión no realizada."
ENDIF
****************************************
* Ejecución de una consulta al servidor*
****************************************
IF 0 < SQLEXEC( nConexion, ;
"SELECT [Link],"+;
"[Link],"+;
"[Link],"+;
"[Link],"+;
"[Link] "+;
"FROM USUARIOS, APLICACIONES, ;
"USER_APLI "+;
"WHERE ;
"USUARIOS.NOMBRE_CORTO = USER_APLI.NOMBRE_CORTO"+;
"AND USER_APLI.ORDEN = [Link]",;
"user_apli" )
ERROR "Ejecución SQL incorrecta."
ENDIF
******************************************
* Si se quiere, se pueden visualizar *
* en un Browse *
******************************************
BROWSE NOWAIT
******************************************
* Se modifican los datos en Oracle *
******************************************
IF 0 < SQLEXEC( nConexion, ;
"UPDATE USUARIOS SET"+ ;
"[Link] = 'PEDRO'"+;
"WHERE [Link] = 'JORGE '" )
ERROR "Ejecución SQL incorrecta."
ENDIF
***********************************
* Desconexión del Servidor *
***********************************
=SQLDISCONNECT( nConexion )
***********
* Fin *
***********

Como se puede ver, el esquema de desarrollo es simple, por lo menos existirá un conexión a una fuente
de datos de ODBC por medio de SQLCONNECT, una o varias ejecuciones de sentencias SQL por medio
de SQLEXEC y un cierre de la conexión con SQLDISCONNECT.

Este esquema se puede perfeccionar y completar definiendo el modo de realizar las transacciones,
definiendo ejecuciones asíncronas o batch, etc.. Pero en definitiva el sistema es simple y las posibilidades
son muchísimas.

SQLCANCEL Cancela una sentencia SQL que se está ejecutando


SQLCOLUMS Consulta los nombres de las columnas disponibles
SQLCOMMIT Confirma los cambios realizados
SQLCONNECT Realiza una conexión
SQLDISCONNECT Realiza una desconexión
SQLEXEC Ejecuta una sentencia SQL
SQLGETPROP Obtiene valores de la configuración
SQLMORERESULT Obtiene más datos de una consulta anterior
SQLROLLBACK Deshace los cambios realizados
SQLSETPROP Cambia valores de la configuración
SQLSTRINGCONNECT Predefine la cadena de conexión
SQLTABLES Consulta los nombres de las tablas disponibles

Definición de conexiones: CREATE CONNECTION


Una de las posibilidades incorporadas a Visual FoxPro es la de utilización de los servicios de ODBC a través
del entorno y no sólo por medio de comandos y funciones. Ejemplo de esto es la posibilidad de crear
conexiones predefinidas por medio del Diseñador de Conexiones. Para ejecutarlo deberemos tener abierta
una base de datos (DBC) y teclear CREATE CONNECTION.

Por medio de este diseñador podemos definir todas las características de una conexión a bases de datos
por medio de ODBC de forma sencilla y sin necesidad de utilizar las funciones SQLSETPROP y
SQLGETPROP para su configuración. Esta conexión se almacena dentro de la base de datos (.DBC) y está
disponible siempre que abramos la base de datos en cuestión.

Creación de Vistas sobre tablas remotas: CREATE SQL VIEW

Otra posibilidad de trabajo diferente al uso de SQL pass-through es la utilización de Vistas Remotas. Las
vistas están definidas en las bases de datos (.DBC), junto a las conexiones, tablas y procedimientos
almacenados y pueden referirse tanto a datos locales (DBF) como a datos remotos (obtenidos por medio
de ODBC).

Para definir una vista y sus propiedades podemos utilizar una serie de ordenes SQL o utilizar el entorno de
desarrollo. La orden básica para crear vistas es CREATE SQL VIEW. Con ella podremos crear una vista
sobre tablas remotas. El caso más sencillo es realizar la vista sobre todos las columnas de una tabla, para
ello la sentencia es simplemente :

CREATE SQL VIEW usuarios ;


REMOTE CONNECTION oradesa ;
SHARE AS SELECT * FROM ;
USUARIOS

En ese instante la vista queda incoporada en la base de datos (DBC) que tengamos abierta en ese
momento. De esta forma podemos vincular una vista remota de Visual FoxPro con cada tabla del servidor
SQL sin apenas complicación y haciendo totalmente transparente el uso de las mismas.

La vista no tiene por qué restringirse a una sóla tabla, es muy posible que deseemos ver como una vista
de Visual FoxPro varias tablas remotas. Siguiendo con el ejemplo anterior podemos definir la vista sobre
los datos de esas tres tablas como se muestra en este ejemplo.
CREATE SQL VIEW User_apli REMOTE CONNECTION oradesa SHARE
AS SELECT [Link], ;
[Link], ;
[Link], ;
[Link], ;
[Link] ;
FROM USUARIOS, APLICACIONES, USER_APLI ;
WHERE USUARIOS.NOMBRE_CORTO = USER_APLI.NOMBRE_CORTO ;
AND USER_APLI.ORDEN = [Link]

De esta forma estamos definiendo una vista sobre algunos de los datos de tres tablas unidas por medio de
una "Join". Podemos complicar la definición de la vista tanto como lo deseemos, sin apenas esfuerzo por
nuestra parte.

Creación de Vistas sobre tablas remotas: DISEÑADOR DE VISTAS

La otra posibilidad es utilizar el Diseñador de Vistas. Para ello deberemos abrir un base de datos con la
sentencia OPEN DATABASE y una vez abierta la modificaremos con MODIFY DATABASE, nos aparecerá el
diseñador de bases de datos y seleccionaremos en el menú 'Database' la 'New Remote View' o pulsaremos
el icono correspondiente de la barra de herramientas.

Se nos pedirá que seleccionemos conexión ODBC almacenada en la base de datos o alguna fuente de
datos predefinida :

Una vez seleccionada la conexión ODBC se nos abrirá el Diseñador de Vistas, muy similar al generador de
consultas. En este diseñador seleccionaremos las tablas remotas sobre las que deseamos hacer la vista.
En este caso hemos seleccionado las tres tablas relacionadas que venimos utilizando, a fin de obtener la
vista con datos de las tres. La definición puede llegar a ser muy sencilla -todos los datos de una sóla
tabla- o muy compleja -pudiendo definir multitud de parámetros, selecciones, órdenes, agrupación,
campos a visualizar y criterios de actualización.
Una vez cerrado el diseñador y dado un nombre a la vista, la definición de la misma será almacenada en
la base de datos. Siempre que tengamos abierta esta base de datos, podremos hacer uso de la vista.

Trabajo sobre vistas remotas: Como tablas Fox

Pero, ¿cómo trabajamos con las vistas remotas? Cuando abrimos una bases de datos que tiene definida
una vista podemos utilizarla como si se tratara de una tabla normal. Por ejemplo, podremos abrirla con
USE, utilizar sus datos en programas, hacer modificaciones con APPEND y REPLACE o con SQL, realizar
búsquedas, etc..

Para mostrar la transparencia en el uso de una vista he aquí el aspecto de una vista remota desde un
BROWSE después de haber hecho un simple USE :

OPEN DATABASE Data1


USE user_list
BROWSE
Todas las operaciones que realizamos sobre la vista, como añadir un registro, o modificar un dato, se
corresponden a operaciones sobre las tablas originales, de este modo si realizamos una modificación en
los datos de la vista, estamos modificando automáticamente los datos en las tablas remotas y si alguien
realiza un cambio en un valor en la tabla remota, nosotros observaremos ese cambio en la vista.

Podemos no darnos cuenta de la importancia de este hecho, podemos trabajar con datos remotos como si
fueran datos de Visual FoxPro, manteniendo una total integridad entre la vista remota que maneja Visual
FoxPro y los datos del servidor SQL, sin que sea necesario por nuestra parte ningún código específico para
ello, simplemente definiendo los datos como una vista remota.

Un ejemplo. Sobre la vista que hemos definido anteriormente vamos a modificar algunos valores y esta
modificación será realizada automáticamente en las base de datos remota.

********************************************
*
* Programa de ejemplo con vistas
*
*******************************************

***************************
* Abre la Base de Datos *
***************************
OPEN DATABASE Data1

*******************************************
* Abre la vista sobre tablas de Oracle *
*******************************************
USE User_apli

*******************************************
* Si se quiere, se pueden visualizar en *
* un Browse *
*******************************************
BROWSE NOWAIT

******************************************
* Se modifican los datos en Oracle *
******************************************
REPLACE Nombre WITH "PEDRO" FOR Nombre == "JORGE "

***********
* Fin *
***********

Las posibilidades son inmensas, podemos relacionar tablas locales con vistas remotas, realizar todo tipo
de operaciones sobre los datos remotos, realizar búsquedas, modificaciones, etc. Es prácticamente
indiferente trabajar sobre tablas locales que sobre vistas remotas, es decir, gracias a la capacidad de
Visual FoxPro de definir vistas remotas podremos trabajar en un entorno Cliente-Servidor de forma
transparente.

Asistente para Upsizing

Gracias a la enorme similitud en el trabajo con tablas locales y con vistas remotas, es posible realizar las
labores de desarrollo sobre vistas o tablas locales y en un momento dado, cercano a la finalización del
proyecto, realizar una migración de las estructuras de datos y relaciones a un Servidor SQL tipo SQL-
Server u ORACLE. Esto nos permite desarrollar sin necesidad de disponer de un servidor SQL, siempre
costoso.

Para ayudar en esta labor de migración desde las tablas y vistas locales a tablas en un servidor SQL existe
el "Upsizing Wizard" (o asistente de 'upsizing'), que nos facilita el traslado del diseño de nuestra base de
datos desde Visual FoxPro a un servidor de Bases de Datos.

Si bien es posible que en la versión definitiva de Visual FoxPro el asistente tenga algunas modificaciones
sobre la versión beta actual, ésta es una de las pantallas del mismo :

Su uso es muy sencillo, siguiendo sus indicaciones seremos capaces de trasladar nuestro modelo de datos
local a un modelo Cliente-Servidor. Es decir, una vez realizado este upsizingnuestra aplicación pasará a
trabajar sobre un diseño Cliente-Servidor, en principio, sin más modificaciones, aun cuando debe de
tenerse en cuenta algunos aspectos sobre el nuevo diseño y ajustar los parámetros de las vistas remotas
a fin de que sea un sistema efectivo.

Conclusión : Una de las mejores herramientas para el modelo Cliente-Servidor

Visual FoxPro está llamado a convertirse en una de las mejores herramientas para el desarrollo de
aplicaciones en entornos Cliente-Servidor. Conjuga un sistema de desarrollo muy optimizado por medio de
"forms", un lenguaje xBase potente y extendido, una programación orientada a objeto sencilla y efectiva,
una velocidad en el manejo de datos locales inmejorable y unas capacidades de acceso a datos remotos
sorprendentemente fáciles.

Por lo tanto, la revolución que sin duda supone la aparición de Visual FoxPro nos acercará a todos un poco
más al interesante mundo del desarrollo de aplicaciones en entornos Cliente-Servidor.

Introducción a la POO en Visual FoxPro : Conceptos básicos (I)


Con este artículo empezamos una pequeña serie que nos acercará a la Programación Orientada a Objeto
(POO) en Visual FoxPro. En este, el primero, haremos una descripción de los conceptos básicos sobre los
que se sustenta la POO a fin de introducirnos en es ta forma de programar.
Si Vd. es nuevo en esto de la POO es posible que en algunos momentos se sienta perdido. No se
preocupe, es normal. La orientación a objeto tiene un gran número de conceptos interrelacionados, por
ello cuando describimos uno de forma separada parece carece r de sentido. No dude en releer el texto una
y otra vez, de esa forma conseguirá tener una visión de conjunto.

Si por el contrario ya lleva algún tiempo aplicando estas técnicas lo que le interesa realmente es la
implementación que de estás técnicas de programación hace Visual Foxpro (VFP), en parte podrá ver
algunas de sus características en este artículo, pero e s realmente en los próximos donde se desarrollará
toda la potencia de VFP. Hemos intentado en todo momento ser rigurosos, pero espero que disculpe que
en algunas ocasiones se haga una descripción poco formal de las características de la POO a fin de facil
itar la compresión.

Si ya conoce algunas de las características de VFP sabrá que es posible realizar POO casi sin darse cuenta,
pero en este artículo haremos hincapié en los entresijos de este tipo de programación desde la codificación
más tradicional, a fin de saber perfect amente lo que estamos haciendo en cada momento. En próximos
artículos describiremos la POO cuando utilizamos Form Designer, el generador de pantallas, o Class
Designer, la herramienta visual para definición de clases, así como el manejo de herramientas de apoyo
como el Class Browser.

No es tan difícil

Está de moda hablar de POO, y no es nuevo este concepto, pero es ahora cuando se está generalizando su
uso. Esta moda a provocado alguna confusión, muchas herramientas han dicho estar orientadas a objeto
sin serlo, otras han realizado implementaciones muy extrañas, y algunas, como VFP, han sabido mantener
un equilibrio y una buena implementación, por lo que son verdaderamente recomendables para desarrollar
con este nuevo sistema de programación.

Quizás uno de los problemas a la hora de afrontar por primera vez la POO, estriba en el temor que puede
producir tantos y tantos nuevos conceptos que se ciernen sobre nosotros. Lo cierto es que la POO no es
tan fiera como la pintan, pero en la medida que cambia algo nuestra forma de pensar los programas, sus
componentes y el modo que se relacionan entre si, requiere un cierto esfuerzo.

Los que llevan años en esto de la programación recuerdan lo difícil y extraño que fue el paso de la
programación lineal (con goto) a la programación estructurada (con call), ahora toca el paso de la
programación estructurada a la POO. La POO viene a comp letar algunas de las lagunas de la
programación estructurada, como esta vino a solucionar algunos de los problemas de la programación
lineal. No es la soluciona a todos nuestros problemas, pero sí facilita nuestra labor de programación y
mantenimiento.

Objetivos de la POO

La POO intenta ser un mejor sistema para el desarrollo de aplicaciones. Como toda técnica de
programación si se hace mal puede ser desastrosa -pensemos los líos que se pueden provocar si
aplicamos mal la programación estructurada- pero es un mejor sistema de desarrollo.

Una de las primeras mejoras que obtenemos por el uso de la POO es el permitirafrontar programas más
complejos y de mayor tamaño con menos esfuerzo. El hecho de trabajar con pequeños elementos bien
definidos, como son los objetos, nos permite aisla r cada componente de la aplicación del resto y de esa
forma aprovechar en mayor medida nuestro esfuerzo.

Una vez adiestrados en las nuevas técnicas de orientación a objeto obtendremos también una mejora
considerable en nuestro rendimiento de desarrollo. Las grandes facilidades para el reaprovechamiento del
código que nos ofrece la orientación a objeto harán que desarrollemos con mayor velocidad, pero
manteniendo unos buenos niveles de calidad.
La reutilización de código en la POO nos otorga una gran flexibilidad. La existencia de la herencia permite
modificar las características que necesitemos de una clase de objeto con seguridad de no alterar las
especificaciones del mismo y aprovechan do todo el desarrollo realizado en el mismo.

El mantenimiento  de aplicaciones se ha visto como uno de los grandes problemas de la programación


actual. Con las técnicas de POO es más sencillo realizar este mantenimiento. Los objetos son elementos
de pequeño tamaño, bien definidos, y por lo tan to más fáciles de mantener. Además la existencia de la
herencia nos va a asegurar que la modificación de algunas características de la clase no van a afectar a los
desarrollos ya terminados.

La definición correcta de los objetos permitirá tener una mejor estructuración de nuestros programas. Con
la POO se tiene un acercamiento más natural a los problemas y por lo tanto los análisis de aplicaciones
orientadas a objeto tienen un acercami ento a la realidad mucho más completa que con la programación
estructurada. Esto no quiere decir que al principio no cueste un poco realizar este tipo de análisis, pero
una vez adquirida las nuevas técnicas, es mucho más sencillo.

La POO es muy fácilmente compresible en entornos gráficos, pues el hecho de que estos entornos
manejen objetos gráficos hace muy recomendable este tipo de programación. Pero debemos tener claro
que es totalmente posible realizar POO en entornos de tipo ca rácter y es posible programar entornos
gráficos sin POO.

No todo son ventajas.

Tampoco podemos decir que todo sean facilidades. Por una parte la POO nos va ha obligar a cambiar
nuestra forma de pensar los programas y por lo tanto es necesario un tiempo para que las nuevas
técnicas se nos hagan habituales y automáticamente nos salga programar de esta forma.

No basta con dominar estas técnicas para que alcancemos las mejores cotas de productividad,
posiblemente sea necesario elaborarnos unas buenas librerías de clases. Para ello es conveniente
desarrollar un par de aplicaciones bajo esta técnicas para darnos cuenta de que es lo que realmente es
factible de ser reutilizado y de esa forma organizar nuestras propias librerías de clases.

La depuración de código orientado a objeto es algo más compleja que la depuración de código
estructurado. Esto no quiere decir que nuestro código va a ser peor o va ha tener más errores, pero sí es
cierto que en el caso de producirse un error deberemos re correr todo el árbol de herencia para
encontrarlo, algo que en programación estructurada no tenemos que hacer.

Pero en general, podemos decir que los inconvenientes son realmente menores que las ventajas, y por lo
tanto es realmente recomendable el desarrollo con POO.

Elementos de la POO

Vamos a hacer un repaso de los distintos elementos que componen la POO, y como cada uno de ellos es
implementado en VFP. En algunos momentos es posible que no entienda algunos de los conceptos, no se
preocupe, poco a poco intentaremos aclarar todo est e entramado de conceptos y sintaxis.  Clase y
Objeto. Son los dos primeros elementos de la POO. Se puede decir que la clase es la generalización de los
objetos y los objetos son la concreción de la clase, aun cuando parezca un galimatías.

Se suelen poner ejemplos bastante filosóficos para describir la relación entre el objeto y la clase (variando
según sea el autor idealista o empirista). Podemos decir que existen un objeto Pedro, otro Antonio y otro
Luisa. Todos estos objetos tienen eleme ntos en común y por ello decimos que son de la clase persona. De
la misma forma otros dicen que tenemos una idea clara de lo que es ser una persona y luego somos
capaces de distinguir hombres concretos.

Para poner otro tipo de ejemplo podemos observar los botones de un entorno gráfico. Todos sabemos
como es un botón en un entorno gráfico, si bien cada botón es diferente de los demás, todos se parecen y
por lo tanto podemos decir que la clase botón es la generalización de las propiedades y comportamientos
de todos los botones de los entorno gráficos.

Definir una clase. Las clases son la descripción de los elementos comunes de los objetos que
generalizan. Así las clases se definen y pueden ser usadas para crear innumerables objetos de este tipo.
Para definir una clase utilizaremos una sencilla s intaxis de VFP. Vamos a empezar definiendo una clase
denominada Persona. Es una clase vacía, pero nos sirve para comenzar :

DEFINE CLASS Persona AS CUSTOM


ENDDEFINE

Para poder utilizar esta definición debemos incluirla en un fichero .PRG y cargado con SET PROCEDURE
TO o bien incluirla al final de nuestro fichero .PRG.

Crear un objeto. Ya podemos crear objetos basado en esta clase, para ello utilizamos la siguiente
expresión :

oPersona1 = CREATEOBJECT( "Persona" )


oPersona2 = CREATEOBJECT( "Persona" )

Debemos tener clara la diferencia entre clase y objeto. La clase es una plantilla, donde definimos las
características de cada uno de los objetos. Los objetos que hemos creado, oPersona1 y oPersona2,
comparten la plantilla que con la que se han creado, la clase Persona, pero son diferentes entre si.

Borrar un objeto. Los objetos se asemejan a las variables en cuanto a que pueden declararse
como LOCAL, PRIVATE o PUBLIC. Esta es la única similitud que tienen las variables y lo s objetos, pero
es una característica muy importante.

Por defecto, los objetos son de tipo PRIVATE y por lo tanto existirán mientras se ejecute el programa que
los creó. Una vez salgamos de este programa el objeto se borrará automáticamente. El objeto puede ser
usado en el programa que lo creó y en todos lo programas llamados desde él.

Si declaramos el objeto como LOCAL el objeto persistirá hasta la salida del programa que lo creó, pero los
programas que sean llamados desde el programa de creación no podrán hacer uso de este objeto, pues
permanece oculto par a ellos, evitando así posibles problemas con los nombres de los objetos.

Al declarar un objeto como PUBLIC estamos indicando que permanezca hasta que lo borremos


explícitamente o salgamos de VFP. Este objeto podrá ser utilizado por cualquier programa desde el
momento que es creado hasta que sea bor rado.

Para borrar un objeto de forma explícita debemos hacer uso del comando  RELEASE. Podemos borrar de
forma explícita no solo los objetos públicos, sino también los locales y privados.

Veamos un ejemplo muy simple de creación de un objeto público y su destrucción :

PUBLIC oPrueba
oPrueba = CREATEOBJECT( "Persona" )
...
RELEASE oPrueba

Encapsulación. Aunque parece un termino extraño, es muy habitual en POO. Hace referencia a la


capacidad de los objetos para incluir dentro de si tanto datos como acciones. Las clases de distinguen
unas de otras justamente por tener unos datos y acciones que las diferencian. Los objetos de una misma
clase se diferencian entre si por tener datos diferentes.

Los datos que caracterizan a una clase se denominan propiedades y sus acciones (o programas) se
denominan métodos. Se dice por lo tanto que la clase encapsula métodos y propiedades, es decir, que
agrupa dentro si tanto métodos como propiedades.

Las propiedades y métodos de una clase se denominan habitualmente propiedades y métodos miembro.

Veamos poco a poco estos nuevos conceptos.

Propiedades. Como hemos dicho, las propiedades son los datos que manejan las clases. Estas
propiedades se declaran en la definición de la clase y permanecen en todo momento asociados a los
objetos creados bajo esa clase.

Para verlo con más claridad vamos a dar contenido a esta definición de la clasePersona que iniciamos
hace un momento. Para ello debemos estudiar que propiedades posee este tipo de objeto. Podemos decir
que todas la personas tienen un nombre, uno s apellidos y una fecha de nacimiento. Hay muchas otras
propiedades para una clase de este tipo, pero empecemos con estas. La implementación en VFP se haría
de la siguiente manera :

DEFINE CLASS persona AS CUSTOM


cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
ENDDEFINE

En nuestra definición de clase declaramos las propiedades con unos valores iniciales, que pueden ser de
cualquiera de los tipos de datos definidos en VFP. Por ejemplo, cNombre y cApellidos los hemos
inicializado como una cadena vacía ydFechaNacimiento como una fecha también vacía, pero pueden ser
numéricos, datetime o de cualquier otro tipo.

Tambien podríamos haber definido las propiedades con cualquier otro tipo de valor por defecto : una texto
o una fecha en concreto, de esta forma, al crear un objeto ya tendría este valor la propiedad.

Por ejemplo podríamos ampliar la definición de la clase incluyendo una propiedad


denominada cEstadoCivil que por defecto fuera la cadena Soltero.

DEFINE CLASS persona AS CUSTOM


cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
cEstadoCivil = "Soltero"
ENDDEFINE

Una vez creado un objeto, si quedemos dar valores a cada una de sus propiedades haremos uso de
operador punto. Para ello pondremos el nombre del objeto, un punto y el nombre de la propiedad :

oPersona1 = CREATEOBJECT( "persona" )


[Link] = "María"
[Link] = "Pérez González"
[Link] = {20-10-75}

oPersona2 = CREATEOBJECT( "persona" )


[Link] = "Pedro"
[Link] = "Jiménez Nieto "
[Link] = {04-12-69}
[Link] = "Casado"

Si, como en este caso, vamos a dar valores a muchas propiedades de un objeto podemos utilizar la
sintaxis abreviada, de la forma siguiente :

WITH oPersona2
.cNombre = "Pedro"
.cApellidos = "Jiménez Nieto "
.dFechaNacimiento = {04-12-69}
.cEstadoCivil = "Casado"
ENDWITH

Desde ese momento podemos hacer uso de estas propiedades, usando también el operador punto:

WAIT WIND [Link]


WAIT WIND [Link]

Como decíamos, las propiedades están asociadas a cada objeto, de esta forma el valor de la
propiedad cNombre es diferente entre los objetos oPersona1 y oPersona2, aun cuando en la definición de
la clase hubiéramos otorgado un va lor por defecto a esta propiedad. Es importante tener clara esta
diferencia. Las propiedades se declaran en la definición de la clase, pero los valores de las propiedades
pueden ser diferentes para cada uno de los objetos de esta clase.

Métodos. El otro elemento característico de una clase son los métodos. Los métodos son acciones que
pueden realizar los objetos, es decir, son funciones o procedimientos asociados a este tipo objeto.

En el caso de las personas podemos decir que pueden nacer, morir, casarse, tener hijos, etc... Para dar un
primer ejemplo de esto veamos uno de sus métodos :

DEFINE CLASS persona AS CUSTOM

cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
cEstadoCivil = "Soltero"

PROCEDURE Nacer
LPARAMETER cNombre, ;
cApellidos, ;
dFecha
[Link] = cNombre
[Link] = cApellidos
[Link] = dFecha
ENDPROC

ENDDEFINE

En este método damos valor a tres propiedades del objetos a partir de los parámetros que se nos han
pasado. Quizás sea todavía poco evidente este código, pero poco a poco iremos entendiendolo.
En primer lugar debemos diferenciar entre los parámetros de este método y las propiedades. Los
parámetros son variables y se perderán al finalizar la ejecución del método. Las propiedades, que son la
que empiezan con la sintaxis This., permanecen mientras dure la existencia del objeto.

Por otra parte, este procedimiento, denominado  Nacer, se diferencia de los procedimientos que estamos
acostumbrados a escribir en que sólo es llamable asociado a un objeto de la clase Persona y no puede ser
invocado de forma independient e. Esta es una de las grandes diferencias entre la programación
estructurada y la POO.

Mensajes. Cuando llamamos a un método de un objeto se dice que estamos enviando un mensaje al


objeto para que realice una determinada acción. Así cuando enviamos un mensaje de nacer a un objeto
persona estamos ejecutando el método correspondiente :

oPersona1 = CREATEOBJECT( "Persona" )


[Link]( "María", ;
"Pérez González", ;
{20-10-75} )
? [Link]
? [Link]

Operador This.

Vemos dentro del código del método Nacer que hemos utilizado una extraña sintaxis, algo
como THIS. Decíamos al principio que la clase es una plantilla y cuando definimos un método dentro de
esta plantill a que es la clase no sabemos cual será el nombre del objeto que utilizará este método, por
eso, cuando vamos a utilizar una propiedad o un método de la clase, debemos anteponer al operador
punto el operador This, para indicar que se trataran las propiedades del objeto que recibe el mensaje, es
decir, que ha sido invocado, y no para otro.

Deciamos que las propiedades mantienen valores diferentes para cada uno de los objetos, pero los
métodos comparten su código entre todos los objetos de una clase. Un método varía en la medida que las
propiedades del objeto que lo llama son diferentes, por ello es tan importante el operador  This.

De esta forma cuando ejecutamos:

[Link]( "María", ;
"Pérez González", ;
{20-10-75} )

el código del método Nacer asocia el primer parámetro, denominado cNombre, a la propiedad cNombre del


objeto que recibe el mensaje. Así [Link] es una propiedad del objeto y cNombre es una variable
que cor responde al primer parámetro del método invocado en el mensaje.

PROCEDURE Nacer
LPARAMAMETER cNombre, ;
cApellidos, ;
cFecha

[Link] = cNombre
...

ENDPROC
Ocultación. Una de las mejoras que implementa la POO, es la posibilidad de limitar el acceso a
determinadas propiedades o métodos. Con ello conseguimos que la utilización del la clase se haga de
forma ordenada.

Las propiedades o métodos protegidos sólo son utilizables desde los métodos pertenecientes a esta clase y
no pueden usarse directamente por otros programas. Vamos con un ejemplo :

DEFINE CLASS Nivel AS CUSTOM

PROTECTED nContador
nContador = 0

PROCEDURE Mas
LPARAMETER nCantidad
[Link] = [Link] ;
+ nCantidad
ENDPROC

PROCEDURE Menos
LPARAMETER nCantidad
[Link] = [Link] ;
- nCantidad
ENDPROC

PROCEDURE Ver
RETURN [Link]
ENDPROC

ENDDEFINE

Esta clase define un interface por medio de los métodos Mas, Menos y Ver. Si intentamos modificar el
valor de nContador de forma directa nos dará un error, pues esta propiedad está protegida.

oTemperatura = CREATEOBJECT( "Nivel" )

[Link] ( 10 )
? [Link]()
[Link] ( 3 )
? [Link]()

* Proboca el Error :
* Property NCONTADOR is not found.
[Link] = 100

Al igual que podemos proteger propiedades, podemos proteger métodos. De esta forma podemos definir
métodos que sólo sean usado por otros métodos de la clase y no puedan ser invocados a partir de los
objetos de esta clase. Para ello basta colocar la cláus ula PROTECTED antes de PROCEDURE.

Polimorfismo. Cuando realizamos programación estructurada debemos tener cuidado de no llamar con el


mismo nombre a dos variables o a dos procedimientos, sin embargo en la POO podemos llamar a un
método o a una propiedad de una clase de igual forma que un método o propiedad de otra. Esta
característica es lo que se denomina polimorfismo. Veamos un ejemplo con propiedades :
DEFINE CLASS ClaseUno AS CUSTOM
Dato = 1000
ENDDEFINE

DEFINE CLASS ClaseDos AS CUSTOM


Dato = "Hola"
ENDDEFINE

Esto, que puede parecer un problema, es muy sencillo de entender si vemos como los utilizamos los dos
objetos :

Objeto1 = CREATEOBJECT( "ClaseUno" )


Objeto2 = CREATEOBJECT( "ClaseDos" )

? [Link]
? [Link]

Aun cuando en los dos objetos llaman a una propiedad denominada Dato, en el primer caso estamos
llamando a una propiedad tipo numérica definida en ClaseUno, y el segundo caso estamos llamado a una
propiedad de tipo carácter definida en ClaseDos.

Igual que las propiedades, en el caso de los métodos también es posible el polimorfismo. De esta forma
podemos definir dos métodos denominados Imprimir, pero que cada uno hace una cosa bien distinta :

DEFINE CLASS ClaseUno AS CUSTOM


PROCEDURE Imprimir
? "Hola esto es una prueba ..."
ENDPROC
ENDDEFINE

DEFINE CLASS ClaseDos AS CUSTOM


PROCEDURE Imprimir
LIST STATUS
ENDPROC
ENDDEFINE

En VFP no es posible definir dos métodos o dos propiedades con igual nombre dentro de una misma clase.

Eventos. Existe una serie de métodos especiales, que normalmente no se ejecutan por ser invocados de
forma explícita, como los que hemos definido hasta ahora, sino que por denominarse de una forma
determinada son lanzados cuando 'pasa algo', es dec ir, cuando se produce un evento. Estos eventos
pueden ser un click, el movimiento del ratón, un pulsación de tecla, etc..

Los dos primeros eventos que vamos a tratar son los denominados  Init y Destroy. El primero se ejecuta
cuando se crea un objeto de esta clase y el segundo cuando se destruye. En otros lenguajes orientados a
objeto estos métodos son denomi nados el constructor y el destructor de la clase, pero el VFP son tratados
como eventos. Los eventos Init y Destroy los encontramos en absolutamente todos los objetos de VFP.
Veamos como hacer uso de esta característica creando dos métod os con estos nombres :

* Lanzamiento automático del


* método INIT
oPrueba = CREATEOBJECT( "Eventos" )
...
* Lanzamiento automático del
* método DESTROY
RELEASE oPrueba

DEFINE CLASS Eventos AS CUSTOM


PROCEDURE Init
? "Creación del objeto ..."
ENDPROC

PROCEDURE Destroy
? "Destrucción del objeto ..."
ENDPROC
ENDDEFINE

Cada tipo clase tiene unos eventos predefinidos y que no podemos añadir más. Así, por ejemplo, la
clase CUSTOM tiene definidos los eventos Init, Destroy y Error, la clase COMMANDBUTTOM los
eventos Click, Destroy, DragDrop, DragOver, Error, ErrorMessage,GotFocus, Init, InteractiveChan
ge, KeyPress, LostFocus, Message, MouseDown, MouseMove,MouseUp, RightClick, UIEnable, Valid, Whe
n, etc.. La mayoría de estos eventos los veremos en próximos artículos de forma detallada. De momento
quedémonos con el concepto de evento.

Objetos como propiedades. Existe la posibilidad, en algunas clases, de definir objetos como


propiedades miembro. Por ejemplo, podemos querer definir una clase matrimonio con dos objetos tipo
persona. Así definiríamos :

DEFINE CLASS Matrimonio AS CUSTOM


dFecha = {}
ADD OBJECT Conyuge1 AS Persona
ADD OBJECT Conyuge2 AS Persona
ENDDEFINE

Para poder hacer uso de ellos no tenemos más que indicar el nombre del objeto miembro. Por ejemplo :

oPareja1 = CREATEOBJECT( "Matrimonio" )


[Link] = "María"
[Link] = "Pedro"
No todas las clases admiten que sean definidos objetos como miembros de la misma, sólo las clases
denominadas contenedoras, entre la que se encuentraCUSTOM.

Introducción a la POO en Visual FoxPro : Conceptos básicos (y II)


Por razones de espacio se interrumpió en el número anterior el relato de los elementos y conceptos de la
programación orientada a objeto y su implementación en Visual FoxPro 3.0. En este artículo
continuaremos con esa descripción.

En el artículo anterior intentamos explicar los conceptos de Clase, Objeto, Encapsulación, Propiedad,
Método, Mensaje, Operador This, Ocultación, Polimorfismo y Evento. Pero dejamos para este artículo uno
de los principios básicos de la programación orientada a objeto, la Herencia.

Herencia. La herencia es un mecanismo que nos va a permitir reutilizar nuestro código de forma fácil y
ordenada. Siempre que hemos definido una clase en los ejemplos anteriores hemos heredado de la clase
base CUSTOM. No hemos sido conscien tes de ello, pero siempre hemos puesto la cláusula  AS CUSTOM, es
decir, estabamos heredando de la clase CUSTOM. Para verlo más claramente vamos a crear una clase
llamada Prueba y utilizaremos la orden DISPLAY OBJECT par a ver como esta formado un objeto de esta
clase:

oPrueba = CREATEOBJECT( "Prueba" )


DISPLAY OBJECTS

DEFINE CLASS Prueba AS CUSTOM


ENDDEFINE

Podremos obtener una salida de DISPLAY OBJECS similar a la figura 1.

Object: OPRUEBA Priv O PRUEBA

Class Tree:
PRUEBA
CUSTOM

Properties:
BASECLASS C "Custom"
CLASS C "Prueba"
CLASSLIBRARY C "C:\TMP\[Link]"
COMMENT C ""
HEIGHT N 17 ( 17,00000000)
LEFT N 0 ( 0,00000000)
NAME C "Prueba1"
PARENTCLASS C "Custom"
PICTURE C ""
TOP N 0 ( 0,00000000)
WIDTH N 100 ( 100,00000000)

Methods and Events:


ADDOBJECT DESTROY ERROR
INIT REMOVEOBJECT SAVEASCLASS

Podemos observar en primer lugar el objeto se llama OPRUEBA y es de tipo PRUEBA. En la sección Class


Tree vemos que esta clase tienen un árbol de herencia compuesto dePRUEBA y CUSTOM. Así mismo vemos
un buen n úmero de propiedades y algunos métodos que nosotros no hemos escrito y esto se debe ha que
al haber definido la clase PRUEBA como una clase heredada de la clase CUSTOM hemos heredado las
propiedades y métodos de esta clase padre.

En VFP siempre es necesario crear una clase heredando de alguna otra. De esta forma, cualquier clase
que creemos tendrá siempre algunas propiedades y algunos métodos que toda clase de VFP posee. La
clase CUSTOM sólo posee los métodos y propiedade s que siempre podremos encontrar en todo objeto,
todas las demás clases predefinidas en VFP (un total de 33) tiene muchas más propiedades y métodos que
los descritos aquí, pero siempre tienen estos métodos y propiedades básicos.

Sigamos con los ejemplos de la clase Persona. Definimos en su momento esta clase, pero ahora nos
damos cuenta que debemos hacer una ampliación. En la aplicación necesitamos tratar también un caso
concreto de persona, los contribuyentes, pero sabe mos que no todas las personas son contribuyentes,
pero todos los contribuyentes son personas. Tras un cierto análisis concluimos que es necesaria la
herencia para incluyendo nuevos datos y métodos de la clase Contribuyente pero manteniendo la
integridad y funcionalidad de la clase Persona. Veamos como se hace esta herencia:
* Clase original
DEFINE CLASS persona AS CUSTOM
cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
cEstadoCivil = "Soltero"

PROCEDURE Nacer
LPARAMETER cNombre, ;
cApellidos, ;
dFecha
[Link] = cNombre
[Link] = cApellidos
[Link] = dFecha
ENDPROC
ENDDEFINE

* Clase nueva
DEFINE CLASS Contribuyente AS Persona
cNIF = ""
nBaseImponibe = 0
cRegimenSS = ""
ENDDEFINE

Cuando definamos un objeto de la clase Contribuyente, podremos hacer uso tanto de las propiedades
definidas en su clase como en las definidas en las clases superiores. Así podremos decir :

oContr = CREATEOBJECT( "Contribuyente" )

* Propiedades definidas
* en la clase Persona
[Link] = "Juan"
[Link] = "López Garrido"

* Propiedad definida
* en la clase Contribuyente
[Link] = "Autonomo"

Como todo Contribuyente es también una Persona, tiene todos los métodos y propiedades de la


clase Persona.

La herencia nos va a facilitar enormemente el mantenimiento del código. Por una parte podemos heredar
en vez de modificar la clase base y de esa forma preservar la naturaleza de los objetos sin necesidad de
modificar cientos de programas.

Por otra aparte, cualquier modificación que realicemos en una clase, se ve reflejada automáticamente en
todas las clases que hereden de ella. Por ejemplo, si añadimos una nueva propiedad a la clase  Persona o
modificamos alguno de sus métodos, esa modificación también se ve reflejada en el comportamiento de
los objetosContribuyente, pues también son del tipo Persona.

Identificar Clases y Herencias. La programación orientada a objeto exige de nuevas técnicas de


análisis orientada objeto. Cuando nos enfrentamos a un programa realizado bajo estas técnicas la
mayores dificultades las tenemos en identificar la clases correctamente y en definir las relaciones entre las
distintas clases.

No es fácil al principio, pero en poco tiempo la definición de clases y las herencias entre ellas será un
trabajo tan sencillo como ahora identificar cuando debemos hacer una subrutina.

Sobreescribir métodos o propiedades. En algunos casos, en la clase hija, queremos modificar el


comportamiento de algún método o el contenido de alguna propiedad de la clase padre. Este hecho se
denomina sobreescribir. Con él somos capaces de modifi car los miembros de una clase padre sin afectar
al código de la misma.

En el siguiente caso la clase Persona tiene un método denominado Imprimir y la claseContribuyente va


a sobreescribir este método con el suyo propio.

DEFINE CLASS persona AS CUSTOM


cNombre = ""
cApellidos = ""
dFechaNacimiento = {}
cEstadoCivil = "Soltero"

PROCEDURE Imprimir
? "Nombre : " ;
+ [Link] ;
+ " " ;
+ [Link]
? "Fecha de nacimiento : " ;
+ DTOC( [Link] )
? "Estado civil : " ;
+ [Link]
ENDPROC
ENDDEFINE

* Clase nueva
DEFINE CLASS Contribuyente AS Persona
cNIF = ""
nBaseImponible = 0
cRegimenSS = ""

PROCEDURE Imprimir
? "Nombre : " ;
+ [Link] ;
+ " " ;
+ [Link]
? "Fecha de nacimiento : " ;
+ DTOC( [Link] )
? "Estado civil : " ;
+ [Link]
? "NIF : " ;
+ [Link]
? "Base Imponible : " ;
+ STR( [Link] )
? "Regimen de la S.S. : " ;
+ [Link]
ENDPROC
ENDDEFINE

Cuando llamemos al método imprimir, dependerá de la clase de objeto que utilicemos se llamará a un
método o a otro.

oPrue1 = CREATEOBJECT( "Persona" )


[Link] = "Juan"
[Link] = "López Garrido"

oPrue2 = CREATEOBJECT( "Contribuyente" )


[Link] = "Pedro"
[Link] = "Goméz Iriarte"

[Link]()&& De persona
[Link]()&& De contribuyente

Cuando escribíamos un método denominado Init o Destroy lo que estamos haciendo es sobreescribir el


método por defecto para este evento. De igual forma podemos sobre escribir cualquier otro método o
evento de una clase.

Recordemos lo que decíamos antes sobre el Polimorfismo, este es otro ejemplo de esta característica,
tenemos dos métodos con el mismo nombre en clases diferentes, pero en este caso, las clases se heredan
una de otra, sobreescribiendo este método.

El Operador ::. Si es observador se habrá percatado que en ejemplo anterior estamos duplicando parte
del código del método Imprimir de la clase Persona en el método imprimir la clase Contribuyente, esto
no parece muy acertado para ayudar al mantenimiento del código.

En algunos casos queremos sobreescribir totalmente el método de la clase padre, pero en otros casos lo
que deseamos en sólo incluir nuevas prestaciones, pero manteniendo el código anterior. Para estos casos
se ha creado el operador :: u operador de resolu ción de alcance. Con el podemos hacer referencia al
método de una clase superior aun cuando este método se hubiera sobreescrito.

Para hacer uso de este operador debemos indicar el nombre de la clase padre, el operador ::, y el nombre
del método. Haciendo uso de este operador podríamos haber escrito la clase  Contribuyente de la
siguiente forma :

DEFINE CLASS Contribuyente AS Persona


cNIF = ""
nBaseImponible = 0
cRegimenSS = ""

PROCEDURE Imprimir

* Llamada al procedimiento
* original de la clase Persona
Persona::Imprimir() && OPERADOR ::

* Resto de impresión
? "Estado civil : " ;
+ [Link]
? "NIF : " ;
+ [Link]
? "Base Imponible : " ;
+ STR( [Link] )
? "Regimen de la S.S. : " ;
+ [Link]
ENDPROC
ENDDEFINE

Esta característica nos asegura un buen mantenimiento de nuestras clases, pues cualquier modificación en
el método de la clase padre, se ve automáticamente reflejado en el método de la clase hija, aunque este
sobre escríto el método.

Conclusiones

Han sido muchos conceptos seguidos y es posible que se sienta un poco aturdido con tantas palabras. No
se preocupe, como decíamos al principio, la POO es un conjunto de conceptos interrelacionados que
difícilmente se entiende unos sin los otros. Poco a po co irá comprendiendo su significado y concluirá que
no están difícil como algunos quieren hacer creer.

En los siguientes artículos haremos referencia a los conceptos aquí esbozados y esperemos que se vaya
encontrando más fácil su compresión a medida que avancemos.

En este artículo solo hemos creado clases partiendo de CUSTOM y siempre han sido clases muy poco
prácticas, pero prometemos realizar algunas clases que si merecen la pena ser utilizadas.

También describiremos las herramientas que VFP nos da para una hacer más fácil la POO, las clases que
nos facilita y el modo de trabajar con ellas.

La POO está aquí y no deberíamos ignorarla por más tiempo. Posiblemente no es necesario este tipo de
programación, pero es realmente muy recomendable, es seguro que no soluciona todos los problemas,
pero es mucho más sencillo el desarrollo, tendremos que esforzarnos un poco al principio, pero nuestro
esfuerzo se verá sobradamente recompensado. En definitiva, la Programación Orientada a Objeto es una
mejor forma de programar.

Uso del API de Windows : Introducción (I) 


Con este artículo empezamos una serie que nos acercará a las posibilidades que nos
ofrece Visual FoxPro (VFP) para llamar a funciones contenidas en librerías de enlace
dinámico (DLLs) y por lo tanto a la posibilidad de llamar a las funciones del Application
Program Inteface (API) de Windows.

No pretendemos dar un curso sobre el API de Windows, sino orientar sobre las
posibilidades que tenemos al alcance de nuestra mano para ampliar VFP. Para ello
describiremos algunas funciones útiles y sencillas que ilustrarán estas posibilidades, pero
no pretendemos ser exhaustivos en las descripciones del API de Windows.

Pero vamos por partes, empecemos por ver como podemos traspasar las barreras de
Visual FoxPro.

Distintas formas de ampliar VFP

Todo lenguaje o entorno de programación es finito, es decir, esta limitado. Pero en


muchos casos es posible ampliar la funcionalidad de un sistema por medio de librerías u
otros complementos.
En el caso de Visual FoxPro podemos hacer uso de FLLs, DLLs y OCXs para ampliar el
entorno, sin entrar en las posibilidades del DDE y del OLE...

Las librerías FLLs existen desde la versión 2.5 para Windows y son librerías escritas en C
especialmente para FoxPro. Estas librerías, una vez cargadas, ofrecen un conjunto de
nuevas funciones para ser usadas desde nuestros programas.

Las FLLs están especialmente pensadas para programas escritos en C que tengan que
hacer uso de las capacidades internas de FoxPro. En realidad son DLLs, pero tienen la
posibilidad manejar bases de datos, crear variables de FoxPro o llamar a procedimientos
escritos en FoxPro desde el código de la librería.

Sin duda la librería más conocida de este tipo es [Link], que es distribuida con
FoxPro y que nos ofrece un buen número de funciones.

Por otra parte, Windows dispone de un estándar para las librerías de enlace dinámico,
las DLLs. No son desarrolladas para un tipo especial de lenguaje , aun cuando nacieron
para ser llamadas desde C, y dan nuevas funciones a los programas que las cargan.
Pueden ser escritas en C, C++, Basic, Delphi, etc..

Existen muchas librerías comerciales en formato de DLL. Windows, por su parte, ofrece
una buena colección librerías DLLs, de hecho, todo el API de Windows o complementos
como el ODBC, MAPI, etc., están contenidos en este tipo de librerías.

Por último, los OCXs son objetos que haciendo uso del las capacidades del OLE permiten
ampliar los elementos que podemos incluir en nuestras Forms. Son la nueva versión de
los famosos VBX y en breve se van a popularizar muchísimo.

Algunos de estos objetos son fundamentalmente visuales como


los [Link] [Link] incluidos en VFP. Otros OCXs son no visuales y
funcionan de forma similar a las librerías, pero con orientación a objeto. Ejemplo de este
tipo de controles son los [Link] y [Link] de VFP.

En el futuro el API de Windows serán métodos de los objetos OLE del sistema, pero de
momento son DLLs con una inmensa serie de funciones. En nuestro caso nos vamos a
centrar estas funciones.

FoxPro 2.x

En FoxPro 2.x para Windows no es posible hacer uso directo del código de una DLL y es
necesario cargar la librería [Link] para registrar y llamar a las funciones de las
librerías de enlace dinámico.

La librería FoxTools se carga con la sencilla orden SET LIBRARY TO [Link]. Si


utilizamos la orden DISPLAY STATUS veremos que son muchas las funciones contenidas en
FoxTools, pero las que nos interesan ahora son sólo dos : RegFn() yCallFn().

Con RegFn registramos una función contenida en una DLL. Debemos pasar el nombre de


la función (con las mayúsculas y minúsculas correctamente situadas), los tipos de los
datos que la función recibe como parámetros, el tipo de dato que se va a devolver y por
último el nombre de la DLL, según este esquema :

NumFuncion = RegFn( NombreFuncion, ;


TiposArgumentos,;
TipoRetorno, ;
NombreDLL )

Con la función CallFn podemos llamar a las funciones registradas con RegFn. En este


caso debemos pasar el número de función que nos retornó RegFn y los parámetros que
hemos registrado. Se nos retornará el valor que devuelva la función de la DLL, que
también debemos haber registrado.

Retorno = CallFN( NumFuncion, ;


Arg1, ;
Arg2, .... )

FoxTools en Visual FoxPro 3.0 : 16 y 32 bits

En la versión 3.0 también podemos hacer uso de la librería FoxTools, pero debemos tener
en cuenta las diferencias entre las librerías construidas en 32 bits y en 16 bits.

Para llamar a librerías de 16 bits desde aplicaciones de 32 bits como Visual FoxPro es
necesario utilizar un mecanismo llamado Universal Thunk y este mecanismo sólo lo
podemos utilizar por medio de FoxTools. Por lo tanto, para llamar desde Visual FoxPro a
funciones contenidas en DLLs de 16 bits debemos usar FoxTools (a excepción de las
llamadas al API de Windows 3.1, pues en este caso la conversión entre 16 y 32 bits la
realiza Win32s).

También podemos usar FoxTools para registrar librerías de 32 bits por medio de la
función RegFn32(). La función RegFn32 es igual que RegFn, pero siempre llama a
funciones de 32 bits, independientemente de la plataforma en la que nos encontremos. Si
queremos llamar a librerías de 16 bits siempre deberemos usarRegFn. En los dos casos las
llamadas a las funciones registradas se hace conCallFn().

Con todo, el uso de FoxTools no es el camino más corto para llamar a funciones de las
DLLs de 32 bits desde Visual FoxPro, existe otro mucho mejor...

Declaración de funciones en Visual FoxPro

En Visual FoxPro podemos llamar a funciones de las DLLs de 32 bits sin necesidad de la
librería FoxTools por medio de la orden DECLARE. Con ella podemos registrar las funciones
de las DLLs sin necesidad de ninguna librería externa.

DECLARE [cTipoRetorno] NombreFuncion


IN NombreLibreria [AS NombreAlias]
[cTipoParam1 [@] NombreParam1,
cTipoParam2 [@] NombreParam2, ...]

Debemos indicar el nombre de la función distinguiendo entre mayúsculas y minúsculas. Si


este nombre no es válido en VFP o no nos gusta podemos darle un ALIAS, es decir, el
nombre con el que vamos a llamar a la función registrada dentro de VFP.

También debemos indicar el tipo del valor de retorno y los tipos de los parámetros. Los
posibles tipos de datos son :

SHORT - Entero de 16 bits


INTEGER - Entero de 32 bits
SINGLE - Coma flotante de 32 bits
DOUBLE - Coma flotante de 64 bits
STRING - Cadena de caracteres
Si algún parámetro debe ser pasado por referencia, indicaremos este hecho con un @
después del tipo del parámetro. También podemos dar un nombre a los parámetros, pero
Visual FoxPro no lo requiere y no tiene más utilidad que darnos alguna ayuda para
recordar el contenido de los parámetros.

Con la cláusula IN damos el nombre de la DLL que deseamos utilizar. Si en vez del
nombre de la librería indicamos WIN32API la función declarada es buscada en las librerías
del sistema.

Por medio de DISPLAY STATUS o LIST STATUS se muestran los nombres de las funciones
registradas. Con CLEAR ALL o CLEAR DLLS se eliminan de la memoria las funciones
registradas.

Entrando en materia : llamar al API de Windows

Gracias a los distintos medios que tenemos para llamar a las funciones contenidas en las
DLLs podemos llamar a las funciones del API de Windows. Para ello debemos registrar o
declarar las funciones del API de Windows.

Si en utilizamos la FoxTools sobre plataformas Win16 y no incluimos el nombre de la DLL


que contiene la función, entonces la librería busca la función automáticamente en las
librerías [Link], [Link] y [Link]. Estos ficheros son las DLLs que contienen el
API de Win16.

Si utilizamos RegCall32 de FoxTools y no indicamos el nombre de la librería o utilizamos


la orden DECLARE con el modificador IN WIN32API y no el nombre de una DLL, entonces
se busca la función en las
librerías [Link], [Link], [Link],[Link] y [Link], los ficheros
que contienen el API del Win32.

En el caso de estar utilizando Visual FoxPro sobre Windows 3.1 con la librería Win32s, e
indicamos WIN32API, entonces se buscará la función en el [Link], la librería
de conversión del API de Win32 a Win16.

En todos los casos, una vez registrada la función ya podemos hacer uso sin más de ella.

Un ejemplo : Atributos de Fichero

Para abrir boca vamos a ver dos funciones muy simples del API de Windows para obtener
y modificar los atributos de un fichero. Todos los ejemplos se van a codificar para Visual
FoxPro, pero no es muy difícil pasar el código a FoxPro 2.x usando la librería FoxTools.

Las funciones que nos interesan se declaran como sigue :

*** Obtiene los atributos de un fichero ***


DECLARE INTEGER GetFileAttributes ;
IN WIN32API ;
STRING cFileName

*** Cambia los atributos de un fichero ***


DECLARE INTEGER SetFileAttributes ;
IN WIN32API ;
STRING cFileName, ;
INTEGER nFileAttributes

Con GetFileAttributes se obtiene un número que indica los atributos que tiene un


fichero del que se ha pasado su nombre como parámetro. Para saber realmente que
atributos tiene podemos hacer uso de estas simples definiciones :

#define _A_RDONLY 1
#define _A_HIDDEN 2
#define _A_SYSTEM 4
#define _A_SUBDIR 16
#define _A_ARCH 32
#define _A_NORMAL 128

A partir de aquí podemos utilizar una nueva función de Visual FoxPro BITAND, que hace
un AND binario para saber si contiene uno de estos valores :

nAttrib=GetFileAttributes("c:\[Link]")
IF nAttrib == -1
ERROR
ENDIF
IF BITAND( nAttrib, _A_RDONLY ) != 0
WAIT WIND "El de sólo lectura..."
ENDIF
IF BITAND( nAttrib, _A_HIDDEN ) != 0
WAIT WIND "El de sólo lectura..."
ENDIF
...

Si por algún motivo la función no tiene éxito devuelve un -1. Debemos comprobar
siempre que el retorno ha sido distinto de -1 antes de comprobar el valor de retorno de la
función.

Para dar unos nuevos atributos a un fichero haremos uso de SetFileAttributespasando


el nombre del fichero y los atributos que deseamos que tenga el fichero como un número
compuesto de las sumas de los atributos (si no incluimos un atributo significa que se lo
quitamos) :

=SetFileAttributes( "c:\[Link]", ;
_A_RDONLY + ;
_A_ARCH )

En este caso si la función no tiene éxito devuelve un 0. Si todo ha sido correcto el retorno
es de un 1.

Función de VFP o función del API de Windows

En muchas ocasiones nos encontraremos que ya existe una función de VFP para hacer lo
que queremos hacer por medio de una función del API de Windows. En este caso
podemos obtener los atributos de un fichero por medio de la funciónADIR() de VFP y no
necesitamos llamar a GetFileAttributes() del API de Windows. En VFP no existe una
función para dar atributos a un fichero y es necesario utilizar el API, una librería externa o
hacer una llamada a la orden ATTRIB del MS-DOS.

Personalmente prefiero utilizar primero las funciones de FoxPro y si no hay una función en
VFP entonces llamar a las funciones del API, si no existe tampoco en el API de Windows
entonces crear una librería y si no hay más remedio llamar a una orden del MS-DOS. Pero
cada uno deberá decidir que quiere hacer.

Vamos a ver un pequeño ejemplo del uso de estos API de Windows y vamos a ver como
hacer uso de la función ADIR() :

*** Cargar las definiciones necesarias ***


#INCLUDE attrib.h

*** Cargar las declaraciones del API ***


DO attrib

*** Crear un fichero para el ejemplo ***


LIST STATUS TO FILE [Link] NOCONSOLE

*** Cambiar los atributos del fichero ***


IF SetFileAttributes( "[Link]", ;
_A_RDONLY + ;
_A_HIDDEN + ;
_A_SYSTEM + ;
_A_ARCH ) == 1
WAIT WIND "Cambiados los atributos"
ELSE
WAIT WIND "No se han podido cambiar"
ENDIF

*** Obtener los atributos del archivo ***


*** con la función del WIN32API ***
nAttrib = GetFileAttributes( "[Link]" )
*** Mostrar los atributos ***
IF nAttrib # -1
? "Atributos con WIN32API : "
IF BITAND( nAttrib, _A_RDONLY ) != 0
?? "R"
ENDIF
IF BITAND( nAttrib, _A_ARCH ) != 0
?? "A"
ENDIF
IF BITAND( nAttrib, _A_SYSTEM ) != 0
?? "S"
ENDIF
IF BITAND( nAttrib, _A_HIDDEN ) != 0
?? "H"
ENDIF
IF BITAND( nAttrib, _A_SUBDIR ) != 0
?? "D"
ENDIF
IF BITAND( nAttrib, _A_NORMAL ) != 0
?? "N"
ENDIF
*** Error ***
ELSE
WAIT WIND "Error con GetFileAttributes"
ENDIF

*** Obtener los atributos del archivo ***


*** con la función del VFP ***
nFichs = ADIR( aFicheros, ;
"[Link]", ;
"AHRSD" )
*** Mostrar los atributos ***
IF nFichs > 0
? "Atributos con ADIR : "
?? aFicheros[1,5]
*** Error ***
ELSE
WAIT WIND "Error con ADIR"
ENDIF

Como se puede observar, el resultado de GetFileAttributes y el de ADIR() son el


mismo, con la ventaja de que ADIR() puede dar el resultado de varios ficheros.

Cubrir el API con funciones propias

Un buen sistema es cubrir (o encapsular) las funciones del API de Windows o de las
librerías que usemos, por medio de funciones propias, que permiten independizar nuestro
código del acceso directo a las mimas y hacer funciones de mayor nivel y por lo tanto con
mayor funcionalidad.

Para dar un ejemplo de esto vamos a hacer una función llamada SetAttri a la que se le
pasa como primer parámetro el nombre del fichero o una máscara y como segundo una
cadena con los atributos que deseamos que tenga el fichero (tal y como se devuelven
por ADIR) :

PROCEDURE SETATTRI
LPARAMETER cFicheros, cAtributos

*** Cargar las definiciones necesarias ***


#INCLUDE attrib.h
*** Cargar las declaraciones del API ***
DO avttrib

*** Incializar variables como locales ***


LOCAL nCambiados, nAtrib, nFichs
LOCAL ARRAY aEncontrados(1,1)
nCambiados = 0

*** Obtener los ficheros ***


nFichs = ADIR( aEncontrados, ;
cFicheros, ;
"AHRS" )

*** Tratar cada uno de los ficheros ***


FOR nCont = 1 TO nFichs
*** Cargar el atributo ***
nAtrib = 0
IF "R" $ UPPER( cAtributos)
nAtrib = nAtrib + _A_RDONLY
ENDIF
IF "A" $ UPPER( cAtributos)
nAtrib = nAtrib + _A_ARCH
ENDIF
IF "S" $ UPPER( cAtributos)
nAtrib = nAtrib + _A_SYSTEM
ENDIF
IF "H" $ UPPER( cAtributos)
nAtrib = nAtrib + _A_HIDDEN
ENDIF
IF "D" $ UPPER( cAtributos)
nAtrib = nAtrib + _A_SUBDIR
ENDIF
IF "N" $ UPPER( cAtributos)
nAtrib = nAtrib + _A_NORMAL
ENDIF

*** Cambiar los atributos ***


IF SetFileAttributes( ;
aEncontrados[nCont,1], ;
nAtrib ) == 1
nCambiados = nCambiados + 1
ENDIF
NEXT
RETURN nCambiados

Conclusión

Espero que esta primera aproximación a la utilización del API de Windows desde Visual
FoxPro haya sido de interés. De momento sólo hemos visto como registrar y llamar a
estas funciones y hemos descrito dos funciones muy simples, pero en el próximo artículo
estudiaremos otras funciones muy útiles.

Si no puede esperara hasta recibir el próximo número de la revista puede ver el fichero de
ayuda sobre el API de Win32 de la versión profesional en CD-ROM de Visual FoxPro o bien
la magnífica información y ejemplos contenidos en los CD-ROM del Microsoft Developer
Network (MSDN) o los manuales de Visual C++ 2.0.

Uso del API de Windows : Funciones para ficheros .INI (II) 


Tal y como prometimos en el artículo anterior, vamos a entrar a fondo en algunas funciones realmente
útiles del API de Windows para ser usadas desde Visual FoxPro (VFP). En este caso vamos a referirnos al
conjunto de funciones que nos permiten crear y consultar los ficheros de configuración de Windows, los
ficheros .INI.

¿Donde guardar nuestra configuración?

Antes o después todos nos hacemos esta pregunta, queremos guardar información sobre la posición de
una ventana, el último usuario que entró en la aplicación, guardar las distintas opciones, etc.. Las
respuestas pueden ser muy variadas, pero en principio existen tres métodos :

Bases de Datos : con este sistema guardamos nuestra configuración en una tabla creada por nosotros
con este fin. En ella almacenamos toda la información que necesitamos para configurar la aplicación, con
las ventajas que da el uso de un tabla de FoxPro.

El problema es que no podemos editarla con facilidad. Si necesitamos ver o modificar el contenido del
fichero de configuración debemos o bien ejecutar el VFP y abrir la tabla o bien hacer alguna pantalla
especial de mantenimiento para la misma.
Un buen ejemplo de este tipo de configuración es el fichero de recursos de Visual FoxPro, normalmente
llamado [Link] y donde FoxPro guarda gran parte de los datos sobre su propia configuración.

[Nota : nosotros podemos también utilizar el fichero de recursos para nuestros intereses abriéndolo
con USE AGAIN e introduciendo en él lo que queramos. Este fichero no tiene por qué llamarse  FOXUSER,
basta con utilizar SET RESOURCE TO para utilizar otro.]

Ficheros .INI : el sistema que habitualmente utilizan los programas de Windows para almacenar los
datos de su configuración son los ficheros .INI, normalmente residentes en el directorio donde está
instalado Windows.

Si hacemos una breve visita a nuestro directorio de Windows podremos encontrar un buen número de
estos ficheros. En concreto yo he encontrado 97 ficheros .INI en el equipo donde escribo estas líneas.

Los ficheros .INI son ficheros de texto y, en principio, son muy fáciles de modificar con un simple editor.
Pero su estructura es en muchas ocasiones excesivamente simple, cuando se hacen muy grandes puede
ser algo lento buscar dentro de ellos. Pero sin duda son el sistema más difundido.

El Registro de Configuraciones : la proliferación de ficheros .INI y su escasa estructuración hicieron


que Microsoft se plantease dar un sistema mucho más práctico para guardar los datos de configuración de
las aplicaciones. Así, partiendo del Registro de Windows 3.1, en Windows NT y Windows 95 se nos ofrece
un auténtico Registro de Configuraciones.

El Registro es una base de datos jerárquica en la que el sistema y las aplicaciones puede, y deben,
guardar la información necesaria para interactuar con otros programas y para su propia configuración.

Como el sistema de Registro está muy limitado en Windows 3.1, es necesaria la compatibilidad con los
ficheros .INI, y Windows 95 y Windows NT siguen soportándolos, duplicando en muchos casos los datos
del registro en este tipo de ficheros.

En nuestro caso vamos a hacer una aproximación a los ficheros .INI, en otros artículos estudiaremos el
Registro y las funciones para manejarlo.

Estructura de los ficheros .INI

Como hemos dicho, los ficheros de configuración de Windows son ficheros de texto con la extensión INI y
almacenados, casi siempre, en el directorio donde se encuentre instalado Windows, aunque pueden estar
en cualquier otro directorio.

La estructura de los ficheros INI se organiza en Secciones. Las secciones están identificadas con una
etiqueta entre corchetes y agrupan información relacionada entre si.

Dentro de una sección se incluyen Claves, es decir, líneas de con un identificador, un signo igual y un
valor.

Un fichero .INI muy simple podría ser :

[Ventana Principal]
Tipo de Ventana=1
Abrir=0
Título=Pantalla Principal
Nivel=0
[Usuario]
Nombre=Pablo Almunia Sanz
Como podemos ver los ficheros de configuración pueden tener varias secciones, los identificadores de
sección o de claves pueden contener espacios o caracteres acentuados, pero cuando accedamos a ellos
deberemos dar su nombre exacto, sin importar las diferencias entre mayúsculas y minúsculas.

Los valores asociados a las claves siempre se guardan en formato de cadenas de caracteres, aun cuando
sean números, valores lógicos, fechas, etc..

[Link] y [Link]

En Windows 3.1 existen dos ficheros .INI que configuran el sistema, son los
conocidos [Link] y [Link]. En estos dos ficheros se guarda casi toda la información necesaria para
el sistema.

[Link] es normalmente el fichero de configuración más grande de todos los equipos. En principio este
fichero está reservado para las configuraciones general de Windows y para las configuraciones que deban
ser compartidas por más de una aplicación. Pero muchos programas deciden no tener su propio fichero
.INI y utilizar este fichero para su configuración.

Nosotros deberemos ser elegantes y utilizar [Link] sólo para las información que deban compartir más
de una aplicación y no para los datos de configuración privados.

En [Link] se almacena información muy interesante sobre las fuentes, extensiones de ficheros
registrados, internacionalización, etc.. Usada con cuidado esta información puede ser muy útil para
nuestra aplicación.

En [Link] se definen todos los controladores, la configuración de la red, etc.. En muchas ocasiones
el mejor sistema para saber si está instalado un determinado dispositivo es consultar este fichero, pero no
debemos modificarlo si no sabemos realmente lo que estamos haciendo pues es un fichero difícil.

Tanto [Link] como [Link] son soportados por Windows 95 y Windows NT. Aun cuando estos


sistemas guardan su información en el Registro de Configuraciones, mantienen una copia de los datos del
registro dentro de los ficheros .INI, pues saben que muchas aplicaciones acceden directamente a ellos.

Antes de lanzarnos a trabajar con [Link] y [Link] desde VFP recomiendo que nos demos un paseo
por la documentación viendo funciones
como AFONT(),APRINTERS(), SYS(), FONTMETRIC(), PTRINFO(), SYSMETRIC(), SET SYSFORMAT, etc. que
nos ofrecen la mejor manera de trabajar con la información del sistema.

[Nota : si desea información detallada sobre la estructura de los ficheros de configuración de Windows
puede acudir a los ficheros [Link] y [Link] de vienen en la instalación de Windows, la amplia
documentación del Windows Resource Kit y a los excelentes CD-ROMs del Microsoft TechNet.]

Los ficheros .INI de las aplicaciones

En la mayoría de los casos las aplicaciones Windows tienen ficheros .INI para su configuración. La
estructura de estos ficheros .INI varia completamente de una aplicación a otra, dependiendo de lo que los
programadores de la misma hayan decidido.

Nosotros deberemos analizar la estructura de nuestro fichero de configuración a fin de que responda a
nuestras propias necesidades. No es muy fácil dar recomendaciones a este respecto, vea lo que hacen
otras aplicaciones y coja las mejores ideas que observe.

En cuanto a acceder a los ficheros .INI de otras aplicaciones debemos tener algunas reservas. No es
conveniente trastear desde nuestra aplicación en los .INI de otras aplicaciones si no se nos ofrece
documentación muy clara al respecto. Muchos programas pueden dejar de funcionar correctamente o
podemos interpretar erróneamente la información en ellos contenida. Pero en algunos casos las
aplicaciones dan información sobre como interpretar e incluso modificar la información de sus ficheros de
configuración y no tenemos porque temer nada.

[Nota : si desea información sobre [Link] consulte el documento de Microsoft  Q107830.]

Como acceder a ficheros .INI desde VFP

Visual FoxPro no dispone ordenes para acceder a los ficheros .INI directamente y debemos utilizar algún
otro sistema.

Podemos construirnos nosotros mismos algunas funciones a base de abrir los ficheros a bajo nivel
con FOPEN(), leer con FREAD() y escribir con FWRITE(). No es muy sencillo, pues debemos reconocer las
distintas estructuras de los ficheros .INI y puede ser problemático en algunas ocasiones.

Las últimas versiones de la librería FOXTOOLS disponen dos funciones,  GetProStr yPutProStr para leer y
escribir en [Link]. En mi opinión son unas funciones bastante limitadas, pero podemos utilizarlas sin
problemas.

Existe otro sistema, bastante sencillo y muy potente, utilizar el API de Windows. Este API nos proporciona
un amplio conjunto de funciones y para leer, escribir y borrar el contenido de cualquier fichero .INI.
Veámoslo :

Leer cadenas de ficheros .INI

El API de Windows nos ofrece una función para consultar el valor de una clave dentro de una sección de
un fichero .INI como una cadena de caracteres. Su declaración es :

DECLARE ;
INTEGER GetPrivateProfileString ;
IN WIN32API;
STRING cSeccion, ;
STRING cClave, ;
STRING cDefecto, ;
STRING @cCadenaRetorno, ;
INTEGER nTama, ;
STRING cNombreFichero

A esta función le debemos pasar la sección que consultamos, la clave que queremos obtener, un valor por
defecto si no encuentra esta clave, sección o fichero, un buffer para la cadena de retorno, el tamaño de
este buffer y el nombre del fichero INI con extensión (el path no es necesario si se encuentra en el
directorio de Windows).

La función devuelve 0 si existe algún problema o, si todo a funcionado correctamente, el tamaño de la


cadena incluida en el buffer. Si se almacena en el buffer el valor por defecto, que le hemos indicado para
el caso de no encontrar el fichero, la sección o la clave, se retorna el tamaño de esta cadena. En ambos
casos no se incluye en el tamaño el carácter nulo -CHR(0)- de fin de cadena.

Veamos un pequeño ejemplo :

*** Cargar las declaraciones ***


DO [Link]
*** Creación de la variable de retorno ***
LOCATE cRetorno, nRet
cRetorno = REPLICATE( CHR(0), 255 )
*** Consultar el ratón instalado ***
nRet = GetPrivateProfileString( ;
"[Link]", ;
"[Link]", ;
"", ;
@cRetorno, ;
255, ;
"[Link]" )
IF nRet > 0
? "Ratón instalado : "
?? SUBSTR( cRetorno, ;
1, ;
AT( CHR(0), cRetorno ) -1 )
ENDIF

Debemos fijarnos en que la cadena que pasamos debe tener un cierto tamaño reservado, sino podemos
provocar un error. Este tamaño puede ser rellenado por cualquier carácter, pero prefiero utilizar el
carácter nulo.

La cadena retornada está siempre terminada por un carácter nulo -CHR(0)-, que no es necesario en Visual
FoxPro, por ello es conveniente hacer siempre un SUBSTRhasta el primer nulo que encontremos.

Los más observadores se habrán percatado que los parámetros de tipo cadena que nosotros pasamos a
las funciones del API de Windows no tienen el carácter nulo final, preceptivo en C. No hay ningún
problema, Visual FoxPro lo incluye por nosotros.

Leer enteros de ficheros .INI

Si lo que queremos hacer es obtener un dato que sabemos que es un número entero podemos utilizar esta
función :

DECLARE ;
INTEGER GetPrivateProfileInt ;
IN WIN32API ;
STRING cSeccion, ;
STRING cClave, ;
INTEGER nValorDefecto, ;
STRING cNombreFichero

Debemos tener cuidado en el tipo de valor que podemos obtener, pues siempre debe ser numérico y que
quepa en un entero de 32 bits. Por ejemplo, un número con decimales no puede ser obtenido por este
sistema y deberá ser leído como cadena y luego convertido.

En esta función, como en la de lectura de cadenas, debemos indicar un retorno por defecto si no se
encuentra la clave, la sección o el fichero. Debemos elegir correctamente este valor de retorno a fin de
poder controlar los posibles errores o bien siempre retorne un valor válido.

Leer una sección completa

Si preferimos leer una sección entera y no clave a clave podemos utilizar esta función :

DECLARE ;
INTEGER GetPrivateProfileSection ;
IN WIN32API ;
STRING cSeccion, ;
STRING @cCadenaRetorno, ;
INTEGER nTama, ;
STRING cNombreFichero
La cadena de retorno contendrá todas las claves de la sección, incluido el identificador, el signo igual y el
valor, separadas las líneas por caracteres nulos y finalizando la cadena con dos caracteres nulos.

Esta función es una clara candidata a estar encapsulada en una función de mayor nivel que, por ejemplo,
ponga todas las líneas de una sección dentro de un matriz de dos dimensiones, donde la primera columna
son las claves y la segunda los valores. Esta función puede ser :

PROCEDURE GtIniSec
LPARAMETER cSeccion, cArray, cNombreFich
*** Tamaño del buffer (puede cambiarse) ***
#define MAX_SECTION 5120
*** Todas la variables locales ***
LOCAL cBuffer, cTmp
LOCAL nResult, nCont
LOCAL nInicioLinea, nFinLinea, nPosIgual
*** Cargar las declaraciones ***
DO profiles
*** Preparar bufferr ***
cBuffer = REPLICATE( CHR(0), MAX_SECTION )
*** Obtener todo la sección ***
nResult = GetPrivateProfileSection( ;
cSeccion, ;
@cBuffer, ;
MAX_SECTION, ;
cNombreFich )
*** Comprobar si existen errores ***
IF nResult == 0
RETURN 0
ENDIF
*** Inicializar variables ***
nInicioLinea = 1
nFinLinea = 1
nCont = 0
cTmp = ""
*** Procesar el resultado ***
DO WHILE nFinLinea < nResult
*** Aumentar el contador de nulos ***
nCont = nCont + 1
*** Posición del próximo nulo ***
nFinLinea = AT( CHR(0), cBuffer, nCont )

*** Cadena de una línea ***


cTmp = SUBSTR( cBuffer, ;
nInicioLinea, ;
nFinLinea - nInicioLinea )

*** Redimensionar el array ***


PUBLIC ARRAY &cArray.[nCont,2]

*** Posición del igual ***


nPosIgual = AT( '=', cTmp )
*** Cargar la clave ***
&cArray.[nCont,1] = ;
SUBSTR( cTmp, 1, nPosIgual - 1 )

*** Cargar el valor si existe ***


IF nPosIgual < LEN( cTmp )
&cArray.[nCont,2] = ;
SUBSTR( cTmp, nPosIgual + 1 )
ELSE
&cArray.[nCont,2] = ""
ENDIF

*** El incio de la siguiente línea ***


*** es el fin de esta ***
nInicioLinea = nFinLinea + 1
ENDDO
*** Retornar el número de líneas ***
RETURN nCont

Para utilizar esta función, que sin duda es mejorable, debemos pasar el nombre de la sección, el nombre
de la matriz y el nombre del fichero, algo como esto :

? ASECTION( "windows", "aVer", "[Link]" )


DISPLAY MEMORY LIKE aVer

Recomiendo sin reservas el encapsular las llamadas del API de Windows dentro de funciones escritas por
nosotros mismos, de esta forma, no sólo damos una mayor funcionalidad a nuestros programas, sino que
aislamos nuestro código antes posibles cambios en el API.

Siguiente Artículo

En este artículo sólo nos ha dado espacio para ver las funciones de lectura de ficheros .INI. En la próxima
entrega veremos las funciones de escritura y un ejemplo completo del uso de estas funciones.

Uso del API de Windows : Funciones para ficheros .INI (III) 


En el artículo anterior vimos las funciones del API de Windows para leer ficheros .INI, ahora vamos a dar
un repaso a la funciones de escritura y completaremos las funciones que manejan ficheros de
configuración.

Escribir cadenas en ficheros .INI

La función de escritura de ficheros .INI más utilizada es, sin duda, la de escritura de cadenas de
caracteres. Esta función se declara en Visual FoxPro así :

DECLARE ;
INTEGER WritePrivateProfileString ;
IN WIN32API ;
STRING cSeccion, ;
STRING cClave, ;
STRING cCadena, ;
STRING cNombreFichero

A la función le pasamos la sección, la clave, el valor y el nombre del fichero. La cadena del valor no tiene
que pasarse por referencia, es decir, no pasamos un buffer, sino simplemente el valor como una cadena.

Si la función por algún motivo no ha podido escribir dentro del fichero devuelve un 0, si todo ha ido
correctamente devuelve un 1.

Escribir enteros en ficheros .INI

No existe una función específica para la escritura del enteros dentro de los ficheros .INI, aunque exista
una función para su lectura. Por lo tanto debemos convertir los valores numéricos en cadenas antes de
escribir por medio deGetPrivateProfileString.

Es una buena idea construir una función para encapsular esta transformación. Esta función podría
llamarse WrIniInt y sería similar a esta :
PROCEDURE WrIniInt
LPARAMETER cSeccion, cClave, ;
nValor, cNombreFichero

RETURN GetPrivateProfileString( ;
cSeccion, ;
cClave, ;
LTRIM( STR( nValor ) ), ;
cNombreFichero )

Es una función muy sencilla, pero puede ser de utilidad si tenemos que escribir muchos enteros en los
ficheros de configuración.

De igual forma, si tenemos guardar muchos números con decimales, fechas o cualquier otro tipo de dato
podemos crear nuestras propias funciones para hacer más sencilla y rápida la escritura y lectura de
ficheros .INI.

Escribir secciones en ficheros .INI

Al igual que podemos leer toda una sección podemos escribirla. La única dificultad es que debemos pasar
una cadena con un formato algo especial : debe contener todas las claves, cada una compuesta por del
identificador, el signo igual y el valor, separadas entre si con un carácter nulo.

Para escribir la sección utilizaremos la función :

DECLARE ;
INTEGER WritePrivateProfileSection ;
IN WIN32API ;
STRING cSeccion, ;
STRING cCadena, ;
STRING cNombreFichero

Al igual que en el caso de lectura de secciones, esta función es una clara candidata a ser recubierta por
una función escrita por nosotros que, por ejemplo, reciba la sección, una matriz por referencia y el
nombre del fichero, construya el buffer necesario y llame a la función del API de Windows. Podría ser algo
similar a esta :

PROCEDURE WrIniSec
LPARAMETER cSeccion, aDatos, cNombreFichero
LOCAL nTama, cCadena, nCont
*** Obtener el tamaño del array ***
nTama = ALEN( aDatos, 1 )
*** Inicializar la cadena ***
cCadena = ''
*** Transformar en la cadena ***
FOR nCont = 1 TO nTama
cCadena = cCadena + ;
aDatos[nCont,1] + ;
'=' + ;
aDatos[nCont,2] + ;
CHR(0)
NEXT
*** Escribir y retornar ***
RETURN WritePrivateProfileSection( ;
cSeccion, ;
cTmp, ;
cNombreFichero )
Téngase en cuenta que la matriz debe pasarse por referencia, es decir con un @antes del nombre, sería
algo así :

DIMENSION aInf[2,2]
aInf[1,1] = "Clave 1"
aInf[1,2] = "1000"
aInf[2,1] = "Clave 2"
aInf[2,2] = "2000"
? WrIniSec( "Escritura 1", ;
@aInf, ;
"[Link]" )

Cada uno puede crear su propia función, admitiendo los parámetros que más le interese.

Crear un fichero, una sección o una clave

No existen funciones especiales para crear ficheros .INI. Si utilizamos una función de escritura sobre un
fichero que no existe entonces la función lo crea automáticamente.

Así mismo si la sección o la clave que pasamos a la función de escritura no existe esta lo crea.

Debemos, por lo tanto, tener cuidado con los nombres que pasamos como parámetros en la funciones de
escritura, pues un pequeño error creará un nuevo fichero, una nueva sección o una nueva clave.

Dejar una clave vacía

Si queremos eliminar el valor de una clave, pero no queremos borrar la clave como tal, debemos pasar
una cadena nula a la función de escritura de cadenas. Un ejemplo puede ser :

=GetPrivateProfileString( ;
"Sección", ;
"Clave", ;
"", ;
"[Link]" )

En este caso dejaremos una línea con clave, un igual y sin ningún valor.

[Sección]
Clave=

Borrar de un fichero .INI

Si lo que queremos hacer es borrar completamente la clave debemos pasar a la función de escritura de
cadenas un cero en el parámetro del valor :

=GetPrivateProfileString( ;
cSeccion, ;
cClave, ;
0, ;
cNombreFichero )

Para eliminar toda una sección debemos utilizar la función de escritura de secciones pasando como
parámetro de los valores un cero :

=WritePrivateProfileSection( ;
cSeccion, ;
0, ;
cNombreFichero )
Con este método se borra tanto el contenido de la sección, es decir, todas sus claves, como el
identificador de las sección dentro del fichero de configuración.

Borrar el fichero

Aunque borremos todas las secciones no borramos el fichero .INI. Por lo tanto, si queremos eliminar el
fichero debemos hacer uso de las funciones de borrar de ficheros de Visual FoxPro, pues en este caso no
existe una función especial del API de Windows para borrar este tipo de ficheros.

Antes de lanzarnos a borrar nuestro fichero de configuración debemos tener presente que el fichero de
configuración podemos instalarlo en el directorio de Windows o en cualquier otro directorio que nosotros
elijamos. Pero si decidimos situarlo en el directorio de instalación de Windows debemos saber que este
directorio no tiene por que ser C:\WINDOWS (yo utilizo siempre C:\WIN).

Para saber donde esta instalado Windows existen varios métodos. Quizás el más sencillo es consultar con
la función GETENV() de FoxPro una variable del sistema llamada WINDIR y que contiene este directorio.

Pero si queremos seguir haciendo uso del API de Windows podemos utilizar la siguiente declaración :

DECLARE ;
INTEGER GetWindowsDirectory ;
IN WIN32API ;
STRING @cBuffer, ;
INTEGER nTama

Esta función espera un buffer como primer parámetro y el tamaño del mismo como segundo parámetro. El
buffer es conveniente que sea al menos de 255 caracteres (por si alguien es muy retorcido en eso de
instalar Windows).

Sea como fuere, una vez conocido el directorio donde está instalado Windows podemos utilizar la
orden DELETE FILE de FoxPro para borrar nuestro fichero .INI (nunca borrar los ficheros de otras
aplicaciones o del sistema, puede ser muy desagradable el resultado).

Funciones del API especiales para [Link]

El API de Windows ofrece una serie de funciones para acceder directamente al fichero [Link], sin
necesidad de indicar el nombre del fichero, tal y como hacíamos hasta ahora. Estas funciones se
comportan exactamente igual que las que hemos visto hasta ahora, pero sólo acceden al fichero [Link].
La declaración de estas funciones es :

DECLARE ;
INTEGER GetProfileInt ;
IN WIN32API ;
STRING cSeccion, ;
STRING cClave, ;
INTEGER nValorDefecto
DECLARE ;
INTEGER GetProfileString ;
IN WIN32API ;
STRING cSeccion, ;
STRING cClave, ;
STRING cValorDefecto, ;
STRING @cCadenaRetorno, ;
INTEGER nTama
DECLARE ;
INTEGER GetProfileSection ;
IN WIN32API ;
STRING cSeccion, ;
STRING @cCadenaRetorno, ;
INTEGER nTama
DECLARE ;
INTEGER WriteProfileString ;
IN WIN32API ;
STRING cSeccion, ;
STRING cClave, ;
STRING cCadena
DECLARE ;
INTEGER WriteProfileSection ;
IN WIN32API ;
STRING cSeccion, ;
STRING cCadena

No existen funciones específicas para [Link], necesitando acceder a este fichero como si fuera un
"PrivateProfile". También es posible acceder al [Link] por medio de las funciones
"PrivateProfile" y no con las funciones específicas.

Ejemplo : Guardar la posición de una ventana

Después de dar un repaso a las funciones del API de Windows para manejar los ficheros .INI vamos a
describir un ejemplo completo, donde se van a almacenar algunos datos sobre la última posición de una
ventana.

Es una buena costumbre que las aplicaciones abran algunos tipos de ventanas en la última posición donde
el usuario las dejó. A todos nos ha pasado que después de configurar a nuestro gusto la situación de las
ventanas de una aplicación, al volver a arrancarla las ventanas están en su posición inicial y no donde las
dejamos nosotros, debiendo volver a situarlas.

Para evitar este feo efecto podemos incluir unas pocas líneas dentro de nuestras pantallas a fin de obtener
los datos de la posición de la ventana cuando se cierra y las guardarlos en un fichero .INI. Cuando la
ventana se vuelve a crear la podemos situar en la última posición conocida.

Un ejemplo de esta técnica es el siguiente programa :

*** Cargar la Form y mostrarla ***


PUBLIC oEjemplo2
oEjemplo2 = CREATEOBJECT( "Ejemplo2" )
[Link]()
RETURN
*** Form del ejemplo en forma de clase ***
DEFINE CLASS Ejemplo2 AS form
BackColor = RGB(192,192,192)
Caption = "Ejemplo 2 sobre INIs"
*** Botón para cerrar ***
ADD OBJECT cmdAceptar ;
AS COMMANDBUTTON WITH ;
Top = 6, ;
Left = 6, ;
Height = 29, ;
Width = 94, ;
Caption = "Aceptar"
*** Evento al crear la form ***
PROCEDURE Load
*** Cargar las declaraciones ***
DO profiles
*** Obtener los valores con ***
*** los que se cerró la última vez ***
WITH This
.Top = GetPrivateProfileInt( ;
"Ejemplo 2", ;
"Top", ;
.Top, ;
"[Link]" )
.Left = GetPrivateProfileInt( ;
"Ejemplo 2", ;
"Left", ;
.Left, ;
"[Link]" )
.Width = GetPrivateProfileInt( ;
"Ejemplo 2", ;
"Width", ;
.Width, ;
"[Link]" )
.Height = GetPrivateProfileInt( ;
"Ejemplo 2", ;
"Height", ;
.Height, ;
"[Link]" )
ENDWITH
ENDPROC
*** Evento al destruir la form ***
PROCEDURE Unload
*** Cargar las declaraciones ***
*** pueden haberse descargado ***
DO profiles

*** Si está minimizado o maximizado ***


*** restaurar al tamaño normal ***
[Link] = 0

*** Guardar los valores de ***


*** posición y tamaño ***
WITH This
= WritePrivateProfileString( ;
"Ejemplo 2", ;
"Top", ;
LTRIM( STR( .Top ) ), ;
"[Link]" )
= WritePrivateProfileString( ;
"Ejemplo 2", ;
"Left", ;
LTRIM( STR( .Left ) ), ;
"[Link]" )
= WritePrivateProfileString( ;
"Ejemplo 2", ;
"Width", ;
LTRIM( STR( .Width ), );
"[Link]" )
= WritePrivateProfileString( ;
"Ejemplo 2", ;
"Height", ;
RTRIM( STR( .Height ) ), ;
"[Link]" )
ENDWITH
ENDPROC
*** Método para cerrar la form ***
PROCEDURE [Link]
[Link]()
ENDPROC
ENDDEFINE

En el disco de la revista se puede encontrar este ejemplo en formato .SCX además de como código.


Debemos fijarnos que los valores por defecto de la función GetPrivateProfileInt para cuando no se
encuentra el fichero, la sección o la clave son la posición actual de la ventana, de esta manera nunca se
da un error.

El fichero [Link] que utilizamos en este programa no existe la primera vez que ejecutamos


la form, pero al cerrar, la función WritePrivateProfileString crea el fichero, la sección y las claves al
no encontrarlas. En nuestra aplicación podemos incluir el fichero .INI dentro de la instalación.

[Nota : si quiere mantener limpio su directorio Windows, borré el [Link] después de las


pruebas.]

Dos comentarios

Primero. Los nombres de las funciones del API de Windows suelen ser bastante extensos, por
ejemplo, WritePrivateProfileSection tiene 26 letras. Si uno no quiere utilizar estos nombres tan largos
puede hacer uso de la cláusula ALIAS de la ordenDECLARE. De esta forma podemos declarar esta función
con otro nombre y llamarla de forma más sencilla :

DECLARE ;
INTEGER WritePrivateProfileSection ;
IN WIN32API ;
ALIAS WPPS ;
STRING cSeccion, ;
STRING cCadena, ;
STRING cNombreFichero
? WPPS( "Sección", Buffer, "[Link]" )

Yo personalmente me he acostumbrado a los nombres del API de Windows, pero es cuestión de


preferencias.

Segundo. Es muy recomendable encapsular todas las funciones del API de Windows en funciones de
mayor nivel. En el caso concreto de las funciones que manejan ficheros .INI es todavía más importante,
pues como dijimos, estos ficheros serán sustituidos paulatinamente por el Registro de Configuraciones.

Si nosotros construimos un grupo de funciones de alto nivel para guardar nuestras configuraciones, las
aplicaciones no tienen por que verse afectadas por el cambio, sólo estas funciones deberán ser
modificadas cambiando sus llamadas a las funciones que manejan .INIs por las que manejan el Registro
de Configuraciones o cualquier otro sistema.

Conclusión

Espero que esta aproximación a la utilización del API de Windows desde Visual FoxPro haya sido de
interés. Esto no se queda aquí y en el próximo artículo estudiaremos otras útiles funciones.

Si no puede esperar y desea avanzar en el conocimiento del API de Windows puede ver el fichero de
ayuda sobre el API de Win32 de la versión profesional en CD-ROM de Visual FoxPro o bien la magnífica
información y ejemplos contenidos en los CD-ROM del Microsoft Developer Network (MSDN) o los
manuales de Visual C++ 2.0 o 4.0.

Uso del API de Windows : Introducción a las Ventanas (IV) 


Después de haber visto en los artículos anteriores los principios básicos sobre las llamadas al API de
Windows desde Visual FoxPro y las funciones para el uso de ficheros .INI, vamos a realizar una pequeña
introducción a las funciones para el manejo de ventanas.
No nos fijaremos tanto en manejar las ventanas creadas por nosotros en nuestra aplicación, y que
podemos manejar perfectamente desde Visual FoxPro, sino que nos centraremos en la ventana o
formulario _SCREEN, es decir, en la ventana principal de Visual FoxPro.

Handle de ventana

Toda ventana en MS-Windows tiene un indicador único denominado handle o manejador. Todo en MS-
Windows es una ventana, un botón, un campo de edición, o cualquier otro elemento visual de Windows es
una ventana y toda ventana tiene un handle.

Muchas funciones que vamos a utilizar requieren como parámetro el handle (manejador) de la ventana de
Visual FoxPro o de alguna otra ventana a la que se asociará el comportamiento de esa función. Pero,
¿como obtenemos el handle de una ventana de Visual FoxPro?

En principio no tenemos un sistema directo para el acceso al handle de la ventana principal de Visual
FoxPro, pero hay dos métodos muy sencillos para obtener este handle.

En primer lugar podemos usar la librería [Link] para usar la función MainHwnd(). Esta función


devuelve el handle de la ventana principal de FoxPro. Sería algo como esto :

SET LIBRARY TO [Link] ADDITIVE


nHwnd = MainHwnd()

Si no queremos utilizar [Link] podemos hacer uso de las funciones del API de Windows para
obtener el handle de la ventana principal de Visual FoxPro. Para ello debemos declarar la
función FindWindow().

DECLARE ;
INTEGER FindWindow ;
IN WIN32API ;
STRING cClassName, ;
STRING cWindName

Para la funcionalidad que deseamos debemos pasar en el primer parámetro un valor 0 (correspondiente a
un puntero nulo) y como segundo parámetro el título de la ventana principal de Visual FoxPro. Sería algo
así :

nHwnd = FindWindow( 0, _SCREEN.Caption )

Esta función, aunque tiene algunos problemas, será utilizada desde ahora en los ejemplos para obtener el
handle de la ventana principal de Visual FoxPro.

Saber si la ventana de Visual FoxPro es la ventana activa

Algunas veces queremos saber si nuestra aplicación es la ventana activa, para ello podemos comprobar si
la ventana principal de Visual FoxPro es la ventana activa.

Utilizaremos la función GetForegroundWindow() del API de Windows que devuelve el handle de la ventana


activa, junto con FindWindow() para obtener el handle de la ventana de Visual FoxPro.

Aquí tenemos un pequeño programa de ejemplo que devuelve .T. si la ventana de nuestra aplicación es la
ventana activa y .F. si no lo es :

DECLARE ;
Integer GetForegroundWindow ;
IN WIN32API
*** Para darnos tiempo a cambiar
WAIT WINDOW ;
"Cambie de aplicación y " + ;
"espere unos segundos..." TIMEOUT 5

*** Obtener el handle de la ventana activa


nActiveHwnd = GetForegroundWindow()

*** Obtener el handle de _SCREEN


nFoxHwnd = FindWindow( 0, _SCREEN.Caption )

*** Comparar y devolver el resultado


? nFoxHwnd == nActiveHwnd

Es una buena idea construir una función similar a esta para comprobar en cualquier momento si nuestra
aplicación corresponde a la ventana activa.

Activar nuestra aplicación

En Windows no todo lo que se puede hacer está bien visto que se haga. Por ejemplo, si una aplicación no
esta activa porque estamos trabajando en ese momento en otra aplicación y nos parece la aplicación
inactiva para darnos un pequeño aviso sin importancia entonces nos molestamos.

No debemos activar nuestra aplicación continuamente para cualquier aviso que tengamos que dar, pero
en algunas ocasiones podemos necesitar activar nuestra aplicación y para ello podemos
utilizar SetForegroundWindow(), una función del API de Windows para este fin. Esta función la
declaramos como :

DECLARE ;
Integer SetForegroundWindow ;
IN WIN32API ;
Integer nHwnd

El programa sería algo así :

WAIT WIND ;
"Cambie de aplicación y " + ;
"espere unos segundos..." TIMEOUT 5

nFoxHwnd = FindWindow( 0, _SCREEN.Caption )

*** Activarla
=SetForegroundWindow( nFoxHwnd )

Debemos recordar una vez más que no debemos abusar de esta posibilidad.

Situar nuestra aplicación siempre encima

Habéis visto lo útil que es esa especie de botón con una chincheta en el diseñador de formularios para
dejar por encima del resto la ventana de propiedades. Si alguno ha buscado en los manuales habrá
encontrado la propiedad AlwayOnTop para fijar este tipo de comportamiento.

Pero si alguien ha intentado utilizar esta propiedad con el formulario  _SCREEN, es decir, con la ventana
principal de Visual FoxPro, habrá comprobado que no hace nada. Esto se debe a que la pantalla principal
de Visual FoxPro (en esta versión) es un formulario, pero un formulario bastante especial. En las versiones
previas al lanzamiento del producto esta propiedad funcionaba, pero por una medida de diseño se
eliminaron todas las propiedades y métodos de _SCREEN que consideraron podían ser 'peligrosas'.
Si de todas formas queremos hacer que nuestra aplicación este 'On Top', es decir, por encima de todas las
demás aplicaciones, podemos echar mano al API de Windows.

En este caso, como en el anterior, debemos advertir que aun cuando sea posible, no es un
comportamiento 'elegante' y debemos utilizarlo sólo cuando sea realmente importante.

Para realizar esto necesitamos utilizar la función SetWindowPos() del API de Windows. Con esta función
se pueden hacer muchísimas cosas con las ventanas, no vamos a explicar todas, pero si tiene curiosidad y
puede hojear la documentación sobre el API de Windows comprobará muchos otros usos de esta función.

De momento sólo la usaremos para situar nuestra ventana fija por encima de todas las demás. La función
se declara de la siguiente manera :

DECLARE ;
Integer SetWindowPos ;
IN WIN32API ;
Integer nWnd, ;
Integer nWndInsertAfter, ;
Integer nTop, ;
Integer nLeft, ;
Integer nHeight, ;
Integer nWidth, ;
Integer nFlags

Además debemos declarar algunas constantes y ya podemos hacer uso de la función :

#define SWP_NOSIZE 1
#define SWP_NOMOVE 2
#define HWND_TOPMOST -1
#define HWND_NOTOPMOST -2

nFoxHwnd = FindWindow( 0, _SCREEN.Caption )

*** Pasar a primer plano fijo


IF 1 = SetWindowPos( nFoxHWND, ;
HWND_TOPMOST, ;
0,0,0,0, ;
SWP_NOSIZE + ;
SWP_NOMOVE )

*** Cambio meramente informativo


_SCREEN.AlwaysOnTop = .T.

ENDIF

La función SetWindowPos() requiere como primer parámetro el handle de la ventana que queremos


modificar, en el segundo la constante para hacer esa ventana siempre visible por encima de las demás,
los cuatro siguientes serían la posición y tamaño de la ventana pero en el último parámetro hemos pasado
las constantes para hacer que no se tenga en cuenta la posición y tamaño de la ventana y que sólo se
tenga en cuenta el situar la ventana en primer plano de forma fija.

El cambio que realizamos en la propiedad AlwaysOnTop del formulario _SCREEN es meramente


informativo, pues no realiza ningún efecto real sobre la ventana principal de Visual FoxPro.

Para deshacer esta situación por encima de todas las ventanas debemos llamar a la función con el
parámetro HWND_NOTOPMOST de la siguiente manera :

SetWindowPos( nFoxHWND, ;
HWND_NOTOPMOST, ;
0,0,0,0, ;
SWP_NOSIZE + SWP_NOMOVE )

Deberíamos también cambiar la propiedad AlwaysOnTop del formulario _SCREEN para mantener la


coherencia entre la situación real de la ventana y esta propiedad.

Ocultar la ventana principal de Visual FoxPro

Otra propiedad de _SCREEN que no tiene efecto real sobre esta ventana es Visible. Si asignamos el
valor .F. a la propiedad Visible deberíamos hacer ese formulario oculto, pero con _SCREEN esto no
funciona. De igual forma el método Hide() no funciona con _SCREEN. Fueron precisamente esta propiedad
y este método los que provocaron en versiones previas al lanzamiento del producto que se replantease un
buen número de funcionalidades de _SCREEN.

De todas formas podemos hacer uso de las funciones del API de Windows para ocultar la ventana principal
de Visual FoxPro. Debemos tener cuidado, pues debemos escribir un código que nos asegure que Visual
FoxPro se volverá a hacer visible o bien termine. Si no lo hacemos así tendremos una aplicación activa,
consumiendo recursos, y a la que no podemos acceder.

La función que utilizaremos será :

DECLARE ;
Integer ShowWindow ;
IN WIN32API ;
Integer nWnd, ;
Integer nCmdShow

Con esta función podemos cambiar el estado de una ventana dentro de una amplia gama de posibilidades.
Como primer parámetro le tenemos que pasar el handle de la ventana y como segundo parámetro la
acción que queremos realizar. Para facilitar el trabajo estas son las acciones soportadas por la función
definidas en constantes :

#define SW_HIDE 0
#define SW_SHOWNORMAL 1
#define SW_NORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_MAXIMIZE 3
#define SW_SHOWNOACTIVATE 4
#define SW_SHOW 5
#define SW_MINIMIZE 6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA 8
#define SW_RESTORE 9
#define SW_SHOWDEFAULT 10
#define SW_MAX 10

Para ocultar la ventana podemos usar un programa similar a este :

nFoxHwnd = FindWindow( 0, _SCREEN.Caption )

nOldWindowState = _SCREEN.WindowState

*** Oculta la ventana


=ShowWindow( nFoxHwnd, SW_HIDE )

*** Mostrar un mensaje


=MessageBox( ;
"Ahora Visual FoxPro esta invisible..." )

*** Resturar la ventana


DO CASE
CASE nOldWindowState = 0
=ShowWindow( nFoxHwnd, SW_SHOWNORMAL )
CASE nOldWindowState = 1
=ShowWindow( nFoxHwnd, SW_SHOWMINIMIZED )
CASE nOldWindowState = 2
=ShowWindow( nFoxHwnd, SW_SHOWMAXIMIZED )
ENDCASE

Ocultar la ventana principal de Visual FoxPro provoca que todas las ventanas hijas, es decir las que están
dentro de la ventana de Visual FoxPro, se hagan invisibles. Las ventanas definidas como  Desktop seguirán
siendo visibles, como unMessageBox().

Utilizar funciones de VFP para manejar la ventana principal

No debemos caer en la tentación de utilizar el API de Windows para hacer cosas que perfectamente
podemos hacer con Visual FoxPro sin necesidad del acceso directo al API. Puede parecer que siempre es
necesario el uso del API, pero Visual FoxPro nos ofrece excelentes mecanismos para controlar su ventana
principal.

Por medio de la función SetWindowPos() podemos cambiar la posición de nuestra ventana, pero es mucho


más sencillo y más correcto hacerlo por medio de las propiedades Top, Left, Height y Width de la
ventana principal de Visual FoxPro.

También podemos usar la función ShowWindow() para cambiar el estado de la ventana principal de Visual


FoxPro entre maximizada, minimizada y normal, pero es más correcto usar la propiedad  WindowState del
formulario _SCREEN.

Debemos dejar el uso del API a funcionalidades no soportadas directamente por esta versión de Visual
FoxPro.

Conocer si nuestra aplicación ya está arrancada

Todos hemos visto aplicaciones que no pueden arrancarse dos veces. Cuando volvemos a arrancar la
aplicación, no se arranca una segunda copia del programa, sino que se vuelve a mostrar la aplicación que
ya estaba arrancada. Existen varias formas de hacer esto, una de ellas es la descrita aquí, no es la mejor,
pero explica el uso de algunas funciones del API que son muy interesantes.

La primera de ellas es la función GetWindow(), para hacer un recorrido por las distintas ventanas
principales que estén visibles en Windows en ese momento.

DECLARE ;
Integer GetWindow ;
IN WIN32API ;
Integer nHwnd, ;
Integer nCmd

Como parámetros requiere un handle de ventana y en que dirección queremos recorrer las ventanas, para
ello hemos definido las siguientes constantes :

#define GW_HWNDFIRST 0
#define GW_HWNDLAST 1
#define GW_HWNDNEXT 2
#define GW_HWNDPREV 3
#define GW_OWNER 4
#define GW_CHILD 5
#define GW_MAX 5

Otras dos funciones que vamos a utilizar son GetWindowTextLength() para obtener el tamaño del título
de una ventana y GetWindowText() para obtener el título de la ventana.

DECLARE ;
Integer GetWindowTextA ;
IN WIN32API ;
Integer nhWnd, ;
String @cString, ;
Integer nMaxCount

DECLARE ;
Integer GetWindowText ;
IN WIN32API ;
Integer nWnd

Por medio de estas funciones podemos recorrer las ventanas principales de las aplicaciones activas y si
alguna tiene el mismo título que la nuestra mostrar esta y terminar con la ejecución de la nuestra. Sería
algo así :

nFoxHwnd = FindWindow( 0, _SCREEN.Caption )

*** Esta será la primera ventana


nCurrWnd = GetWindow( nFoxHwnd, ;
GW_HWNDFIRST )

*** Recorrer todas las ventanas


DO WHILE nCurrWnd # 0

*** Tamaño del título


nLength = GetWindowTextLength( nCurrWnd )
IF nLength > 0

*** Preparar un buffer para el título


cTmp = REPLICATE( CHR(0), nLength + 1 )

*** Obtener el título


=GetWindowText( nCurrWnd, ;
@cTmp, ;
nLength + 1 )

*** Si el handle es distinto


*** y el título igual
IF nFoxHwnd != nCurrWnd ;
AND ;
_SCREEN.Caption == SUBSTR( cTmp, ;
1, ;
nLength )

*** Activar la otra ventana


*** y terminar la ejecución
=SetForegroundWindow( nCurrWnd )
QUIT

ENDIF
ENDIF
*** Obtener la siguiente ventana
nCurrWnd = GetWindow( nCurrWnd, ;
GW_HWNDNEXT )
ENDDO

Conclusión

No hemos visto todas las posibilidades, pero ya nos podemos hacer una idea de como superar las posibles
limitaciones de Visual FoxPro.

Aunque parezcamos bastante repetitivos, debemos volver a recordar que aunque se puedan hacer
determinadas cosas por medio del API no es muy correcto hacerlas sin más. Debemos, por lo tanto, ser
respetuosos con las demás aplicaciones que puedan estar corriendo en Windows.

Espero que esta aproximación a la utilización del API de Windows desde Visual FoxPro haya sido de
interés. Esto no se queda aquí y en el próximo artículo seguiremos profundizando en el manejo de
ventanas.

Si no puede esperar y desea avanzar en el conocimiento del API de Windows puede ver el fichero de
ayuda sobre el API de Win32 de la versión profesional en CD-ROM de Visual FoxPro o bien la magnífica
información y ejemplos contenidos en los CD-ROM del Microsoft Developer Network (MSDN) o los
manuales de Visual C++ 4.x.

Uso del API de Windows : Introducción a las Ventanas (V) 


Después de haber visto en los artículos anteriores los principios básicos sobre las llamadas al API de
Windows desde Visual FoxPro (enero) y las funciones para el uso de ficheros .INI (febrero y marzo),
vamos a seguir con las funciones para el manejo de ventanas que iniciamos el mes pasado (abril). En este
caso nos fijaremos en como manejar las ventanas de otras aplicaciones.

Conocer la lista de aplicaciones arrancadas

En ocasiones nuestra aplicación deberá actuar en conjunción con otras aplicaciones dentro de Windows.
Para este tipo de interacciones es interesante saber si la otra aplicación ya está arrancada. Por ejemplo, si
vamos a abrir un canal DDE o una conversación por medio de OLE Automation podemos saber si la otra
aplicación ya está arrancada.

Para ello utilizaremos la misma técnica que describimos el mes pasado para descubrir si nuestra aplicación
ya estaba arrancada, pero en este caso crearemos una matriz con dos columnas, la primera con todos los
títulos de las ventanas de las aplicaciones y las segunda con los handles de las mismas (pues nos serán de
utilidad más adelante). Nosotros deberemos buscar dentro de esta matriz si la aplicación que buscamos
está arrancada.

Para usar la función utilizaremos un código similar a este :

? aTask( "aVentanas", .F. )

Con esta llamada conseguiremos una matriz pública con los nombres de todas las ventanas principales. Si
pasamos como segundo parámetro una valor verdadero (.T.) obtendremos también las ventanas ocultas
de Windows. La función devuelve el número de ventanas encontradas.

*** Constantes
#define GW_HWNDFIRST 0
#define GW_HWNDNEXT 2
#define GW_OWNER 4
#define GW_CHILD 5

PROCEDURE ATask
LPARAMETER cMatriz, lOcultos

*** Declaraciones de la funciones del API


DECLARE ;
Integer GetWindow ;
IN WIN32API ;
Integer nHwnd, ;
Integer nCmd
DECLARE ;
Integer GetWindowText ;
IN WIN32API ;
Integer nHwnd, ;
String @cString, ;
Integer nMaxCount
DECLARE ;
Integer GetWindowTextLength ;
IN WIN32API ;
Integer nWnd
DECLARE ;
Integer IsWindowVisible ;
IN WIN32API ;
Integer nWnd
DECLARE ;
Integer GetDesktopWindow ;
IN WIN32API

*** Declaración de variables


PRIVATE nFoxHwnd, nCont, nCurrWnd
PRIVATE nLength, cTmp
RELEASE MEMORY &cMatriz
PUBLIC (cMatriz)

*** Obtención del handle del DeskTop


nHwnd = GetDesktopWindow()
nInitHwnd = GetWindow( nHwnd, GW_CHILD )

*** Esta será la primera ventana


nCurrWnd = GetWindow( nInitHwnd, ;
GW_HWNDFIRST )
*** Inicializar contador
nCont = 0

*** Recorrer todas las ventanas


DO WHILE nCurrWnd # 0

*** Comprobar si no tiene padre


IF GetWindow(nCurrWnd, GW_OWNER) = 0

*** Si debemos las ventanas ocultas


IF IsWindowVisible( nCurrWnd ) = 1 ;
OR lOcultos

*** Tamaño del título


nLength=GetWindowTextLength(nCurrWnd)
IF nLength > 0

nCont = nCont + 1

*** Obtener el título


cTmp=REPLICATE( CHR(0), nLength+1 )
=GetWindowText( nCurrWnd, ;
@cTmp, ;
nLength + 1 )
*** Insertar un nuevo elemento
DIMENSION &cMatriz.[nCont,2]
&cMatriz.[nCont,1] = ;
SUBSTR( cTmp, 1, nLength )
&cMatriz.[nCont,2] = ;
nCurrWnd
ENDIF
ENDIF
ENDIF
*** Obtener la siguiente ventana
nCurrWnd = GetWindow( nCurrWnd, ;
GW_HWNDNEXT )
ENDDO && (nCurrWnd # 0)

*** Retornar el número de procesos


RETURN nCont

Las función del API de Window que se utilizan este módulo que no hemos explicado en artículos anteriores
son GetDesktopWindow() y IsWindowVisible().

GetDesktopWindow() obtiene el handle del Escritorio de Windows, lo que nos permitirá


utilizar GetWindows() a partir de este handle.

IsWindowVisible() nos permite comprobar si una determinada ventana es visible o está oculta. Si


pasamos como segundo parámetro al módulo aTask un valor verdadero (.T.) obtendremos también las
ventanas ocultas. Probablemente nos sorprenderemos al ver las ventanas que andan ocultas por nuestro
Windows.

Diferencias de aTask con FindWindow

Alguien observador podría preguntarse por que no usar directamente la funciónFindWindow() del API de
Windows pasando el nombre de la ventana de principal de la aplicación con la que queremos interactuar
en vez del método indirecto de utilizaraTask y luego buscar en la matriz resultante.

Podemos usar la función FindWindow() sin problemas, pero deberemos pasar exactamente el título de la


ventana. Como muchas aplicaciones incluyen en el título el nombre del documento abierto, no es tan
sencillo saber exactamente cual es el título de la ventana.

Personalmente prefiero utilizar una función propia como aTask, pues me permite hacer búsquedas
aproximadas dentro de la matriz, y no necesito saber el título exacto de la ventana principal. Esta es una
sencilla función para buscar una ventana con un título aproximado :

PROCEDURE FindHwnd
LPARAMETER cTitle

*** Llama a [Link]


FOR nCont = 1 TO aTask( "aFindHwnd" )
*** Busca dentro de la cadena
IF UPPER( cTitle ) ;
$ UPPER( aFindHwnd[nCont,1] )

RELEASE MEMORY aFindHwnd


RETURN aFindHwnd[nCont,2]
ENDIF
NEXT
RELEASE MEMORY aFindHwnd
RETURN 0

Cada uno deberá estudiar en que casos prefiere utilizar un método u otro.

Las Ventanas no son Procesos

Hemos llamado a la función anterior aTask, es decir, Array de Tareas, por similitud a la Lista de Tareas de
Windows 3.x. Realmente lo que estamos haciendo es obtener los títulos de las ventanas principales que se
están ejecutando en Windows. Esto no es exactamente lo mismo que las tareas. No debemos confundir las
ventanas principales de las aplicaciones con las tareas (tasks) o los hilos (threads) de las aplicaciones.
Muchas tareas de Win32 no disponen de ventanas asociadas.

Las funciones del API de Windows que estamos estudiando se dedican al manejo de ventanas y por eso
nos hemos preocupado de las tareas como tales, pero en otras ocasiones nos puede interesar utilizar el
identificador del proceso o ID de alguno de los threads de una aplicación. Este tipo de funciones están
fuera del ámbito de estos artículos introductorios, pero son realmente interesantes.

Como curiosidad decir que Visual FoxPro tienen dos thread, uno para el interface y otro para el
interpretador de comandos y el gestor de base de datos. Nosotros podemos crear un nuevo thread en
Visual FoxPro, llamando al API de Windows, pero este thread no es utilizado por nuestro programa y no
sirve para nada, por ello es mejor no hacerlo.

Si tiene interés en profundizar por este camino puede empezar por dos artículos publicados en la
desaparecida Revista Microsoft de Programadores (RMP) : ¿Cual es la diferencia entre ventanas y tareas
en Windows 3.1? (Febrero de 1994) ySubiendo a 32 bits : Procesos, Theads y gestión de memoria en
Chicago ( Diciembre de 1994). En el CD-Rom de Microsoft Developed Network dispone de la versión en
inglés de estos artículos.

Arrancar otro programa

Todos hemos utilizado la orden RUN ó el símbolo ! de Visual FoxPro para arrancar otro programa.
Personalmente prefiero usar RUN a !, pues este último puede confundirse con el operador lógico !, que es
igual a un .NOT.. De todas las maneras es realmente muy sencillo.

En Windows tenemos la posibilidad de indicar una serie de operadores a esta orden a fin de controlar la
forma de ejecutar la otra aplicación, por ejemplo el Bloc de Notas :

*** Arranca y sigue la ejecución de FoxPro


RUN /N notepad
*** Arranca activo y a tamaño normal
RUN /N1 notepad
*** Arranca activo y a minimizado
RUN /N2 notepad
*** Arranca activo y a maximizado
RUN /N3 notepad
*** Arranca inactivo y a tamaño normal
RUN /N4 notepad
*** Arranca inactivo y a minimizado
RUN /N7 notepad

En la mayoría de los casos basta con utilizar RUN ó ! con alguno de estos modificadores, pero si queremos
arrancar una aplicación, por ejemplo, con su ventana principal oculta, debemos hacer uso de la
función WinExec() del API de Windows. Esta función se declara como :

DECLARE ;
Integer WinExec ;
IN WIN32API ;
String cCmdLine, ;
Integer nCmdShow

Como primer parámetro recibe la cadena que indica el programa a ejecutar, como segundo parámetro el
modo en el que se desea visualizar esta aplicación. Los posibles modos de visualización son los mismos
que describimos el mes pasado cuando hablamos de la función ShowWindows() :

#define SW_HIDE 0
#define SW_SHOWNORMAL 1
#define SW_NORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_MAXIMIZE 3
#define SW_SHOWNOACTIVATE 4
#define SW_SHOW 5
#define SW_MINIMIZE 6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA 8
#define SW_RESTORE 9
#define SW_SHOWDEFAULT 10
#define SW_MAX 10

La función devuelve un número por encima de 32 si ha tenido éxito, si existe algún problema para
arrancar esta aplicación, devuelve un código de error por debajo de este número 32.

Para arrancar el Bloc de Notas de forma oculta deberías ejecutar algo como esto :

=WinExec( "Notepad", SW_HIDE )

Después de esto no vemos la aplicación arrancada, pero si ejecutamos la funciónaTask con el segundo


parámetro a .T., tal y como describíamos al principio de este artículo, veremos que si se ha arrancado la
aplicación y que podemos hacerla visible.

Algunas aplicaciones no podremos hacerlas invisibles, pues entre su código de arranque tienen una
modificación del estado de su ventana, por ejemplo a maximizado, y esto provoca que se hagan siempre
visibles. Deberemos probar la aplicación que queremos arrancar de forma oculta.

Para hacer visible la aplicación utilizaremos la función ShowWindow() del API de Windows, pasando el


handle de la ventana y como segundo parámetro alguno de los distintos modos de visualización o
simplemente SW_SHOW.

Si no hacemos visible la otra aplicación o no la cerramos desde nuestro código, la aplicación que hemos
arrancado quedará de forma permanente, consumiendo recursos, pues el usuario no tiene medio de
cancelar esta ejecución. Por ello debemos ser muy cuidadosos a la hora de aplicar esta técnica.

Primera aproximación a los mensajes : Cerrar otra aplicación

Cualquier actuación con otra aplicación debe realizarse con sumo cuidado. Tal y como decíamos en el
artículo anterior : aunque se puedan hacer determinadas cosas por medio del API no es muy correcto
hacerlas sin más. Debemos, por lo tanto, ser respetuosos con las demás aplicaciones que puedan estar
corriendo en Windows.

En este caso, si arrancamos otra aplicación de forma invisible nosotros somos responsable de cerrarla.
Para cerrar otra aplicación podemos utilizar una nueva técnica, enviar un mensaje.

Windows se basa, en gran medida, en el paso de mensajes desde Windows a las aplicación, incluso entre
las aplicaciones entre si.
Todas ventanas de Windows tienen asociadas un programa que gestiona los mensajes de esa ventana. Los
mensajes para un programador de Visual FoxPro se puede asemejar a los eventos, es decir, cuando, por
ejemplo, se mueve el ratón, Windows envía un mensaje indicando que se ha realizado este movimiento y
Visual FoxPro ejecuta el código asociado a este evento.

Los mensajes pueden ser de muchos tipos. Basta ver la documentación del API de Windows y observar
que el inmenso número de mensajes que se pueden enviar o recibir. Los mensajes los pueden producir el
usuarios, Windows (por ejemplo el mensaje de un Timer) u otras aplicaciones.

Por ejemplo, para cerrar una ventana, podemos enviar un mensaje WM_CLOSE. Si esta ventana es la
ventana principal de una aplicación, se cierra esta aplicación.

Para enviar este mensaje a otra aplicación en Win32 debemos usar la funciónPostMessage(). Veamos un
pequeño ejemplo :

#DEFINE WM_CLOSE 16

DECLARE ;
Integer PostMessage ;
IN WIN32API ;
Integer nWnd, ;
Integer nMsg, ;
Integer nParam, ;
Integer nParam

*** Arranca en NotePad


WAIT WINDOW ;
"Se va a arrancar el Bloc, " + ;
"escriba algo en él y espere..." ;
TIMEOUT 3
RUN /N NotePad

*** Cierra preguntando si ha habido cambios


WAIT WINDOW ;
"El Block se va ha cerrar..." ;
TIMEOUT 5
*** La función FindHwnd es un PRG
nHwndNote = FindHwnd( "Bloc de notas" )
=PostMessage( nHwndNote, WM_CLOSE, 0, 0 )

La función PostMessage() necesita el handle de la ventana a la que enviamos el mensaje, en código de


mensaje, y dos parámetros necesarios para pasar variantes a algunos mensajes.

El mensaje WM_CLOSE provoca que la aplicación se cierre, pero muchas aplicaciones, como el Bloc de


Notas, preguntan si deseamos guardar el documento abierto antes de cerrar. Si necesitamos cancelar una
aplicación de forma rotunda, podemos utilizar otro mensaje, el WM_QUIT, que corresponde al número 18.
No debemos usar sin más este último método, pues en algunas aplicaciones puede producir la corrupción
de datos y debemos ser muy, muy, cuidadosos con esta posibilidad.

No vamos a describir aquí los mensajes que podemos utilizar. Sirvan estos dos como botón de muestra. Si
consulta la documentación sobre el API de Windows podrá descubrir todas las posibilidades del envío de
mensajes.

Los mensajes en VFP

Normalmente utilizaremos los mensajes que reciben las ventanas de Visual FoxPro por medio de los
eventos de las ventanas. Recordemos que, normalmente los mensajes de Windows corresponden a un
evento de Visual FoxPro. Así, por ejemplo, el mensaje WM_MOUSEMOVE corresponde con el
evento MouseMove de Visual FoxPro. Esto no siempre es tan sencillo. No todos los mensajes de Windows
corresponden con un evento de Visual FoxPro, sobre todo los mensajes menos habituales.

No debemos preocuparnos por esta limitación, pero si necesitamos utilizar alguno de estos mensajes no
reflejados directamente en VFP o los mensajes enviados por otras aplicaciones, como por ejemplo los
enviados por Windows Socket, deberemos usar alguna librería u OCX que nos permita manejarlos.

Por ejemplo, el control OCX de comunicaciones que viene con Visual FoxPro (Microsoft Comm Control)
enlaza los mensajes producidos en una comunicación asíncrona con una serie de eventos de este control y
manejables por Visual FoxPro.

Si recuerda lo que decíamos en el artículo anterior, todo en Windows en una ventana, un Botón o un
Check Box son ventana y por lo tanto si conseguimos en handle de un control, podemos enviar un
mensaje a ese control.

En Visual FoxPro esto no es totalmente cierto, pues gran parte de los controles de Visual FoxPro son
controles especiales y sólo son ventanas cuando tienen el foco. Si obtenemos el handle de la ventana de
este control cuando tiene el foco, podemos enviar algunos mensajes a la misma.

Conclusión

Debemos repetir, una vez más, que no todo lo que se puede hacer con el API de Windows esta bien
hecho. Por lo tanto, sean respetuosos con las demás aplicaciones que puedan estar corriendo en Windows
y utilicen lo aquí expuesto con cuidado.

No hemos hecho más que esbozar brevemente algunas de las posibilidades del manejo de ventanas. Le
invito a seguir estudiando por su cuenta las funciones y mensajes que nos ofrece para el manejo de
ventanas. El próximo artículo cambiaremos te tema y lo dedicaremos a las funciones para el manejo del
Registro de Win32.

Si no puede esperar y desea avanzar en el conocimiento del API de Windows puede ver el fichero de
ayuda sobre el API de Win32 de la versión profesional en CD-ROM de Visual FoxPro o bien la magnífica
información y ejemplos contenidos en los CD-ROM del Microsoft Developer Network (MSDN) o los
manuales de Visual C++ 4.x.

Uso del API de Windows : Manejo del Registro (y VI)


Ya llegamos al final de esta serie de artículos sobre el API de Windows. Para terminar
hemos elegido casi el mismo tema con el que empezamos, la forma de guardar
información sobre nuestra aplicación u obtener información sobre otras aplicaciones. En
los meses de febrero y marzo vimos como hacer esto por medio de ficheros .INI, este
mes veremos como utilizar el Registro (registry) de Windows.

¿Que es el Registro de Windows?

Es una base de datos con una estructura jerárquica donde las aplicaciones pueden
guardar información sobre su configuración y el sistema operativo almacena información
sobre si mismo.

Antes, las aplicaciones y el sistema operativo utilizaba los archivos .INI, pero su
proliferación y su escasa estructuración hicieron que Microsoft se plantease dar un
sistema mucho más práctico para guardar los datos de configuración de las aplicaciones.

El primer Registro se incluye en Windows 3.1, pero es en Windows NT y Windows 95


cuando se nos ofrece un auténtico Registro de Configuraciones.
El Registro de Windows 3.1 está muy limitado y es necesaria la utilización de los
ficheros .INI. Por su parte Windows 95 y Windows NT siguen soportando los ficheros .INI,
duplicando en muchos casos los datos del Registro en este tipo de ficheros, pero se basan
en el Registro de Configuraciones.

Vamos a realizar una aproximación a las funciones que disponemos para acceder a los
Registros de Configuración de Windows.

Estructura del Registro

Tal y como hemos dicho, el Registro tiene una estructura jerárquica. Está jerarquía se
basada en una serie de claves (keys) que pueden contener valores u otras claves.

En Windows 3.1 las claves sólo pueden contener un valor, pero en Windows 95 y Windows
NT las claves contienen tantos valores como se quiera, identificados cada uno de ellos por
una etiqueta.

Si quiere ver el Registro de Windows 3.1 debe ejecutar REGEDIT /V. Es importante la


inclusión del parámetro, pues nos permite ver el Registro con su estructurar jerárquica.
En Windows 95 deberá ejecutar REGEDIT y en Windows NT REGEDIT32.

Como vemos los Registros de Windows 3.1, Windows 95 y Windows NT varían en cuanto a
su formato, y como veremos ahora, también varían en cuanto a su contenido.

Claves principales

En Windows 3.1 sólo se dispone de una entrada principal al Registro, identificada por el
nombre HKEY_CLASSES_ROOT. A partir de esta raíz cuelgan el árbol con todas las entradas
del Registro.

Windows 3.1 guarda información sobre los ficheros registrados y sobre OLE, pero las
aplicaciones pueden hacer uso de él para guardar su configuración.

En Windows 95 y Windows NT el Registro tiene una conjunto de entradas principales


colgando de una raíz, denominada ROOT. La lista de las entradas de Windows 95 y
Windows NT se pueden ver en la figura 1.

Win16 y Win32s
HKEY_CLASSES_ROOT - ficheros registrados y OLE - ficheros registrados y OLE
Win95
HKEY_CLASSES_ROOT - por compatibilidad con Win16 - por compatibilidad con Win16
HKEY_CURRENT_USER - configuración del usuario actual - configuración del usuario actual 
HKEY_LOCAL_MACHINE - configuración de esta máquina - configuración de esta máquina 
HKEY_USERS - configuración por defecto de usuarios - configuración por defecto de
usuarios 
HKEY_CURRENT_CONFIG - configuración actual de esta máquina - configuración actual de
esta máquina 
HKEY_DYN_DATA - estado dinámico de esta máquina - estado dinámico de esta máquina
WinNT
HKEY_CLASSES_ROOT - por compatibilidad con Win16 - por compatibilidad con Win16
HKEY_CURRENT_USER - configuración del usuario actual - configuración del usuario actual 
HKEY_LOCAL_MACHINE - configuración de esta máquina - configuración de esta máquina 
HKEY_USERS - configuración por defecto de usuarios - configuración por defecto de
usuarios
Fig. 1 - Entradas del Registro

Como podemos ver, existen dos entradas en el Registro que sólo están disponibles en
Windows 95: HKEY_CURRENT_CONFIG y HKEY_DYN_DATA. Estas entradas están
especialmente pensadas para obtener información sobre dispositivos Plug&Play y para
gestionar la conexión y desconexión de dispositivos en portátiles.

Debemos saber que algunas entradas del Registro están `mapeadas', por
ejemplo,HKEY_CLASSES_ROOT corresponde internamente a la
entradaHKEY_LOCAL_MACHINE\Software\Classes, pues esta entrada está soportada sólo
por compatibilidad con Windows 3.1.

Si estamos trabajando en Windows 3.1 con un producto Win32s como es Visual FoxPro
3.0, debemos ser consciente de que aun cuando disponemos de parte de las funciones del
API de Win32, el Registro es el de Windows 3.1 y por lo tanto las entradas que hagamos o
consultemos deben ser compatibles con este sistema operativo.

No vamos a describir aquí la interesante información que podemos encontrar en las


distintas entradas del Registro, pues nos centraremos en las funciones de acceso al
Registro. Para obtener información sobre la información a la que podemos acceder lo
mejor es consultar los “Resource Kit” de Windows 95 y Windows NT.

Tipos de información

Todos los datos que pueden almacenarse en las distintas entradas de este Registro de
Windows 3.1 son de tipo carácter y, como hemos dicho, sólo puede haber uno por clave.

En Windows 95 y Windows NT la información almacenada en el Registro pueden ser de los


siguientes tipos :

*** Tipo no definido


#define REG_NONE 0

*** Cadena de caracteres


#define REG_SZ 1

*** Binario
#define REG_BINARY 3

*** Número de 32 bits


#define REG_DWORD 4

*** Número de 32 bits


*** con formato Little-Endian
#define REG_DWORD_LITTLE_ENDIAN 4

*** Número de 32 bits


*** con formato Big-Endian
#define REG_DWORD_BIG_ENDIAN 5

**** Enlace simbólico de Unicode


#define REG_LINK 6

*** Matriz de cadenas


#define REG_MULTI_SZ 7

*** Lista de recursos de dispositivo


#define REG_RESOURCE_LIST 8
Los tipos más utilizados son los de cadenas de caracteres y de número, pero debemos
estar preparados para encontrarnos datos de cualquier tipo si consultamos información de
entradas del Registro no creadas por nosotros mismos.

Donde guardar nuestra configuración

Debemos pensar en que tipo de información queremos guardar y en que plataforma nos
encontramos. La información relacionada con la instalación de nuestro programa, por
ejemplo, ubicación de directorios, si estamos en Windows 95 y Windows NT, deben
almacenarse dentro de HKEY_LOCAL_MACHINE\Software.

Si la configuración que queremos guardar está relacionada con un determinado usuario,


por ejemplo, la posición donde dejó una ventana o su última configuración del menú, y
seguimos en Windows 95 o Windows NT, deberemos trabajar
conHKEY_CURRENT_USER\Software para el usuario actual y HKEY_USERS\.Default\

Software para definir los valores por defecto de todos los usuarios que se puedan crear.

Por convención, dentro de la clave \Software cada empresa inscribe una clave con el


nombre de su empresa, de la que cuelgan las claves de cada programa registrado y a
partir de aquí la claves con la versión del mismo.

Si nuestra empresa se llama “XXXX”, nuestra aplicación “ZZZZ” y está en al versión 1.0
deberemos hacer unas entradas como estas:

HKEY_CURRENT_USER\Software\Xxxx\Zzzz\1.0
HKEY_LOCAL_MACHINE\Software\Xxxx\Zzzz\1.0
HKEY_USERS\Software\Xxxx\Zzzz\1.0

Si nos encontramos en Windows 3.1, bien desde FoxPro 2.x o en Visual FoxPro 3.0 con
Win32s, deberemos usar siempre HKEY_CLASSES_ROOT. Normalmente, después de esta
clave crearemos una entrada con el nombre del producto y una subclave con su version.

También es posible crear una estructura con una las


entradas Software\Empresa\Producto

\Version, pero es poco habitual usar una entrada tan larga en el Registro de Windows
3.1.

Como obtener información

Las funciones del API de Windows se utilizan siempre en una secuencia similar : 1.- se
realiza la apertura de la clave, 2.- se opera sobre ella o sus valores y 3.- se cierra.

Para abrir una la clave podemos utilizar la siguiente función :

DECLARE ;
Integer RegOpenKeyEx ;
IN WIN32API ;
Integer nKey, ;
String cSubKey, ;
Integer nReserved, ;
Integer nSamDesired, ;
Integer @nResult

Para usarla debemos pasar primero el número que identifica una clave superior,
normalmente una de la claves básicas que podemos identificar con las definiciones
siguientes :

#define HKEY_CLASSES_ROOT -2147483648


#define HKEY_CURRENT_USER -2147483647
#define HKEY_LOCAL_MACHINE -2147483646
#define HKEY_USERS -2147483645
#define HKEY_CURRENT_CONFIG -2147483653
#define HKEY_DYN_DATA -2147483654

Después pasamos una cadena con el nombre de la clave que queremos abrir, un 0, una
máscara de seguridad y por último una variable por referencia donde se almacenará el
número de la clave abierta.

La máscara de seguridad se parece mucho a los permisos que tenemos cuando abrimos
un fichero a bajo nivel. Los tipos de permisos de acceso los podemos ver en las siguiente
definiciones :

#define KEY_QUERY_VALUE 1
#define KEY_SET_VALUE 2
#define KEY_CREATE_SUB_KEY 4
#define KEY_ENUMERATE_SUB_KEYS 8
#define KEY_NOTIFY 16
#define KEY_CREATE_LINK 32
#define KEY_READ 1+8+16
#define KEY_WRITE 2+4
#define KEY_EXECUTE KEY_READ
#define KEY_ALL_ACCESS 1+2+4+8+16+32

Una vez abierta la clave podemos obtener la información que nos interesa utilizando la
siguiente función :

DECLARE ;
Integer RegQueryValueEx ;
IN WIN32API ;
Integer nKey, ;
String cValueName, ;
Integer nReserved, ;
Integer @nType, ;
String @cData, ;
Integer @nSizeData

Debemos pasar el número que obtenemos con RegOpenKey(), el nombre de la subclave


que queremos leer, un 0, una variable por referencia donde se nos devolverá el tipo del
valor y, también por referencia un buffer, y el tamaño de este buffer.

Una vez que terminemos de operar con la clave debemos cerrarla por medio de la
siguiente función:

DECLARE ;
Integer RegCloseKey ;
IN WIN32API ;
INTEGER nKey

Todas las funciones devuelve un 0 si todo a funcionado correctamente u otro valor si


existe algún problema.

Ahora ya podemos hacer un pequeño programa de ejemplo. En este programa


obtendremos una de la información que graba Visual FoxPro sobre su configuración para
el usuario activo.

#include "registro.h"
DO [Link]

nKey = 0
=RegOpenKeyEx( HKEY_CURRENT_USER, ;
"Software\Microsoft\VisualFoxPro\3.0\Options",;
0, ;
KEY_READ, ;
@nKey )

nType = 0
nSize = 255
cValor = REPLICATE( CHR(0), nSize )

=RegQueryValueEx( nKey, "_BROWSER", 0, @nType, @cValor, @nSize )

=RegCloseKey( nKey )

? SUBSTR( cValor, 1, nSize-1 )

Como crear y escribir

Para crear una entrada en el Registro se utiliza esta función :

DECLARE ;
Integer RegCreateKeyEx ;
IN WIN32API ;
Integer nKey, ;
String cSubKey, ;
Integer nReserved, ;
String cClass, ;
Integer nOptions, ;
Integer nDesired, ;
String @cSecurityAttributes, ;
Integer @nResult, ;
Integer @nDisposition

Por medio de esta función crearemos y/o abriremos una entrada en el Registro. Si ya
existe la entrada, esta se abre, si no existe, esta se crea. Para saber cual es la acción
realizada podemos ver el valor devuelto en el último parámetro pasado por referencia,
que puede tener estos dos valores :

#DEFINE REG_CREATED_NEW_KEY 1
#DEFINE REG_OPENED_EXISTING_KEY 2

Para introducir un valor dentro de una clave utilizamos la función :

DECLARE ;
Integer RegSetValueEx ;
IN WIN32API ;
Integer nKey, ;
String cValueName, ;
Integer nReserved, ;
Integer nType, ;
String cData, ;
Integer nSizeData
Si el valor existe se reemplaza, pero si no existe se crea otra entrada con este valor. Si se
pasa como segundo parámetro una cadena nula, el valor se incluye a nivel de clave.

Veamos un pequeño ejemplo que nos mostrará la forma de usar estas dos funciones:

#include "registro.h"
DO [Link]

nKey = 0
nResult = 0

=RegCreateKeyEx( HKEY_LOCAL_MACHINE, ;
"Software\MiEmpresa\MiProducto\2.0", ;
0, ;
0, ;
REG_OPTION_NON_VOLATILE, ;
KEY_ALL_ACCESS, ;
0,;
@nKey, ;
@nResult )

IF nResult == REG_OPENED_EXISTING_KEY
WAIT WIND "Entrada abierta..."
ELSE
WAIT WIND "Entrada creada..."
ENDIF

= RegSetValueEx( nKey, "Config", ;


0, REG_SZ, "creada", 5 )

= RegCloseKey( nKey )

Ejemplo : Guardar la posición de una ventana

Por medio de estas pocas funciones podemos hacer muchas cosas. Para ejemplificarlo
vamos a crear una ventana que por medio de los eventos Load y UnLoad obtiene y guarda
la posición de la ventana y de esa forma el usuario siempre la vea donde la dejó.

Este ejemplo es muy parecido al que dimos en el mes de marzo, pero en ese caso
trabajábamos sobre ficheros .INI y ahora sobre el Registro de Configuraciones.

#include "registro.h"

#define EMPRESA "MiEmpresa"


#define PRODUCTO "Ejemplos"
#define VERSION "1.0"

*** Cargar la Form y mostrarla


PUBLIC oEjemplo3
oEjemplo3 = CREATEOBJECT( "Ejemplo3" )
[Link]()
RETURN

*** Form del ejemplo en forma de clase


DEFINE CLASS Ejemplo3 AS FORM

BackColor = RGB(192,192,192)
Caption = "Ejemplo sobre Registry"
Name = "Ejemplo3"
*** Botón para cerrar ***
ADD OBJECT cmdAceptar ;
AS COMMANDBUTTON WITH ;
Top = 6, ;
Left = 6, ;
Height = 29, ;
Width = 94, ;
Caption = "Aceptar"

*** Evento al crear la form


PROCEDURE Load
*** Cargar las declaraciones
DO Registro

*** Obtener los valores con


*** los que se cerró la última vez
nKey = 0
WITH This
IF RegOpenKeyEx( HKEY_CURRENT_USER, ;
"Software\"+;
EMPRESA+"\"+;
PRODUCTO+"\"+;
VERSION+"\"+;
.name, ;
0, ;
KEY_ALL_ACCESS, ;
@nKey ) == NO_ERROR

nType = 0
cValor = REPLICATE( CHR(0), 4 )
=RegQueryValueEx( nKey, "Top", 0, ;
@nType, @cValor, 4 )
.Top = CTOWORD(cValor)

nValor = 0
=RegQueryValueEx( nKey, "Left", 0, ;
@nType, @cValor, 4 )
.Left = CTOWORD(cValor)

nValor = 0
=RegQueryValueEx( nKey, "Width",0, ;
@nType, @cValor, 4 )
.Width = CTOWORD(cValor)

nValor = 0
=RegQueryValueEx( nKey, "Height",0,;
@nType, @cValor, 4 )
.Height = CTOWORD(cValor)
ENDIF
ENDWITH
ENDPROC

*** Evento al destruir la form


PROCEDURE Unload
*** Cargar las declaraciones
*** pueden haberse descargado
DO registro

*** Si está minimizado o maximizado


*** restaurar al tamaño normal
[Link] = 0

*** Abrir o crear la clave si no existe


nKey = 0
nResult = 0

WITH This
IF RegCreateKeyEx(HKEY_CURRENT_USER,;
"Software\"+;
EMPRESA+"\"+;
PRODUCTO+"\"+;
VERSION+"\"+;
.name, ;
0, ;
0, ;
REG_OPTION_NON_VOLATILE, ;
KEY_ALL_ACCESS, ;
0,;
@nKey, ;
@nResult ) == NO_ERROR

*** Guardar los valores de


*** posición y tamaño
=RegSetValueEx( nKey, "Top", 0, ;
REG_DWORD, WORDTOC(.Top), 4 )
=RegSetValueEx( nKey, "Left", 0, ;
REG_DWORD, WORDTOC(.Left), 4 )
=RegSetValueEx( nKey, "Width", 0,;
REG_DWORD, WORDTOC(.Width), 4 )
=RegSetValueEx( nKey, "Height",0,;
REG_DWORD, WORDTOC(.Height), 4)
ENDIF
ENDWITH
ENDPROC

*** Método para cerrar la form


PROCEDURE [Link]
[Link]()
ENDPROC

ENDDEFINE

*** Combierte un número en un buffer


PROCEDURE WORDTOC
LPARAMETER nNumber
RETURN CHR(BITAND(255,nNumber))+;
CHR(BITAND(65280,nNumber)%255)+;
CHR(BITAND(16711680,nNumber)%255)+;
CHR( BITAND(4278190080,nNumber)%255)

*** Combierte un buffer en un número


PROCEDURE CTOWORD
LPARAMETER cBuffer
RETURN ASC(SUBSTR(cBuffer,1,1))+;
ASC(SUBSTR(cBuffer,2,1))*256+;
ASC(SUBSTR(cBuffer,3,1))*65536+;
ASC(SUBSTR(cBuffer,4,1))*16777216

Una de las características a resaltar de este ejemplo, es la creación de dos


funciones,WORDTOC y CTOWORD para poder pasar un número en un buffer del tipo cadena de
caracteres. Visual FoxPro no puede definir un tipo void o any y los buffers deben ser de
tipo String.

Diferencias entre los Sistemas Operativos


Los programas anteriores sólo son válidos en Windows 95 y Windows NT, si quiere
ejecutarlos en Windows 3.1 desde FoxPro 2.x o Visual FoxPro con Win32s se encontrará
que no funcionan, por varios motivos.

Por una parte hemos trabajado con las configuraciones del Registro propias de Windows
NT y Windows 95, recuerde lo que decíamos en los apartados sobre las “claves
principales” y sobre “donde guardar nuestra configuración” acerca las diferencias de
claves entre las distintas versiones de Windows. Si trabajamos sobre Windows 3.1
debemos usar siempre HKEY_CLASSES_ROOT.

Por otra parte, no todas las funciones están disponibles en todas las plataformas. Si
observa la figura 2 puede ver que funciones están disponibles en que versiones de
Windows.

Función Win16 Win32s Win95 WinNT


RegCloseKey X X X X
RegConnectRegistry - - X X
RegCreateKey X Usar RegCreateKeyEx
RegCreateKeyEx - X X X
RegDeleteKey X X X X
RegDeleteValue - - X X
RegEnumKey X Usar RegEnumKeyEx
RegEnumKeyEx - X X X
RegEnumValue - X X X
RegFlushKey - - X X
RegGetKeySecurity - - - X
RegLoadKey - - X X
RegNotifyChangeKeyValue - - - X
RegOpenKey X Usar RegOpenKeyEx
RegOpenKeyEx - X X X
RegQueryInfoKey - - X X
RegQueryMultipleValues - - X -
RegQueryValue X Usar RegQueryValueEx
RegQueryValueEx - X X X
RegReplaceKey - - X X
RegRestoreKey - - - X
RegSaveKey - - X X
RegSetKeySecurity - - - X
RegSetValue X Usar RegSetValueEx
RegSetValueEx - X X X
RegUnLoadKey - X X X
Fig. 2 - Funciones del Registro soportadas por Windows

En Windows 3.1 utilizaríamos la función RegCreateKey en vez


de RegCreateKeyEx, RegOpenKeyen vez de RegOpenKeyEx, RegQueryValue en vez
de RegQueryValueEx y RegSetValue en vez deRegSetValueEx.
Otra limitación, que ya comentamos, es que en Windows 3.1 cada clave sólo puede tener
un valor, por lo que en el ejemplo anterior deberíamos hacer que todas las entradas
fueran claves.

Por último debemos recordar que los datos que puede manejar Windows 3.1 se reducen al
tipo carácter.

Para saber en que sistema operativo estamos podemos usar la función OS(1) de FoxPro y
Visual FoxPro y a partir de esta información bifurcar nuestro código.

Más posibilidades

Como puede ver en la figura 2, las posibilidades del API de Windows sobre el Registro de
configuraciones no se limitan a las aquí presentadas. Pero en la mayoría de las veces con
estas pocas funciones uno puede trabajar sin problemas.

Si quiere todas las declaraciones de las funciones para el manejo del Registro de
Configuraciones puede verlas en el disco que acompaña a la revista.

Sobre su uso puede consultar el fichero de ayuda sobre el API de Win32 de la versión
profesional en CD-ROM de Visual FoxPro

Encapsular las funciones del API

Tal y como hemos venido proponiendo, es muy conveniente cubrir las funciones del API
de Windows con funciones propias que aíslen nuestro código de llamadas directas.

Es muy sencillo construir una función para leer del Registro de Configuraciones y otra
para escribir en él. También puede hacer una función que lea de ficheros .INI o del
Registro dependiendo del sistema operativo o utilice unas funciones u otras dependiendo
también del sistema operativo en el que nos encontremos.

Cada uno podrá estudiar que tipo de encapsulación le es más conveniente para su
proyecto.

Conclusión

Antes de terminar quisiera aconsejarles prudencia. Debemos tener cuidado con las
escrituras en claves del Registro que no sean de nuestra aplicación. Aun cuando podemos
manejar información de otras aplicaciones y el propio sistema operativo modificando
entradas en el Registro, debemos ser muy cuidadosos a la hora de utilizar este método.

Las lectura de las entradas del Registro es casi siempre inofensiva, pero debemos estar
seguros de que sabemos interpretar su contenido en todos los sistemas operativos a fin
de no provocar confusión.

Como hemos dicho, no hemos descrito todas las posibilidades del Registro, sobre todo no
hemos descrito las posibilidades de obtener información sobre el Sistema Operativo,
Usuarios, etc. por este medio. Le invito a seguir estudiando por su cuenta las funciones y
entradas que se nos ofrecen en el Registro.

Si quiere iniciarse en los secretos del Registro de Windows puede leer el capítulo 33 del
Resouce Kit de Windows 95 y el capítulo 10 del Resouce Kit de Windows NT. Son
realmente aclaratorios.
Si desea avanzar en el conocimiento del API de Windows puede ver el fichero de ayuda
sobre el API de Win32 de la versión profesional en CD-ROM de Visual FoxPro o bien la
magnífica información y ejemplos contenidos en los CD-ROM del Microsoft Developer
Network (MSDN) o los manuales de Visual C++ 4.x.

Espero que estos últimos seis artículos sobre el API de Windows hayan sido de su interés.

También podría gustarte