Mostrando entradas con la etiqueta aplicación. Mostrar todas las entradas
Mostrando entradas con la etiqueta aplicación. Mostrar todas las entradas

19 septiembre 2008

Enviar mensajes entre aplicaciones con IdUDPClient

En el último artículo vimos como enviar mensajes entre dos aplicaciones sin utilizar ningún protocolo de comunicación cuando los dos ejecutables se encuentran en el mismo PC. Pero si el mensaje hay que enviarlo entre dos ejecutables que se encuentran en dos PC distintos estamos obligados a utilizar un protocolo de comunicación y el más sencillo es el protocolo UDP.

El protocolo UDP goza de la ventaja de que no se establece una conexión entre la IP origen y la IP destino, sino que envía la información sin saber si va a llegar o no (al contrario de TCP que si establece una conexión segura entre ambos puntos).

El protocolo UDP es utilizado comúnmente en aplicaciones P2P para envío rápido de mensajes entre clientes y servidores así como en juegos online donde es necesario transmitir las coordenadas de cada personaje en tiempo real sin que existan demoras.

CREANDO LA APLICACIÓN QUE ENVÍA EL MENSAJE

Creamos un nuevo proyecto y en el formulario principal insertamos estos componentes:


Va a constar de dos componentes TEdit para guardar la IP del destinatario y su puerto (que por defecto le hemos puesto el 80). También va a tener un componente TMemo llamado Mensaje que va a contener las líneas de texto a enviar. Luego añadimos el componente IdUDPClient que se encuentra dentro de la paleta de componentes Indy Clients y que vamos a llamar Cliente.

En el evento OnClick del botón Enviar escribimos el siguiente código:

procedure TFEnviar.BEnviarClick(Sender: TObject);
begin
Cliente.Host := IP.Text;
Cliente.Port := StrToIntDef( Puerto.Text, 0 );
Cliente.Send( Mensaje.Text );
end;

Es algo tan sencillo como pasarle la IP destino (el Host), el puerto y ejecutamos el método Send que envía la información.

El único inconveniente que tiene este método es que no sabe si el destinatario existe o si le ha llegado el mensaje, pero es muy rápido.

CREANDO LA APLICACIÓN QUE RECIBE EL MENSAJE

Volvemos a crear un nuevo proyecto con este formulario:


Sólo va a contener un componente TMemo llamado Mensaje y el componente IdUPDServer que llamaremos Servidor. Al servidor le vamos a poner en su propiedad Port el valor 80. Después lo activamos poniendo su propiedad Active a True.

Ahora escribimos este código en el evento OnUDPRead:

procedure TFRecibir.ServidorUDPRead(Sender: TObject; AData: TBytes;
ABinding: TIdSocketHandle);
begin
Mensaje.Lines.Add( 'De: ' + ABinding.PeerIP );
Mensaje.Lines.Add( 'Mensaje: ' + PChar( AData ) );
end;

Mediante la propiedad ABinding.PeerIP obtenemos la IP del que envía el mensaje y la variable AData contiene el mensaje enviado en bytes.

Al ejecutar ambos programas y pulsar el botón Enviar este sería el resultado:


Si el mensaje se va a enviar entre dos aplicaciones que se encuentran en el mismo PC sólo hay que poner como dirección IP la 127.0.0.1 y funciona perfectamente. Incluso si seguimos escribiendo mensajes se añadirán a los que ya están en el destino.

Con algo tan simple como esto ya tenemos un pequeño cliente de mensajería entre dos equipos de la red local o en Internet. Para que la conversación pudiera ser bidireccional habría que crear un solo programa que tuviera un cliente UDP y un servidor UDP. También habría que dejar a elegir al usuario que IP y puertos va a utilizar para enviar y recibir.

Si la información que vamos a enviar es confidencial también sería interesante que las cadenas de caracteres que se envíen fueran encriptadas de algún modo.

Pruebas realizadas en RAD Studio 2007.

12 septiembre 2008

ENVIAR MENSAJES ENTRE APLICACIONES SIN COMUNICACIONES

