23 noviembre 2007

Creando aplicaciones multicapa (IV)

Una vez que hemos creado nuestro servidor DCOM vamos a implementar nuestra aplicación cliente que se va a encargar de llamarlo.

CREANDO LA APLICACION CLIENTE

Para conectar un cliente multicapa al servidor DCOM vamos a utilizar un componente de la clase TDOMConnection que buscará en tiempo de diseño el servidor.

Como se verá a continuación no es necesario añadir un componente de la clase TDataSetProvider ni ningún otro relacionado con motores de bases de datos específicos. Eso ya se lo hemos dejado al servidor de aplicaciones. Tampoco es necesario que el servidor de aplicaciones que creamos en el artículo anterior se esté ejecutando mientras diseñamos el cliente.

Para realizar las pruebas vamos a crear un nuevo proyecto que contenga un sólo formulario y los siguientes componentes:


- Un componente TDBGrid llamado ListadoClientes destinado a listar todos los campos de la tabla CLIENTES.

- Un componente TClientDataSet llamado TClientes.

- Un componente TDataSource que lo vamos a llamar DSClientes.

- Un componente TDCOMConnection que lo llamaremos Conexion.

Ahora vamos a vincular unos componentes a otros:

- Vinculamos el componente TDataSource llamado DSClientes con la rejilla ListadoClientes a través de su propiedad DataSource.

- Vinculamos el componente TClientDataSet llamado TClientes al componente TDataSource llamado DSCliente en su propiedad DataSet.

- Asociamos el componente TDOMConnection llamado Conexion al componente TClientDataSet llamado TClientes utilizando su propiedad RemoteServer.

Aquí nos detenemos para analizar un problema. El componente TClientDataSet no mostrará nada que no venga de un componente TDataSetProvider. Como dicho componente se encuentra en el servidor de aplicaciones, lo primero que hay que hacer es conectar con el servidor de aplicaciones para poder vincular su DataSetProvider:

- Seleccionamos el componente TDOMConnection y en su propiedad ServerName elegimos ServidorDatos.ServidorDCOM. Al hacer esto debe rellenarnos automáticamente el campo ServerGUID, el cual es un identificador de la interfaz del módulo de datos remoto que creamos en el servidor de aplicaciones.

- Activamos la propiedad Connected en el componente TDOMConnection y veremos que se ejecuta automáticamente el servidor de aplicaciones mostrándonos su formulario principal.

- Dejando el servidor de aplicaciones en ejecución seleccionamos el componente TClientDataSet y en su propiedad ProviderName seleccionamos DSPClientes.

Con sólo realizar estos pasos, si activamos la propiedad Active del componente TClientDataSet nos mostrará en tiempo de diseño los datos de la tabla clientes:


Al compilar y ejecutar nuestro programa cliente ya tenemos un programa que maneja los datos del servidor sin preocuparse del motor de bases de datos o de otros usuarios conectados.

El componente TDOMConnection tiene la propiedad llamada ComputerName la cual contiene el nombre del equipo donde se va a alojar el servidor de aplicaciones. Si no seleccionamos ninguno se asume que el servidor de aplicaciones y la aplicación cliente residen en la misma máquina.

Si intentamos cerrar el servidor de aplicaciones mientras está conectado el cliente saldrá este mensaje:

There are still active COM objects in this application. One o more clients may have references to these objects, so manually closing this application may cause those client application(s) to fail.

Are you sure want to close this application?

Lo que traducido al castellano sería:

Todavía hay objetos COM activos en esta aplicación. Uno o más clientes podrían estar utilizando estos objetos, así que si cierra esta aplicación podría causar un error de conexión en los clientes.

¿Esta seguro de cerrar esta aplicación?

Esto significa que nunca debemos cerrar el servidor mientras quede algun cliente conectado a nosotros. Además, si el último cliente que estaba conectado al servidor de aplicaciones desconecta entonces el servidor de aplicaciones se cerrará automáticamente al ver que no hay ninguna conexión abierta.

En la siguiente parte de este artículo veremos como crear un servidor de aplicaciones y una aplicación cliente utilizando otros protocolos distintos a DCOM.

Pruebas realizadas en Delphi 7.

20 noviembre 2007

Creando aplicaciones multicapa (III)

Vamos seguir con la base teórica de como funcionan las aplicaciones multicapa antes de comenzar a crear un ejemplo práctico.

ELIGIENDO EL PROTOCOLO DE CONEXION

Cada protocolo de comunicación que podemos usar para conectar de las aplicaciones cliente a las aplicaciones servidor tienen sus propias ventajas e inconvenientes. Antes de elegir un protocolo tenemos que considerar que servicio va a prestar la aplicación, cuantos clientes va a ser atendidos, que reglas de negocio se van a establecer, etc.

USANDO CONEXIONES DCOM

DCOM es el protocolo que proporciona el acceso más directo y rápido de comunicación, no requiriendo aplicaciones externas aparte del servidor de aplicaciones.

Este protocolo proporciona servicios de seguridad cuando se utiliza un módulo de datos transaccional. Cuando usamos DCOM podemos identificar quien llama al servidor de aplicaciones (ya sea con COM+ o MTS). Por tanto, es posible determinar que reglas de acceso vamos a asignar a las aplicaciones cliente.

Son los clientes los que instancian directamente el módulo de datos remoto para realizar la comunicación, no requiriendo ninguna aplicación externa que haga de intermediario.

USANDO CONEXIONES SOCKET

La comunicación mediante sockets nos permiten crear clientes muy ligeros. Se suele utilizar este protocolo si no tenemos la seguridad que los sistemas clientes soporten DCOM. Los sockets proporcionan un sistema comunicación simple para establecer conexiones entre los clientes y el servidor.

En lugar de instanciar el módulo de datos remoto desde el cliente (como sucede con DCOM), los sockets usan una aplicación separada en el servidor (ScktSrvr.exe), la cual acepta las peticiones de los clientes e instancia el módulo de datos remoto usando COM. El componente de conexión en el cliente y ScktSrvr.exe en el servidor son los responsables de organizar las llamadas mediante la interfaz IAppServer.

El programa ScktSrvr.exe también puede ejecutarse como un servicio NT. Para ello habría que registrarlo como servicio usando línea de comandos. También se puede eliminar de la lista de servicios del mismo modo.

Uno de los inconvenientes que tienen los sockets es que no hay protección en el servidor contra fallos en la conexión con los clientes. Mientras este protocolo de comunicación consume menos ancho de banda que el protocolo DCOM (el cual envía periódicamente mensajes de comprobación y mantenimiento), no puede detectar si un cliente se está ausente o no.

USANDO CONEXIONES WEB

Una las ventajas de utilizar el protocolo de comunicación HTTP es que los clientes pueden comunicarse con el servidor de aplicaciones aunque este protegido por un cortafuegos. Al igual que las conexiones socket, los mensajes HTTP proporcionan un sistema sencillo y de poco consumo de ancho de banda para comunicar los clientes y el servidor.

En lugar de instanciar el módulo de datos remoto desde el cliente (como sucede con DCOM), las conexiones basadas en HTTP pueden usar un servidor de aplicaciones web (como Apache) o el servidor puede ser la librería httpsrvr.dll, el cual acepta las peticiones de los clientes e instancia el módulo de datos remoto usando COM. El componente de conexión en la máquina cliente y httpsrvr.dll en el servidor son los responsable de organizar las llamadas a la interfaz IAppServer.

Las conexiones web aportan también la ventaja de la seguridad SSL suministrada por la librería wininet.dll (una librería de utilidades de internet que corre en los sistemas cliente). Una vez que hemos configurado el servidor web en la máquina que hace de servidor de aplicaciones, podemos establecer los nombre y claves de acceso para los usuario aprovechando las propiedades de conexión que aporta el componente web.

Las conexiones web tienen la ventaja de tener en una memoria caché los objetos de módulos de datos que se van instanciando, conocido como pooling. Esto permite que nuestro servidor cree un número de instancias de módulos remotos limitado para dar servicio a los clientes, sin tener que estar instanciando y destruyendo objetos sin parar cada vez que se conecta o desconecta un cliente.

A diferencia de otras conexiones con componentes, no podemos crear funciones que permitan comunicar directamente el servidor de aplicaciones con las aplicaciones clientes, lo que se llaman funciones callback.

USANDO CONEXIONES SOAP

El protocolo SOAP es el estándar utilizado para crear aplicaciones de servicios web. Este protocolo envia y recibe mensajes codificados en documentos XML, y los envía utilizando el protocolo HTTP.

Las conexiones SOAP tienen la ventaja de que son multiplataforma, ya que son soportadas por prácticamente todos los sistemas operativos actuales. Al utilizar como transporte el protocolo HTTP tienen las mismas ventajas: permite servir a los clientes a través de un cortafuegos y pueden utilizarse diversos servidores HTTP.

