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.

26 octubre 2007

Moviendo sprites con el teclado y el ratón

Basándome en el ejemplo del artículo anterior que mostraba como realizar el movimiento de sprites con fondo vamos a ver como el usuario puede mover los sprites usando el teclado y el ratón.

CAPTURANDO LOS EVENTOS DEL TECLADO

La clase TForm dispone de dos eventos para controlar las pulsaciones de teclado: OnKeyDown y OnKeyUp. Necesitamos ambos eventos porque no sólo me interesa saber cuando un usuario ha pulsado una tecla sino también cuando la ha soltado (para controlar las diagonales).

Para hacer esto voy a crear cuatro variables booleanas en la sección private el formulario que me van a informar de cuando están pulsadas las teclas del cursor:

type
private
{ Private declarations }
Sprite: TSprite;
Buffer, Fondo: TImage;
bDerecha, bIzquierda, bArriba, bAbajo: Boolean;

Estas variables las voy a actualizar en el evento OnKeyDown:

procedure TFormulario.FormKeyDown( Sender: TObject; var Key: Word; Shift: TShiftState );
begin
case key of
VK_LEFT: bIzquierda := True;
VK_DOWN: bAbajo := True;
VK_UP: bArriba := True;
VK_RIGHT: bDerecha := True;
end;
end;

y en el evento OnKeyUp:

procedure TFormulario.FormKeyUp( Sender: TObject; var Key: Word; Shift: TShiftState );
begin
case key of
VK_LEFT: bIzquierda := False;
VK_DOWN: bAbajo := False;
VK_UP: bArriba := False;
VK_RIGHT: bDerecha := False;
end;
end;

Al igual que hice con el ejemplo anterior voy a utilizar un temporizador (TTimer) llamado TmpTeclado con un intervalo que va a ser también de 10 milisegundos y cuyo evento OnTimer va a encargarse de dibujar el sprite en pantalla:

procedure TFormulario.TmpTecladoTimer( Sender: TObject );
begin
// ¿Ha pulsado la tecla izquierda?
if bIzquierda then
if Sprite.x > 0 then
Dec( Sprite.x );

// ¿Ha pulsado la tecla arriba?
if bArriba then
if Sprite.y > 0 then
Dec( Sprite.y );

// ¿Ha pulsado la tecla derecha?
if bDerecha then
if Sprite.x + Sprite.Imagen.Width < ClientWidth then
Inc( Sprite.x );

// ¿Ha pulsado la tecla abajo?
if bAbajo then
if Sprite.y + Sprite.Imagen.Height < ClientHeight then
Inc( Sprite.y );

DibujarSprite;
end;

Este evento comprueba la pulsación de todas las teclas controlando que el sprite no se salga del formulario. El procedimiento de DibujarSprite sería el siguiente:

procedure TFormulario.DibujarSprite;
var
Origen, Destino: TRect;
begin
// Copiamos el fondo de pantalla al buffer
Origen.Left := Sprite.x;
Origen.Top := Sprite.y;
Origen.Right := Sprite.x + Sprite.Imagen.Width;
Origen.Bottom := Sprite.y + Sprite.Imagen.Height;
Destino.Left := 0;
Destino.Top := 0;
Destino.Right := Sprite.Imagen.Width;
Destino.Bottom := Sprite.Imagen.Height;
Buffer.Canvas.CopyMode := cmSrcCopy;
Buffer.Canvas.CopyRect( Destino, Fondo.Canvas, Origen );

// Dibujamos el sprite en el buffer encima del fondo copiado
Sprite.Dibujar( 0, 0, Buffer.Canvas );

// Dibujamos el contenido del buffer a la pantalla
Canvas.Draw( Sprite.x, Sprite.y, Buffer.Picture.Graphic );
end;

Prácticamente es el mismo visto en el artículo anterior. Ya sólo hace falta poner el marcha el mecanismo que bien podría ser en el evento OnCreate del formulario:

begin
TmpTeclado.Enabled := True;
Sprite.x := 250;
Sprite.y := 150;
end;