La forma más usual de copiar información entre dos aplicaciones de Windows es usando algún protocolo de comunicaciones, como TCP o UDP. Pero si la comunicación se va a efectuar entre dos aplicaciones que se encuentran en el mismo PC podemos enviar la información sin utilizar ningún protocolo de comunicación.

Para ello se utiliza un mensaje especial que tiene la API de Windows llamado WM_COPYDATA. Lo que permite este mensaje es copiar cualquier tipo de dato, ya sea texto o binario entre dos aplicaciones Windows tal como si se pasara por el portapapeles. De este modo la comunicación es instantánea y asíncrona.

Una aplicación práctica que se le puede dar a este método es la de dividir nuestro programa en varios ejecutables y se que envíen la información entre sí (siempre que estemos dentro el mismo ordenador).

CREANDO LA APLICACIÓN QUE ENVÍA EL MENSAJE

Lo primero que vamos a hacer es crear un nuevo proyecto y al formulario principal de la aplicación lo vamos a llamar FEnviar:


Este formulario sólo va a contener un objeto de la clase TMemo llamado Mensaje para escribir el texto a enviar y un botón para hacerlo (TButton). El proyecto lo podemos guardar con el nombre Enviar.exe.

Para poder transmitir un mensaje es necesario definir una estructura de datos llamada TCopyDataStruct:

TCopyDataStruct = packed record
dwData: DWORD; // de uso libre: para indicar por ejemplo el tipo de información a transmitir
cbData: DWORD; // el tamaño en bytes de los datos que se van a enviar
lpData: Pointer; // puntero a los datos que se van a enviar
end;

Esta estructura ya viene predefinida en la API original de Windows (en C) del siguiente modo:

typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;

El parámetro dwData se le deja libre al programador para que pueda utilizarlo a su propio criterio. Por ejemplo se podría utilizar para decirle al receptor el tipo de dato que vamos a enviar.

En el parámetro cbData debemos indicar el número de bytes que vamos a enviar y lpData es un puntero a los datos que enviamos.

Por último hacemos el evento OnClick del botón Enviar:

procedure TFPrincipal.BEnviarClick(Sender: TObject);
var
CopyDataStruct : TCopyDataStruct;
hReceptor: THandle;
Respuesta: integer;
begin
CopyDataStruct.dwData := 0; // use it to identify the message contents
CopyDataStruct.cbData := Mensaje.Width;
CopyDataStruct.lpData := PChar( Mensaje.Text );

// Comprobamos si existe el receptor

hReceptor := FindWindow( PChar( 'TFRecibir' ), PChar( 'Recibir' ) );

if hReceptor = 0 then
begin
ShowMessage( 'No he encontrado al receptor.' );
Exit;
end;

// Enviamos el mensaje y recogemos la respuesta del receptor

Respuesta := SendMessage( hReceptor, WM_COPYDATA, Integer( Handle ),
Integer( @CopyDataStruct ) ) ;

if Respuesta = 1234 then
ShowMessage( 'El mensaje ha sido recibido satisfactoriamente.' );
end;

Lo primero que hace es almacenar el mensaje en la estructura de datos y luego trata de buscar una ventana llamada Recibir de la clase TFRecibir (que vamos a crear ahora después) que va a recoger la información que se envía.

El programa comprueba si existe esta ventana antes de enviar la información con SendMessage. Como el receptor puede mandar cualquier número entero como respuesta, he establecido el número 1234 como mensaje de que todo ha ido bien.

CREANDO LA APLICACIÓN QUE RECIBE EL MENSAJE

Creamos un nuevo proyecto llamado Recibir.exe cuya ventana principal se llamara FRecibir (de la clase TFRecibir) y que sólo va a contener un objeto TMemo llamado Mensaje que va a recoger la información:


Aquí también tenemos que definir la estructura de datos TCopyDataStruct definida en el proyecto anterior. Otro registro que también vamos a definir es una estructura encargada de recibir de Windows el mensaje de tipo WM_COPYDATA:

TWMCopyData = packed record
Msg: Cardinal;
From: HWND;// Handle de la ventana que envía la información
CopyDataStruct: PCopyDataStruct; // datos enviados
Result: Longint; // usado para enviar la respuesta
end;