CONSTRUYENDO UNA APLICACION MULTICAPA

Resumiendo a grandes rasgos, los pasos generales para crear una aplicación de bases de datos multicapa son los siguientes:

1. Crear el servidor de aplicaciones.

2. Registrar el servidor de aplicaciones como un servicio o instalarlo y ejecutarlo como una aplicación (recomendado).

3. Crear la aplicación cliente.

El orden de creación es importante. Debemos crear y ejecutar el servidor de aplicaciones antes de crear el cliente. Esto se debe a que cuando vayamos a construir la aplicación cliente, en tiempo de diseño tenemos que conectar con el servidor de aplicaciones para realizar pruebas de conexión. Aunque se también se podría un crear un cliente sin especificar el servidor de aplicaciones en tiempo de diseño, pero no lo recomiendo porque es más incómodo.

Si no estamos creando la aplicación cliente en el mismo equipo que el servidor, y estamos usando una conexión DCOM, podemos registrar el servidor de aplicaciones en la máquina cliente. Esto hace que los componentes de conexión se enteren de donde está el servidor de aplicaciones en tiempo de diseño, así podemos elegir el componente Provider desde el inspector de objetos.

CREANDO UN SERVIDOR DE APLICACIONES DCOM

La mayor diferencia entre crear un servidor de aplicaciones y la típica aplicación de bases de datos cliente/servidor reside en el módulo de datos remoto. Vamos a crear una aplicación EXE que va a hacer de servidor de aplicaciones para una base de datos Firebird 2.0 que tiene una tabla llamada CLIENTES.

Para crear un servidor de aplicaciones, hay que seguir los pasos:

1. Crear un nuevo proyecto: File -> New -> Application.

2. Guardar el proyecto como ServidorDatos.exe

3. Añadimos un módulo de datos remoto al proyecto: File -> New -> Other.

4. Nos vamos a la pestaña Multitier, seleccionamos Remote Data Module y pulsamos Ok.

5. Rellenamos los campos:

CoClass Name: ServidorDCOM

Instancing: Multiple Instance

Threading Model: Apartment

6. Pulsamos Ok. Nos aparecerá una nueva ventana que representa el nuevo módulo de datos remoto creado.

7. Guardamos el módulo de datos remoto en disco con el nombre UServidorDCOM.pas

8. Insertamos en el módulo de datos remoto el componente TIBDatabase con el nombre BaseDatos.

9. Hacemos doble clic sobre el componente BaseDatos y configuramos lo siguiente:

Connection: Remote
Protocol: TCP
Database: 127.0.0.1:D:\Desarrollo\DelphiAlLimite\Multicapa\DCOM\BaseDatos.fdb
LoginPrompt: Desactivado

10. Pulsamos Ok.

11. Añadimos al módulo de datos remoto un componente TIBTransaction llamado Transaccion.

12. Vinculamos el objeto Transaccion al componente TIBDatabase a través de su propiedad DefaultTransaction.

13. Asignamos BaseDatos en la propiedad DefaultDatabase del componente Transaccion.

14. Insertamos un componente TIBQuery llamado TClientes. En su propiedad Database ponemos BaseDatos. Y su propiedad SQL escribimos:

SELECT * FROM CLIENTES

15. Hacemos doble clic en el componente TClientes y pulsamos la combinación de teclas CTRL + A para añadir todos los campos.

16. Insertamos un componente TDataSetProvider llamado DSPClientes. En su propiedad DataSet seleccionamos TClientes.

Con esto ya tenemos nuestro propio servidor de aplicaciones conectado a la base de datos Firebird. Como puede apreciarse es casi lo mismo que crear una aplicación cliente/servidor.

La diferencia está en que no es necesario instanciar en memoria el módulo de datos remoto ya que la aplicación cliente se encargará de hacerlo.

En la siguiente parte de este artículo crearemos la aplicación cliente encargada de conectarse con este servidor de aplicaciones que hemos creado.

Pruebas realizadas en Delphi 7.

16 noviembre 2007

Creando aplicaciones multicapa (II)

Después de tener una visión global de cómo funcionan las aplicaciones multicapa vamos a ver cada una de sus partes.

LA ESTRUCTURA DE LA APLICACION CLIENTE


El usuario que va a utilizar la aplicación cliente no notará la diferencia entre una aplicación cliete/servidor y una aplicación multicapa, ya que el acceso a la información se realiza a través de los componentes estándar TClientDataSet.

El componente ClientDataSet se comunica con el proveedor de datos a través de la interfaz IAppServer. Se pueden seleccionar diferentes protocolos de comunicación según el componente de conexión que se utilice, donde tenemos los siguientes componentes:

Componente Protocolo
--------------------------------------------------
TDCOMConnection DCOM
TSocketConnection Windows sockets (TCP/IP)
TWebConnection HTTP
TSOAPConnection SOAP (HTTP y XML)

LA ESTRUCTURA DE LA APLICACION SERVIDOR

Una vez instalado el servidor de aplicaciones, cuando se ejecuta por primera vez no establece una conexión con los clientes. Más bien son los clientes los que inician y mantienen la conexión con el servidor de aplicaciones. Todo esto sucede automáticamente sin que tengamos que manejar solicitudes o administrar interfaces.

La base de un servidor de aplicaciones es el módulo de datos remoto, el cual esta especializado en soportar la interfaz IAppServer (para los servidores de aplicaciones que tienen servicios web, el módulo de datos remoto soporta la interfaz IAppServerSOAP, y usa como preferencia IAppServer).

Las aplicaciones cliente usan las interfaces de módulos de bases de datos remotos para comunicarse con los componentes Provider del servidor de aplicaciones. Cuando el módulo de datos remoto usa IAppServerSOAP, el componente de conexión se adapta a este para la interfaz IAppServer que el que usa el componente ClientDataSet.

Hay tres tipos de módulos de datos remotos:

TRemoteDataModule: Se usa este tipo si los clientes van a utilizan protocolos DCOM, HTTP, sockets o una conexión OLE hacia el servidor de aplicaciones, a menos que queramos instalar el servidor de aplicaciones con COM+.

TMTSDataModule: Se utiliza este tipo si vamos a crear un servidor de aplicaciones como librería DLL que esté instalada con COM+ (or MTS). Se puede utilizar este módulo de datos remoto MTS con protocolos DCOM, HTTP, sockets u OLE.

TSoapDataModule: Este es un módulo de datos que implementa una interfaz IAppServerSOAP en una aplicación de servicios web. Utilizaremos este tipo de módulo de datos para proveer datos a los clientes con acceso a servicios web.

Si el servidor de aplicaciones es distribuido mediante COM+ (o MTS), el módulo de datos incluye eventos para cuando el servidor de aplicaciones sea activado o desactivado. Esto permite conectar automáticamente con los motores de bases de datos cuando se active y desconectarlas cuando se desactive.

EL CONTENIDO DEL MODULO DE BASES DE DATOS REMOTO

Como cualquier otro módulo de datos, se puede incluir cualquier componente no visual en el módulo de datos remoto. Pero hay que tener en cuenta ciertos aspectos:

- Para cada dataset que tiene el módulo de datos remoto en los clientes, debemos incluir un DataSetProvider. Un DataSetProvider parte la información en paquetes que son enviados a los ClientDataSet y aplican las actualizaciones de las bases de datos contra el servidor de aplicaciones.

- Para cada documento XML que el módulo de datos remoto envía al cliente, debemos incluir un proveedor XML. Un proveedor XML actua como un DataSetProvider, exceptuando que la actualización de los datos se efectua a través de documentos XML en vez de ir al motor de bases de datos.

No hay que confundir los componentes de conexión a bases de datos con los componentes de conexión a servidores de aplicaciones, ya que estos últimos utilizan los componentes de las pestañas DataSnap y WebServices.

MODULOS DE DATOS REMOTOS TRANSACCIONALES

Si vamos a crear un servidor de aplicaciones que va a utilizar los protocolos COM+ o MTS entonces podemos sacar ventaja de esto creando módulos de datos transaccionales (Transactional Data Module) en lugar de un módulo de datos remoto ordinario (Remote Data Module). Esto sólo se puede hacer en sistemas opetarivos Windows 2000 en adelante.

Al utilizar un módulo de datos transaccional tenemos las siguientes ventajas:

- Seguridad: COM+ (o MTS) proporciona unas reglas de seguridad en nuestro servidor de aplicaciones. A los clientes se les asignan reglas, las cuales determinan como pueden acceder a la interfaz MTS del módulo de datos.

- Los módulos de datos transaccionales permiten mantener la conexión de los clientes abierta cuando se conectan y desconectan muchas veces hacia el servidor de aplicaciones. De este modo, mediante unas pocas conexiones podemos controlar a muchos clientes que se conectan y se desconectan continuamente (como si fuera un servidor web).

- Los módulos de datos transaccionales pueden participar en transacciones que abarquen múltiples bases de datos o incluir funciones que no están implementadas en las bases de datos.

