11 agosto 2006

Guardando y cargando opciones

Hay ocasiones en que nos interesa que las opciones de nuestro programa permanezcan en el mismo después de terminar su ejecución. Principalmente se suelen utilizar cuatro maneras de guardar las opciones:

1º En un archivo de texto plano.

2º En un archivo binario.

3º En un archivo INI.

4º En el registro del sistema de Windows.

Vamos a suponer que tenemos un formulario de opciones con campos de tipo string, integer, boolean, date, time y real.

Los archivos de opciones se crearán en el mismo directorio en el que se ejecuta nuestra aplicación.

GUARDANDO OPCIONES EN TEXTO PLANO

Para ello utilizamos un archivo de tipo TextFile para guardar la información:

procedure TFPrincipal.GuardarTexto;
var F: TextFile;
begin
// Asignamos el archivo de opciones al puntero F
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'opciones.txt' );

// Abrimos el archivo en modo creación/escritura
Rewrite( F );

// Guardamos las opciones
WriteLn( F, IMPRESORA.Text );
WriteLn( F, IntToStr( COPIAS.Value ) );

if VISTAPREVIA.Checked then
WriteLn( F, 'CON VISTA PREVIA' )
else
WriteLn( F, 'SIN VISTA PREVIA' );

WriteLn( F, DateToStr( FECHA.Date ) );
WriteLn( F, HORA.Text );
WriteLn( F, FormatFloat( '###0.00', MARGEN.Value ) );

CloseFile( F );
end;

CARGANDO OPCIONES DESDE TEXTO PLANO

Antes de abrir el archivo comprobamos si existe:

procedure TFPrincipal.CargarTexto;
var F: TextFile;
sLinea: String;
begin
// Si no existe el archivo de opciones no hacemos nada
if not FileExists( ExtractFilePath( Application.ExeName ) + 'opciones.txt' ) then
Exit;

// Asignamos el archivo de opciones al puntero F
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'opciones.txt' );

// Abrimos el archivo en modo lectura
Reset( F );

// Cargamos las opciones
ReadLn( F, sLinea );
IMPRESORA.Text := sLinea;

ReadLn( F, sLinea );
COPIAS.Value := StrToInt( sLinea );

ReadLn( F, sLinea );
VISTAPREVIA.Checked := sLinea = 'CON VISTA PREVIA';

ReadLn( F, sLinea );
FECHA.Date := StrToDate( sLinea );

ReadLn( F, sLinea );
HORA.Text := sLinea;

ReadLn( F, sLinea );
MARGEN.Value := StrToFloat( sLinea );

CloseFile( F );
end;

GUARDANDO OPCIONES EN UN ARCHIVO BINARIO

Lo que hacemos en esta ocación es crear un registro (record) que contenga las opciones de nuestro programa. Después volcamos todo el contenido del registro en un archivo binario del mismo tipo.

En la interfaz de nuestra unidad definimos:

type
TOpciones = record
sImpresora: String[100];
iCopias: Integer;
bVistaPrevia: Boolean;
dFecha: TDate;
tHora: TTime;
rMargen: Real;
end;

Y creamos el procemimiento que lo graba:

procedure TFPrincipal.GuardarBinario;
var
// Creamos un registro y un fichero para el mismo
Opciones: TOpciones;
F: file of TOpciones;
begin
// Metemos las opciones del formulario en el registro
Opciones.sImpresora := IMPRESORA.Text;
Opciones.iCopias := COPIAS.Value;
Opciones.bVistaPrevia := VISTAPREVIA.Checked;
Opciones.dFecha := FECHA.Date;
Opciones.tHora := StrToTime( HORA.Text );
Opciones.rMargen := MARGEN.Value;

// Asignamos el archivo de opciones al puntero F
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'opciones.dat' );

// Abrimos el archivo en modo creación/escritura
Rewrite( F );

// Guardamos de golpe todas las opciones
Write( F, Opciones );

// Cerramos el fichero
CloseFile( F );
end;

CARGANDO OPCIONES DESDE UN ARCHIVO BINARIO

Utilizamos el registro creado anteriormente:

procedure TFPrincipal.CargarBinario;
var
// Creamos un registro y un fichero para el mismo
Opciones: TOpciones;
F: file of TOpciones;
begin
// Asignamos el archivo de opciones al puntero F
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'opciones.dat' );

// Abrimos el archivo en modo creación/escritura
Reset( F );