Ahora viene la parte especial del programa. Debemos definir un procedimiento dentro del formulario principal que recoja el mensaje de tipo WM_COPYDATA:

TFRecibir = class(TForm)
Mensaje: TMemo;
procedure WMCopyData( var Msg: TWMCopyData ); message WM_COPYDATA;
private
{ Private declarations }
public
{ Public declarations }
end;

Esta sería su implementación:

procedure TFRecibir.WMCopyData( var Msg: TWMCopyData );
begin
Mensaje.Text := PChar( Msg.CopyDataStruct.lpData );
Msg.Result := 1234; // Mensaje de respuesta de que todo ha ido bien
end;

En cuanto la aplicación Enviar.exe pulse el botón Enviar se ejecutará en la aplicación Recibir.exe automáticamente el procedimiento WMCopyData de manera instantánea:


Lo que he hecho en ese procedimiento es mandar al Memo llamado Mensaje el texto que hemos recibido.

Una cosa importante que hay que tener en cuenta es que si hay varios receptores de mensaje ejecutándose a la vez (Recibir.exe) entonces sólo lo recibirá el primero de ellos.

Como he mencionado al principio del artículo, este tipo de mensaje es muy útil si tenemos varios ejecutables dentro del mismo PC que necesitan enviarse mensajes de información rápidamente, ya sea como activación de procesos automáticos y como sincronización entre distintas aplicaciones.

Pruebas realizadas en RAD Studio 2007.

11 abril 2008

Cazando errores con EurekaLog

Cuando uno encuentra herramientas como esta, a veces le dan ganas de darse cabezazos contra la pared después de haber sufrido los castigos del debugger de Delphi. EurekaLog es un experto que se acopla perfectamente a cualquier versión de Delphi y que permite capturar excepciones, access violation y pérdidas de memoria todo ello en una sóla herramienta (y además sin modificar para nada las opciones de Delphi o de nuestro proyecto).

Funciona incluso con las últimas versiones de Delphi (Delphi for Win32 y RadStudio 2007). Para versiones muy antiguas de Delphi, como por ejemplo Delphi 5 a veces requiere tener instalado algún Update que otro.

Aunque sea un programa comercial podemos bajar la demo de su página oficial:

http://www.eurekalog.com/downloads.php

La última versión a fecha de este artículo es la 6.0.12, teniendo el archivo de instalación un tamaño de 12.4 MB. Antes de ejecutar la instalación es conveniente tener todos los Delphi cerrados. La instalación no tiene muchas complicaciones:

Vamos pulsado el botón Next hasta que finalice la instalación. En uno de estos pasos reconoce las distintas versiones de Delphi que tenemos instaladas, permitiendo seleccionar las que nos interesen:

Cuando termina la instalación nos da la posibilidad de visualizar un videotutorial online sobre cómo funciona EurekaLog, lo cual facilita mucho las cosas.

Ahora arrancamos Delphi y abrimos un proyecto cualquiera para probarlo. Para activar EurekaLog en nuestro proyecto hay que ejecutar las opciones Proyect -> Eureka Options:

En la ventana que aparece debemos activar la opción Activate EurekaLog. Esto hará que intercepte los errores en tiempo de ejecución sin necesidad de tener activada la opción Integrated Debugging del propio Delphi. Si también queremos interceptar los errores de pérdida de memoria debemos irnos a la sección llamada Avanced Options y seleccionar la opción Catch Memory Leaks:

Después pulsamos el botón Ok y ya tenemos EurekaLog listo para cazar bichos. Vamos a verlo con unos ejemplos.

CAPTURANDO ACCESS VIOLATIONS

Uno de los errores que más solemos cometer es el utilizar objetos que no hemos creado. Por ejemplo, voy a crear un formulario con un botón en el cual si hago clic intentaré añadir un elemento a un objeto StringList sin haberlo creado anteriormente:

procedure TForm1.Button1Click( Sender: TObject );
var S: TStringList;
begin
S.Add( 'añadiendo elemento' );
end;