- Pudemos crear nuestro servidor de aplicaciones como un módulo de datos remoto cuya instancia es activada y desactivada según se necesite. Cuando se usa la activación en tiempo real, nuestros módulos de datos remotos son instanciados solamente si los necesitan los clientes. Esto evita de gastar recursos que no se vayan a utilizar.

Pero no todo son ventajas. Con una simple instancia de un módulo de datos el servidor de aplicaciones se puede manejar todas las llamadas a bases de datos a través de una simple conexión de bases de datos. Pero si se abusa de esto se puede crear un cuello de botella y puede impactar en la ejecución cuando hay muchos clientes, con lo tenemos que mantener un equilibrio entre el número de clientes que van a acceder al servidor de aplicaciones y el número de módulos de datos remotos que se van instanciar.

También puede ocurrir el efecto contrario: si se utilizan múltiples instancias de un módulo de bases de datos remotas, cada instancia puede mantener una conexión a bases de datos independiente. De modo que si hay muchas instancias del módulos de datos remotos se abrirían demasiadas conexiones con la base de datos, disminuyendo el rendimiento del motor de bases de datos como si fuera la típica aplicación cliente/servidor con muchos usuarios.

AGRUPAMIENDO DE MODULOS DE DATOS REMOTOS

El agrupamiento de objetos (pooling) nos permite crear una caché de módulos de datos remotos que estarán disponibles en memoria para las llamadas de las aplicaciones cliente. De esta manera se conservarán recursos en el servidor y evitará el tener que instanciar y destruir de memoria los módulos de datos remotos cuando se utilicen o se dejen de utilizar.

Cada vez que no estamos usando el módulo de datos transaccional obtenemos todas las ventajas que proporciona el tener una cache de objetos agrupados tanto si la conexión se efectua a través de DCOM como si es mediante el componente TWebConnection. Además podemos limitar el número conexiones a bases de datos.

Cuando el servidor de aplicaciones web recibe una petición del cliente, este las pasa a su primer módulo de datos remoto disponible en la caché de módulos de datos remotos. Si no hay disponible ninguna instancia de módulo de datos remoto, el servidor crea una nueva (no sobrepasando el máximo de módulos especificado por nosotros). Esto proporciona una gran flexibilidad permitiendo manejar varios clientes a través de un simple instancia de módulo de datos remoto (el cual puede actuar como un cuello de botella) e irá creando nuevas instancias según se vaya necesitando.

Cuando una instancia de un módulo de datos remoto que está en la caché no recibe ninguna petición de clientes durante un tiempo prolongado, es automáticamente liberado. Esto evita que el servidor de aplicaciones se sature con muchas instancias abiertas.

En la siguiente parte de este artículo abarcaremos los tipos de conexión que se pueden realizar entre las aplicaciones clientes y el servidor de aplicaciones.

Pruebas realizadas en Delphi 7.

14 noviembre 2007

Creando aplicaciones multicapa (I)

En este artículo que estará separado en varias partes vamos a ver como crear aplicaciones de bases de datos multicapa cliente/servidor. Este tipo de aplicaciones esta dividido en unidades lógicas, llamadas capas, las cuales se ejecutan en distintas máquinas. Las aplicaciones multicapa distribuyen los datos y se comunican en una red de área local o bien sobre Internet. Esto proporciona muchas ventajas tales como centralizar la lógica de negocio en un sólo servidor y donde varios clientes van tirando de él. Además podemos crear aplicaciones que comuniquen varios centros de trabajo se estén separados geográficamente a través de Internet.

Una aplicación multicapa queda particionada de la siguiente manera:

- Aplicación Cliente: se encarga de mostrar la interfaz de usuario.

- Servidor de aplicaciones: reside en la red local central y es accesible por todos los clientes donde reciben datos directamente de este servidor.

- Servidor de bases de datos: en este servidor es donde está instalado el motor de bases de datos (Interbase, Firebird, Oracle, etc.), aunque el servidor de aplicaciones y el servidor de bases de datos pueden ser la misma máquina.

En este modelo a tres capas los clientes sólo pueden comunicarse con el servidor de aplicaciones y en ningún caso directamente con el motor de bases de datos, como ocurre en las aplicaciones cliente/servidor habituales.

Este tipo de aplicaciones multicapa no tiene porque estar compuesto sólo de tres capas, podría constar de varios servidores de bases de datos y servidores de aplicaciones.

VENTAJAS DE CREAR UN MODELO MULTICAPA

En este modelo de bases de datos la aplicación cliente sólo se dedica a mostrar los datos al usuario, no sabe nada sobre como los datos son actualizados y mantenidos.

El servidor de aplicaciones (capa media) coordina y procesa las peticiones y actualizaciones de múltiples clientes. El servidor maneja todos los detalles, define el conjunto de datos e interactua con el servidor de bases de datos.

Las ventajas de este modelo multicapa son las siguientes:

- Encapsulación de lógica de negocio. Diferentes clientes de la aplicacion pueden acceder al mismo servidor intermedio. Esto permite evitar la redundancia (y coste de mantenimiento) de duplicar las reglas de negocio para cada aplicación cliente separada.

- Aplicaciones clientes pequeñas. Al delegar las tareas más pesadas en la capa media las aplicaciones clientes ocupan menos y consumen menos procesador y memoria, permitiendo instalarse en máquinas de bajo rendimiento. Esto trae la ventaja de que por muchos clientes que accedan a la aplicación, el motor de bases de datos sólo tiene una conexión, que va directamente al servidor de aplicaciones, evitando así problemas de concurrencia o latencia de datos entre distintas aplicaciones cliente. Estas aplicaciones clientes también pueden funcionar a través de Internet ya que su consumo de ancho de banda es mínimo, al contrario de conectar directamente con el motor de bases de datos.

- Procesar datos distribuidos. Distribuir el trabajo de una aplicación entre varias máquinas puede mejorar la ejecución, ya que el balanceo de carga permite reducir la carga de las máquinas que funcionan como servidor de aplicaciones. Por ejemplo, si vemos que una aplicación de gestión se relentiza podemos distribuir en una máquina las compras, en otra las ventas y la gestión de recibos en otra.

- Incrementar la seguridad. Podemos aislar la funcionalidad en las capas dando restricciones de seguridad. Esto proporciona unos niveles de seguridad configurables y flexibles. Las capas intermedias pueden limitar los puntos de entrada a material protegido, permitiendo controlar el control de acceso más fácilmente. Si usamos HTTP o COM+, podemos utilizar los modelos de seguridad que soportan.

COMPOSICION DE LAS APLICACIONES DE BASES DE DATOS MULTICAPA

Las aplicaciones multicapa usan los componentes de la pestaña DataSnap, los de la pestaña Data Access y a veces los de la pestaña WebServices, más un módulo de datos remoto que es creado por el asistente de la pestaña Multitier o WebServices del cuadro de diálogo New Items. Estos componentes proporcionan las funcionalidades para empaquetar información transportable sólo con los datos que se hayan modificado.

Los componentes que necesitamos para aplicaciones multicapa son los siguientes:

Módulo de bases de datos remoto: Los módulos de datos pueden actual como un servidor COM o implementar un servicio web para dar a las aplicaciones clientes acceso a los datos. Este componente es utilizado en el servidor de aplicaciones en la capa intermedia (el servidor de aplicaciones).

Provider: Es el que proporciona los datos creando paquetes de datos y resolviendo las actualizaciones de los clientes. Este componente es utilizado en el servidor de aplicaciones en la capa intermedia (servidor de aplicaciones).

ClientDataSet: es un dataset especializado que usa la librería midas.dll o midaslib.dcu para manejar los datos almacenados en los paquetes que se envían y reciben. Este componente es utilizado por la aplicación cliente. Tiene una caché local y sólo envía al servidor los datos que han cambiado.

Connection: Es una familia de componentes que estan localizados en el servidor y que utilizan la interfaz IAppServer para leer las peticiones de las aplicaciones clientes. Cada componente de conexión está especializado en protocolo particular de comunicaciones.

Los componentes proveedores y clientes requiren las librerías midas.dll o midaslib.dcu cuando se va a distribuir la aplicación en otros equipos.

FUNCIONAMIENTO DE UNA APLICACION A TRES CAPAS

Los siguientes pasos ilustran la secuencia normal de eventos para una aplicación a tres capas:

1. El usuario cliente arranca la aplicación. El cliente conecta con el servidor de aplicaciones (el cual puede ser elegido en tiempo de ejecución). Si el servidor de aplicaciones no estuviera arrancado, lo arranca automáticamente. Los clientes reciben una interfaz IAppServer para comunicarse con el servidor de aplicaciones.

2. El cliente solicita los datos al servidor de aplicaciones, donde puede requerir todos los datos de una vez o pedir una parte de ellos poco a poco.

