Tutorial sobre Foxpro

77
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

Transcript of Tutorial sobre Foxpro

Tutorial sobreVisual 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 FPSQL.FLL. 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 USUARIOS.NOMBRE,"+; "USUARIOS.APELLIDOS,"+; "APLICACIONES.APLICACION,"+; "APLICACIONES.VERSION,"+; "APLICACIONES.COMENTARIO "+; "FROM USUARIOS, APLICACIONES, ; "USER_APLI "+; "WHERE ; "USUARIOS.NOMBRE_CORTO = USER_APLI.NOMBRE_CORTO"+;

"AND USER_APLI.ORDEN = APLICACIONES.ORDEN",; "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"+ ; "USUARIOS.NOMBRE = 'PEDRO'"+; "WHERE USUARIO.NOMBRE = '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á ejecutandoSQLCOLUMS Consulta los nombres de las columnas disponiblesSQLCOMMIT Confirma los cambios realizadosSQLCONNECT Realiza una conexión

SQLDISCONNECT Realiza una desconexiónSQLEXEC Ejecuta una sentencia SQL

SQLGETPROP Obtiene valores de la configuraciónSQLMORERESULT Obtiene más datos de una consulta anteriorSQLROLLBACK Deshace los cambios realizadosSQLSETPROP Cambia valores de la configuración

SQLSTRINGCONNECT Predefine la cadena de conexiónSQLTABLES 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 USUARIOS.NOMBRE, ; USUARIOS.APELLIDOS, ; APLICACIONES.APLICACION, ; APLICACIONES.VERSION, ; APLICACIONES.COMENTARIO ;

FROM USUARIOS, APLICACIONES, USER_APLI ; WHERE USUARIOS.NOMBRE_CORTO = USER_APLI.NOMBRE_CORTO ; AND USER_APLI.ORDEN = APLICACIONES.ORDEN

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 Data1USE user_listBROWSE

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 CUSTOMENDDEFINE

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 oPruebaoPrueba = 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" )oPersona1.cNombre = "María"oPersona1.cApellidos = "Pérez González"oPersona1.dFechaNacimiento = {20-10-75}

oPersona2 = CREATEOBJECT( "persona" )oPersona2.cNombre = "Pedro"oPersona1.cApellidos = "Jiménez Nieto "oPersona2.dFechaNacimiento = {04-12-69}oPersona2.cEstadoCivil = "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 oPersona1.cNombreWAIT WIND oPersona2.cNombre

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 This.cNombre = cNombre This.cApellidos = cApellidos This.dFechaNacimiento = 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" )oPersona1.Nacer( "María", ; "Pérez González", ; {20-10-75} )? oPersona1.cNombre ? oPersona1.cApellidos

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:

oPersona1.Nacer( "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í This.cNombre 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 NacerLPARAMAMETER cNombre, ; cApellidos, ; cFecha

This.cNombre = 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 This.nContador = This.nContador ; + nCantidad ENDPROC

PROCEDURE Menos LPARAMETER nCantidad This.nContador = This.nContador ; - nCantidad ENDPROC

PROCEDURE Ver RETURN This.nContador 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" )

oTemperatura.Mas ( 10 )? oTemperatura.Ver()oTemperatura.Menos ( 3 )? oTemperatura.Ver()

* Proboca el Error :* Property NCONTADOR is not found.oTemperatura.nContador = 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 = 1000ENDDEFINE

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" )

? Objeto1.Dato? Objeto2.Dato

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

DEFINE CLASS ClaseDos AS CUSTOM PROCEDURE Imprimir LIST STATUS ENDPROCENDDEFINE

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 INIToPrueba = CREATEOBJECT( "Eventos" )...* Lanzamiento automático del* método DESTROYRELEASE 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, InteractiveChange, KeyPress, LostFocus, Message, MouseDown, MouseMove,MouseUp, RightClick, UIEnable, Valid, When, 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 PersonaENDDEFINE

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

oPareja1 = CREATEOBJECT( "Matrimonio" )oPareja1.Conyuge1.Nombre = "María"oPareja2.Conyuge2.Nombre = "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 CUSTOMENDDEFINE

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\prueba.FXP" 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 originalDEFINE CLASS persona AS CUSTOM cNombre = "" cApellidos = "" dFechaNacimiento = {} cEstadoCivil = "Soltero"

PROCEDURE Nacer LPARAMETER cNombre, ; cApellidos, ; dFecha This.cNombre = cNombre This.cApellidos = cApellidos This.dFechaNacimiento = dFecha

ENDPROCENDDEFINE

* Clase nuevaDEFINE 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 PersonaoContr.cNombre = "Juan"oContr.cApellidos = "López Garrido"

* Propiedad definida* en la clase ContribuyenteoContr.cRegimenSS = "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 : " ; + This.cNombre ; + " " ; + This.cApellidos ? "Fecha de nacimiento : " ; + DTOC( This.cFechaNacimiento ) ? "Estado civil : " ; + This.cEstadoCivil ENDPROCENDDEFINE

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

PROCEDURE Imprimir ? "Nombre : " ; + This.cNombre ; + " " ; + This.cApellidos ? "Fecha de nacimiento : " ; + DTOC( This.dFechaNacimiento ) ? "Estado civil : " ; + This.cEstadoCivil ? "NIF : " ; + This.cNIF ? "Base Imponible : " ; + STR( This.nBaseImponible ) ? "Regimen de la S.S. : " ; + This.cRegimenSS ENDPROCENDDEFINE

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" )oPrue1.cNombre = "Juan"oPrue1.cApellidos = "López Garrido"

oPrue2 = CREATEOBJECT( "Contribuyente" )oPrue2.cNombre = "Pedro"oPrue2.cApellidos = "Goméz Iriarte"

oPrue1.Imprimir()&& De personaoPrue2.Imprimir()&& 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 : " ; + This.cEstadoCivil ? "NIF : " ; + This.cNIF ? "Base Imponible : " ; + STR( This.nBaseImponible ) ? "Regimen de la S.S. : " ; + This.cRegimenSS ENDPROCENDDEFINE

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 FOXTOOLS.FLL, 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 MSOUTL32.OCX yPICCLP32.OCX 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 MSCOMM32.OCX y MSMAPI32.OCX 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 FOXTOOLS.FLL 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 FOXTOOLS.FLL. 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 bitsINTEGER - Entero de 32 bitsSINGLE - Coma flotante de 32 bitsDOUBLE - Coma flotante de 64 bitsSTRING - 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 USER.EXE, KRNL386.EXE y GDI.EXE. 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 KERNEL32.DLL, GDI32.DLL, USER32.DLL,ADVAPI32.DLL y MPR.DLL, 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 ficheroW32SCOMB.DLL, 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:\command.com")IF nAttrib == -1 ERRORENDIFIF BITAND( nAttrib, _A_RDONLY ) != 0 WAIT WIND "El de sólo lectura..."ENDIFIF 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:\command.com", ; _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 ver.txt NOCONSOLE

*** Cambiar los atributos del fichero ***IF SetFileAttributes( "ver.txt", ; _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( "ver.txt" )*** 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, ; "ver.txt", ; "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 SETATTRILPARAMETER cFicheros, cAtributos

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

*** Incializar variables como locales ***LOCAL nCambiados, nAtrib, nFichsLOCAL 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 ENDIFNEXTRETURN 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 FOXUSER.DBF 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..

WIN.INI y SYSTEM.INI

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

WIN.INI 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 WIN.INI 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 WIN.INI 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 SYSTEM.INI 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 WIN.INI como SYSTEM.INI 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 SYSTEM.INI y WIN.INI 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 SYSINI.WRI y WININI.WRI 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 FOXPRO.INI 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 WIN.INI. 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 profiles.prg *** Creación de la variable de retorno *** LOCATE cRetorno, nRet cRetorno = REPLICATE( CHR(0), 255 ) *** Consultar el ratón instalado *** nRet = GetPrivateProfileString( ; "boot.description", ; "mouse.drv", ; "", ; @cRetorno, ; 255, ; "system.ini" ) 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", "win.ini" ) 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, ; "Ejemplos.ini" )

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", ; "", ; "ejemplo.ini" )

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

El API de Windows ofrece una serie de funciones para acceder directamente al fichero WIN.INI, 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 WIN.INI. 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 SYSTEM.INI, necesitando acceder a este fichero como si fuera un "PrivateProfile". También es posible acceder al ficheroWIN.INI 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" ) oEjemplo2.Show() 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, ; "ejemplos.ini" ) .Left = GetPrivateProfileInt( ; "Ejemplo 2", ; "Left", ; .Left, ; "ejemplos.ini" ) .Width = GetPrivateProfileInt( ; "Ejemplo 2", ; "Width", ; .Width, ; "ejemplos.ini" ) .Height = GetPrivateProfileInt( ; "Ejemplo 2", ; "Height", ; .Height, ; "ejemplos.ini" ) 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 *** This.WindowState = 0

*** Guardar los valores de *** *** posición y tamaño *** WITH This = WritePrivateProfileString( ; "Ejemplo 2", ; "Top", ; LTRIM( STR( .Top ) ), ; "ejemplos.ini" ) = WritePrivateProfileString( ; "Ejemplo 2", ; "Left", ; LTRIM( STR( .Left ) ), ; "ejemplos.ini" ) = WritePrivateProfileString( ; "Ejemplo 2", ;

"Width", ; LTRIM( STR( .Width ), ); "ejemplos.ini" ) = WritePrivateProfileString( ; "Ejemplo 2", ; "Height", ; RTRIM( STR( .Height ) ), ; "ejemplos.ini" ) ENDWITH ENDPROC *** Método para cerrar la form *** PROCEDURE cmdAceptar.Click ThisForm.Release() 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 EJEMPLOS.INI 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 ficheroEJEMPLOS.INI 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, "ejemplos.ini" )

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 FOXTOOLS.FLL 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 FOXTOOLS.FLL ADDITIVE nHwnd = MainHwnd()

Si no queremos utilizar FOXTOOLS.FLL 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 aTask.PRG

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 Win32sHKEY_CLASSES_ROOT - ficheros registrados y OLE - ficheros registrados y OLE

Win95HKEY_CLASSES_ROOT - por compatibilidad con Win16 - por compatibilidad con Win16HKEY_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

WinNTHKEY_CLASSES_ROOT - por compatibilidad con Win16 - por compatibilidad con Win16HKEY_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.0HKEY_LOCAL_MACHINE\Software\Xxxx\Zzzz\1.0HKEY_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 registro.prg

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

nType = 0nSize = 255cValor = 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 registro.prg

nKey = 0nResult = 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 mostrarlaPUBLIC oEjemplo3oEjemplo3 = CREATEOBJECT( "Ejemplo3" )oEjemplo3.Show()RETURN

*** Form del ejemplo en forma de claseDEFINE 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 This.WindowState = 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 cmdAceptar.Click ThisForm.Release() ENDPROC

ENDDEFINE

*** Combierte un número en un bufferPROCEDURE WORDTOCLPARAMETER nNumberRETURN 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úmeroPROCEDURE CTOWORDLPARAMETER cBufferRETURN 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 WinNTRegCloseKey X X X X

RegConnectRegistry - - X X

RegCreateKey X Usar RegCreateKeyExRegCreateKeyEx - X X X

RegDeleteKey X X X X

RegDeleteValue - - X X

RegEnumKey X Usar RegEnumKeyExRegEnumKeyEx - X X X

RegEnumValue - X X X

RegFlushKey - - X X

RegGetKeySecurity - - - X

RegLoadKey - - X X

RegNotifyChangeKeyValue - - - X

RegOpenKey X Usar RegOpenKeyExRegOpenKeyEx - X X X

RegQueryInfoKey - - X X

RegQueryMultipleValues - - X -RegQueryValue X Usar RegQueryValueExRegQueryValueEx - X X X

RegReplaceKey - - X X

RegRestoreKey - - - X

RegSaveKey - - X X

RegSetKeySecurity - - - X

RegSetValue X Usar RegSetValueExRegSetValueEx - 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.