Veamos como se comporta EurekaLog en este caso. Al hacer clic sobre el botón nos aparece esta ventana en vez de la típica de Access Violation:

Si pulsamos la opción click here nos mostrará esta otra ventana:

En la primera pestaña (General) nos informa de la excepción que ha provocado la aplicación así como todas las características del equipo donde se está ejecutando. Esta información es muy interesante para averiguar en que máquina esta corriendo nuestra aplicación.

En la segunda pestaña (Call Stack) podemos ver en que línea de código se ha producido el error:

Y ahora viene lo mejor de todo. Si hacemos doble clic sobre la línea azul saltará directamente a Delphi y se irá a la línea de código donde se ha producido el error. Vosotros os preguntareis ahora, ¿y que tiene de especial si el depurador de Delphi ya lo hace? Pues porque EurekaLog caza errores donde Delphi no llega. ¿Cuántos access violations os ha llevado Delphi al depurador en ensamblador? ¿Cuántas veces os ha explotado Delphi sin tener ni idea donde se ha producido el error? A mi por lo menos se me ha quedado el puntero del depurador en Application.Run y se ha quedado como Dios.

Con EurekaLog he llegado a cazar el 95% de errores de Delphi donde el debugger no llegaba. Además me sitúa el cursor exactamente en la línea de código donde se ha producido el error.

En la tercera pestaña (Modules) podemos ver nuestro ejecutable y cuantas librerías DLL hay cargadas en memoria:

En la cuarta pestaña (Processes) se ve los ejecutables que estaban corriendo en el sistema en el momento del error:

En la quinta pestaña (Assembler) nos muestra el punto de ejecución en ensamblador donde se ha quedado el procesador:

Y en la última pestaña (CPU) tenemos un volcado de los registros del procesador, la pila y la memoria del segmento actual:

Si pulsamos el botón Ok, la aplicación seguirá su curso y devolverá el control a Delphi.

AVERIGUANDO DONDE SE PRODUCEN LAS PERDIDAS DE MEMORIA

Este es otro de los agujeros negros que sufrimos los programadores de Delphi, las pérdidas de memoria (Memory Leaks). Yo hasta ahora había utilizado la unidad MemCheck.pas para cazar los errores, pero es muy incómoda su instalación y seguimiento. En este caso EurekaLog también es insuperable, cazando las perdidas de memoria al vuelo. Veamos otro ejemplo.

Voy a crear un StringList y a salirme del programa sin hacer Free del objeto:

procedure TForm1.Button1Click( Sender: TObject );
var S: TStringList;
begin
S := TStringList.Create;
S.Add( 'añadiendo elemento' );
end;

Al finalizar el programa saltará la siguiente ventana de EurekaLog:

Cuando pulsemos sobre la opción click here y nos vayamos a la pestaña Call Stack nos dirá exactamente que línea ha creado algo en memoria y no lo ha liberado:

Haciendo doble clic sobre la misma saltará a la línea de código responsable del problema:

S := TStringList.Create;

Esto deja a Delphi 2007 en ridículo en cuestión de cazar pérdidas de memoria, ya que este último IDE de CodeGear no dice ni en que unidad ni en que línea se ha producido la perdida de memoria, sólo nos dice que objetos han quedado sin liberar. Yo no se vosotros, pero para mi, eso y nada es lo mismo.

ENVIANDO LOS ERRORES POR CORREO ELECTRONICO

¿Cuántas veces os ha ocurrido que a un cliente vuestro le salta un Access Violation y a vosotros os funciona bien con la misma base de datos? Eso a mi me ha pasado un día si y otro también. Pues en ese caso enviamos el ejecutable de nuestra aplicación a nuestros clientes con EurekaLog activado y compilado, y en momento que se produzca la explosión a ellos también les aparecerá la misma ventana de EurekaLog con el error, con lo cual podrán decirnos en que unidad y línea de código se ha producido el error.

Pero no sólo eso, si no queremos complicarnos la vida, EurekaLog permite enviar por correo electrónico toda la información perteneciente al error que se ha producido. Para ello volvemos a la ventana de opciones de EurekaLog y nos situamos en la primera sección (Email & WebSend):