3. El servidor de aplicaciones lee la información solicitada por el cliente del motor de bases de dtaos (estableciendo una conexión con si fuera necesario), empaqueta la información y devuelve la información al cliente. La información adicional (por ejemplo, las características de los campos) pueden ser incluida en los metadatos del paquete de datos. Este proceso de empaquetamiento de datos es llamado providing.

4. El cliente decodifica el paquete recibido y muestra los datos al usuario.

5. Cuando el usuario de la aplicación cliente realiza modificaciones sobre los datos (añadir, borrar o modificar registros) estas modificaciones son almacenadas en un log temporal.

6. Finalmente los clientes envian las actualizaciones al servidor de aplicaciones, el cual responde normalmente a cada acción del usuario. Para aplicar los cambios, los paquetes de los clientes leerán y cambiarán el log antes de enviar sus paquetes de datos al servidor.

7. El servidor de aplicaciones decodifica el paquete y efectua los cambios (en el contexto de una transacción cuando sea apropiada). Si un registro no puede ser actualizado (por ejemplo, porque otra aplicación cambie el registro antes de que el cliente lo solicite y después de que el cliente haya aplicado los cambios), el servidor de aplicaciones intenta reconciliar los cambios de los clientes con los datos actuales, y guarda los registros que podrían no ser actualizados. Este proceso de guardar y resolver problemas con los registros es llamado resolving.

8. Cuando el servidor de aplicaciones finaliza el proceso de actualización de registros, devuelve los registros no actualizados al cliente para que pueda resolverlos por el mismo.

9. El cliente resuelve los registros que el servidor no ha podido actualizar. Hay muchas maneras de que un cliente pueda resolver estos registros. Generalmente el cliente intenta corregir la situación asegurándose de que los registros estén validados antes de enviarlos. Si la situación puede ser rectificada, entonces vuelve a enviar los datos al servidor.

10. El cliente desempaqueta los datos que vienen del servidor y refresca su caché local.

En la siguiente parte de este artículo veremos la estructura de una aplicación cliente.

Pruebas realizadas en Delphi 7.

12 noviembre 2007

Creando tablas de memoria con ClientDataSet

Una de las cosas que más se necesitan en un programa de gestión es la posibilidad crear tablas de memoria para procesar datos temporalmente, sobre todo cuando los datos origen vienen de tablas distintas.

Es muy común utilizar componentes de tablas de memoria tales como los que llevan los componentes RX (TRxMemoryData) o el componente kbmMemTable. Pues veamos como hacer tablas de memoria utilizando el componente de la clase TClientDataSet sin tener que utilizar ningún componente externo a Delphi.

DEFINIENDO LA TABLA

Lo primero es añadir a nuestro proyecto un componente ClientDataSet ya sea en un formulario o en un módulo de datos. Como vamos a crear una tabla de recibos lo vamos a llamar TRecibos.

Ahora vamos a definir los campos de los que se compone la tabla. Para ello pulsamos el botón [...] en la propiedad FieldDefs. En la ventana que se abre pulsamos el botón Add New y vamos creando los campos:

Name DataTpe Size
-----------------------------------
NUMERO ftInteger 0
CLIENTE ftString 80
IMPORTE ftFloat 0
PAGADO ftFloat 0
PENDIENTE ftFloat 0

Para crear la tabla de memoria pulsamos el componente TClientDataSet con el botón derecho del ratón y seleccionamos Create DataSet. Una vez creado sólo nos falta hacer que los campos sean persistentes. Eso se consigue haciendo doble clic sobre el componente y pulsando la combinación de teclas CTRL + A.

Con estos sencillos pasos ya hemos creado una tabla de memoria y ya podemos abrirla para introducir datos. No es necesario abrir la tabla ya que estas tablas de memoria hay que dejarlas activas por defecto.

DANDO FORMATO A LOS CAMPOS

Como tenemos tres campos de tipo real vamos a dar formato a los mismos del siguiente modo:

1. Hacemos doble clic sobre el componente ClientDataSet.

2. Seleccionamos los campos IMPORTE, PAGADO y PENDIENTE.

3. Activamos en el inspector de objetos su propiedad Currency.

Con esto ya tenemos los campos en formato moneda y con decimales.

REALIZANDO CALCULOS AUTOMATICAMENTE

A nuestra tabla de recibos le vamos a hacer que calcule automáticamente el importe pendiente. Esto lo vamos a hacer antes de que se ejecute el Post, en el evento BeforePost:

procedure TFormulario.TRecibosBeforePost( DataSet: TDataSet );
begin
TRecibosPENDIENTE.AsFloat := TRecibosIMPORTE.AsFloat - TRecibosPAGADO.AsFloat;
end;

De este modo, tanto si insertamos un nuevo registro como si lo modificamos realizará el cálculo del importe pendiente antes de guardar el registro.

AÑADIENDO, EDITANDO Y ELIMINANDO REGISTROS DE LA TABLA

Insertamos tres registros:

begin
TRecibos.Append;
TRecibosNUMERO.AsInteger := 1;
TRecibosCLIENTE.AsString := 'TRANSPORTES PALAZON, S.L.';
TRecibosIMPORTE.AsFloat := 1500;
TRecibosPAGADO.AsFloat := 500;
TRecibos.Post;

TRecibos.Append;
TRecibosNUMERO.AsInteger := 2;
TRecibosCLIENTE.AsString := 'TALLERES CHAPINET, S.L.';
TRecibosIMPORTE.AsFloat := 200;
TRecibosPAGADO.AsFloat := 200;
TRecibos.Post;

TRecibos.Append;
TRecibosNUMERO.AsInteger := 3;
TRecibosCLIENTE.AsString := 'GRUAS MARTINEZ, S.L.';
TRecibosIMPORTE.AsFloat := 625;
TRecibosPAGADO.AsFloat := 350;
TRecibos.Post;
end;

Si queremos modificar el primer registro:

begin
TRecibos.First;
TRecibos.Edit;
TRecibosCLIENTE.AsString := 'ANTONIO PEREZ BERNAL';
TRecibosIMPORTE.AsFloat := 100;
TRecibosPAGADO.AsFloat := 55;
TRecibos.Post;
end;

Y para eliminarlo:

begin
TRecibos.First;
TRecibos.Delete;
end;

MODIFICANDO LOS CAMPOS DE LA TABLA

Si intentamos añadir un nuevo campo a la tabla en FieldDefs y luego pulsamos CTRL + A para hacer el campo persistente veremos que desaparece de la definición de campos. Para hacerlo correctamente hay que hacer lo siguiente:

1. Pulsamos el componente ClientDataSet con el botón derecho del ratón.

2. Seleccionamos Clear Data.

3. Añadimos el nuevo campo en FieldDefs.

4. Volvemos a pulsar el el botón derecho del ratón el componente y seleccionamos Create DataSet.

5. Pulsamos CTRL + A para hacer persistente el nuevo campo.

Estos son los pasos que hay que seguir si se crean, modifican o eliminan campos en la tabla.

CREANDO CAMPOS VIRTUALES PARA SUMAR COLUMNAS

Vamos a crear tres campos virtuales que sumen automáticamente el valor de las columnas IMPORTE, PAGADO y PENDIENTE para totalizarlos. Comencemos con el cálculo del importe total:

1. Pulsamos el componente ClientDataSet con el botón derecho del ratón.

2. Seleccionamos Clear Data.

3. Hacemos doble clic en el componente ClientDataSet.

4. Pulsamos la combinación de teclas CTRL + N para añadir un nuevo campo:

Name: TOTALIMPORTE
FieldType: Agregate

5. Pulsamos Ok. Seleccionamos el campo creado escribimos en su propiedad Expression:

SUM(IMPORTE)

y activamos su propiedad Active. También activamos su propiedad Currency.

6. Creamos en el formulario un campo de tipo DBText y asociamos en nuevo campo creado:

DataSource: TRecibos
DataField: TOTALIMPORTE

7. Volvemos a pulsar el el botón derecho del ratón el componente y seleccionamos Create DataSet.

8. Activamos en el componente TClientDataSet la propiedad AggregatesActive.

Igualmente habría que crear dos campos más para sumar las columnas del importe pagado y el importe pendiente.

Utilizar ClientDataSet para crear tablas de memoria es ideal para procesar listados en tablas temporales sin tener que volcar el resultado en ninguna base de datos. Además podemos importar y exportar datos a XML usando el menú contextual de este componente.

Pruebas realizadas en Delphi 7.

09 noviembre 2007

Creando informes con Rave Reports (y V)

Hasta ahora hemos visto como crear informes utilizando una conexión ADO, pero surgen los siguientes inconvenientes:

- Hay que instalar un driver ODBC en el cliente.

- Hay que configurar la conexión ODBC para que apunte a nuestra base de datos.

- Cada vez que el programa va a imprimir se abre una nueva conexión a la base de datos (imaginaos 20 usuarios trabajando a la vez).

