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.

Publicidad