Seleccionamos la opción SMTP Client dentro del campo Send Mode y rellenamos el resto de campos con la cuenta de correo que vamos a utilizar para enviar los errores. Esa cuenta se puede utilizar para enviarse errores así misma, gastando pocos recursos de nuestro servidor SMTP.

Cuando a nuestro cliente le ocurra un error con nuestra aplicación le aparecerá esta ventana:

Cuando pulse el botón Send Error Report comenzará a enviar el error por correo electrónico con los datos de la cuenta que le hemos dicho (si os fijáis en la imagen se puede enviar a otro correo opcional):

Junto con el mensaje de correo llevará adjunto un archivo comprimido con zip llamado BugReport.zip. Este a su vez tiene dos archivos dentro:

Screenshot.png que contiene una imagen capturada de todo el escritorio de Windows cuando se produjo el error.

ProbandoEureka.elf (Igual que el nombre de nuestro ejecutable pero con extensión .elf) que al doble clic sobre el mismo y nos abrirá un visor con el error:

Es decir, nos manda la pantalla capturada (para saber que estaba haciendo el usuario antes de saltar el error) y el archivo de depuración con la línea de código que provocó el error.

Aunque EurekaLog sea una herramienta comercial (cuesta 99 €) sin duda merece la pena comprarla si eso va a mejorar la productividad y ahorrarnos muchos dolores de cabeza. En resumen, otra herramienta imprescindible para Delphi.

Pruebas realizadas en Delphi 7.

28 marzo 2008

El objeto Application

Todas las aplicaciones Win32 que se crean con Delphi tienen definido un objeto global accesible desde cualquier formulario o unidad llamado Application, de la clase TApplication, el cual esta definido dentro de la unidad Forms. Este objeto se encarga de encapsular todo lo referente a nuestra aplicación, evitando el tener que procesar el envío y recepción de mensajes como ocurre cuando se programa en C/C++ mediante las funciones PeekMessage y SendMessage.

De hecho, si miramos la unidad DPR de cualquier proyecto de Delphi podemos ver como el objeto Application se incializa, después llama al formulario principal y posteriormente comienza la ejecución de la aplicación mediante el método Run:

program Project1;

uses
Forms,
Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

Este código fuente cambia dependiendo de las preferencias que tengamos a través del menú Project -> Options (pestaña Forms). Todos los formularios que pongamos en la sección Auto-create forms serán los que se creen después de Application.Initialize.

Windows funciona internamente como un buzón donde van llegando mensajes y los va procesando en el orden de llegada, consiguiendo así el efecto de multitarea, aunque internamente sólo puede procesar un mensaje a la vez (menos en los últimos sistemas con procesador de dos o más núcleos).

El objeto Application nos abstrae de toda esta complejidad interna y se encarga de controlar todos los eventos que se producen en nuestra aplicación. Si queremos modificar el comportamiento de nuestra aplicación para ciertos eventos, tenemos en la pestaña Additional el componente ApplicationEvents que podemos colocar en el formulario principal de nuestra aplicación, controlando así eventos tan comunes como OnActivate, OnRestore, OnMessage, etc.

Por ejemplo, se puede utilizar el evento OnIdle para que nuestro programa realice cálculos complejos en segundo plano cuando el procesador esté desocupado, aprovechando al máximo el rendimiento de nuestro PC. También se puede utilizar el evento OnMessage para interceptar mensajes de Windows que incluso Delphi no tiene definidos en el objeto Application.

MOSTRANDO MENSAJES

Esta es una de las funciones que más se suelen utilizar del objeto Application:

function MessageBox( const Text, Caption: PChar; Flags: Longint = MB_OK ): Integer;

Esta función muestra al usuario un mensaje dentro de un cuadro de dialogo. Para mostrar un mensaje informativo sería así:

Application.MessageBox( 'Mensaje informativo.', 'Atención',
MB_ICONINFORMATION );

se vería así:


Otro mensaje sería para informar al usuario que se ha interrumpido un proceso:

Application.MessageBox( 'No ha rellenado el nombre del proveedor.',
'Acceso denegado', MB_ICONSTOP );

este sería el resultado:


También se puede hacer una pregunta al usuario por si desea realizar algo:

if Application.MessageBox( '¿Desea continuar?', 'Guardando factura',
MB_ICONQUESTION OR MB_YESNO ) = ID_YES then
....

este cuadro de diálogo tendría dos botones:

El parámetro flags de esta función permite que aparezcan los siguientes botones:

MB_ABORTRETRYIGNORE Anular, Reintentar y Omitir.
MB_OK Aceptar (por defecto).
MB_OKCANCEL Aceptar y Cancelar.
MB_RETRYCANCEL Reintentar y Cancelar.
MB_YESNO Si y No.
MB_YESNOCANCEL Si, No y Cancelar.

Entre los posibles mensajes que devuelve esta función (según lo que hagamos con flags) tenemos:

Constante Valor numérico Resultado
--------- -------------- ---------
IDOK 1 El usuario ha pulsado Aceptar.
IDCANCEL 2 El usuario ha pulsado Cancelar.
IDABORT 3 El usuario ha pulsado Abortar.
IDRETRY 4 El usuario ha pulsado Reintentar.
IDIGNORE 5 El usuario ha pulsado Ignorar.
IDYES 6 El usuario ha pulsado Si.
IDNO 7 El usuario ha pulsado No.

CONTROLANDO EL ESTADO DE LA APLICACIÓN

Tenemos los siguientes procedimientos disponibles para controlar el estado global de la aplicación:

procedure Minimize;

Este procedimiento minimiza la aplicación en la barra de tareas. Es importante tener bien claro cual es el formulario principal de la aplicación, ya que si minimizamos otra ventana provocará un efecto no deseado: se minimiza en la esquina inferior izquierda del escritorio. Para evitar esto siempre tenemos que procurar que el formulario que se pueda minimizar sea siempre el formulario principal. Si no fuera así, entonces recomiendo que toda la aplicación sea de tipo MDI.

procedure Restore;

Vuelve a mostrar la ventana principal de la aplicación si ha sido minimizada por el usuario o por el propio programa con el método Minimize.

procedure BringToFront;

Este procedimiento enfoca de nuevo nuestra aplicación superponiendo la ventana principal de nuestra aplicación a otras aplicaciones que se estén visualizando en este momento en Windows. Puede ser interesante cuando tenemos que informar al usuario de algún evento que se ha producido en nuestra aplicación (como hacen los antivirus).

procedure CreateForm( FormClass: TFormClass; var Reference );

Este es otro de los procedimientos más utilizados por el objeto Application. Crea el formulario que le pasemos como parámetro. El primer parámetro es la variable de la instancia que se va a crear y el segundo es la clase de la variable. Por ejemplo:

Application.CreateForm( FCliente, TFCliente );

procedure Terminate;

Hay que estar muy seguro para llamar a este procedimiento, ya que cierra toda la aplicación por la fuerza. Si tenemos algún formulario que haya instanciado algunos objetos que no ha liberado de memoria se podría quedar memoria desperdiciada en todo Windows.

procedure ProcessMessages;

Es importante llamar a este procedimiento cuando estamos haciendo un proceso largo que gasta bastante procesador, ya que si no lo hacemos así el usuario tiene la sensación de que la aplicación se ha colgado. Hay que llamar a este procedimiento en cada iteración del bucle.

LEYENDO DATOS DE NUESTRA APLICACION

El objeto Application también es muy útil para obtener información. Veamos que funciones pueden utilizarse para esto:

property Active: Boolean;

Esta propiedad nos dice si la aplicación esta activa y enfocada.

property ExeName: string;

Nos devuelve la ruta y nombre completo de donde se está ejecutando nuestra aplicación. Si nos interesa saber sólo la ruta se podría hacer esto:

ExtractFilePath( Application.ExeName );

property MainForm: TForm;

Nos devuelve la referencia al formulario principal de la aplicación. Si el formulario principal cambiara, no haría falta modificar el código de nuevo.