- Puede faltar alguna DLL a la hora de instalar el programa en el cliente relacionada con conexiones ODBC.

Para solucionar esto vamos a ver como utilizar los objetos Direct Data View en el diseñador Rave Reports que nos van a evitar todos estos problemas.

PASANDO DE ADO, BDE y DBX

Los componentes Direct Data View que se encuentran en el editor Rave Designer permiten vincular los informes directamente con los componentes TRvDataSetConnection que se encuentren en nuestro programa Delphi, sin tener que establecer ninguna conexión de base de datos ni preocuparnos por drivers externos.

Funciona exactamente igual que los informes generados por QuickReport, donde el origen de datos es nuestro mismo programa, ahorrándonos conexiones extras. Vamos a ver un ejemplo de cómo generar un listado de clientes realizando una conexión directa entre Delphi y Rave Reports.

Los siguientes pasos serían para crear una conexión a Firebird desde Delphi:

1. Abrimos Delphi y creamos un formulario de prueba.

2. Insertamos un componente de la clase TIBDatabase en el formulario con el nombre BaseDatos.

3. Hacemos doble clic en el componente TIBDatabase y rellenamos los siguientes datos:

Connection: Remote
Server: 127.0.0.1
Database: D:\Desarrollo\DelphiAlLimite\Rave\BaseDatos.fdb
UserName: SYSDBA
Password: masterkey
Login Prompt: Desactivado

4. Añadimos un componente TIBTransaction y lo llamamos Transaccion.

5. Asociamos el componente Transaccion al componente BaseDatos a través de su propiedad DefaultTransaction.

6. En la propiedad DefaultDatabase del objeto TIBTransaction seleccionamos BaseDatos.

7. Insertamos en el formulario un componente TIBQuery con el nombre TClientes. En su propiedad Database seleccionamos BaseDatos. Y en su propiedad SQL ponemos:

SELECT * FROM CLIENTES

8. Hacemos doble clic sobre el componente TIBQuery y pulsamos la combinación de teclas CTRL + A para traernos los campos. Al hacer esto habrá dejado la base de datos abierta (BaseDatos). La dejamos así por ahora.

9. Insertamos en el formulario un componente TRvProject y lo llamamos Proyecto.

10. Insertamos otro componente de TRvSystem y lo llamamos Sistema. Asociamos este componente con TRvProject con su propiedad Engine.

11. Añadimos al formulario un componente TRvDataSetConnection que llamaremos DSClientes. En su propiedad DataSet elegimos TClientes.

Con toda esta movida ya tenemos una conexión a una base de datos Firebird y un DataSet especial para que Rave pueda recoger los datos.

CREANDO EL INFORME EN RAVE REPORTS

Dejando Delphi abierto con nuestra base de datos conectada (BaseDatos) ejecutamos el programa Rave Designer. Una vez estamos en el diseñador vamos a efectuar los siguiente pasos:

1. Pulsamos el botón New Data Object.

2. Seleccionamos Direct Data View y pulsamos Next.

3. Nos aparecerá una lista de conexiones abiertas en Delphi donde se estén utilizando componente TRvDataSetConnection. En nuestro caso aparecera DSClientes (DT). Lo seleccionamos y pulsamos Finish.

4. Renombramos el objeto DataView1 creado y lo llamamos Clientes.

5. Seleccionamos en el menú de arriba Tools -> Report Wizards -> Simple Table.

6. Seleccionamos Clientes y pulsamos Next.

7. Seleccionamos los campos ID, NOMBRE, DIRECCION, NIF, PROVINCIA y pulsamos Next.

8. Dejamos la ordenación como está y pulsamos Next.

9. En Report Title ponemos Listado de Clientes y pulsamos Next.

10. Seleccionamos la fuente a nuestro gusto y pulsamos Generate.

11. Guardamos el informe en el mismo directorio que nuestro proyecto de Delphi con el nombre Clientes.rav.

Si pulsamos el botón de la impresora y mostramos una vista previa veremos como se trae directamente de Delphi los datos y los imprime.

La potencia que nos da esta forma de trabajar es enorme, ya que es nuestro proyecto Delphi el que suministra los datos, siendo posible filtrar y ordenar tablas de bases de datos a nuestro antojo sin que Rave Reports no tenga que enterarse de donde proceden los datos.

Ya podemos cerrar el programa Rave Designer. Ahora vamos a ver como lanzar el informe desde Delphi.

LANZANDO EL INFORME RAVE DESDE DELPHI

En Delphi ya podemos desconectar la base de datos en tiempo de diseño. Para lanzar el listado hacemos lo siguiente:

begin
BaseDatos.DatabaseName := '127.0.0.1:' + ExtractFilePath( Application.ExeName ) + 'BaseDatos.fdb';

try
BaseDatos.Open;
except
raise;
end;

TClientes.Open;
Proyecto.ProjectFile := ExtractFilePath( Application.ExeName ) + 'clientes.rav';
Proyecto.Execute;
end;

Esto abrirá la base de datos Firebird, también la tabla clientes y ejecutará el informe.

A partir de aquí podemos hacer lo que nos venga en gana para modificar el listado de clientes: modificar la SQL para cambiar la ordenación, filtrar por ID, NIF, etc.

FILTRANDO TABLAS MAESTRO/DETALLE EN RAVE REPORTS

Utilizando las tablas FACTURAS y DETALLEFAC que vimos anteriormente podemos filtrar el detalle de la factura a partir de la cabecera. Resumiendo un poco, los pasos serían los siguientes:

1. Vinculamos las tablas FACTURAS y DETALLEFAC en el informe utilizando componentes Direct Data View.

2. Volvemos a vincular los campos con estos dos componentes.

3. Seleccionamos la banda Detalle y configuramos las siguientes propiedades:

MasterDataView: Facturas
MasterKey: ID
DetailKey: IDFACTURA

Con estos simples pasos imprimiremos las facturas desde Delphi sin tener que preocuparnos de filtrar la tabla detalle. También habría que crear otro objeto Direct Data View para enlazar a la tabla CLIENTES y poder traernos los datos. Para imprimir la factura desde Delphi filtrando al cliente:

begin
BaseDatos.DatabaseName := '127.0.0.1:' + ExtractFilePath( Application.ExeName ) + 'BaseDatos.fdb';

try
BaseDatos.Open;
except
raise;
end;

// Filtramos la factura
TFacturas.SQL.Clear;
TFacturas.SQL.Add( 'SELECT * FROM FACTURAS WHERE ID=2' );
TFacturas.Open;

// Filtramos el cliente según la factura
TClientes.SQL.Clear;
TClientes.SQL.Add( 'SELECT * FROM CLIENTES WHERE ID=' + TFacturas.FieldByName( 'IDCLIENTE' ).AsString );
TClientes.Open;

// Abrimos el detalle (ya se encarga Rave de filtrarlo respecto a la cabecera)
TDetalleFac.Open;

// Lanzamos el informe
Proyecto.ProjectFile := ExtractFilePath( Application.ExeName ) + 'factura.rav';
Proyecto.Execute;
end;

Se supone que hemos creado en el formulario de Delphi dos componentes TIBQuery llamados TFactura y TDetalleFac para la cabecera y detalle de la factura.

Con esto finalizamos la introducción al editor de informes Rave Reports.

Pruebas realizadas en Delphi 7, Rave Reports 5.0 y Firebird 2.0.

07 noviembre 2007

Creando informes con Rave Reports (IV)

Después de crear los informes utilizando el programa Rave Designer ahora vamos a ver como abrir esos informes con extensión RAV dentro de nuestro proyecto de Delphi.

ABRIENDO INFORMES RAVE DESDE DELPHI

Para llamar a los informes creados desde Delphi vamos a insertar en nuestro formulario los componentes de la clase TRvProject y TRvSystem situados en la pestaña Rave. Al objeto RvProject lo vamos a llamar Proyecto y al componente RvSystem lo llamaremos Sistema.


También vamos a vincular el componente Sistema al componente Proyecto a través de su propiedad Engine. Como estamos utilizando una conexión ADO tenemos que añadir en la sección uses de nuestro formulario la unidad RvDLADO, porque en caso contrario nos mostraría el error:

No DATA Link drivers have been loaded.

El objeto RvSystem realmente no es obligatorio utilizarlo, pero es recomendable porque con el mismo podemos personalizar todo lo relacionado con la impresión de documentos, desde el título, hasta los mensajes, la forma de como mostrar la vista previa, etc.

Pues con esto ya podemos lanzar la vista previa del informe:

begin
Proyecto.ProjectFile := ExtractFilePath( Application.ExeName ) + 'factura.rav';
Proyecto.Execute;
end;

Primero se nos abre una ventana para seleccionar el tipo de impresión y la impresora por donde va a salir:


Si queremos quitar esta ventana y que lance directamente la vista previa del informe tenemos que desactivar la propiedad ssAllowSetup que se encuentra dentro de la propiedad SystemSetups del componente RvSystem (que hemos llamado Sistema).

Para lanzar directamente el informe a la impresora sin vista previa hay que configurar la propiedad DefaultDest del componente RvSystem con el valor rdPrinter.

INSTALANDO NUESTRA APLICACION EN OTRO PC

Uno de los problemas que suelen ocurrir al llevarnos la aplicación a otro ordenador es que siempre falta que configurar algo para que pueda imprimirse el informe.

En mi caso he probado a ejecutar la aplicación en una máquina virtual limpia (VMWare con Windows XP) y sólo he tenido que hacer lo siguiente para que imprima el informe:

- Hay que instalar el driver ODBC de Firebird 2.0 como mencioné en otro artículo anterior.

- Hay que ir a herramientas administrativas dentro del panel de control de Windows y configurar nuestra conexión hacia el archivo BaseDatos.fdb. El alias de la conexión tiene que llamarse igual como lo hicimos anteriormente: BaseDatos_Rave.

- Me ha ocurrido que al hacer un test de conexión me faltaba la siguiente librería:

MSVCR71.DLL

Para solucionarlo lo copiamos de otro Windows XP que lo tenga y lo metemos en la carpeta C:\Windows\System32\ y listo. O bien lo bajamos de Internet.

Y con esto ya tenemos nuestros informes funcionando en otra máquina. Naturalmente damos por hecho de que tiene instalado el motor de bases de datos Firebird 2.0.

En el próximo artículo seguiremos viendo más cosas interesantes relacionadas con Rave Reports.

Pruebas realizadas en Delphi 7 y Firebird 2.0.

06 noviembre 2007

Creando informes con Rave Reports (III)

Una vez que sabemos crear listados a partir de una tabla vamos a ver como se haría para tablas maestro/detalle, como puede ser una factura.

Nuestra factura consta de dos tablas. La cabecera de la factura está implementada en esta tabla:

CREATE TABLE FACTURAS (
ID INTEGER NOT NULL,
IDCLIENTE INTEGER,
BASEIMP DOUBLE PRECISION,
IVA DOUBLE PRECISION,
IMPORTEIVA DOUBLE PRECISION,
TOTAL DOUBLE PRECISION,
PRIMARY KEY (ID)
);

Y el detalle de la misma en esta otra:

CREATE TABLE DETALLEFAC (
ID INTEGER NOT NULL,
IDFACTURA INTEGER,
UNIDADES DOUBLE PRECISION,
ARTICULO VARCHAR(100),
PRECIO DOUBLE PRECISION,
TOTALLINEA DOUBLE PRECISION,
PRIMARY KEY (ID)
);

Cada línea de detalle está vinculada a la cabecera mediante el campo IDFACTURA.

VINCULANDO LAS TABLAS MAESTRO/DETALLE AL INFORME

Primero tenemos que incluir dos objetos DriverDataView para las tablas de cabecera y detalle. Hacemos lo siguiente:

1. Pulsamos el botón New Data Object.

2. Seleccionamos Driver Data View y pulsamos el botón Next.

3. Seleccionamos BaseDatos y pulsamos el botón Finish.

4. En la ventana que aparece pulsamos el botón Editor.

5. Escribimos una SQL que nos permita traernos los datos del cliente:

SELECT * FROM FACTURAS
LEFT JOIN CLIENTES ON FACTURAS.IDCLIENTE=CLIENTES.ID

6. Pulsamos el botón Ok.

7. Estando en el editor seleccionamos a la derecha el nuevo DriverDataView1 creado y a la izquierda ponemos su propiedad Name a Facturas.

Siguiendo los mismos pasos tenemos que vincular la tabla DETALLEFAC con la SQL:

SELECT * FROM DETALLEFAC

CREANDO INFORMES PARA TABLAS MAESTRO/DETALLE

En esta ocasión vamos a ver como crear una factura utilizando directamente el editor sin asistentes. El resultado sería el siguiente:


Los pasos para crear esta factura serían los siguientes:

1. Insertamos un componente Region que esta situado en la pestaña Report.

2. Dentro de la región creada insertamos tres componentes de tipo DataBand (pestaña Report).

3. A las tres bandas creadas las vamos a llamar Cabecera, Detalle y Pie.

4. A la propiedad DataView de la bandas Cabecera y Pie le asignamos la tabla Facturas.

5. A la propiedad DataView de la banda Detalle le asignamos la tabla DetalleFac.

6. La cabecera se compone de:


- 6 etiquetas en negrita de tipo Text situadas en la pestaña Standard.

- 8 campos de tipo DataText situados en la pestaña Report para los campos: ID, NOMBRE, DIRECCION, POBLACION, CP, PROVINCIA y NIF.

- 4 líneas utilizando el componente Line situado en la pestaña Drawing.

7. El detalle se compone de:


- 4 componetes de tipo DataText para los campos UNIDADES, ARTICULO, PRECIO y TOTALLINEA. Para que los campos UNIDADES, PRECIO y TOTALLINEA salgan con formato a dos decimales tenemos que seleccionar en el árbol del proyecto a la derecha (debajo de DetalleFac) los campos DetalleFacUNIDADES, DetalleFacPRECIO y DetalleFacTOTALLINEA y establecer su propiedad DisplayFormat con el valor ###,###,#0.00. A dichos campos también hay que ponerle su propiedad FontJustify con el valor pjRight (eso se hace en el folio) para que aparezcan alineados a la derecha.

8. El pie tiene el siguiente diseño:


- 3 etiquetas de tipo Text.

- 3 campos de tipo DataText para los campos BASEIMP, IMPORTEIVA y TOTAL, dando el formato de número real como hemos hecho con los campos moneda del detalle.

- 1 rectángulo de tipo Rectangle situado en la pestaña Drawing.

Y por último fuera de la región en la parte superor del folio he añadido una etiqueta con la palabra FACTURA.

Al ejecutar el informe este sería el resultado:


Como puede verse es muy fácil crear informes utilizando Rave Reports siempre que tengamos claro de que tabla extraer la información.

En el próximo artículo veremos como ejecutar estos informes desde Delphi.

Pruebas realizadas con Firebird 2.0 y Rave Reports 5.0.

05 noviembre 2007

Creando informes con Rave Reports (II)

Una vez establecida la conexión con la base de datos vamos a crear un objeto DriverDataView para vincularlo a nuestro informe.

CREANDO UN VINCULO PARA LA TABLA CLIENTES

Para enlazar la tabla de clientes hay que hacer lo siguiente:

1. Pulsamos el botón NewDataObject y pulsamos el botón Next.

2. Seleccionamos Driver Data View y pulsamos el botón Next.

3. Seleccionamos BaseDatos y pulsamos el botón Finish. Se abrirá la siguiente ventana:


4. Pulsamos el botón Editor y se mostrará de esta forma:


5. Escribimos la siguiente SQL:

SELECT * FROM CLIENTES

6. Pulsamos el botón Ok.

7. Si nos fijamos a la derecha del editor a aparecido el objeto DriverDataView1. Lo seleccionamos y en la parte izquierda del editor cambiamos su propiedad Name a Clientes.

Con esto ya tenemos vinculada la tabla CLIENTES a nuestro informe.

UTILIZANDO EL ASISTENTE PARA GENERAR INFORMES

Vamos a ver los pasos para generar el listado de clientes utilizando el Wizard:

1. Seleccionamos Tools -> Report Wizards -> Simple Table. Aparecera esta ventana:


2. Seleccionamos Clientes y pulsamos Next:


3. Vamos a seleccionar los campos ID, NOMBRE, CP, PROVINCIA y NIF. Pulsamos Ok.

4. En la siguiente ventana se nos da la opción de ordenar los campos a nuestro gusto. En este caso he puesto el NIF después del NOMBRE. Al pulsar Next nos aparecera esta ventana:


5. En el campo Report Title ponemos Listado de Clientes. También podemos configurar los márgenes a nuestro gusto, aunque en este caso lo dejamos como está y pulsamos el botón Next. La siguiente ventana es esta:


6. Aquí podemos cambiar la fuente para el título, la cabecera de los campos y la fuente de los campos. En la cabecera de los campos le he configurado una fuente a Tahoma tamaño 10 y negrita. Para los campos lo he dejado en Tahoma 10. Al pulsar el botón Generate y aparecerá lo siguiente:


Con esto ya tenemos hecho el listado. Si pulsamos el botón Execute Report (la impresora) y seleccionamos Preview podemos ver como queda la vista previa del listado.

Como puede verse a primera vista los campos nos aparecen demasiado pegados los unos de los otros. Lo que hay que hacer es ajustar a mano en el editor las columnas para que queden a nuestro gusto.