// Guardamos de golpe todas las opciones
Read( F, Opciones );

// Cerramos el fichero
CloseFile( F );

// Copiamos las opciones del registro en el formulario de opciones
IMPRESORA.Text := Opciones.sImpresora;
COPIAS.Value := Opciones.iCopias;
VISTAPREVIA.Checked := Opciones.bVistaPrevia;
FECHA.Date := Opciones.dFecha;
HORA.Text := TimeToStr( Opciones.tHora );
MARGEN.Value := Opciones.rMargen;
end;

GUARDANDO OPCIONES EN UN ARCHIVO INI

Los dos casos anteriores tienen un defecto: si ampliamos el número de opciones e intentamos cargar las opciones con el formato antiguo se puede provocar un error de E/S debido a que los formatos de texto o binario han cambiado.

Lo más flexible en este claso es utilizar un archivo INI, el cual permite agrupar opciones y asignar un nombre a cada una:

procedure TFPrincipal.GuardarINI;
var INI: TIniFile;
begin
// Creamos el archivo INI
INI := TINIFile.Create( ExtractFilePath( Application.ExeName ) + 'opciones.ini' );

// Guardamos las opciones
INI.WriteString( 'OPCIONES', 'IMPRESORA', IMPRESORA.Text );
INI.WriteInteger( 'OPCIONES', 'COPIAS', COPIAS.Value );
INI.WriteBool( 'OPCIONES', 'VISTAPREVIA', VISTAPREVIA.Checked );
INI.WriteDate( 'OPCIONES', 'FECHA', FECHA.Date );
INI.WriteTime( 'OPCIONES', 'HORA', StrToTime( HORA.Text ) );
INI.WriteFloat( 'OPCIONES', 'MARGEN', MARGEN.Value );

// Al liberar el archivo INI se cierra el archivo opciones.ini
INI.Free;
end;

CARGANDO OPCIONES DE UN ARCHIVO INI

Aunque aquí comprobamos si existe el archivo, no es necesario, ya que cargaría las opciones por defecto:

procedure TFPrincipal.CargarINI;
var INI: TIniFile;
begin
// Si no existe el archivo no hacemos nada
if not FileExists( ExtractFilePath( Application.ExeName ) + 'opciones.ini' ) then
Exit;

// Creamos el archivo INI
INI := TINIFile.Create( ExtractFilePath( Application.ExeName ) + 'opciones.ini' );

// Guardamos las opciones
IMPRESORA.Text := INI.ReadString( 'OPCIONES', 'IMPRESORA', '' );
COPIAS.Value := INI.ReadInteger( 'OPCIONES', 'COPIAS', 0 );
VISTAPREVIA.Checked := INI.ReadBool( 'OPCIONES', 'VISTAPREVIA', False );
FECHA.Date := INI.ReadDate( 'OPCIONES', 'FECHA', Date );
HORA.Text := TimeToStr( INI.ReadTime( 'OPCIONES', 'HORA', Time ) );
MARGEN.Value := INI.ReadFloat( 'OPCIONES', 'MARGEN', 0.00 );

// Al liberar el archivo INI se cierra el archivo opciones.ini
INI.Free;
end;

GUARDANDO OPCIONES EN EL REGISTRO DEL SISTEMA

Si queremos que nadie nos toque las opciones del programa podemos guardarlo todo en el registro de Windows:

procedure TFPrincipal.GuardarRegistroSistema;
var Reg: TRegistry;
begin
// Creamos un objeto para manejar el registro
Reg := TRegistry.Create;

// Guardamos las opciones
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey( '\Software\MiPrograma', True ) then
begin
Reg.WriteString( 'IMPRESORA', IMPRESORA.Text );
Reg.WriteInteger( 'COPIAS', COPIAS.Value );
Reg.WriteBool( 'VISTAPREVIA', VISTAPREVIA.Checked );
Reg.WriteDate( 'FECHA', FECHA.Date );
Reg.WriteTime( 'HORA', StrToTime( HORA.Text ) );
Reg.WriteFloat( 'MARGEN', MARGEN.Value );
Reg.CloseKey;
end;
finally
Reg.Free;
end;
end;

Para probar si se ha guardado la información pulsa el botón INICIO y opción EJECUTAR: REGEDIT. Las opciones se habrán guardado en la carpeta:

\HKEY_LOCAL_MACHINE\SOFTWARE\MiPrograma


CARGANDO OPCIONES DESDE EL REGISTRO DEL SISTEMA

