31 agosto 2007

Trabajando con archivos de texto y binarios (IV)

En el artículo anterior vimos como movernos por un archivo binario utilizando la propiedad Position de la clase TFileStream. Ahora vamos a ver lo mismo utilizando AssignFile.

RECORRIENDO UN ARCHIVO BINARIO EN MODO LECTURA

Si utilizamos AssignFile para leer un archivo en vez de TFileStream podemos mover el puntero en cualquier dirección utilizando el procedimiento Seek:

procedure Seek( var F; N: Longint );

Este procedimiento toma como primer parámetro el puntero al archivo (F: File of Byte) y como segundo parámetro la posición donde queremos movernos, siendo cero la primera posición. Para irse al final del archivo se hace:

Seek( F, FileSize(F) )

Vamos a abrir el archivo prueba.dat de 100 KB creado anteriormente y nos vamos a ir a la posición 50 para leer 10 bytes volcando la información en un campo memo en formato hexadecimal:

var F: file of byte;
i: Integer;
Buffer: array[0..9] of Byte;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'prueba.dat' );
Reset( F );
Seek( F, 50 );
BlockRead( F, Buffer, 10 );

for i := 0 to 9 do
Memo.Text := Memo.Text + IntToHex( Buffer[i], 2 ) + ' ';

CloseFile( F );
end;

El resultado sería:

32 33 34 35 36 37 38 39 3A 3B

El único inconveniente que tiene la función Seek es que sólo funciona en modo lectura (Reset). No se puede utilizar en modo escritura (Rewrite) para irse al final del archivo y seguir añadiendo datos.

Si no sabemos donde estamos se puede utilizar la función FilePos para averiguarlo:

ShowMessage( 'posición: ' + IntToStr( FilePos( F ) ) );


LEYENDO LAS PROPIEDADES DE UN ARCHIVO

Veamos de que funciones dispone Delphi para leer los atributos de un archivo (fecha, modo de acceso. etc.):

function FileAge( const FileName: string ): Integer;

Esta función devuelve la fecha y hora de última modificación de un archivo en formato TTimeStamp. Para pasar de formato TTimeStamp a formato TDateTime utilizamos la función FileDateToDateTime. Por ejemplo:

FileDateToDateTime( FileAge( 'prueba.txt' ) ) -> devuelve la fecha y hora de modificación del archivo prueba.txt

También disponemos de una función para obtener los atributos de un archivo:

function FileGetAttr( const FileName: string ): Integer;

Devuelve un valor entero conteniendo los posibles atributos de un archivo (puede tener varios a la vez). Por ejemplo, para averiguar si el archivo esta oculto se haría lo siguiente:

var
iAtributos: Integer;
begin
if ( iAtributos and faHidden <> 0 ) and ( iAtributos and faArchive <> 0 ) then
...
end;

Esta función no sólo comprueba archivos, sino también directorios y unidades de disco. Todos los posibles valores se encuentran en binario activando el bit correspondiente. Los valores posibles son:

Constante Valor Tipo de archivo
-------------------------------------------------------------------------------------------
faReadOnly 1 Sólo lectura
faHidden 2 Oculto
faSysFile 4 Archivo del sistema
faVolumeID 8 Unidad de disco
faDirectory 16 Directorio
faArchive 32 Archivo
faSymLink 64 Enlace simbólico
faAnyFile 71 Cualquier archivo

Otra función interesante es FileSize la cual devuelve en bytes el tamaño de un archivo. El único inconveniente es que hay que abrir el archivo para averiguarlo:

var
F: File of byte;
begin
AssignFile( F, 'c:\prueba.txt' );
Reset( F );
ShowMessage( IntToStr( FileSize( F ) ) + ' bytes' );
CloseFile( F );
end;


MODIFICANDO LAS PROPIEDADES DE UN ARCHIVO

Las función para modificar las propiedad de un archivo es:

function FileSetAttr( const FileName: string; Attr: Integer ): Integer;

Si se pudo modificar el atributo del archivo devuelve un 0 o en caso de error el código del mismo. Por ejemplo para hacer un archivo de sólo lectura y oculto:

FileSetAttr( 'c:\prueba.txt', faReadOnly or faHidden );

En el próximo artículo terminaremos de ver las funciones más importates para el manejo de archivos.

Pruebas realizadas en Delphi 7.

30 agosto 2007

Trabajando con archivos de texto y binarios (III)

Vamos a ver las distintas maneras que tenemos de manejar archivos binarios.

CREANDO UN ARCHIVO BINARIO

El crear un archivo binario utilizando AssignFile no es muy diferente de crear un archivo de texto:

procedure TFPrincipal.CrearArchivoBinario;
var
F: File of byte;
i: Integer;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'prueba.dat' );
Rewrite( F );

for i := 1 to 10 do
Write( F, i );

CloseFile( F );
end;

Como se puede apreciar, la variable F representa un puntero a un tipo de archivo de bytes. Después utilizamos la función Write para ir guardando byte a byte dentro del archivo.

Cuando los archivos binarios son pequeños no hay problema pero cuando son grandes la lentitud puede ser insoportable (parece como si se hubiera colgado el programa). Cuando ocurre esto entonces hay que crear un buffer temporal para ir guardando los bytes en bloques de 1 Kb, 10 Kb, 20 Kb, etc. Hoy en día todo lo que entra y sale de un disco duro para por la memoria caché del mismo (a parte de la memoria virtual de Windows).

Vamos a ver un ejemplo de como crear un archivo binario grande (100 Kb) utilizando un buffer. Vamos a guardar en el archivo bytes consecutivos (0, 1, 2, .. 255, 0, 1, 2, ...):

procedure TFPrincipal.CrearArchivoBinarioConBuffer;
var
F: File of byte;
i, j: Integer;
b: Byte;
Buffer: array[1..1024] of byte;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'prueba.dat' );
Rewrite( F );

b := 0;
// Guardamos 100 veces el buffer de 1 KB (100 KB)
for j := 1 to 100 do
begin
for i := 1 to 1024 do
begin
Buffer[i] := b;
Inc( b );
end;

BlockWrite( F, Buffer, 1024 );
end;

CloseFile( F );
end;

Hemos creado un buffer de 1024 bytes para guardar la información mediante el procedimiento BlockWrite que toma como primer parámetro el puntero F, como segundo parámetro el buffer del cual va a guardar la información y como tercer parámetro la longitud del buffer. Cuando más grande sea nuestro buffer menos accesos a disco se necesita y más suelto va nuestro programa.

Ahora veremos como hacer lo mismo utilizando la clase TFileStream.

CREANDO UN ARCHIVO BINARIO CON LA CLASE TFILESTREAM

El parecido con la rutina anterior es casi idéntico salvo que hemos sustituido un fichero de tipo File of byte por la clase TFileStream. El método Write toma como parámetros el buffer y su longitud:

procedure TFPrincipal.CrearStreamBinario;
var F: TFileStream;
Buffer: array[0..1023] of byte;
i, j: Integer;
b: Byte;
begin
F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'prueba.dat', fmCreate );

b := 0;
// Guardamos 100 veces el buffer de 1 KB (100 KB)
for j := 1 to 100 do
begin
for i := 0 to 1023 do
begin
Buffer[i] := b;
Inc( b );
end;

F.Write( Buffer, 1024 );
end;

F.Free;
end;

MODIFICANDO UN ARCHIVO BINARIO

En un archivo que no sea de tipo TextFile no se puede utilizar el procedimiento Append para añadir datos al final del mismo. Usando AssignFile se pueden utilizar dos métodos:

- Leer la información de todo el archivo en un buffer, añadir información al final del mismo y posteriormente guardarlo todo sobrescribiendo el archivo con Rewrite.

- Otro método sería crear una copia del archivo y añadirle datos al final del mismo. Luego habría que borrar el original y sustituirlo por este nuevo.

Ambos métodos no los voy a mostrar ya que sería algo primitivo en los tiempos que estamos. Para ello nada mejor que la clase TFileStream para tratamiento de archivos binarios.

MODIFICANDO UN ARCHIVO BINARIO UTILIZANDO LA CLASE TFILESTREAM

Añadir datos a un archivo binario utilizando la clase TFileStream es tan fácil como irse al final del archivo y ponerse a escribir. De hecho sólo hemos añadido una línea de código al archivo anterior y hemos cambiado el método de apertura:

procedure TFPrincipal.AnadirStreamBinario;
var F: TFileStream;
Buffer: array[0..1023] of byte;
i, j: Integer;
b: Byte;
begin
F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'prueba.dat', fmOpenWrite );
F.Position := F.Size;

b := 0;
// Guardamos 100 veces el buffer de 1 KB (100 KB)
for j := 1 to 100 do
begin
for i := 0 to 1023 do
begin
Buffer[i] := b;
Inc( b );
end;

F.Write( Buffer, 1024 );
end;

F.Free;
end;


LEYENDO UN ARCHIVO BINARIO

Para la lectura de un archivo binario también utilizaremos un buffer para acelerar el proceso:

procedure TFPrincipal.CargarStreamBinario;
var F: TFileStream;
Buffer: array[0..1023] of byte;
begin
F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'prueba.dat', fmOpenRead );

// ¿No ha llegado al final de archivo?
while F.Position < F.Size do
begin
// Leemos un bloque de 1024 bytes
F.Read( Buffer, 1024 );
// ya tenemos un bloque de información en el buffer
// podemos hacer lo que queramos con el antes de cargar el siguiente bloque
end;

F.Free;
end;

¿Cómo sabemos cuando se termina el archivo? Lo sabemos por la variable Position que se va moviendo automáticamente a través del método Read. Cuando llegue al final (F.Size) cerramos el archivo.

Con esto llegamos a la conclusión de que para el manejo de archivos de texto lo ideal es utilizar AssignFile, StringList o campos Memo, pero para archivos binarios TFileStream cumple mejor su función.

En el próximo artículo seguiremos viendo más cosas sobre el tratamiento de archivos.

Pruebas realizadas en Delphi 7.

29 agosto 2007

Trabajando con archivos de texto y binarios (II)

Vamos a ver otra manera de manejar ficheros de texto utilizando algunas clases que lleva Delphi para hacer nuestra labor mucho más fácil.

Para ello utilizaremos la clase TFileStream que hereda de la clase TStream para manejar flujos de datos, en este caso archivos. Una de las ventanas de utilizar FileStream en vez de los clásicos métodos de pascal tales como Rewrite, WriteLn, etc. es que controla automáticamente los buffer en disco según el tamaño de los mismos en Windows.

CREANDO UN ARCHIVO DE TEXTO USANDO LA CLASE TFILESTREAM

La clase TFileStream proporciona un método rápido y flexible para el manejo de archivos. Veamos como crear un archivo detexto:

procedure TFormulario.CrearArchivoStream;
var
F: TFileStream;
s: String;
begin
F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'prueba.txt', fmCreate );
s := 'Añadiendo información al archivo de texto.' + #13 + #10;
F.Write( s[1], Length( s ) );
F.Free;
end;

El constructor Create de la clase TFileStream toma como primer parámetro la ruta del archivo y como segundo parámetro el tipo de acceso. Los tipos de acceso son:

fmCreate -> Crea un nuevo archivo. Si el archivo ya existe lo sobrescribe.
fmOpenRead -> Abre el archivo en modo de solo lectura.
fmOpenWrite -> Abre el archivo en modo de escritura.
fmOpenReadWrite -> Abre el archivo en modo lectura/escritura.

Después se graba la información mediante el método Write el cual lee el contenido de un buffer (puede ser texto o binario) pasando como segundo parámetro su longitud (Length). Finalmente liberamos la clase con Free y el mismo objeto cierra automáticamente el archivo.

Le hemos pasado como primer parámetro s[1] porque es la dirección de memoria donde comienza la variable s (los string empiezan por 1). Al final de la cadena le hemos metido cambién los caracteres de retorno de carro para que pase a la siguiente línea, ya que un objeto FileStream lo trata todo como texto continuo.

Cuando la cantidad de información a mover es muy grande, éste metodo es mucho más rápido que utilizar el clasico Rewrite, WriteLn, etc. (a menos claro que creemos nuestro buffer).


AÑADIENDO TEXTO A UN ARCHIVO CON FILESTREAM

Para añadir líneas a nuestro archivo creado anteriormente abrimos el archivo en modo fmOpenWrite, nos vamos al final del archivo utilizando la propiedad Position y añadimos el texto:

procedure TFPrincipal.AnadirArchivoStream;
var
F: TFileStream;
s: String;
begin
F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'prueba.txt', fmOpenWrite );
F.Position := F.Size;
s := 'Añadiendo información al archivo de texto.' + #13 + #10;
F.Write( s[1], Length( s ) );
F.Free;
end;

Si no se hubiese utilizado la propiedad Position habría machadado la información introducida anteriormente. Podemos movernos a nuestro antojo con la propiedad Position para guardar información en un fichero en cualquier sitio. Para irse al principio de un archivo sólo hay que hacer:

F.Position := 0;

LEYENDO LOS DATOS MEDIANTE FILESTREAM

El siguiente procedimiento lee el contenido del archivo en modo fmOpenRead en la variable s que hace de buffer y posteriormente lo manda al memo:

procedure TFPrincipal.CargarArchivoStream;
var
F: TFileStream;
s: String;
begin
F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'prueba.txt', fmOpenRead );
SetLength( s, F.Size ); // *** Me faltaba esto, gracias por la corrección ***
F.Read( s[1], F.Size );
Memo.Text := s;
F.Free;
end;

Aunque en este caso no tenemos la flexibilidad que nos aportaban las funciones ReadLn y WriteLn para archivos de texto.

BLOQUEANDO EL ARCHIVO A OTROS USUARIOS

Una de las cualidades de las que goza el objeto FileStream es la de bloquear el acceso a otros usuarios mientras estamos trabajando con un archivo. La protección se realiza cuando se abre el archivo. Por ejemplo, si queremos abrir el archivo en modo lectura exclusivamente para nosotros hacemos:

F := TFileStream.Create( ExtractFilePath( Application.ExeName ) + 'prueba.txt', fmReadOnly or fmShareExclusive );

Mediante el operador OR mezclamos las opciones de apertura con las opciones de bloqueo. Las posibles opciones de bloqueo son:

fmShareCompat -> Permite compatibilizar la apertura con otro usuario o programa
fmShareExclusive -> Sólo nosotros tenemos derecho acceso de lectura/escritura mientras este abierto
fmShareDenyWrite -> Bloqueamos el archivo para que nadie pueda escribir salvo nosotros
fmShareDenyRead -> Bloqueamos el archivo para que nadie pueda leerlo salvo nosotros
fmShareDenyNone -> Permite la lectura/escritura por parte de otros usuarios.

También se podrían manipular archivos de texto facilmente mediante objetos TStringList como vimos con anterioridad. Yo personalmente utilizo TStringList, objetos Memo o RitchEdit ya que permiten una manipulación del texto en memoria bastante flexible antes de guardarla en disco.

En el próximo artículo veremos el tratamiento de archivos binarios.

Pruebas realizadas en Delphi 7.

28 agosto 2007

Trabajando con archivos de texto y binarios (I)

Voy a mostrar a continuación las distintas maneras que tenemos para crear, editar o eliminar archivos de texto o binarios. También aprenderemos a movermos por los directorios y por dentro de los archivos obteniendo la información del sistema cuando sea necesario.

CREANDO UN ARCHIVO DE TEXTO

Los pasos para crear un archivo son los siguientes:

1º Se crea una variable de tipo TextFile, la cual es un puntero a un archivo de texto.

2º Se utiliza la función AssignFile para asignar el puntero F al archivo de texto.

3º A continuación abrimos el archivo en modo escritura mediante el procedimiento Rewrite.

4º Escrimimos en el archivo mediante la función WriteLn.

5º Cerramos el archivo creado con el procedimiento CloseFile.

Vamos a crear un procedimiento para crear el archivo prueba.txt al lado de nuestro programa:

procedure TFormulario.CrearArchivoTexto;
var
F: TextFile;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'prueba.txt' );
Rewrite( F );
WriteLn( F, 'Esto es el contenido del archivo de texto.' );
CloseFile( F );
end;

MODIFICANDO UN ARCHIVO DE TEXTO

Cuando se utiliza la función Rewrite no comprueba si el archivo ya existía anteriormente, lo que puede provocar que elimine la información anterior. Para prevenir esto lo que hacemos es preguntar si ya existe el archivo y si es así entonces añadimos al contenido del archivo mediante el procedimiento Append:

procedure TFormulario.AnadirArchivoTexto;
var
F: TextFile;
sArchivo: string;
begin
sArchivo := ExtractFilePath( Application.ExeName ) + 'prueba.txt';
AssignFile( F, sArchivo );

if FileExists( sArchivo ) then
Append( F )
else
Rewrite( F );

WriteLn( F, 'Añadiendo información al archivo de texto.' );
CloseFile( F );
end;

ABRIENDO UN ARCHIVO DE TEXTO

Vamos a abrir el archivo de texto en modo lectura para volcar su contenido en un campo memo del formulario. Para ello abrimos el archivo con el procedimiento Reset y leemos cada línea de texto mediante ReadLn:

procedure TFormulario.CargarArchivoTexto;
var F: TextFile;
sLinea: String;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'prueba.txt' );
Reset( F );

while not Eof( F ) do
begin
ReadLn( F, sLinea );
Memo.Lines.Add( sLinea );
end;

CloseFile( F );
end;

El procedimiento ReadLn obliga a leer la línea de texto dentro de una variable. Después incrementa automáticamente el puntero F hasta la siguiente línea de texto, siendo la función Eof la que nos dice si ha llegado al final del archivo. Aunque en este caso lo más fácil sería utilizar la propiedad LoadFromFile del objeto memo:

Memo.Lines.LoadFromFile( ExtractFilePath( Application.ExeName ) + 'prueba.txt' );

ELIMINANDO UN ARCHIVO

La eliminación de un archivo se hace con la función DeleteFile la cual está dentro de la unidad SysUtils:

procedure TFormulario.EliminarArchivoTexto;
var sArchivo: String;
begin
sArchivo := ExtractFilePath( Application.ExeName ) + 'prueba.txt';
if FileExists( sArchivo ) then
DeleteFile( sArchivo );
end;

Todas estas funciones (Rewrite, Append, etc.) vienen del lenguaje estándar de Pascal, ya que en Delphi hay maneras mucho más sencillas de realizar estas tareas, como veremos más adelante.

Pruebas ralizadas en Delphi 7.

27 agosto 2007

Funciones y procedimientos para fecha y hora (y III)

Sigamos con las funciones para el tratamiento de TTime, TDate y TDateTime:

function IsLeapYear( Year: Word ): Boolean;

Esta función nos dice si un año es bisiesto. Por ejemplo:

IsLeapYear( 2004 ) devuelve True
IsLeapYear( 2006 ) devuelve False
IsLeapYear( 2007 ) devuelve False
IsLeapYear( 2008 ) devuelve True
IsLeapYear( 2009 ) devuelve False

function MonthOfTheYear( const AValue: TDateTime ): Word;

Devuelve el número de mes de un año a partir de un valor TDateTime (evita el tener que utilizar DecodeTime). Por ejemplo:

MonthOfTheYear( StrToDate( '20/01/2007' ) ) devuelve 1
MonthOfTheYear( StrToDate( '27/08/2007' ) ) devuelve 8
MonthOfTheYear( StrToDate( '31/12/2007' ) ) devuelve 12


function Now: TDateTime;

Esta función dos devuelve la fecha y hora actual del sistema (reloj de Windows). Por ejemplo:

Now devuelve 27/08/2007 10:05:01


function RecodeDate( const AValue: TDateTime; const AYear, AMonth, ADay: Word ): TDateTime;

Esta función modifica la fecha de un valor TDateTime sin afectar a la hora. Por ejemplo:

var
dt: TDateTime;
begin
dt := StrToDateTime( '27/08/2007 15:21:43' );
ShowMessage( 'Fecha y hora antes de modificar: ' + DateTimeToStr( dt ) );
dt := RecodeDate( dt, 2005, 7, 26 );
ShowMessage( 'Fecha y hora después de modificadar: ' + DateTimeToStr( dt ) );
end;

Al ejecutarlo muestra:

Fecha y hora antes de modificar: 27/08/2007 15:21:43
Fecha y hora después de modificar: 26/07/2005 15:21:43


function RecodeTime( const AValue: TDateTime; const AHour, AMinute, ASecond, AMilliSecond: Word ): TDateTime;

Modifica la hora de un valor TDateTime sin afectar a la fecha. Por ejemplo:

var
dt: TDateTime;
begin
dt := StrToDateTime( '27/08/2007 15:21:43' );
ShowMessage( 'Fecha y hora antes de modificar: ' + DateTimeToStr( dt ) );
dt := RecodeTime( dt, 16, 33, 14, 0 );
ShowMessage( 'Fecha y hora después de modificar: ' + DateTimeToStr( dt ) );
end;

El resultado sería:

Fecha y hora antes de modificar: 27/08/2007 15:21:43
Fecha y hora después de modificar: 27/08/2007 16:33:14


procedure ReplaceDate( var DateTime: TDateTime; const NewDate: TDateTime );

Este procedimiento modifica la fecha de una variable TDateTime sin afectar a la hora. Por ejemplo:

var
dt: TDateTime;
begin
dt := StrToDateTime( '27/08/2007 18:15:31' );
ShowMessage( 'Fecha y hora antes de modificar: ' + DateTimeToStr( dt ) );
ReplaceDate( dt, StrToDate( '01/09/2007' ) );
ShowMessage( 'Fecha y hora después de modificar: ' + DateTimeToStr( dt ) );
end;

El resultado sería:

Fecha y hora antes de modificar: 27/08/2007 18:15:31
Fecha y hora después de modificar: 01/09/2007 18:15:31


procedure ReplaceTime( var DateTime: TDateTime; const NewTime: TDateTime );

Modifica la hora de una variable TDateTime sin afectar a la fecha. Por ejemplo:

var
dt: TDateTime;
begin
dt := StrToDateTime( '27/08/2007 19:33:22' );
ShowMessage( 'Fecha y hora antes de modificar: ' + DateTimeToStr( dt ) );
ReplaceTime( dt, StrToTime( '14:21:05' ) );
ShowMessage( 'Fecha y hora después de modificar: ' + DateTimeToStr( dt ) );
end;

El resultado sería:

Fecha y hora antes de modificar: 27/08/2007 19:33:22
Fecha y hora después de modificar: 27/08/2007 14:21:05


function Time: TDateTime;
function GetTime: TDateTime;


Estas dos funciones devuelven la hora actual en formato TDateTime. Por ejemplo:

ShowMessage( 'Time devuelve ' + TimeToStr( Time ) );
ShowMessage( 'GetTime devuelve ' + TimeToStr( GetTime ) );
ShowMessage( 'Time devuelve ' + DateTimeToStr( Time ) );
ShowMessage( 'GetTime devuelve ' + DateTimeToStr( GetTime ) );

Al ejecutarlo muestra:

Time devuelve 10:36:10
GetTime devuelve 10:36:10
Time devuelve 30/12/1899 10:36:10
GetTime devuelve 30/12/1899 10:36:10

Si os fijais bien cuando mostramos la hora en formato TDateTime vemos que la fecha esta nula (30/12/1899). Mucho cuidado con eso si no quereis que el usuario se quede con cara de flipado, sobre todo al guardar o cargar el valor de un campo de la base de datos.

function Tomorrow: TDateTime;

Nos devuelve la fecha de mañana. Por ejemplo:

ShowMessage( 'DateToStr( Tomorrow ) devuelve ' + DateToStr( Tomorrow ) );
ShowMessage( 'DateToStr( Tomorrow ) devuelve ' + DateTimeToStr( Tomorrow ) );
ShowMessage( 'DateToStr( Tomorrow ) devuelve ' + TimeToStr( Tomorrow ) );

Nos mostraría:

DateToStr( Tomorrow ) devuelve 28/08/2007
DateToStr( Tomorrow ) devuelve 28/08/2007
DateToStr( Tomorrow ) devuelve 0:00:00

Como vemos en el ejemplo sólo nos da el día de mañana, pero no la hora (0:00:00).

function Yesterday: TDateTime;

Nos devuelve el día de ayer en formato TDateTime. Por ejemplo:

DateToStr( Yesterday ) devuelve 26/08/2007

Las siguientes funciones ya las hemos visto anteriormente:

function DateToStr( Date: TDateTime ): string; overload;
function TimeToStr( Time: TDateTime ): string; overload;
function DateTimeToStr( DateTime: TDateTime ): string; overload;
function StrToDate( const S: string ): TDateTime; overload;
function StrToTime( const S: string ): TDateTime; overload;
function StrToDateTime( const S: string ): TDateTime; overload;


Pruebas realizadas en Delphi 7.

24 agosto 2007

Funciones y procedimientos para fecha y hora (II)

Vamos a seguir viendo funciones para fecha y hora:

procedure DecodeDateTime( const AValue: TDateTime; out AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond: Word );

Este procedimiento decodifica un valor TDateTime en valores enteros para el año, mes, día, hora, minutos, segundos y milisegundos. Por ejemplo:

var
wAnyo, wMes, wDia: Word;
wHora, wMinutos, wSegundos, wMilisegundos: Word;
begin
DecodeDateTime( StrToDateTime( '24/08/2007 12:34:53' ), wAnyo, wMes, wDia, wHora, wMinutos, wSegundos, wMilisegundos );
Memo.Lines.Add( 'Día: ' + IntToStr( wDia ) );
Memo.Lines.Add( 'Mes: ' + IntToStr( wMes ) );
Memo.Lines.Add( 'Año: ' + IntToStr( wAnyo ) );
Memo.Lines.Add( IntToStr( wHora ) + ' horas' );
Memo.Lines.Add( IntToStr( wMinutos ) + ' minutos' );
Memo.Lines.Add( IntToStr( wSegundos ) + ' segundos' );
Memo.Lines.Add( IntToStr( wMilisegundos ) + ' milisegundos' );
end;

Al ejecutarlo mostraría:

Día: 24
Mes: 8
Año: 2007
12 horas
34 minutos
53 segundos
0 milisegundos

function EncodeDate( Year, Month, Day: Word ): TDateTime;

Convierte los valores enteros de año, mes y día a un valor TDateTime o TDate. Por ejemplo:

EncodeDate( 2007, 8, 24 ) devuelve 24/08/2007

function EncodeTime( Hour, Min, Sec, MSec: Word ): TDateTime;

Convierte los valores enteros de hora, minutos, segundos y milisegundos en un valor TDateTime o TTime. Por ejemplo:

EncodeTime( 12, 34, 47, 0 ) devuelve 12:34:47

function EncodeDateTime( const AYear, AMonth, ADay, AHour, AMinute, ASecond, AMilliSecond: Word ):TDateTime;

Convierte los valores enteros de año, mes, día, hora, minutos, segundos y milisegundos en un valor TDateTime. Por ejemplo:

EncodeDateTime( 2007, 8, 24, 12, 34, 47, 0 ) devuelve 24/08/2007 12:34:47


function EndOfADay( const AYear, ADayOfYear: Word ): TDateTime; overload;
function EndOfADay( const AYear, AMonth, ADay: Word ): TDateTime; overload;

Devuelve un valor TDateTime que representa la última fecha y hora de un día en concreto. Por ejemplo:

EndOfADay( 2007, 8, 13 ) devuelve 13/08/2007 23:59:59
EndOfADay( 2007, 1, 1 ) devuelve 01/01/2007 23:59:59
EndOfADay( 2007, 2, 1 ) devuelve 01/02/2007 23:59:59
EndOfADay( 2007, 8, 23 ) devuelve 08/23/2007 23:59:59
EndOfADay( 2007, 1 ) devuelve 01/01/2007 23:59:59
EndOfADay( 2007, 32 ) devuelve 01/02/2007 23:59:59

NOTA IMPORTANTE: La función con tres parámetros es erronea en la versión de Delphi 7 Build 4.453. Da los siguientes valores:

EndOfADay( 2007, 8, 13 ) devuelve 12/09/2007 23:59:59 (mal)
EndOfADay( 2007, 1, 1 ) devuelve 31/01/2007 23:59:59 (mal)
EndOfADay( 2007, 2, 1 ) devuelve 28/02/2007 23:59:59 (mal)
EndOfADay( 2007, 8, 23 ) devuelve 12/09/2007 23:59:59 (mal)
EndOfADay( 2007, 1 ) devuelve 01/01/2007 23:59:59 (bien)
EndOfADay( 2007, 32 ) devuelve 01/02/2007 23:59:59 (bien)

Con lo cual es recomendable actualizarse a una versión superior (yo me he actualizado a la versión Delphi 7.0 Build 8.1 y sigue sin hacerme ni puto caso).

function EndOfAMonth( const AYear, AMonth: Word ): TDateTime;

Devuelve un valor TDateTime que representa la última fecha y hora de un mes. Por ejemplo:

EndOfAMonth( 2007, 1 ) devuelve 31/01/2007 23:59:59
EndOfAMonth( 2007, 2 ) devuelve 28/02/2007 23:59:59
EndOfAMonth( 2007, 8 ) devuelve 31/08/2007 23:59:59

function EndOfAWeek( const AYear, AWeekOfYear: Word; const ADayOfWeek: Word = 7 ): TDateTime;

Devuelve un valor TDateTime que representa la última fecha y hora de una semana. El parámetro AWeekOfYear representa el número de semana a lo largo del año. Por ejemplo:

EndOfAWeek( 2007, 1 ) devuelve 07/01/2007 23:59:59
EndOfAWeek( 2007, 2 ) devuelve 14/01/2007 23:59:59
EndOfAWeek( 2007, 3 ) devuelve 21/01/2007 23:59:59

function EndOfAYear( const AYear ): TDateTime;

Devuelve un valor TDateTime que representa la última fecha y hora del año que le pasemos. Por ejemplo:

EndOfAYear( 2007 ) devuelve 31/12/2007 23:59:59
EndOfAYear( 2008 ) devuelve 31/12/2008 23:59:59
EndOfAYear( 2009 ) devuelve 31/12/2009 23:59:59

function IncDay( const AValue: TDateTime; const ANumberOfDays: Integer = 1 ): TDateTime;

Devuelve el valor fecha y hora AValue incrementando el número de días especificado en ANumberOfDays (por defecto 1). Por ejemplo:

IncDay( StrToDate( '24/08/2007' ) ) devuelve 25/08/2007
IncDay( StrToDate( '24/08/2007' ), 10 ) devuelve 03/09/2007

function IncMonth( const Date: TDateTime; NumberOfMonths: Integer = 1 ): TDateTime;

Devuelve el valor fecha y hora AValue incrementando el número de meses especificado en NumberOfMonths (por defecto 1). Por ejemplo:

IncMonth( StrToDate( '24/08/2007' ) ) devuelve 24/09/2007
IncMonth( StrToDate( '24/08/2007' ), 3 ) devuelve 24/11/2007

procedure IncAMonth( var Year, Month, Day: Word; NumberOfMonths: Integer = 1 );

Este procedimiento es similar a la función IncMonth pero en vez de pasarle la fecha en formato TDateTime se le pasa en formato año, mes y día. Por ejemplo:

var
wAnyo, wMes, wDia: Word;
begin
wDia := 24;
wMes := 8;
wAnyo := 2007;
IncAMonth( wAnyo, wMes, wDia );
Memo.Lines.Add( 'Día: ' + IntToStr( wDia ) );
Memo.Lines.Add( 'Mes: ' + IntToStr( wMes ) );
Memo.Lines.Add( 'Año: ' + IntToStr( wAnyo ) );
end;

Al ejecutarlo obtenemos:

Día: 24
Mes: 9
Año: 2007
Día: 1
Mes: 4
Año: 2007