Una de las cosas que mas me gustan de este editor es que se pueden seleccionar a la vez componentes de distintas bandas para moverlos o redimensionarlos. Con la tecla Mayúsculas pulsada y con los cursores del teclado podemos ampliar o reducir el tamaño de los campos. Y con la tecla Control pulsada se pueden mover varios campos a la vez. El comportamiento es muy similar al editor de formularios de Delphi.

ANALIZANDO EL INFORME CREADO

Si nos fijamos bien en el informe creado aparecen los siguientes objetos:

1. Primero crea un objeto Region (pestaña Report) para colocar todo el diseño encima.

2. Dentro de la región creada introduce tres bandas mediante el componente Band que se encuentra en la pestaña Report:

- ClientesTitleBand: La primera banda para el título Listado de Clientes.

- ClientesBand: La segunda banda contiene los títulos de los campos.

- ClientesDataBand: La tercera banda incluye los campos que se van a imprimir. Esta última banda es de tipo DataBand (también en la pestaña Report).

Lo bueno de tenerlo todo dentro de un componente Region es que si tenemos problemas con los márgenes de impresión, ajustando la región a nuestro gusto no aperecán los típicos problemas de folios duplicados en impresoras laser o multifunción que hace que salgan dos copias.

En el siguiente artículo vamos a crear un listado para tablas maestro/detalle.

Pruebas realizadas con Firebird 2.0 y Rave Reports 5.0.

02 noviembre 2007

Creando informes con Rave Reports (I)

Rave Reports es una herramienta externa asociada a Delphi que permite la creación de informes a partir de una base de datos. Se pueden crear toda variedad de informes, desde un simple folio con una banda hasta otros más complejos con múltiples bandas. Incluye las siguientes características:

- Integración de gráficos.
- Párrafos justificados.
- Modificación de opciones de impresora.
- Control de las fuentes.
- Vista previa antes de imprimir.
- Exportación a PDF, HTML, RTF y archivos de texto.

CREANDO UN INFORME PARA NUESTRA APLICACION

Vamos a suponer que tengo una base de datos creada en Firebird 2.0 cuyo nombre es BaseDatos.fdb. Esta base de datos tiene la siguiente tabla:

CREATE TABLE CLIENTES (
ID INTEGER NOT NULL,
NOMBRE VARCHAR(100),
DIRECCION VARCHAR(100),
POBLACION VARCHAR(50),
CP VARCHAR(5),
PROVINCIA VARCHAR(50),
IMPORTEPTE DOUBLE PRECISION,
NIF VARCHAR(15),
ALTA TIMESTAMP,
HORALLAMAR TIME,
ULTIMOPAGO DATE,
NUMPAGOS INTEGER,
OBSERVACIONES BLOB
);

Entonces vamos a crear un informe para sacar un listado de clientes. Para ello abrimos el diseñador de informes Rave desde Delphi a través de la opción Tools -> Rave Designer. Nos aparecera el siguiente entorno:


Los informes que genera este diseñador de informes tienen extensión RAV. Por defecto, el nombre del informe que vamos a crear se llama Project1.rav. Le vamos a cambiar el nombre seleccionando File -> Save As... con el nombre listado_clientes.rav.

Antes de comenzar a diseñar el listado tenemos que establecer una conexión con nuestra base de datos Firebird llamada BaseDatos.fdb para poder extraer la información.

CONFIGURANDO LA CONEXION ODBC

Como vamos a utilizar una conexión ADO para vincularlo a este informe, primero tenemos que establecer el orígen de datos ODBC. Al ser mi base de datos es Firebird me he instalado el driver ODBC que se encuentra en la página:

http://www.firebirdsql.org/

El driver se encuentra en el apartado Development -> Firebird ODBC Driver.

Aparte de utilizar este driver para conexiones ADO nos puede ser muy útil para vincular nuestras bases de datos a prográmas ofimáticos tales como Word, Excel y Access.

Una vez descargado e instalado el driver ODBC vamos a realizar los siguientes pasos:

1. Abrimos el panel de control de Windows.

2. Hacemos doble clic en el icono Herramientas Administrativas.

3. Hacemos doble clic en el icono Orígenes de datos (ODBC).

4. Teniendo seleccionada la pestaña DSN de usuario pulsamos el botón Agregar.

5. Seleccionamos Firebird/Interbase(r) Driver y pulsamos el botón Finalizar.

6. Se abrirá la siguiente ventana:


7. Vamos a rellenar los siguientes parámetros:

Nombre de Origen de Datos (DSN): BaseDatos_Rave
Description: BaseDatos_Rave
Base de Datos: D:\Desarrollo\DelphiAlLimite\Rave\BASEDATOS.FDB (donde esté el archivo FDB)
Cliente: C:\Firebird2\bin\fbclient.dll (donde esté instalado Firebird 2.0)
Cuenta de Base de Datos: SYSDBA
Contraseña: masterkey

8. Pulsamos el botón Comprobar conexión y debe mostrar ¡La conexión fue exitosa!

9. Pulsamos el botón Aceptar y veremos en la ventana anterior nuestra nueva conexión: BaseDatos_Rave.

10. Pulsamos el botón Aceptar y cerramos el panel de control de Windows.

Una vez configurada la conexión ODBC en Windows vamos a vincularla a nuestro informe.

CONECTANDO CON LA BASE DE DATOS DESDE RAVE REPORTS

Para establecer una conexión con nuestra base de datos Firebird nos tenemos que ir a la barra de botones que se encuentra en la esquina superior izquierda del diseñador de informes Rave y pulsar el botón New Data Object:
Al pulsarlo nos aparecen las siguientes opciones:


Seleccionamos Database Connection y pulsamos Next. La conexión sólo se puede realizar a través de ADO, BDE o DBExpress, aunque pueden añadirse otros drivers de conexión dentro del directorio:

C:\Archivos de programa\Borland\Delphi7\Rave5\DataLinks\

Seleccionamos ADO y pulsamos el botón Finish. Nos aparecerá la siguiente ventana:


Seleccionamos la opción Use Connection String y pulsamos el botón [...] que aparece a la derecha. Aparecerá la ventana de las propiedades de vínculo de datos:


Seleccionamos la pestaña Conexión y en la opción Usar el nombre de origen de datos seleccionamos BaseDatos_Rave. Pulsamos el botón Probar Conexión y nos saldrá el mensaje: La prueba de conexión fue satisfactoria. Pulsamos el botón Aceptar y en la ventana anterior pulsamos Ok.

Después de mucho sacrificio (es más fácil recompilar el núcleo de Linux) ya tenemos en el diseñador Rave la conexión directa con nuestra base de datos:


Si seleccionamos Database1 a la izquierda veremos sus propiedades:


Vamos a renombar las propiedades FullName y Name a BaseDatos.

En el próximo artículo vamos a vincular la tabla clientes y vamos a realizar el informe.

Pruebas realizadas con Firebird 2.0 y Rave Reports 5.0.

30 octubre 2007

Como manejar excepciones en Delphi (y II)

Una vez que hemos visto lo que es una excepción y cómo proteger nuestro código usando bloques protegidos vamos a pasar a ver como pueden meterse unas excepciones dentro de otras para dar más seguridad a nuestro código. Es lo que se llaman excepciones anidadas.

EXCEPCIONES ANIDADAS

Vamos a ver un ejemplo de como anidar una excepción dentro de otra:

var
F: TextFile;
begin
AssignFile( F, 'C:\noexiste.txt' );

try
Reset( F );

try
CloseFile( F );
except
on E: Exception do
ShowMessage( 'Excepción 2: ' + E.Message );
end;

except
on E: Exception do
ShowMessage( 'Excepción 1: ' + E.Message );
end;
end;

En este ejemplo hemos metido un bloque protegido dentro de otro, donde cada uno controla su propia excepción. En este caso provocaría la excepción 1 ya que el archivo no existe.

DETENIENDO LA EXCEPCION

Cuando se provoca una excepción, una vez la hemos procesado con la sentencia on E: Exception, la ejecución continua hacia el siguiente bloque de código. Si queremos detener la ejecución del programa debemos utilizar el comando raise:

var
F: TextFile;
begin
AssignFile( F, 'C:\noexiste.txt' );
ShowMessage( '1' );

try
Reset( F );

except
on E: Exception do
raise;
end;

ShowMessage( '2' );
end;

En este ejemplo nunca llegaría a ejecutarse el segundo ShowMessage ya que raise detiene la ejecución del procedimiento.

FORZANDO A QUE FINALICE LA EJECUCION

Hay bloques de código en los cuales cuando se provoca una excepción ni podemos continuar con la ejecución ni podemos cortar la ejecución. Por ejemplo, supongamos que abro un archivo en módo sólo lectura e intento escribir en el mismo. Esto provocaría una excepción, pero lo que no puedo hacer es detener en seco la ejecución del programa ya que hay que cerrar el archivo que hemos abierto.