Antes de cargar las opciones comprueba si existe la clave:

procedure TFPrincipal.CargarRegistroSistema;
var Reg: TRegistry;
begin
// Creamos un objeto para manejar el registro
Reg := TRegistry.Create;

// Guardamos las opciones
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey( '\Software\MiPrograma', True ) then
begin
IMPRESORA.Text := Reg.ReadString( 'IMPRESORA' );
COPIAS.Value := Reg.ReadInteger( 'COPIAS' );
VISTAPREVIA.Checked := Reg.ReadBool( 'VISTAPREVIA' );
FECHA.Date := Reg.ReadDate( 'FECHA' );
HORA.Text := TimeToStr( Reg.ReadTime( 'HORA' ) );
MARGEN.Value := Reg.ReadFloat( 'MARGEN' );
Reg.CloseKey;
end;
finally
Reg.Free;
end;
end;

Pruebas realizadas en Delphi 7

01 agosto 2006

Mejorar el aspecto de las ventanas

Voy a explicar el buen uso de un interfaz, desde el punto de vista de la distribución y alineamiento de los controles en las ventanas.

Muchos creerán que un interfaz atractiva es aquella que rediseña completamente la ventana con nuevos gráficos y miles de iconos. Y personalmente no es así. Cualquier aspecto gráfico que nos presente el sistema operativo es bueno, siempre y cuando respetemos unas reglas, que con la experiencia, he ido aprendiendo. Sin más preámbulos, comencemos la lección:

La regla de oro de un interfaz es la siguiente: EL ESPACIO.

Actualmente la resolución de pantalla se ha incrementado notablemente, sobre todo en ordenadores portátiles. Por ello, la resolución mínima indiscutible es a partir de 1024 x 768. De esta forma, no hay que tener reparo en crear ventanas con generosos espacios en blanco entre los controles. Ello da sensación de ventanas más limpias, menos agobiantes. Por supuesto, siempre y cuando la o las ventanas contengan pocos controles o no sean ventanas que ocupen toda la pantalla, cómo por ejemplo "Microsoft Outlook 2003".

Pueden aplicar las reglas que más creen oportunas, pero a modo de guía, pueden utilizar las medidas que utilizo yo para alinear y distrubir adecuadamente los controles, y presentar ventanas claras y elegantes. Las reglas son:

1. Al crear una ventana, siempre creo un rectángulo con un margen de 14 píxeles, a partir de los bordes de la ventana. De esta forma, todos los controles respetan ese marco, y mejora la sensación de alineación. Por supuesto, este marco de guía o referencia sólo tendrá utilidad en modo diseño, luego, al iniciar la aplicación, no debe aparecer.


2. A partir de aquí comienzo a colocar componentes en la ventana, siempre de arriba hacia abajo (según la importancia), y de izquierda a derecha. Y aquí entran en juego las medidas en píxeles de espacios en blanco que hay que respetar para un interfaz limpia:

* Si se muestra un comentario en la parte superior de la ventana para informar sobre la tarea que realiza, mantener 20 píxeles de espacio entre el comentario y el comienzo de los controles. También dejar 20 píxeles entre el último control mostrado y los botones de aceptar, cancelar, etc...

* Al mostrar controles no relacionados entre sí, mantener 14 píxeles de espacio.

* Al mostrar controles relacionados, mantener 8 píxeles de margen.

Imágenes de ejemplo:







Lo mostrado hasta ahora son reglas básicas para casí la mayoría de las ventanas. Pero, cuando hay páginas con pestañas, los márgenes de espacios en blanco varian. Lo mejor es dejaros estas capturas, y estudiaís las medidas:






Espero que el artículo sea de utilidad, y mejore el aspecto de vuestras aplicaciones.


Pruebas realizadas en Delphi 7.

27 julio 2006

Efectos de animación en las ventanas

En este artículo explicaré de forma detallada cómo crear animaciones para las ventanas de delphi con los mismos efectos que disponen los sistemas operativos Windows, y aclararé cuándo aplicarlos y los problemas que tienen.

La función encargada de animar ventanas es la siguiente (api de windows):

AnimateWindow

Y los parámetros que la definen son los siguientes:

hWnd - Manejador o Handle de la ventana, a la cuál se aplica el efecto.
dwTime - Velocidad para reproducir el efecto. A más tiempo, más suave y con más lentitud es el efecto.
dwFlags - Parámetros que definen el tipo de efecto, la orientación y la activación de la ventana.
Se pueden combinar varios parámetros para conseguir efectos personalizados.