CAPTURANDO LOS EVENTOS DEL RATON

Para capturar las coordenadas del ratón vamos a utilizar el evento OnMouseMove del formulario:

procedure TFormulario.FormMouseMove( Sender: TObject; Shift: TShiftState; X, Y: Integer );
begin
Sprite.x := X;
Sprite.y := Y;
end;

Para controlar los eventos del ratón voy a utilizar un temporizador distinto al del teclado llamado TmpRaton con un intervalo de 10 milisegundos. Su evento OnTimer sería sencillo:

procedure TFormulario.TmpRatonTimer( Sender: TObject );
begin
DibujarSprite;
end;

Aquí nos surge un problema importante: como los movimientos del ratón son más bruscos que los del teclado volvemos a tener el problema de que el sprite nos va dejando manchas en pantalla. Para solucionar el problema tenemos que restaurar el fondo de la posición anterior del sprite antes de dibujarlo en la nueva posición..

Para ello voy a guardar en la clase TSprite las coordenadas anteriores:

type
TSprite = class
public
x, y, xAnterior, yAnterior: Integer;
ColorTransparente: TColor;
Imagen, Mascara: TImage;
constructor Create;
destructor Destroy; override;
procedure Cargar( sImagen: string );
procedure Dibujar( x, y: Integer; Canvas: TCanvas );
end;

Al procedimiento DibujarSprite le vamos a añadir que restaure el fondo del sprite de la posición anterior:

procedure TFormulario.DibujarSprite;
var
Origen, Destino: TRect;
begin
// Restauramos el fondo de la posición anterior del sprite
if ( Sprite.xAnterior <> Sprite.x ) or ( Sprite.yAnterior <> Sprite.y ) then
begin
Origen.Left := Sprite.xAnterior;
Origen.Top := Sprite.yAnterior;
Origen.Right := Sprite.xAnterior + Sprite.Imagen.Width;
Origen.Bottom := Sprite.yAnterior + Sprite.Imagen.Height;
Destino := Origen;
Canvas.CopyMode := cmSrcCopy;
Canvas.CopyRect( Destino, Fondo.Canvas, Origen );
end;

// Copiamos el fondo de pantalla al buffer
Origen.Left := Sprite.x;
Origen.Top := Sprite.y;
Origen.Right := Sprite.x + Sprite.Imagen.Width;
Origen.Bottom := Sprite.y + Sprite.Imagen.Height;
Destino.Left := 0;
Destino.Top := 0;
Destino.Right := Sprite.Imagen.Width;
Destino.Bottom := Sprite.Imagen.Height;
Buffer.Canvas.CopyMode := cmSrcCopy;
Buffer.Canvas.CopyRect( Destino, Fondo.Canvas, Origen );

// Dibujamos el sprite en el buffer encima del fondo copiado
Sprite.Dibujar( 0, 0, Buffer.Canvas );

// Dibujamos el contenido del buffer a la pantalla
Canvas.Draw( Sprite.x, Sprite.y, Buffer.Picture.Graphic );

Sprite.xAnterior := Sprite.x;
Sprite.yAnterior := Sprite.y;
end;

Y finalmente activamos el temporizador que controla el ratón y ocultamos el cursor del ratón para que no se superponga encima de nuestro sprite:

begin
TmpRaton.Enabled := True;
Sprite.x := 250;
Sprite.y := 150;
ShowCursor( False );
end;

Al ejecutar el programa podeis ver como se mueve el sprite como si fuera el cursor del ratón.

Aunque se pueden hacer cosas bonitas utilizando el Canvas no os hagais muchas ilusiones ya que si por algo destaca la librería GDI de Windows (el Canvas) es por su lentitud y por la diferencia de velocidad entre ordenadores.

Para hacer cosas serías habría que irse a la librerías SDL (mi favorita), OpenGL o DirectX ( aunque hay decenas de motores gráficos 2D y 3D para Delphi en Internet que simplifican el trabajo).

Pruebas realizadas en Delphi 7.

Publicidad