property ShowMainForm: Boolean;

Si desactivamos esta propiedad, cuando arranque el programa por primera vez no mostrará la ventana principal. Esto puede ser útil para mostrar la típica ventana de presentación antes de mostrar la pantalla principal. También puede utilizarse si vamos a crear un control de usuarios con clave de acceso y necesitamos que no muestre la ventana principal antes de que se haya validado el usuario.

property Title: string;

Modificando esta propiedad podemos cambiar en tiempo de ejecución el título de nuestra aplicación según su estado (se ve sobre todo cuando está minimizada en la barra de tareas).

property Handle: HWND;

Un handle es un identificador único que Windows da a todas las aplicaciones y ventanas que crea. Este handle (que en realidad es un valor numérico) es muy utilizado por funciones de la API de Windows. Por ello, esta propiedad es muy interesante para leer el handle de nuestra aplicación (cada vez que se ejecuta una aplicación Windows le da un handle distinto).

Aunque el objeto Application tiene más funciones y propiedades creo que hemos cubierto aquí las más importantes.


Pruebas realizadas en Delphi 7.

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.

19 julio 2007

Meter recursos dentro de un ejecutable

Una de las cosas que mas hacen engordar el tamaño de un ejecutable es meter los mismos botones con imagenes en distintos formularios. Si diseñamos nuestros propios botones Aceptar y Cancelar y los vamos replicando en cada formulario el tamaño de nuestro programa puede crecer considerablemente.

Para evitar esto lo que vamos a hacer es meter la imagen dentro de un recurso compilado y posteriormente accederemos a la misma dentro del programa. La ventaja de utilizar este método es que la imagen sólo esta una vez en el programa independientemente del número de formularios donde vaya a aparecer.

Supongamos que vamos a meter la imagen MiBoton.jpg dentro de nuestro programa. Creamos al lado de la misma el archivo imagenes.rc el cual contiene:

1 RCDATA MiBoton.jpg

Se pueden meter en un archivo de recursos tantas imagenes como se desee, así como otros tipos de archivo (sonidos, animaciones flash, etc.). Las siguientes líneas serían:

2 RCDATA imagen2.jpg
3 RCDATA imagen3.jpg
...

Ahora abrimos una ventana de símbolo del sistema dentro del mismo directorio donde este la imagen y compilamos el recurso:

c:\imagenes\brc32 -r -v imagenes.rc

Lo cual nos creará el archivo imagenes.res.

Ahora para utilizar el recurso dentro de nuestro programa hay que añadir debajo de implementation (del formulario principal de la aplicación) la directiva {$R imagenes.res}:

implementation

{$R *.dfm}
{$R imagenes.res}

Esto lo que hace es unir todos los recursos de imagenes.res con nuestro ejecutable. Para cargar la imagen dentro de un objeto TImage hacemos lo siguiente:

procedure TFPrincipal.FormCreate( Sender: TObject );
var
Recursos: TResourceStream;
Imagen: TJPegImage;
begin
Imagen := TJPegImage.Create;
Recursos := TResourceStream.Create( hInstance, '#1', RT_RCDATA );
Recursos.Seek( 0, soFromBeginning );
Imagen.LoadFromStream( Recursos );
Imagen1.Canvas.Draw( 0, 0, Imagen );
Recursos.Free;
Imagen.Free;
end;

Donde Imagen1 es un objeto TImage. Aunque pueda parecer algo molesto tiene las siguientes ventajas:

- Evita que un usuario cualquiera utilice nuestras imagenes (a menos claro que sepa utilizar un editor de recursos).

- Facilita la distribución de nuestro programa (va todo en el exe).

- Se puede añadir cualquier tipo de archivo o dato dentro del ejecutable.

- Ahorramos mucha memoria al cargar una sola vez el recurso.

Pruebas realizadas en Delphi 7.

28 junio 2007

Ejecutar un programa y esperar a que termine

Uno de los problemas habituales con los que se enfrenta un programador es que su cliente le pida algo que o bien no sabe como programarlo o no dispone del componente o librería necesaria para llevar tu tarea a cabo.