Dentro del parámetro dwFlags, se pueden realizar los efectos de animación que detallo:

Tipos de efectos

AW_SLIDE

Esta es una animación de deslizamiento. Este parámetro es ignorado si se utiliza la bandera AW_CENTER. De forma predeterminada, y si no se indica este parámetro, todas las ventanas utilizan el efecto de persiana, o enrollamiento.

AW_BLEND

Aplica un efecto de aparición gradual. Recuerde utilizar este parámetro si la ventana tiene prioridad sobre las demás. Este efecto sólo funciona con Windows 2000 y Windows XP.

AW_HIDE

Oculta la ventana, sin animación. Hay que combinar con otro parámetro para que la ocultación muestre animación. Por ejemplo con AW_SLIDE o AW_BLEND.

AW_CENTER

Este efecto provoca que la ventana aparezca desde el centro de la pantalla o escritorio. Para que funcione, debe ser combinado con el parámetro AW_HIDE para mostrar la ventana, o no utilizar AW_HIDE para ocultarla.

Orientación al mostrar u ocultar

AW_HOR_POSITIVE

Animar la ventana de izquierda a derecha. Este parámetro puede ser combinado con las animaciones de deslizamiento o persiana. Si utiliza AW_CENTER o AW_BLEND, no tendrá efecto.

AW_HOR_NEGATIVE

Animar la ventana de derecha a izquierda. Este parámetro puede ser combinado con las animaciones de deslizamiento o persiana. Si utiliza AW_CENTER o AW_BLEND, no tendrá efecto.

AW_VER_POSITIVE

Animar la ventana de arriba hacia abajo. Este parámetro puede ser combinado con las animaciones de deslizamiento o persiana. Si utiliza AW_CENTER o AW_BLEND, no tendrá efecto.

AW_VER_NEGATIVE

Animar la ventana de abajo hacia arriba. Este parámetro puede ser combinado con las animaciones de deslizamiento o persiana. Si utiliza AW_CENTER o AW_BLEND, no tendrá efecto.


Otros parámetros

AW_ACTIVATE

Este parámetro traspasa el foco de activación a la ventana antes de aplicar el efecto. Recomiendo utilizarlo, sobre todo cuando las ventanas contiene algún tema de Windows XP. No utilizar con la bandera AW_HIDE.


Utilizando la función en Delphi

¿En qué evento utilizar esta función?

Normalmente, y a nivel personal y por experiencias negativas, siempre la utilizo en el evento FormShow de la ventana a la cuál aplicar el efecto. Un ejemplo sería el siguiente:

procedure TFForm1.FormShow(Sender: TObject);
begin
AnimateWindow( Handle, 400, AW_ACTIVATE or AW_SLIDE or AW_VER_POSITIVE );
end;

(Este efecto va mostrando la ventana de arriba hacia abajo con deslizamiento).


Problemas con los temas de Windows XP y las ventanas de Delphi

Naturalmente, no todo es una maravilla, y entre los problemas que pueden surgir al crear estos efectos, están los siguientes:

- Temas visuales de Windows XP:

Cuando un efecto de animación es mostrado, a veces ciertos controles de la ventana, cómo los TEdit, ComboBox, etc, no terminan de actualizarse, quedando con el aspecto antiguo de Windows 98. Para solucionar este problema, hay que escribir la función "RedrawWindow" a continuación de AnimateWindow:

procedure TFForm1.FormShow(Sender: TObject);
begin
AnimateWindow( Handle, 400, AW_ACTIVATE or AW_SLIDE or AW_VER_POSITIVE );
RedrawWindow( Handle, nil, 0, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN );
end;

- Ocultando ventanas entre ventanas de Delphi:

Por un problema desconocido de delphi (por lo menos desconozco si Delphi 2006 lo hace), ocultar una ventana con animación, teniendo otras ventanas de delphi (de tu aplicación) detrás, produce un efecto de redibujado fatal, que desmerece totalmente el efecto realizado. Esto no pasa si las ventanas que aparecen detrás no son de Delphi, o de tu propia aplicación. Por ese motivo, personalmente nunca utilizo este efecto de ocultación de ventanas.

Últimos consejos

Por último, permitidme daros un consejo. Estos efectos también son válidos en Windows 2000, pero los efectos pueden no ser tan fluidos como en Windows XP. Por ello, no estaría mal que estos efectos sean una opción de configuración de vuestra aplicación, es decir, permitir al usuario activarlos o desactivarlos.