function IncWeek( const AValue: TDateTime; const ANumberOfWeeks: Integer = 1 ): TDateTime;

Devuelve el valor fecha y hora AValue incrementando el número de semanas especificado en ANumberOfWeeks (por defecto 1). Por ejemplo:

IncWeek( StrToDate( '24/08/2007' ) ) devuelve 31/08/2007
IncWeek( StrToDate( '24/08/2007' ), 2 ) devuelve 07/09/2007

function IncYear( const AValue: TDateTime; const ANumberOfYears: Integer = 1 ): TDateTime;

Devuelve el valor fecha y hora AValue incrementando el número de años especificado en ANumberOfYears (por defecto 1). Por ejemplo:

IncYear( StrToDate( '24/08/2007' ) ) devuelve 24/08/2008
IncYear( StrToDate( '24/08/2007' ), 2 ) devuelve 24/08/2009

En el próximo artículo veremos más funciones interesantes.

Pruebas realizadas en Delphi 7.

23 agosto 2007

Funciones y procedimientos para fecha y hora (I)

Si hay algo de lo que no podemos escapar los programadores de gestión es el tener que lidiar con campos de fecha y hora tales como cálculo de días entre fechas, averiguar en que fecha caen los días festivos, cronometrar el tiempo que tardan nuestras rutinas, etc.

Para la mayoría de funciones tenemos que añadir la unidad DateUtils:

uses
Windows, Messages, ..., DateUtils;

Veamos entonces que funciones nos pueden ser útiles para ello:

function DayOfTheMonth( const AValue: TDateTime ): Word;

Esta función extrae el número de día de una fecha en concreto sin tener que utilizar la función DecodeDate. Por ejemplo:

var Fecha: TDate;
begin
Fecha := StrToDate( '23/08/2007' );
ShowMessage( IntToStr( DayOfTheMonth( Fecha ) ); // muestra 23
end;

function DayOfTheWeek( const AValue: TDateTime ): Word;

Nos díce en que día de la semana (1-7) cae una fecha en concreto (el primer día es el Lunes, al contrario de otras funciones de fecha y hora donde el primer día es el Domingo). Por ejemplo:

Fecha := StrToDate( '5/08/2007' );
ShowMessage( IntToStr( DayOfTheWeek( Fecha ) ) ); // devuelve 7 (Domingo)
Fecha := StrToDate( '10/08/2007' );
ShowMessage( IntToStr( DayOfTheWeek( Fecha ) ) ); // devuelve 5 (Viernes)
Fecha := StrToDate( '23/08/2007' );
ShowMessage( IntToStr( DayOfTheWeek( Fecha ) ) ); // devuelve 4 (Jueves)

function DayOfWeek( Date: TDateTime ): Integer;

Esta función es igual a la función DayOfTheWeek con la diferencia de que el primer día de la semena es el Domingo. Por ejemplo:

Fecha := StrToDate( '5/08/2007' );
ShowMessage( IntToStr( DayOfWeek( Fecha ) ) ); // devuelve 1 (Domingo)
Fecha := StrToDate( '10/08/2007' );
ShowMessage( IntToStr( DayOfWeek( Fecha ) ) ); // devuelve 6 (Viernes)
Fecha := StrToDate( '23/08/2007' );
ShowMessage( IntToStr( DayOfWeek( Fecha ) ) ); // devuelve 5 (Jueves)

function DayOfTheYear( const AValue: TDateTime ): Word;

Al pasarle una fecha nos devuelve el número de día que corresponde a lo largo del año. Por ejemplo:

Fecha := StrToDate( '5/08/2007' );
ShowMessage( IntToStr( DayOfTheYear( Fecha ) ) ); // devuelve 217
Fecha := StrToDate( '10/08/2007' );
ShowMessage( IntToStr( DayOfTheYear( Fecha ) ) ); // devuelve 222
Fecha := StrToDate( '23/08/2007' );
ShowMessage( IntToStr( DayOfTheYear( Fecha ) ) ); // devuelve 235

function DaysBetween( const ANow, AThen: TDateTime ): Integer;

Devuelve el número de días que hay entre las fechas ANow y AThen. Por ejemplo:

var
Fecha1, Fecha2: TDate;
begin
Fecha1 := StrToDate( '10/08/2007' );
Fecha2 := StrToDate( '23/08/2007' );
ShowMessage( IntToStr( DaysBetween( Fecha1, Fecha2 ) ) ) ; // devuelve 13
end;

function DaysInMonth( const AValue: TDateTime ): Word;

Nos dice cuantos días tiene el mes que le pasamos como fecha. Por ejemplo:

DaysInMonth( StrToDate( '01/01/2007' ) ) devuelve 31
DaysInMonth( StrToDate( '01/02/2007' ) ) devuelve 28
DaysInMonth( StrToDate( '01/04/2007' ) ) devuelve 30

function DaysInYear( const AValue: TDateTime ): Word;

Nos dice el número de días que tiene un año según la fecha que le pasamos. Por ejemplo:

DaysInYear( StrToDate( '01/01/2007' ) ) devuelve 365

function DaySpan( const ANow, AThen: TDateTime): Double;

Devuelve el número de días de diferencia entre dos fechas y horas incluyendo la parte fraccional. Por ejemplo:

var
FechaHora1, FechaHora2: TDateTime;
begin
FechaHora1 := StrToDateTime( '10/08/2007 13:00' );
FechaHora2 := StrToDateTime( '23/08/2007 19:00' );
ShowMessage( FloatToStr( DaySpan( FechaHora1, FechaHora2 ) ) ); // devuelve 13,25
end;

procedure DecodeDate( Date: TDateTime; var Year, Month, Day: Word );

Este procedimiento convierte un valor de fecha TDate en valores enteros para el año, mes y día. Por ejemplo:

var
wAnyo, wMes, wDia: Word;
begin
DecodeDate( StrToDate( '23/08/2007' ), wAnyo, wMes, wDia );
Memo.Lines.Add( 'Día: ' + IntToStr( wDia ) );
Memo.Lines.Add( 'Mes: ' + IntToStr( wMes ) );
Memo.Lines.Add( 'Año: ' + IntToStr( wAnyo ) );
end;

Al ejecutarlo mostraría en el campo memo:

Día: 23
Mes: 8
Año: 2007

procedure DecodeTime( Time: TDateTime; var Hour, Min, Sec, MSec: Word );

Este procedimiento convierte un valor de hora TTime en valores enteros para la hora, minutos, segundos y milisegundos. Por ejemplo:

var
wHora, wMinutos, wSegundos, wMilisegundos: Word;
begin
DecodeTime( StrToTime( '14:25:37' ), wHora, wMinutos, wSegundos, wMilisegundos );
Memo.Lines.Add( IntToStr( wHora ) + ' horas' );
Memo.Lines.Add( IntToStr( wMinutos ) + ' minutos' );
Memo.Lines.Add( IntToStr( wSegundos ) + ' segundos' );
Memo.Lines.Add( IntToStr( wMilisegundos ) + ' milisegundos' );
end;

Al ejecutar este código mostraría:

14 horas
25 minutos
37 segundos
0 milisegundos

En el siguiente artículo seguiremos con más funciones para fecha y hora.

Pruebas realizadas en Delphi 7.

22 agosto 2007

Conversiones entre tipos numéricos

Hasta ahora hemos visto como pasar cualquier tipo de variable a string o al revés. Ahora vamos a ver funciones para convertir valores de un tipo numérico a otro.

PASAR NÚMEROS REALES A NÚMEROS ENTEROS

function Trunc( X: Extended ): Int64;

Esta función convierte un tipo Extended al tipo entero Int64 truncando los decimales (sin redondeos). Por ejemplo:

Trunc( 1234.5678 ) devuelve 1234

function Round( X: Extended ): Int64;

Esta función convierte un tipo Extended al tipo entero Int64 pero redondeando la parte entera según la parte decimal. Por ejemplo:

Round( 1234.5678 ) devuelve 1235
Round( 1234.4678 ) devuelve 1234

function Ceil( const X: Extended ): Integer;

Convierte un valor Extended en Integer redondeando al número entero que este mas próximo hacia arriba. Por ejemplo:

Ceil( 1234.5678 ) devuelve 1235
Ceil( 1234.4678 ) devuelve 1235
Ceil( 1235.5678 ) devuelve 1236
Ceil( 1235.4678 ) devuelve 1236

function Floor( const X: Extended ): Integer;

Convierte un valor Extended en Integer redondeando al número entero que este más próximo hacia abajo. Por ejemplo:

Floor( 1234.5678 ) devuelve 1234
Floor( 1234.4678 ) devuelve 1234
Floor( 1235.5678 ) devuelve 1235
Floor( 1235.4678 ) devuelve 1235

También tenemos una función un poco rara para extraer el exponente y la mantisa de un número real Extended:

procedure FloatToDecimal( var DecVal: TFloatRec; const Value; ValueType: TFloatValue; Precision, Decimals: Integer );

Convierte un número Extented o Currency a formato decimal guardándolo en la siguiente estructura de datos:

type TFloatRec = record
Exponent: Smallint;
Negative: Boolean;
Digits: array[0..20] of Char;
end;

Por ejemplo:

var
e: Extended;
RegistroFloat: TFloatRec;
begin
e := 1234.5678;
FloatToDecimal( RegistroFloat, e, fvExtended, 10, 4 );
end;

Al ejecutarlo da los siguientes valores:

RegistroFloat.Digits = 12345678
RegistroFloat.Negative = False
RegistroFloat.Exponent = 4

TRUNCANDO NÚMEROS REALES

function Int( X: Extended ): Extended;

Aunque esta función devuelve sólo la parte entera de un valor Extended, el valor devuelto sigue siendo Extended. El resultado es similar a la función Trunc (sin redondeos). Por ejemplo:

Int( 1234.5678 ) devuelve 1234
Int( 1234.4678 ) devuelve 1234

function Frac( X: Extended ): Extended;

Devuelve la parte fraccionaria de un valor Extended. Por ejemplo:

Frac( 1234.4678 ) devuelve 0.5678

CONVIRTIENDO VALORES EXTENDED Y CURRENCY

Como vimos anteriormente, el tipo Currency no es un auténtico formato en punto flotante sino un entero de punto fijo con 4 decimales como máximo, es decir, los valores Currency se almacenan como un entero multiplicado por 10000. Veamos de que funciones disponemos para convertir de un tipo a otro:

function FloatToCurr( const Value: Extended ): Currency;

Esta función convierte de tipo Extended a Currency. Por ejemplo:

FloatToCurr( 1234.5678 ) devuelve 1234.5678 (de tipo Currency)
FloatToCurr( 123.456789 ) devuelve 123.4568 (de tipo Currency y perdemos 2 decimales)

function TryFloatToCurr( const Value: Extended; out AResult: Currency ): Boolean;

Esta función es similar a FloatToCurr pero sólo comprueba si se puede convertir a Currency. El procedimiento sería:

var
c: Currency;
begin
if TryFloatToCurr( 123.456789, c ) then
ShowMessage( CurrToStr( c ) );
end;

CONVIRTIENDO VALORES ENTEROS A VALORES REALES

Se puede convertir un valor entero a real convirtiendo la variable directamente:

var
i: Integer;
r: Real;
begin
i := 1234;
r := i; // realiza el casting automáticamente
ShowMessage( FloatToStr( r ) ); // muestra 1234
end;

Esto no ocurre en todos los casos pero es cuestión de probar (aunque es recomendable utilizar los tipos Variant para esa labor).

Pruebas realizadas en Delphi 7.

21 agosto 2007

Convertir cualquier tipo de variable a String (y II)

Vamos a seguir viendo funciones para la conversión de cualquier tipo de variable a String:

function FloatToStr( Value: Extended ): string; overload;

Convierte un valor Extended en String. Por ejemplo:

FloatToStr( 1234.5678 ) devuelve 1234,5678

function FloatToStrF( Value: Extended; Format: TFloatFormat; Precision, Digits: Integer ): string; overload;

Convierte un valor Extended en String usando el formato, precisión y dígitos según los parámetros Format, Precision y Digits respectivamente. Por ejemplo:

FloatToStrF( 1234.5678, ffCurrency, 8, 2 ) devuelve 1.234,57 €
FloatToStrF( 1234.5678, ffCurrency, 8, 4 ) devuelve 1.234,5678 €
FloatToStrF( 1234.5678, ffGeneral, 8, 2 ) devuelve 1234,5678
FloatToStrF( 1234.5678, ffGeneral, 8, 4 ) devuelve 1234,5678
FloatToStrF( 1234.5678, ffExponent, 8, 2 ) devuelve 1,2345678E+03
FloatToStrF( 1234.5678, ffExponent, 8, 4 ) devuelve 1,2345678E+0003
FloatToStrF( 1234.5678, ffFixed, 8, 2 ) devuelve 1234,57
FloatToStrF( 1234.5678, ffFixed, 8, 4 ) devuelve 1234,5678
FloatToStrF( 1234.5678, ffNumber, 8, 2 ) devuelve 1.234,57
FloatToStrF( 1234.5678, ffNumber, 8, 4 ) devuelve 1.234,5678

function Format( const Format: string; const Args: array of const ): string; overload;

Esta función es similar al comando printf del lenguaje C. Su misión es dar múltiples formatos a una misma cadena de texto según sus argumentos. Con algunos ejemplos se verá más claro:

Format( '%d', [1234] ) devuelve -1234
Format( '%e', [1234.5678] ) devuelve 1,23456780000000E+003
Format( '%f', [1234.5678] ) devuelve 1234,57
Format( '%g', [1234.5678] ) devuelve 1234,5678
Format( '%n', [1234.5678] ) devuelve 1.234,57
Format( '%m', [1234.5678] ) devuelve 1.234,57 €
sTexto := 'Prueba';
Format( '%p', [sTexto] ) devuelve 0012FE14
Format( '%s', [sTexto] ) devuelve Prueba
Format( '%u', [1234] ) devuelve 1234
Format( '%x', [1234] ) devuelve 4D2

Cada los siguientes tipos de formato:

d = Decimal (Integer)
e = Científico
f = Punto fijo
g = General
m = Moneda
n = Número (Real)
p = Puntero (La dirección de memoria)
s = String
u = Decimal sin signo
x = Hexadecimal

Dentro de una misma cadena de formato se pueden introducir distintos parámetros:

var
sSerie: String;
iNumero: Integer;
rImporte: Real;
begin
sSerie := 'A';
iNumero := 5276;
rImporte := 120.35;
ShowMessage( Format( 'Factura nº %s-%d -> Importe %f', [sSerie, iNumero, rImporte] ) );
end;

Al ejecutarlo muestra:

Factura nº A-5276 -> Importe 120,35

De otra manera tendríamos que hacer:

ShowMessage( 'Factura nº ' + sSerie + '-' + IntToStr( iNumero ) + ' -> Importe ' + FloatToStr( rImporte ) );

function FormatCurr( const Format: string; Value: Currency ): string; overload;

Convierte un valor Currency a String según el formato determinado por el parámetro Format. Por ejemplo:

FormatCurr( '#####', 1234.5678 ) devuelve 1235
FormatCurr( '00000', 1234.5678 ) devuelve 01235
FormatCurr( '########', 1234.5678 ) devuelve 1235
FormatCurr( '00000000', 1234.5678 ) devuelve 00001235
FormatCurr( '0.####', 1234.5678 ) devuelve 1234,5678
FormatCurr( '0.00000', 1234.5678 ) devuelve 1234,56780

function FormatDateTime( const Format: string; DateTime: TDateTime ): string; overload;

Esta función es similar al procedimiento DateTimeToString para dar formato a un campo TDateTime. Por ejemplo:

var
dt: TDateTime;
begin
dt := StrToDateTime( '20/06/2007 11:27' );
FormatDateTime( 'd/m/y', dt ); // devuelve 20/6/07
FormatDateTime( 'dd/mm/yyyy', dt ); // devuelve 20/06/2007
FormatDateTime( 'dd-mm-yyyy', dt ); // devuelve 20-06-2007
end;

function FormatFloat( const Format: string; Value: Extended ): string; overload;

Convierte un valor Extended a String utilizar el formato determinado por Format. Es similar a la función FormatCurr. Por ejemplo:

FormatFloat( '#####', 1234.5678 ) devuelve 1235
FormatFloat( '00000', 1234.5678 ) devuelve 01235
FormatFloat( '########', 1234.5678 ) devuelve 1235
FormatFloat( '00000000', 1234.5678 ) devuelve 00001235
FormatFloat( '0.####', 1234.5678 ) devuelve 1234,5678
FormatFloat( '0.00000', 1234.5678 ) devuelve 1234,56780


function IntToHex( Value: Integer; Digits: Integer ): string; overload;
function IntToHex( Value: Int64; Digits: Integer ): string; overload;


Estas dos funciones convierten un valor Integer o Int64 a String en formato hexadecimal, especificando el número de dígitos a través del parámetro Digits. Por ejemplo:

IntToHex( 123456, 6 ) devuelve 01E240
IntToHex( 123456, 5 ) devuelve 1E240
IntToHex( 123456, 4 ) devuelve 1E240


function IntToStr( Value: Integer ): string; overload;
function IntToStr( Value: Int64 ): string; overload;


Ambas funciones convierten un valor Integer o Int64 a String en formato decimal. Por ejemplo:

IntToStr( 123456 ) devuelve 123456


procedure Str( X [: Width [: Decimals ]]; var S );

Este procedimiento da un formato a la cadena de texto S la cual esta en una variable. Además da la posibilidad de especificar el ancho en dígitos del número y sus decimales. Por ejemplo:

var
sTexto: String;
begin
Str( 1234, sTexto ); // sTexto = '1234'
Str( 1234:10, sTexto ); // sTexto = ' 1234'
Str( 1234.5678:10:2, sTexto ); // sTexto = ' 1234.57'
Str( 1234.5678:10:4, sTexto ); // sTexto = ' 1234.5678'
end;

function WideCharToString( Source: PWideChar ): string;

Convierte una cadena de texto Unicode (16 bits) a String (8 bits). Por ejemplo:

var
Unicode: array[0..4] of WideChar;
sTexto: String;
begin
Unicode[0] := 'H';
Unicode[1] := 'o';
Unicode[2] := 'l';
Unicode[3] := 'a';
Unicode[4] := #0; // Terminamos la cadena
sTexto := WideCharToString( Unicode );
end;

Pruebas realizadas en Delphi 7.

20 agosto 2007

Convertir cualquier tipo de variable a String (I)

Vamos a ver de que funciones dispone Delphi para pasar cualquier tipo de variable a una cadena de texto (String):

function CurrToStr( Value: Currency ): string; overload;

Convierte un valor Currency a String. Por ejemplo:

CurrToStr( 1234.5678 ) devuelve 1234,5678

function CurrToStrF( Value: Currency; Format: TFloatFormat; Digits: Integer ): string; overload;

Convierte un valor Currency a String usando un formato específico determinado por el parámetro Format y por el número de dígitos Digits. Por ejemplo:

CurrToStrF( 1234.5678, ffCurrency, 2 ) devuelve 1.234,57 €
CurrToStrF( 1234.5678, ffCurrency, 4 ) devuelve 1.234,5678 €
CurrToStrF( 1234.5678, ffCurrency, 0 ) devuelve 1.235 €

function DateTimeToStr( DateTime: TDateTime ): string; overload;

Convierte un valor TDateTime a String. Por ejemplo:

var
dt: TDateTime;
begin
dt := StrToDateTime( '20/06/2007 11:27' );
ShowMessage( DateTimeToStr( dt ) );
dt := StrToDateTime( '20/06/2007 00:00' );
ShowMessage( DateTimeToStr( dt ) );
end;

El resultado de ejecutar esto sería:

20/06/2007 11:27:00
20/06/2007

procedure DateTimeToString( var Result: string; const Format: string; DateTime: TDateTime ); overload;

Convierte un valor TDateTime a String usando un formato especificado por el parámetro Format y devolviendo el valor en la variable Result. Por ejemplo:

var
dt: TDateTime;
s: String;
begin
dt := StrToDateTime( '20/06/2007 11:27' );
DateTimeToString( s, 'd/m/y', dt );
ShowMessage( s );
DateTimeToString( s, 'dd/mm/yy', dt );
ShowMessage( s );
DateTimeToString( s, 'dd/mm/yyyy', dt );
ShowMessage( s );
DateTimeToString( s, 'dd-mm-yyyy', dt );
ShowMessage( s );
DateTimeToString( s, 'ddd, d ''de'' mmm ''de'' yyyy', dt );
ShowMessage( s );
DateTimeToString( s, 'dddd, d ''de'' mmmm ''de'' yyyy', dt );
ShowMessage( s );
DateTimeToString( s, 'dd/mm/yyyy hh:nn:ss', dt );
ShowMessage( s );
DateTimeToString( s, 'dd/mm/yyyy hh:nn:ss am/pm', dt );
ShowMessage( s );
DateTimeToString( s, 'ddddd', dt );
ShowMessage( s );
DateTimeToString( s, 'dddddd', dt );
ShowMessage( s );
end;

Mostraría los siguientes resultados:

20/6/07
20/06/07
20/06/2007
20-06-2007
mié, 20 de jun del año 2007
miércoles, 20 de junio del año 2007
20/06/2007 11:27:00
20/06/2007
miércoles, 20 de junio de 2007

Estas son todas las letras para dar formato a un campo fecha/hora:

y = Los dos últimos dígitos del año (sin completar con cero por la izquierda)
yy = Los dos últimos dígitos del año (completando con cero por la izquierda)
yyyy = Los 4 dígitos del año
m = Los dos dígitos del mes (sin completar con cero por la izquierda)
mm = Los dos dígitos del mes (completando con cero por la izquierda)
mmm = El nombre del mes en formato corto (Ene, Feb, Mar, etc.)
mmmm = El nombre del mes en formato largo (Enero, Febrero, Marzo, etc.)
d = Los dos dígitos del día (sin completar con cero por la izquierda)
dd = Los dos dígitos del día (completando con cero por la izquierda)
ddd = El nombre del día en formato corto (Lun, Mar, Mié, etc.)
dddd = El nombre del día en formato largo (Lunes, Martes, Miercoles, etc.)
ddddd = Fecha en formato abreviado (20/06/2007)
dddddd = Fecha en formato extendido (miércoles, 20 de junio de 2007)

c = Formato corto de fecha y hora (20/06/2007 11:27:00)
h = Los dos dígitos de la hora (sin completar con cero por la izquierda)
hh = Los dos dígitos de la hora (completando con cero por la izquierda)
n = Los dos dígitos de los minutos (sin completar con cero por la izquierda)
nn = Los dos dígitos de los minutos (completando con cero por la izquierda)
s = Los dos dígitos de los segundos (sin completar con cero por la izquierda)
ss = Los dos dígitos de los segundos (completando con cero por la izquierda)
z = Los dígitos de los milisegundos (sin completar con cero por la izquierda)
zzz = Los 3 dígitos de los segundos (completando con cero por la izquierda)
t = Formato abreviado de hora (11:27)
tt = Formato extendido de hora (11:27:00)

am/pm = Formato de hora am/pm
a/p = Formato de hora a/p
ampm = Igual que a/p pero con TimeAMString, TimePMString
/ = Sustituido por el valor de DateSeparator
: = Sustituido por el valor de TimeSeparator

Y estas son las variables de los formatos predeterminados para fecha/hora:

DateSeparator = /
TimeSeparator = :
ShortDateFormat = dd/mm/yyyy
LongDateFormat = dd mmm yyyy
TimeAMString = AM
TimePMString = PM
ShortTimeFormat = hh:mm
LongTimeFormat = hh:mm:ss
ShortMonthNames = Jan Feb ...
LongMonthNames = Enero, Febrero ...
ShortDayNames = Lun, Mar ...
LongDayNames = Lunes, Martes ...
TwoDigitCenturyWindow = 50

function DateToStr( Date: TDateTime ): string; overload;

Convierte un valor TDateTime a String. Por ejemplo:

var
d: TDate;
begin
d := StrToDate( '20/08/2007' );
ShowMessage( DateToStr( d ) ); // Mostraría 20/08/2007
end;

En el próximo artículo continuaremos viendo más funciones de conversión.

Pruebas realizadas en Delphi 7.

17 agosto 2007

Convertir una variable String a otro tipo

Delphi posee un amplio repertorio de funciones para convertir una cadena de texto a otro tipo de variable. Veamos de que funciones disponemos:

function StringToWideChar( const Source: string; Dest: PWideChar; DestSize: Integer ): PWideChar;

Esta función devuelve la cadena Source en formato de caractéres unicode. Por ejemplo:

var
sTexto: String;
PWS: PWideChar;
begin
sTexto := 'Convirtiendo texto a caracteres Unicode (16 bits)';
New( PWS );
StringToWideChar( sTexto, PWS, Length( sTexto ) + 1 );
ShowMessage( PWS );
Dispose( PWS );
end;

function StrToCurr( const S: string ): Currency; overload;

Convierte una cadena de texto S a un valor Currency. Por ejemplo:

StrToCurr( '1234,5678' ) devuelve 1234,5678
StrToCurr( '1234,,5678' ) provoca el error: '1234,,5678' is not a valid floating point value

function StrToCurrDef( const S: string; const Default: Currency ): Currency; overload;

Esta función es igual que StrToCurr aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToCurrDef( '1234,5678', 0 ) devuelve 1234,5678
StrToCurrDef( '1234,,5678', 0 ) devuelve 0 y no provoca ningún error

function StrToDate( const S: string ): TDateTime; overload;

Convierte la cadena de texto S a un valor de fecha. Por ejemplo:

StrToDate( '17/06/2007' ) devuelve 17/06/2007
StrToDate( '17-06-2007' ) provoca el error: '17-06-2007' is not valid date

function StrToDateDef( const S: string; const Default: TDateTime ): TDateTime; overload;

Esta función es igual que StrToDate aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToDateDef( '17/06/2007', StrToDate( '1/1/2007' ) ) devuelve 17/06/2007
StrToDateDef( '17-06-2007', StrToDate( '1/1/2007' ) ) devuelve 01/01/2007 y no provoca ningún error
StrToDateDef( '17/15/2007', StrToDate( '1/1/2007' ) ) devuelve 01/01/2007 y no provoca ningún error

function StrToDateTime( const S: string ): TDateTime; overload;

Convierte la cadena de texto S a un valor de fecha y hora. Por ejemplo:

StrToDateTime( '17/06/2007 12:25' ) devuelve 17/06/2007 12:25:00
StrToDateTime( '17-06-2007 12:25' ) provoca el error: '17-06-2007 12:25' is not a valid date an time

function StrToDateTimeDef( const S: string; const Default: TDateTime ): TDateTime; overload;

Esta función es igual que StrToDateTime aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToDateTimeDef( '17/06/2007 12:25', StrToDateTime( '1/1/2007 0:0:0' ) ) devuelve 17/06/2007 12:25:00
StrToDateTimeDef( '17-06-2007 12:25', StrToDateTime( '1/1/2007 0:0:0' ) ) devuelve 01/01/2007 12:00:00 sin errores
StrToDateTimeDef( '17/15/2007 12:25', StrToDateTime( '1/1/2007 0:0:0' ) ) devuelve 01/01/2007 12:00:00 sin errores

function StrToFloat( const S: string ): Extended; overload;

Convierte la cadena de texto S a un valor Extended. Por ejemplo:

StrToFloat( '1234,56789' ) devuelve 1234,56789
StrToFloat( '1234,,56789' ) provoca el error: '1234,,56789' is not valid floating point value

function StrToFloatDef( const S: string; const Default: Extended ): Extended; overload;

Esta función es igual que StrToFloat aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToFloatDef( '1234,56789', 0 ) devuelve 1234,56789
StrToFloatDef( '1234,,56789', 0 ) devuelve 0 y no provoca ningún error

function StrToInt( const S: string ): Integer;

Convierte la cadena de texto S a un valor Integer (permite caracteres hexadecimales). Por ejemplo:

StrToInt( '1234' ) devuelve 1234
StrToInt( '$FFFF' ) devuelve 65535
StrToInt( 'FFFF' ) provoca el error: 'FFFF' is not a valid integer value

function StrToIntDef( const S: string; const Default: Integer ): Integer;

Esta función es igual que StrToInt aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToIntDef( '1234', 0 ) devuelve 1234
StrToIntDef( '$FFFF', 0 ) devuelve 65535
StrToIntDef( 'FFFF', 0 ) devuelve 0 y no provoca ningún error
StrToIntDef( '', 0 ) devuelve 0 y no provoca ningún error

function StrToInt64( const S: string ): Int64;

Convierte la cadena de texto S a un valor Int64 (permite caracteres hexadecimales). Por ejemplo:

StrToInt64( '12345678' ) devuelve 12345678
StrToInt64( '$FFFFFFFF' ) devuelve 4294967295

function StrToInt64Def( const S: string; Default: Int64 ): Int64;

Esta función es similar a StrToInt64 aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToInt64Def( '12345678', 0 ) devuelve 12345678
StrToInt64Def( '$FFFFFFFF', 0 ) devuelve 4294967295
StrToInt64Def( 'FFFFFFFF', 0 ) devuelve 0 y no provoca níngun error
StrToInt64Def( '', 0 ) devuelve 0 y no provoca níngun error

function StrToTime( const S: string ): TDateTime; overload;

Convierte la cadena de texto S a un valor TTime o TDateTime. Por ejemplo:

StrToTime( '12:32:54' ) devuelve 12:32:54
StrToTime( '12:32' ) devuelve 12:32:00
StrToTime( '12:32:67' ) provoca el error: '12:32:67' is not a valid time

function StrToTimeDef( const S: string; const Default: TDateTime ): TDateTime; overload;

Esta función es igual a StrToTime aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToTimeDef( '12:32:54', StrToTime( '00:00:00' ) ) devuelve 12:32:54
StrToTimeDef( '12:32', StrToTime( '00:00:00' ) ) devuelve 12:32:00
StrToTimeDef( '12:32:67', TimeToStr( '00:00:00' ) ) devuelve 0:00:00 y no provoca ningún error

function StrToBool( const S: string ): Boolean;

Convierte la cadena de texto S a un valor Boolean. Por ejemplo:

StrToBool( 'True' ) devuelve True
StrToBool( 'False' ) devuelve False
StrToBool( 'Fals' ) provoca el error: 'Fals' is not valid boolean value

function StrToBoolDef( const S: string; const Default: Boolean ): Boolean;

Esta función es igual a StrToBool aceptando además como segundo parámetro el valor que deseamos que tenga por defecto en el caso de que falle la conversión. Por ejemplo:

StrToBoolDef( 'True', False ) devuelve True
StrToBoolDef( 'False', False ) devuelve False
StrToBoolDef( 'Tru', False ) devuelve False y no provoca ningún error
StrToBoolDef( 'Fals', False ) devuelve False y no provoca ningún error

procedure Val( S; var V; var Code: Integer );

Este procedimiento convierte la cadena de texto S a un valor numérico. Si la conversión falla nos devuelve en la variable Code la posición en la cadena de texto S donde está el error. Por ejemplo:

var
s: String;
e: Extended;
begin
s := '1234.5678';
Val( s, e, iCodigo );

if iCodigo = 0 then
ShowMessage( 'Conversión correcta: ' + FloatToStr( e ) )
else
ShowMessage( 'Error en la posición: ' + IntToStr( iCodigo ) )
end;

Al ejecutar este bloque de código devolverá:

Conversion correcta: 1234,5678

Pero si sustituimos la primera línea por:

s := '1234,5678';

Entonces mostrará el mensaje:

Error en la posición 5.

Aquí hay que tener mucho cuidado, porque al contrario que la función FloatToStr si ponemos la notación decimal igual que la tenemos en Windows provocará un error. En este caso la notación correcta es el punto y no la coma (se podría solucionar cambiando la variable DecimalSeparator).

Pruebas realizadas en Delphi 7.

16 agosto 2007

El objeto StringList (y III)

IMPORTAR Y EXPORTAR ELEMENTOS DE LA LISTA

Se pueden guardar los elementos de la lista en un archivo de texto utilizando para ello el método SaveToFile:

procedure SaveToFile( const FileName: string ); virtual;

Por ejemplo vamos a guardar nuestra lista de clientes en el mismo directorio donde ejecutamos el programa:

Clientes.SaveToFile( ExtractFilePath( Application.ExeName ) + 'clientes.txt' );

Si más adelante deseamos recuperar el listado utilizamos el procedimiento:

procedure LoadFromFile( const FileName: string ); virtual;

Por ejemplo:

Clientes.LoadFromFile( ExtractFilePath( Application.ExeName ) + 'clientes.txt' );

Si tenemos dos listas en memoria también podemos copiar elementos de una lista a otra. Por ejemplo:

var
Animales, Animales2: TStringList;
begin
Animales := TStringList.Create;
Animales2 := TStringList.Create;

Animales.Add( 'PERRO' );
Animales.Add( 'GATO' );
Animales.Add( 'LEÓN' );

Animales2.Add( 'VACA' );
Animales2.Add( 'PATO' );
Animales2.Add( 'ÁGUILA' );

// Añadimos a la lista de ANIMALES el contenido de ANIMALES2 y ordenamos la lista
Animales.AddStrings( Animales2 );
Animales.Sort;

// Mostramos el resultado en un campo Memo
Memo.Lines.Add( Animales.Text );

Animales2.Free;
Animales.Free;
end;

El resultado de la lista Animales después de ordenarla sería:

ÁGUILA
GATO
LEÓN
PATO
PERRO
VACA

Otra de las cosas que se pueden hacer es asignar los elementos de una lista a otra, eliminando los elementos de la lista original (sería algo así como copiar y pegar). Por ejemplo si queremos sustituir todos los elementos de la lista Animales por los de Animales2 habría que hacer:

Animales.Assign( Animales2 );

lo cual elimina todos los elementos de la lista Animales e inserta todos los de Animales2.

LISTAS ORDENADAS AUTOMÁTICAMENTE

Anteriormente vimos como una lista podía ordenarse con el método Sort, pero resulta algo molesto y lento tener que volver a ordenar la lista cada vez que se inserta, modifica o elimina un elemento de la misma. Para evitar eso los objetos StringList disponen de la propiedad booleana Sorted la cual una vez esta activa ordenará todos los elementos de la lista automáticamente independientemente de las operaciones que se realicen en la misma.

Vamos a crear una nueva lista ordenada:

var
Vehiculos: TStringList;
begin
Vehiculos := TStringList.Create;
Vehiculos.Sorted := True;
Vehiculos.Add( 'RENAULT' );
Vehiculos.Add( 'PEUGEOT' );
Vehiculos.Add( 'MERCEDES' );
Memo.Lines.Add( Vehiculos.Text );
Vehiculos.Free;
end;

Al ejecutar este código mostrará en el campo Memo los elemento ya ordenados:

MERCEDES
PEUGEOT
RENAULT

Tanto si insertamos o eliminamos elementos, la lista seguirá ordenada:

Vehiculos.Delete( 3 );
Vehiculos.Add( 'BMW' );

Quedaría se la siguiente manera:

BMW
MERCEDES
PEUGEOT

Lo que no se puede hacer en una lista ordenada es modificar sus elementos:

Vehiculos[1] := 'AUDI';

Ya que provocaría el error:

Operation not allowed on sorted list (operación no permitida en una lista ordenada).

Para ello habría que desconectar la propiedad Sorted, modificar el elemento y volver a activarla:

Vehiculos.Sorted := False;
Vehiculos[1] := 'AUDI';
Vehiculos.Sorted := True;

Ahora si tendríamos el efecto deseado:

AUDI
BMW
PEUGEOT

Otra de las operaciones que no se pueden hacer en una lista ordenada es añadir elementos duplicados. En el caso anterior si hacemos:

Vehiculos.Add( 'BMW' )

no hará absolutamente nada. Para poder añadir elementos duplicados en una lista ordenada hay que modificar la propiedad Duplicates la cual puede contener los siguientes valores:

dupIgnore -> Ignora el añadir elementos duplicados (así es como está por defecto)
dupAccept -> Acepta elementos duplicados
dupError -> Da un error al añadir elementos duplicados

Si hacemos lo siguiente:

Vehiculos.Duplicates := dupAccept;
Vehiculos.Add( 'BMW' );

Entonces si funcionará correctamente:

AUDI
BMW
BMW
PEUGEOT

Pero si activamos:

Vehiculos.Duplicates := dupError;
Vehiculos.Add( 'BMW' );

Nos mostrará el mensaje:

String list does not allow duplicates (StringList no acepta duplicados).

ASOCIANDO OBJETOS A LOS ELEMENTOS DE UNA LISTA

Una de las características más importantes de una lista TStringList es la de asociar a cada elemento de la lista la instacia de un objeto creado. Vamos a ver un ejemplo donde voy a crear la clase TProveedor y voy a utilizarla para crear una lista de proveedores donde cada elemento tiene su propio objeto:

type
TProveedor = class
sNombre: String;
cSaldo: Currency;
bPagado: Boolean;
end;

var
Proveedores: TStringList;
Proveedor: TProveedor;
begin
Proveedores := TStringList.Create;

Proveedor := TProveedor.Create;
with Proveedor do
begin
sNombre := 'SUMINISTROS PALAZÓN';
cSaldo := 1200.24;
bPagado := False;
end;

Proveedores.AddObject( 'B78234539', Proveedor );

Proveedor := TProveedor.Create;
with Proveedor do
begin
sNombre := 'ELECTRIDAUTON, S.L.';
cSaldo := 2460.32;
bPagado := True;
end;

Proveedores.AddObject( 'B98654782', Proveedor );

Proveedor := TProveedor.Create;
with Proveedor do
begin
sNombre := 'FONTANERIA MARTINEZ, S.L.';
cSaldo := 4800.54;
bPagado := False;
end;

Proveedores.AddObject( 'B54987374', Proveedor );
end;

Hemos creado una lista de proveedores e instanciado tres proveedores asociandolos a la lista mediante el C.I.F. del proveedor. Ahora bien, ¿como accedemos a cada proveedore de la lista? Pues muy fácil. El objeto StringList dispone de la propiedad Objects la cual nos devuelve la referencia a un objeto en cuestión. Por ejemplo vamos a buscar a FONTANERIA MARTINEZ por su C.I.F.:

var
iProveedor: Integer;
begin
iProveedor := Proveedores.IndexOf( 'B54987374' );

if iProveedor > -1 then
begin
Proveedor := Proveedores.Objects[iProveedor] as TProveedor;
Memo.Lines.Add( '' );
Memo.Lines.Add( 'BUSCANDO AL PROVEEDOR: B54987374' );
Memo.Lines.Add( '' );
Memo.Lines.Add( 'NOMBRE: ' + Proveedor.sNombre );
Memo.Lines.Add( 'SALDO: ' + CurrToStr( Proveedor.cSaldo ) );
Memo.Lines.Add( 'PAGADO: ' + BoolToStr( Proveedor.bPagado, True ) );
end;
end;

El resultado sería:

BUSCANDO AL PROVEEDOR: B54987374

NOMBRE: FONTANERIA MARTINEZ, S.L.
SALDO: 4800,54
PAGADO: False

Esto es algo mucho más útil que utilizar un array:

var
Proveedores: array of TProveedor;
....

ya que es mucho más flexible y no hay que preocuparse por los huecos al añadir o eliminar registros. Esto nos permite volcar información de registros de la base de datos a memoria mapeando el contenido de un registro (Clientes, Facturas, etc) dentro de su clase correspondiente (Por ejemplo: se podrían cargar una cantidad indeterminada de albaranes del mismo cliente para sumarlos y pasarlos a factura).

Para añadir objetos a un StringList aparte de la función AddObject se pueden insertar objetos mediante el procedimiento:

procedure InsertObject( Index: Integer; const S: string; AObject: TObject ); override;

el cual inserta el elemento en la posición que deseemos desplazando el resto de la lista. Otra de las ventajas de asociar objetos a un StringList en vez de utilizar un array es que al liberarlo de memoria también elimina cada instancia de objeto asociado a cada elemento de la lista:

Proveedores.Free;

no teniendo así que preocuparnos con errores de liberación de memoria.

Con esto terminados de vez las características más importantes de la clase TStringList.

Pruebas realizadas en Delphi 7.

15 agosto 2007

El objeto StringList (II)

Vamos a seguir viendo las características de la clase TStringList.

ELIMINANDO ELEMENTOS DE LA LISTA

Con el método Delete se elimina un elementos de la lista la cual vuelve a reagrupar sus elemento asignando un nuevo índice. Actualmente la lista de clientes contiene:

CARLOS ABENZA MARTINEZ
PASCUAL CAMPOY FERNANDEZ
MARIA PALAZÓN PÉREZ
PEDRO SANCHEZ GARCIA
ROSA GUILLÉN LUNA
LUIS GARCIA ROJO
MANUEL ESCUDERO BERNAL

Vamos a eliminar a ROSA de la lista:

Clientes.Delete( 4 );

Ahora el cliente LUIS tiene un índice de 4 (en vez de 5 como antes), es decir, al contrario de un array dinámico, si en un objeto StringList se elimina un elemento de la lista no queda ningún hueco, ya que vuelven a agruparse sus elementos.

Un error común para eliminar todos los elementos de la lista sería hacer lo siguiente:

var
i: Integer;
begin
for i := 0 to Clientes.Count - 1 do
Clientes.Delete( i );
end;

Nos daría el error:

List index out of bounds(3)

¿Por qué ocurre esto? Pues por la sencilla razón de que cada que se elimina un elemento, al reagruparse el resto de los elementos se va también disminuyendo el número de los mismos y sus índices, con lo cual cuando intenta acceder a un elemento que ya no existe provoca un error. La manera correcta de hacerlo sería:

var
i: Integer;
begin
for i := 0 to Clientes.Count - 1 do
Clientes.Delete( 0 );
end;

Como vemos arriba se elimina sólo el primer elemento y como se van eliminando como si fuera una pila de platos al final desaparecen todos. Naturalmente una clase StringList dispone del método Clear que elimina todos los elementos de la lista:

Clientes.Clear;

MOVIENDO ELEMENTOS EN LA LISTA

Supongamos que tenemos la anterior lista de clientes:

CARLOS ABENZA MARTINEZ
PASCUAL CAMPOY FERNANDEZ
MARIA PALAZÓN PÉREZ
PEDRO SANCHEZ GARCIA
LUIS GARCIA ROJO
MANUEL ESCUDERO BERNAL

Vamos a mover a MARIA a la primera posición de la lista:

Clientes.Move( 2, 0 );

El comando Move acepta como primer parámetro en que posición esta el elemento que deseamos mover y como segundo parámetro su destino. En el caso anterior llevamos a MARIA de la posición 2 a la 0:

MARIA PALAZÓN PÉREZ
CARLOS ABENZA MARTINEZ
PASCUAL CAMPOY FERNANDEZ
PEDRO SANCHEZ GARCIA
LUIS GARCIA ROJO
MANUEL ESCUDERO BERNAL

Ahora bien, no hay que confundirse el mover elementos en la lista con intercambiarlos. El mover un elemento sólo lo elimina de la posición actual y lo lleva a la posición deseada, pero mantiene el orden natural de la lista. Si lo que queremos es intercambiar dos elementos para ello disponemos del método Exchange el cual tiene los mismos parámetros que Move salvo que lo que hace es intercambiar las posiciones de ambos elementos.

Vamos a ver como cambiar la posición de MANUEL (que es la 5) con la de CARLOS (que es la 1):

Clientes.Exchange( 5, 1 );

Su resultado sería:

MARIA PALAZÓN PÉREZ
MANUEL ESCUDERO BERNAL
PASCUAL CAMPOY FERNANDEZ
PEDRO SANCHEZ GARCIA
LUIS GARCIA ROJO
CARLOS ABENZA MARTINEZ


BUSCANDO ELEMENTOS EN LA LISTA

Se puede averiguar la posición de un elemento en la lista con con los métodos IndexOf e IndexOfName:

function IndexOf( const S: string ): Integer; override;
function IndexOfName(const Name: string): Integer; virtual;


El método IndexOf toma como parámetro el nombre del elemento que se desea buscar y devuelve su índice si lo encuentra (el primer elemento es el cero) y -1 si no lo encuentra. Por ejemplo:

Clientes.IndexOf( 'PEDRO SANCHEZ GARCIA' ) devuelve 3
Clientes.IndexOf( 'PEDRO SANCHEZ' ) devuelve -1

La función IndexOf sólo busca el primer elemento encontrado en la lista, de modo que si hay dos elementos iguales sólo muestra el índice del primero (de ahí la importancia de tener la lista ordenada).

Después tenemos la función IndexOfName destinada a buscar elementos de tipo NOMBRE = VALOR como vimos anteriormente con el diccionario:

Diccionario.IndexOfName( 'BOOK' ) devuelve 0
Diccionario.IndexOfName( 'MOUSE' ) devuelve 1
Diccionario.IndexOfName( 'TABLE' ) devuelve 2

Por último tenemos la función Find utilizada para buscar elementos sólo si la lista esta ordenada. Por ejemplo:

var
iPosicion: Integer;
bEncontrado: Boolean;
begin
Clientes.Sort; // ordenamos la lista
bEncontrado := Clientes.Find( 'PEDRO SANCHEZ GARCIA', iPosicion );
end;

La función Find es similar a función IndexOf añadiendo como segundo parámetro una variable donde depositar la posición del elemento buscado y cuyo valor devuelto en la función será True o False dependiendo si ha encontrado el elemento en la lista.

Después de ejecutar este código la lista quedaría de la siguiente manera:

CARLOS ABENZA MARTINEZ
LUIS GARCIA ROJO
MANUEL ESCUDERO BERNAL
MARIA PALAZÓN PÉREZ
PASCUAL CAMPOY FERNANDEZ
PEDRO SANCHEZ GARCIA

La variable bEncontrado valdría True y iPosicion sería igual a 5.

En el próximo artículo terminaremos de ver todas las funcionalidades de un StringList.

Pruebas realizadas en Delphi 7.

14 agosto 2007

El objeto StringList (I)

Un objeto de la clase TStringList es muy útil para procesar listas de cadenas de texto en memoria. Cada elemento de la lista puede ordenarse, insertar nuevos elementos, eliminar otros, etc.

La lista pueder ser construida de vacío o bien se pueden cargar los elementos desde un archivo de texto separando cada elemento por comas.

La clase TStringList hereda de la clase TStrings, aunque no se recomienda esta última por ser algo incompleta.

CREANDO UNA LISTA

Veamos un sencillo ejemplo para crear una lista de clientes:

var Clientes: TStringList;
begin
Clientes := TStringList.Create;
Clientes.Add( 'PEDRO SANCHEZ GARCIA' );
Clientes.Add( 'MARIA PALAZÓN PÉREZ' );
Clientes.Add( 'CARLOS ABENZA MARTINEZ' );
Clientes.Add( 'ANA GONZALEZ RUIZ' );
end;

ACCEDIENDO A LOS ELEMENTOS DE LA LISTA

A los elementos de la lista se accede como si fuera un array dinámico (el primer elemento es el 0). Si escribimos:

ShowMessage( Clientes[2] );

nos mostrará CARLOS ABENZA MARTINEZ.

En cualquier momento se puede modificar un elemento de la lista:

Clientes[3] := 'ROSA GUILLÉN LUNA';

Se puede recorrer cada elemento y mostrarlo en un campo Memo:

var
i: Integer;
begin
for i := 0 to Clientes.Count - 1 do
Memo.Lines.Add( Clientes[i] );
end;

Como muestra arriba se puede averiguar el número de elementos de la lista mediante la propiedad Count. Aunque hay una manera mucho más fácil de mostrar los elementos. Un objeto StringList dispone de la propiedad Text que nos devuelve todos los elementos de la lista separados en líneas. Por ejemplo:

ShowMessage( Clientes.Text ) nos devuelve:

PEDRO SANCHEZ GARCIA
MARIA PALAZÓN PÉREZ
CARLOS ABENZA MARTINEZ
ROSA GUILLÉN LUNA

También podemos obtener cada elemento separado por comas:

ShowMessage( Clientes.CommaText ) nos devuelve:

"PEDRO SANCHEZ GARCIA","MARIA PALAZÓN PÉREZ","CARLOS ABENZA MARTINEZ","ROSA GUILLÉN LUNA"

Nos ha devuelto el resultado separando cada elemento con comas además de encerrar cada uno con comillas dobles. Si nos interesa cambiar las comillas dobles por comillas simples, un StringList dispone de la propiedad QuoteChar la cual define el carácter delimitador de la cadena. Después solo hay que consultar el resultado mediante el valor DelimitedText en lugar de Text:

Clientes.QuoteChar := ''''; // o también Clientes.QuoteChar := #39;
ShowMessage( Clientes.DelimitedText );

El resultado sería:

'PEDRO SANCHEZ GARCIA','MARIA PALAZÓN PÉREZ','CARLOS ABENZA MARTINEZ','ROSA GUILLÉN LUNA'

Y por supuesto se puede cambiar el separador de los elementos de la lista (la coma):

Clientes.Delimiter := '-';
ShowMessage( Clientes.DelimitedText );

Lo cual mostraría:

'PEDRO SANCHEZ GARCIA'-'MARIA PALAZÓN PÉREZ'-'CARLOS ABENZA MARTINEZ'-'ROSA GUILLÉN LUNA'

ALMACENANDO VALORES DOBLES

Otra característica que hace extremadamente potente a los StringList es la posibilidad de almacenar valores dobles, es decir, cada elemento de la lista puede contener un nombre y un valor a la vez. Veamos un ejemplo:

var
Diccionario: TStringList;
begin
Diccionario := TStringList.Create;
Diccionario.CommaText := 'BOOK=LIBRO, MOUSE=RATON, TABLE=MESA';
Memo.Lines.Add( 'BOOK = ' + Diccionario.Values['BOOK'] );
Memo.Lines.Add( 'MOUSE = ' +Diccionario.Values['MOUSE'] );
Memo.Lines.Add( 'TABLE = ' +Diccionario.Values['TABLE'] );
Diccionario.Free;
end;

El resultado que nos mostraría dentro del campo Memo sería:

BOOK = LIBRO
MOUSE = RATON
TABLE = MESA

Se podría también utilizar para almacenar un registro de una base de datos en memoria antes de ejecutar la SQL:

var
Tabla: TStringList;
begin
Tabla := TStringList.Create;
Tabla.CommaText := 'ID=1, NOMBRE=CARLOS, DNI=65872841R, SALDO=130.25';
Memo.Lines.Add( 'ID = ' + Tabla.Values['ID'] );
Memo.Lines.Add( 'NOMBRE = ' + Tabla.Values['NOMBRE'] );
Memo.Lines.Add( 'DNI = ' +Tabla.Values['DNI'] );
Memo.Lines.Add( 'SALDO = ' + Tabla.Values['SALDO'] );
Tabla.Free;
end;

Su resultado sería:

ID = 1
NOMBRE = CARLOS
DNI = 65872841R
SALDO = 130.25

OPERACIONES QUE SE PUEDEN REALIZAR DENTRO DE UNA LISTA

Una de las operaciones más básicas de una lista es la ordenación mediante el comando Sort:

Clientes.Sort;
Memo.Lines.Add( Clientes.Text );

La lista quedaría de la siguiente manera:

CARLOS ABENZA MARTINEZ
MARIA PALAZÓN PÉREZ
PEDRO SANCHEZ GARCIA
ROSA GUILLÉN LUNA

Para añadir elementos hemos visto el comando Add:

Clientes.Add( 'LUIS GARCIA ROJO' );

También se puede utilizar el comando Append:

Clientes.Append( 'MANUEL ESCUDERO BERNAL' );

La diferencia esta en que Add devuelve la posición donde se ha insertado el elemento, en cambio Append no devuelve nada. La lista quedaría de la siguiente manera:

CARLOS ABENZA MARTINEZ
MARIA PALAZÓN PÉREZ
PEDRO SANCHEZ GARCIA
ROSA GUILLÉN LUNA
LUIS GARCIA ROJO
MANUEL ESCUDERO BERNAL

Para añadir elementos a la lista también disponemos del comando Insert el cual toma como primer parámetro en que posición de la lista deseamos insertar el elemento y como segundo parámetro el elemento en cuestión. Por ejemplo vamos a introducir un cliente entre CARLOS y MARIA:

Clientes.Insert( 1, 'PASCUAL CAMPOY FERNANDEZ' );

Y la lista quedaría así:

CARLOS ABENZA MARTINEZ
PASCUAL CAMPOY FERNANDEZ
MARIA PALAZÓN PÉREZ
PEDRO SANCHEZ GARCIA
ROSA GUILLÉN LUNA
LUIS GARCIA ROJO
MANUEL ESCUDERO BERNAL

En el próximo artículo seguiremos viendo más cosas que se pueden hacer con un StringList.

Pruebas realizadas en Delphi 7.

Publicidad