Un ejemplo puede ser realizar una copia de seguridad en formatos ZIP, RAR, 7Z, etc., convertir de un formato de video o sonido a otro e incluso llamar a comandos del sistema para realizar procesos criticos en un servidor. Entonces sólo se nos ocurre llamar a un programa externo que realice la tarea por nosostros (y que soporte parámetros).

Sé lo que estáis pensando (la función WinExec), pero en este caso no me vale ya que el programa tiene que esperar a que termine de ejecutarse antes de pasar al siguiente proceso.

Aquí os muestro un procedimiento que ejecuta un programa y se queda esperando a que termine:

function EjecutarYEsperar( sPrograma: String; Visibilidad: Integer ): Integer;
var
sAplicacion: array[0..512] of char;
DirectorioActual: array[0..255] of char;
DirectorioTrabajo: String;
InformacionInicial: TStartupInfo;
InformacionProceso: TProcessInformation;
iResultado, iCodigoSalida: DWord;
begin
StrPCopy( sAplicacion, sPrograma );
GetDir( 0, DirectorioTrabajo );
StrPCopy( DirectorioActual, DirectorioTrabajo );
FillChar( InformacionInicial, Sizeof( InformacionInicial ), #0 );
InformacionInicial.cb := Sizeof( InformacionInicial );

InformacionInicial.dwFlags := STARTF_USESHOWWINDOW;
InformacionInicial.wShowWindow := Visibilidad;
CreateProcess( nil, sAplicacion, nil, nil, False,
CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,
nil, nil, InformacionInicial, InformacionProceso );

// Espera hasta que termina la ejecución
repeat
iCodigoSalida := WaitForSingleObject( InformacionProceso.hProcess, 1000 );
Application.ProcessMessages;
until ( iCodigoSalida <> WAIT_TIMEOUT );

GetExitCodeProcess( InformacionProceso.hProcess, iResultado );
MessageBeep( 0 );
CloseHandle( InformacionProceso.hProcess );
Result := iResultado;
end;

El parámetro iVisibilidad puede ser:

SW_SHOWNORMAL -> Lo normal
SW_SHOWMINIMIZED -> Minimizado (ventanas MS-DOS o ventanas no modales)
SW_HIDE -> Oculto (ventanas MS-DOS o ventanas no modales)

La función devuelve un cero si la ejecución terminó correctamente.

Por ejemplo para ejecutar la calculadora de Windows y esperar a que termine:

procedure EjecutarCalculadora;
begin
if EjecutarYEsperar( 'C:\Windows\System32\Calc.exe', SW_SHOWNORMAL ) = 0 then
ShowMessage( 'Ejecución terminada con éxito.' )
else
ShowMessage( 'Ejecución no terminada correctamente.' );
end;

Pruebas realizadas en Delphi 7.

14 junio 2007

Cómo ocultar una aplicación

Vamos a ver como hacer que una aplicación cualquiera hecha en Delphi quede oculta de la barra de tareas de Windows y del escritorio. Sólo podrá verse ejecutando el administrador de tareas en la pestaña Procesos.

Para ello vamos a añadir en el evento OnCreate del formulario principal de nuestra aplicación lo siguiente:

procedure TFPrincipal.FormCreate(Sender: TObject);
begin
// Hacemos que el formulario sea invisible poniendolo en la
// esquina superior izquierda, tamaño cero y aplicación invisible
BorderStyle := bsNone;
Left := 0;
Top := 0;
Width := 0;
Height := 0;
Visible := False;
Application.Title := '';
Application.ShowMainForm := False;

// Lo ocultamos de la barra de tareas
ShowWindow( Application.Handle, SW_HIDE );
SetWindowLong( Application.Handle, GWL_EXSTYLE,
GetWindowLong(Application.Handle, GWL_EXSTYLE) or
WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW);
end;

Esto nos puede ser util para crear programas residentes ocultos al usuario para administración de copias de seguridad, reparación automática de bases de datos y envío de mailing automátizado.

Pruebas realizadas en Delphi 7.

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.

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

Publicidad