También recomiendo no abusar de estos efectos, al final terminan siendo un poco molestos. Realizarlos en las ventanas principales es mejor que en todas las ventanas.

Espero que el artículo sea de utilidad, y dé un toque de elegancia a vuestras aplicaciones.

Pruebas realizadas en Delphi 7.

26 julio 2006

Leyendo el correo con INDY

Vamos a dividir el proceso de descargar el correo en dos partes. Primero leemos las cabeceras de nuestros mensajes y después descargamos los mensajes que nos interesen. Esto puede ser útil para descartar el correo spam desde el mismo servidor.

Vamos a crear dos componentes de la paleta INDY en tiempo real, con lo cual hay que añadir al comienzo de nuestra unidad:

uses
IdPOP3, IdMessage;

A continuación vamos a crear un procedimiento donde le pasamos los datos de nuestra cuenta de correo y vuelca el contenido en un ListView (el cual se supone que contiene tres columnas: Dirección, Asunto y Fecha/hora).

procedure LeerCorreo( sServidor, sUsuario, sClave: String; Mensajes: TListView );
var
POP3: TIdPOP3;
Mensaje: TIdMessage;
i: Integer;
begin
// creamos el objeto POP3
POP3 := TIdPOP3.Create( nil );
POP3.Host := sServidor;
POP3.Username := sUsuario;
POP3.Password := sClave;
POP3.Port := 110;

// conectamos con el servidor
try
POP3.Connect;
except
raise Exception.Create( 'Error al conectar con el servidor.' );
end;

Mensaje := TIdMessage.Create( nil );
for i := 1 to POP3.CheckMessages do
begin
// Leemos la cabecera del mensaje
Mensaje.Clear;
POP3.RetrieveHeader( i, Mensaje );

Mensajes.Items.Add;
Mensajes.Items[i-1].SubItems.Add( Mensaje.From.Address ); // dirección
Mensajes.Items[i-1].SubItems.Add( Mensaje.Subject ); // asunto
Mensajes.Items[i-1].SubItems.Add( DateTimeToStr( Mensaje.Date ) ); // Fecha-hora
end;

FreeAndNil( Mensaje );
FreeAndNil( POP3 );
end;

Una vez tenemos las cabeceras del mensaje en nuestra lista ListView supongamos que haciendo doble clic se descarga el mensaje del servidor a nuestro disco duro. Antes de eso vamos a crear una clase llamada TMensaje que contenga el mensaje descargado del servidor. La implementación de la misma sería:

type
TMensaje = class
iNumero: Integer; // Nº de mensaje dentro de nuestro buzón de correo
sServidor, sUsuario, sClave: String;
sAsunto, sMensaje: String;
Adjuntos: TStringList;
sRutaAdjuntos: String; // Ruta donde se guardaran los archivos adjuntos

constructor Create;
destructor Destroy; override;
function Descargar: Boolean;
end;

Y aquí viene la implementación de sus métodos:

constructor TMensaje.Create;
begin
Adjuntos := TStringList.Create;
end;

destructor TMensaje.Destroy;
begin
FreeAndNil( Adjuntos );
inherited;
end;

function TMensaje.Descargar: Boolean;
var
POP3: TIdPOP3;
Mensaje: TIdMessage;
i: Integer;
sAdjunto: String; // Nombre del archivo adjunto
begin
// creamos el objeto POP3
POP3 := TIdPOP3.Create( nil );
POP3.Host := sServidor;
POP3.Username := sUsuario;
POP3.Password := sClave;
POP3.Port := 110;

// Conectamos con el servidor
try
POP3.Connect;
except
raise Exception.Create( 'Error al conectar con el servidor.' );
end;

// Leemos todo el mensaje
Mensaje := TIdMessage.Create( nil );
try
POP3.Retrieve( iNumero, Mensaje );
except
raise Exception.Create( 'Error al leer el mensaje.' );
end;

// y desconectamos del servidor
POP3.Disconnect;

// Mostramos el mensaje en otro formulario
sAsunto := Mensaje.Subject;