Para solucionar esto, los bloques protegidos permiten finalizar la ejecución en el caso de que se produzca una excepción mediante la claúsula finally. En nuestro ejemplo nos interesa que se cierre el archivo abierto:

var
F: TextFile;
begin
AssignFile( F, 'C:\prueba.txt' );

try
Reset( F );

try
WriteLn( F, 'intentando escribir' );
finally
ShowMessage( 'Finalizando...' );
CloseFile( F );
end;

except
on E: Exception do
raise;
end;
end;

Tenemos dos excepciones anidadas: una para abrir el archivo con una sentencia except que detiene la ejecución y otra dentro que utiliza la sentencia finally para cerrar el archivo en el caso de que se produzca un error.

TRATANDO EXCEPCIONES EN COMPONENTES VCL

Los componentes VCL también pueden provocar muchas excepciones si no sabemos utilizarlos correctamente. Un error típico es el intentar acceder a un elemento que no existe dentro de un componente ListBox llamado Lista:

begin
Lista.Items.Add( 'PABLO' );
Lista.Items.Add( 'MARIA' );
Lista.Items.Add( 'CARLOS' );

try
ShowMessage( Lista.Items[4] );
except
on E: EStringListError do
ShowMessage( 'La lista sólo tiene tres elementos.' );
end;
end;

En este caso se ha provocado una excepción de la clase EStringListError, aunque bien se podría haber controlado de este modo:

try
ShowMessage( Lista.Items[4] );
except
on E: Exception do
ShowMessage( 'La lista sólo tiene tres elementos.' );
end;

Los componentes VCL disponen principalmente de estas clases de excepciones:

EAbort: Finaliza la secuencia de eventos sin mostrar el mensaje de error.

EAccessViolation: Comprueba errores de acceso a memoria inválidos.

EBitsError: Previene intentos para acceder a arrays de elementos booleanos.

EComponentError: Nos informa de un intento inválido de registar o renombar un componente.

EConvertError: Muestra un error al convertir objetos o cadenas de texto string.

EDatabaseError: Especifica un error de acceso a bases de datos.

EDBEditError: Error al introducir datos incompatibles con una máscara de texto.

EDivByZero: Errores de división por cero.

EExternalException: Significa que no reconoce el tipo de excepción (viene de fuera).

EIntOutError: Representa un error de entrada/salida a archivos.

EIntOverflow: Especifica que se ha provocado un desbordamiento de un tipo de dato.

EInvalidCast: Comprueba un error de conversión de tipos.

EInvalidGraphic: Indica un intento de trabajar con gráficos que tienen un formato desconocido.

EInvalidOperation: Ocurre cuando se ha intentado realizar una operación inválida sobre un componente.

EInvalidPointer: Se produce en operaciones con punteros inválidos.

EMenuError: Controla todos los errores relacionados con componentes de menú.

EOleCtrlError: Detecta problemas con controles ActiveX.

EOleError: Especifica errores de automatización de objetos OLE.

EPrinterError: Errores al imprimir.

EPropertyError: Ocurre cuando se intenta asignar un valor erroneo a una propiedad del componente.

ERangeError: Indica si se intenta asignar un número entero demasiado grande a una propiedad.

ERegistryExcepcion: Controla los errores en el resigtro.

EZeroDivide: Controla los errores de división para valores reales.

EXCEPCIONES SILENCIOSAS

Para dar un toque profesional a un programa hay ocasiones en que nos interesa controlar la excepción pero que no se entere el usuario del programa. Lo que no se puede hacer es abandonar la excepción con los comandos Break o con Exit ya que puede ser peor el remedio que la enfermedad.

Para salir elegantemente de una excepción hay que utilizar el comando Abort:

try
{ sentencias }
except
Abort;
end;

De este modo se controla la excepción y el usuario no ve nada en pantalla.

Con esto finalizamos el tratamiento de excepciones en Delphi.

Pruebas realizadas en Delphi 7.

29 octubre 2007

Como manejar excepciones en Delphi (I)

Las excepciones son condiciones excepcionales que se producen en nuestra aplicación en tiempo de ejecución y que requieren un tratamiento especial. Un ejemplo de excepciones podría ser las divisiones por cero o los desbordamientos de memoria. El tratamiento de excepciones proporciona una forma estándar de controlar los errores, descubriendo anticipadamente los problemas y posibilitando al programador anticiparse a los fallos que puedan ocurrir.

Cuando ocurre un error en un programa, se produce una excepción, lo que significa que crea un objeto excepción y situa el puntero de la pila en el primer punto donde se ha provocado la excepción. El objeto excepción contiene información sobre todo lo que ha ocurrido.

Esto nos permite crear aplicaciones más robustas ya que se puede llegar a averiguar el lugar en concreto donde se ha producido el error, particularmente en áreas donde los errores puedan causar la perdida de datos y recursos del sistema.

Cuando creamos una respuesta a la excepción tenemos que hacerlo en dentro de bloques de código, los cuales se llaman bloques de código protegidos.

DEFINIENDO BLOQUES DE CODIGO PROTEGIDOS

Los bloques de código protegidos comienzan con la palabra reservada try. Si ocurre algún error dentro del bloque de código protegido, el tratamiento del error se introduce en un bloque de código que comienza con except.

Vamos a ver un ejemplo que provoca una excepción al abrir un archivo que no existe:

var
F: TextFile;
begin
AssignFile( F, 'c:\nosexiste.txt' );

try
Reset( F );
except
on E: Exception do
Application.MessageBox( PChar( E.Message ), 'Error', MB_ICONSTOP );
end;
end;

La primera parte de un bloque protegido comienza con la palabra try. El bloque try contiene el código que potencialmente puede provocar la excepción. Al provocar la excepción saltará directamente al comienzo del bloque de código que comienza con la palabra reservada except.

Como puede apreciarse en el código anterior hemos creado un objeto E que representa la excepción creada. El objeto E pertenece a la clase Exception que a su vez hereda directamente de la clase TObject. Este objeto contiene propiedades y métodos para manejar la excepción provocada.

PROVOCANCO NUESTRA PROPIA EXCEPCION

Nosotros también podemos crear nuestras propias excepciones que hereden de la clase Exception. Por ejemplo, voy a crear una excepción si una variable de tipo string está vacía. Primero defino el tipo especial de excepción:

type
ECadenaVacia = class( Exception );

Y ahora la provoco en el programa:

var
sCadena: String;
begin
if sCadena = '' then
raise ECadenaVacia.Create( 'Cadena vacia.' );
end;

El comando raise provoca a propósito la excepción para detener la ejecución del programa. No es necesario que creemos nuestros tipos de excepción. También podía haber sido así:

if sCadena = '' then
raise Exception.Create( 'cadena vacia' );

Cuando se provoca una excepción la variable global ErrorAddr declarada dentro de la unidad System contiene un puntero a la dirección de memoria donde se ha provocado el error. Esta variable es de sólo lectura a título informativo.

CONTROLANDO UNA EXCEPCION SEGUN SU TIPO

Dentro de un mismo bloque de código podemos controlar que tipo de excepción queremos controlar. Por ejemplo, si ejecutamos el código:

var
s: string;
i: Integer;
begin
s := 'prueba';
i := StrToInt( s );
end;

Mostrará el error:

'prueba' not is a valid integer value

Si queremos saber el tipo de excepción podemos sacarla por pantalla:

var
s: string;
i: Integer;
begin
s := 'prueba';

try
i := StrToInt( s );
except
on E: Exception do
Application.MessageBox( PChar( E.ClassName + ': ' + E.Message ), 'Error', MB_ICONSTOP );
end;
end;

Hemos incluido en el mensaje de la excepción la clase y el mensaje de error. Este sería el resultado:

EConvertError: 'prueba' not is a valid integer value

Así, mediante la propiedad ClassName de la clase Exception podemos averiguar la clase a la que pertenece la excepción. Ahora mediante la sentencia on podemos aislar la excepción de la forma:

on tipo do sentencia

En nuestro caso sería así:

try
i := StrToInt( s );
except
on E: EConvertError do
Application.MessageBox( 'Error de conversión', 'Error', MB_ICONSTOP )
else
Application.MessageBox( 'Error desconocido', 'Error', MB_ICONSTOP );
end;

Si se produjera una excepción que no fuese de la clase EConvertError mostraría el mensaje Error desconocido.

De este modo podemos aislar dentro de un mismo bloque de código los distintos tipos de excepción que se puedan producir.

Otro ejemplo podría ser la división de dos números enteros:

try
Resultado = b div c;
except
on EZeroDivide do Resultado := MAXINT;
on EIntOverflow do Resultado := 0;
on EIntUnderflow do Resultado := 0;
end;

Aquí hemos aislado cada uno de los casos que se puedan producir al dividir dos números enteros, alterando el resultado a nuestro antojo.

En el próximo artículo seguiremos viendo más características de las excepciones.

Pruebas realizadas en Delphi 7.

Publicidad