// ¿Tiene mensajes algún mensaje adjunto?
if Mensaje.MessageParts.Count > 0 then
begin
// Leemos todas las partes del mensaje
for i := 0 to Mensaje.MessageParts.Count - 1 do
begin
// ¿Esta parte es de texto?
if ( Mensaje.MessageParts.Items[i] is TIdText ) then
sMensaje := TIdText( Mensaje.MessageParts.Items[i] ).Body.Text
else
// ¿Esta parte es un archivo binaro adjunto?
if ( Mensaje.MessageParts.Items[i] is TIdAttachment ) then
begin
// Guardamos el nombre del archivo adjunto en una variable para hacerlo más legible
sAdjunto := TIdAttachment( Mensaje.MessageParts.Items[i] ).FileName;

// Si ya existe el archivo adjunto lo borramos para que no de error
if FileExists( sRutaAdjuntos + sAdjunto ) then
DeleteFile( sRutaAdjuntos + sAdjunto );

// Guardamos el archivo adjunto y lo añadimos a la lista de adjuntos
TIdAttachment( Mensaje.MessageParts.Items[i] ).SaveToFile( sRutaAdjuntos + sAdjunto );
Adjuntos.Add( sAdjunto );
end
end
end
else
// Tomamos todo el mensaje como un mensaje de texto
sMensaje := Mensaje.Body.Text;

FreeAndNil( Mensaje );
FreeAndNil( POP3 );

Result := True;
end;

Si nos fijamos en el código fuente vemos que el método Descargar controla si el mensaje lleva archivos adjuntos o no. Aunque los mensajes de correo electrónico suelen codificarse de cuarenta leches distintas, generalmente hay dos tipos:

1º Texto plano o página web sin contenido.
2º Multiparte, donde cada parte puede ser texto plano, página web, archivo binario, etc.

En ambos casos la codificación utilizada es MIME.

Si el archivo que nos envían tiene una plantilla de página web (casi todos hoy en día) hay que complicarse un poco la vida y sacarlo mediante un navegador (browser). Eso lo dejaré para otra ocasión.

Pues bien, por último creamos el método que hace doble clic en nuestra lista de mensajes y muestra el contenido del mensaje en otro formulario:

procedure TFFormPrincipal.MensajesDblClick( Sender: TObject );
var i: Integer;
Mensaje: TMensaje;
begin
// ¿Ha selecionado un mensaje de la lista?
if Mensajes.Selected <> nil then
begin
Mensaje := TMensaje.Create;
Mensaje.iNumero := Mensajes.Selected.Index+1;
Mensaje.sServidor := Servidor.Text;
Mensaje.sUsuario := Usuario.Text;
Mensaje.sClave := Clave.Text;
Mensaje.sRutaAdjuntos := ExtractFilePath( Application.ExeName ) + 'Adjuntos\';

if Mensaje.Descargar then
begin
Application.CreateForm( TFMensaje, FMensaje );
FMensaje.Asunto.Text := Mensaje.sAsunto;
FMensaje.Mensaje.Text := Mensaje.sMensaje;

for i := 0 to Mensaje.Adjuntos.Count-1 do
FMensaje.Adjuntos.Items.Add( Mensaje.Adjuntos[i] );

FMensaje.ShowModal;
end;

FreeAndNil( Mensaje );
end;
end;

Esta es la base de un programa lector de correo POP3 aunque realmente hay que controlar muchas más cosas, como por ejemplo el borrar del servidor los mensajes ya descargados (Ahora no lo hace).


Pruebas realizadas en Delphi 7.

19 julio 2006

Enviando un correo con INDY

Vamos a crear un procedimiento para mandar correos electrónicos utilizando el componente TIdSMTP de la paleta de componentes INDY CLIENTS.

El componente no hace falta ponerlo en el formulario, ya que lo creo en tiempo real dentro del procedimiento. Sólo hace falta añadir en el apartado USES de nuestro formulario lo siguiente:

uses
IdSMTP, IdMessage;

Vamos con el procedimiento que envía un mensaje de correo electrónico:

procedure EnviarMensaje( sUsuario, sClave, sHost, sAdjunto, sAsunto, sDestino, sMensaje: String );
var SMTP: TIdSMTP;
Mensaje: TIdMessage;
Adjunto: TIdAttachment;
begin
// Creamos el componente de conexión con el servidor
SMTP := TIdSMTP.Create( nil );
SMTP.Username := sUsuario;
SMTP.Password := sClave;
SMTP.Host := sHost;
SMTP.Port := 25;
SMTP.AuthenticationType := atLogin;

// Creamos el contenido del mensaje
Mensaje := TIdMessage.Create( nil );
Mensaje.Clear;
Mensaje.From.Name := sDestino;
Mensaje.From.Address := sDestino;
Mensaje.Subject := sAsunto;
Mensaje.Body.Text := sMensaje;
Mensaje.Recipients.Add;
Mensaje.Recipients.Items[0].Address := sDestino;

// Si hay que meter un archivo adjunto lo creamos y lo asignamos al mensaje
if sAdjunto <> '' then
begin
if FileExists( sAdjunto ) then
Adjunto := TIdAttachment.Create( Mensaje.MessageParts, sAdjunto );
end
else
Adjunto := nil;

// Conectamos con el servidor SMTP
try
SMTP.Connect;
except
raise Exception.Create( 'Error al conectar con el servidor.' );
end;

// Si ha conectado enviamos el mensaje y desconectamos
if SMTP.Connected then
begin
try
SMTP.Send( Mensaje );
except
raise Exception.Create( 'Error al enviar el mensaje.' );
end;

try
SMTP.Disconnect;
except
raise Exception.Create( 'Error al desconectar del servidor.' );
end;
end;

// Liberamos los objetos creados
if Adjunto <> nil then
FreeAndNil( Adjunto );

FreeAndNil( Mensaje );
FreeAndNil( SMTP );

Application.MessageBox( 'Mensaje enviado correctamente.',
'Fin de proceso',MB_ICONINFORMATION );
end;
Y este es un ejemplo de envío de mensajes:

EnviarMensaje( 'juanito33', 'djeuE21', 'smtp.terra.es',
'c:\documento.zip', 'Te envio mi documento',
'felipe8843@terra.es', 'Adjunto archivo: documento.zip' );

Con un poco de imaginación se puede hacer que muestre el estado de la conexión en la barra de estado e incluso una barra de progreso para ver cuanto queda por terminar de enviar.

Pruebas realizadas en Delphi 7.

17 julio 2006

Conectando a pelo con INTERBASE o FIREBIRD

Aunque Delphi contiene componentes para mostrar directamente datos de una tabla, en ocasiones nos obligan a mostrar el contenido de una base de datos en una página web o en una presentación multimedia con SDL, OPENGL ó DIRECTX. En este caso, los componentes de la pestaña DATA CONTROLS no nos sirven de nada. Nos los tenemos que currar a mano.

Voy a mostraros un ejemplo de conexión con una base de datos de INTERBASE o FIREBIRD mostrando el resultado directamente dentro de un componente ListView, aunque con unas modificaciones se puede lanzar el resultado a un archivo de texto, página web, XML o lo que sea.

Lo primero es conectar con la base de datos:

function ConectarBaseDatos( sBaseDatos: String ): TIBDatabase;
var DB: TIBDatabase;
begin  DB := TIBDatabase.Create( nil );
  DB.Name := 'IB';
  DB.DatabaseName := '127.0.0.1:' + sBaseDatos;
  DB.Params.Add( 'user_name=SYSDBA' );
  DB.Params.Add( 'password=masterkey' );
  DB.SQLDialect := 3;
  DB.LoginPrompt := False;
  try
   DB.Open;
  except
   raise Exception.Create( 'No puedo conectar con INTERBASE/FIREBIRD.' + #13 + #13 + 'Consulte con el administrador del programa.' );
  end;

  Result := DB;
end;

Si nos fijamos en el procedimiento, primero se crea en tiempo real un componente de conexión a bases de datos TIBDatabase. Después le decimos con que IP va a conectar (en principio en nuestra misma máquina) y la ruta de la base de datos que es la que se le pasa al procedimiento.

Más adelante le damos el usuario y password por defecto y desactivamos en Login. Finalmente contectamos con la base de datos controlando la excepción si casca.

Un ejemplo de conexión sería:

var DB: TIBDatabase;

DB := ConectarBaseDatos( 'c:\bases\bases.gdb' ); // PARA INTERBASE Ó
DB := ConectarBaseDatos( 'c:\bases\bases.fdb' ); // PARA FIREBIRD

if DB = nil then
  Exit;

Una vez conectados a la base de datos vamos a ver como listar los registros de una tabla dentro de un ListView:

procedure ListarTabla( DB: TIBDatabase; sTabla: String; Listado: TListView );
var Campos: TStringList;
  i: Integer;
  Consulta: TIBSQL;
  Transaccion: TIBTransaction;
begin
  if DB = nil then Exit;

  // Creamos un stringlist para meter los campos de la tabla
  Campos := TStringList.Create;
  DB.GetFieldNames( sTabla, Campos );

  // Creamos una transacción para la consulta
  Transaccion := TIBTransaction.Create( nil );
  Transaccion.DefaultDatabase := DB;

  // Creamos una consulta
  Consulta := TIBSQL.Create( nil );
  Consulta.Transaction := Transaccion;
  Consulta.SQL.Add( 'SELECT * FROM ' + sTabla );
  Transaccion.StartTransaction;
  try
   Consulta.ExecQuery;
  except
   Transaccion.Rollback;
   raise;
  end;

  // Creamos en el listview una columna por cada campo
  Listado.Columns.Clear;
  Listado.Columns.Add;
  Listado.Columns[0].Width := 0;
  for i := 0 to Campos.Count - 1 do
  begin
   Listado.Columns.Add;
   Listado.Columns[i+1].Caption := Campos[i];
   Listado.Columns[i+1].Width := 100;
  end;

  // Listamos los registros
  Listado.Clear;
  while not Consulta.Eof do
  begin
   Listado.Items.Add;

   for i := 0 to Campos.Count - 1 do
    Listado.Items[Listado.Items.Count-1].SubItems.Add( Consulta.FieldByName(
Campos[i] ).AsString );

   Consulta.Next;
  end;

  // Una vez hemos terminado liberamos los objetos creados
  FreeAndNil( Campos );
  FreeAndNil( Consulta );
  FreeAndNil( Transaccion );
end;

Por supuesto, todo este proceso se puede mejorar refactorizando código y dividiendo las partes más importantes en clases más pequeñas. Haciendo muchas pruebas con objetos TIBSQL y TIBQuery me he dado cuenta que para operaciones donde se requiere velocidad los objetos TIBSQL con mucho más rápidos que los TIBQuery, aunque estos últimos son mucho más completos.

Pruebas realizadas en Delphi 7

13 julio 2006

Cómo crear un hilo de ejecución

Hay ocasiones en que necesitamos que nuestro programa realize paralelamente algún proceso secundario que no interfiera en la aplicación principal, ya que si nos metemos en bucles cerrados o procesos pesados (traspaso de ficheros, datos, etc.) nuestra aplicación se queda medio muerta (no se puede ni mover la ventana, minimizarla y menos cerrarla).

Para ello lo que hacemos es crear un hilo de ejecución heredando de la clase TThread del siguiente modo:

THilo = class( TThread )
  Ejecutar: procedure of object;
  procedure Execute; override;
end;


La definición anterior hay que colocarla dentro del apartado Type de nuestra unidad (en la sección interface). Le he añadido el procedimiento Ejecutar para poder mandarle que procedimiento queremos que se ejecute paralelamente.

En el apartado implementation de nuestra unidad redifinimos el procedimiento de la clase TThread para que llame a nuestro procedimiento Ejecutar:

procedure THilo.Execute;
begin
  Ejecutar;
  Terminate;
end;


Con esto ya tenemos nuestra clase THilo para crear todos los hilos de ejecución que nos de la gana. Ahora vamos a ver como se crea un hilo y se pone en marcha:

var
Hilo: THilo; // variable global o pública

procedure CrearHilo;
begin
  Hilo.Ejecutar := ProcesarDatos;
  Hilo.Priority := tpNormal;
  Hilo.Resume;
end;

procedure ProcesarDatos;
begin
  // Este es el procedimiento que ejecutará nuestro hilo
  // Cuidado con hacer procesos críticos aquí
  // El procesamiento paralelo de XP no es el de Linux
  // Se puede ir por las patas abajo...
end;


Si en cualquier momento queremos detener la ejecución del hilo:

Hilo.Terminate;
FreeAndNil( Hilo );


Los hilos de ejecución sólo conviene utilizarlos en procesos críticos e importantes. No es conveniente utilizarlos así como así ya que se pueden comer al procesador por los piés.

Pruebas realizadas en Delphi 7

12 julio 2006

Bienvenidos a Delphi al Límite

Esta página nace como necesidad de reunir todos los trucos de Delphi y llegar mucho más allá de lo que se pretende con este lenguaje.

Todos los trucos y piezas de código colocadas aquí los habré probado personalmente y el código fuente estará en castellano, es decir, no me limitaré a hacer Copy-Paste.

También comentaré con que versión se ha probado el código fuente incluido.

Vamos a ver hasta donde puede llegar Delphi...

Publicidad