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.

13 agosto 2007

Operaciones con cadenas de texto (y V)

Aquí finalizamos con las funciones de manipulación de cadenas de texto:

procedure SetLength( var S; NewLength: Integer );

Establece el tamaño de una cadena de texto o un array dinámico. Por ejemplo:

var
sTexto: String;
begin
sTexto := '123456789';
SetLength( sTexto, 4 );
end;

Después de ejecutar esto sTexto será igual a 1234 (hemos recortado la cadena).

También se utiliza para establecer el tamaño de un array dinámico antes de utilizarlo.

var
Valores: array of Integer;
begin
SetLength( Valores, 3 );
Valores[0] := 10;
Valores[1] := 20;
Valores[2] := 30;

// Ampliamos el tamaño del array
SetLength( Valores, 5 );
Valores[3] := 40;
Valores[4] := 50;
end;

procedure SetString( var S: string; Buffer: PChar; len: Integer );

El procedimiento SetString fija la longitud de la cadena S antes de copiar el número de caracteres determinado por len de la cadena Buffer. Vamos a ver un ejemplo que copia el contenido de un array de caracteres a un string:

var
Original: array[1..3] of char;
sDestino: String
begin
Original[1] := 'A';
Original[2] := 'B';
Original[3] := 'C';
SetString( sDestino, PChar( Addr( Original ) ), 3 );
end;

Ahora la variable sDestino vale 'ABC'.

function StringOfChar( Ch: Char; Count: Integer ): string;

Esta función devuelve una nueva cadena de texto creada con el carácter Ch y repetido tantas veces como determine el parámetro Count. Por ejemplo:

StringOfChar( '0', 5 ) devuelve 00000
StringOfChar( 'A', 6 ) devuelve AAAAAA

Esta función viene bien para completar con ceros por la derecha o por la izquierda los números que se pasan a formato texto.

function StringReplace( const S, OldPattern, NewPattern: string; Flags: TReplaceFlags ): string;

Esta función devuelve una cadena de texto reemplazando las subcadenas encontradas dentro de la cadena S que coincidan con OldPattern y serán sustituidas por la cadena NewPattern. Los valores del parámetro Flags pueden ser:

rfReplaceAll: reemplaza todas las cadenas encontradas
rfIgnoreCase: no distingue mayúsculas de minúsculas

Por ejemplo:

StringReplace( 'CAMBIANDO-EL-TEXTO', '-', '.', [] ) devuelve:

CAMBIANDO.EL-TEXTO

StringReplace( 'CAMBIANDO-EL-TEXTO', '-', '.', [rfReplaceAll] ) devuelve:

CAMBIANDO.EL.TEXTO

StringReplace( 'CAMBIANDO EL TEXTO', 'EL', 'mi', [rfReplaceAll,rfIgnoreCase] ) devuelve:

CAMBIANDO mi TEXTO

StringReplace( 'CAMBIANDO EL TEXTO', 'EL', 'mi', [rfReplaceAll] ) devuelve:

CAMBIANDO mi TEXTO

StringReplace( 'cambiando el texto', 'EL', 'mi', [rfReplaceAll] ) devuelve:

cambiando el texto

StringReplace( 'cambiando el texto', 'EL', 'mi', [rfReplaceAll,rfIgnoreCase] ) devuelve:

cambiando mi texto

function StrScan( const Str: PChar; Chr: Char ): PChar;

Devuelve un puntero al primer carácter que encuentre dentro de la cadena Str. Por ejemplo:

StrScan( 'BUSCANDO UN CARÁCTER', 'T' ) devuelve TER
StrScan( 'BUSCANDO UN CARÁCTER', 'U' ) devuelve USCANDO UN CARÁCTER

function StuffString( const AText: string; AStart, ALength: Cardinal; const ASubText: string ): string;

Esta función devuelve la cadena AText insertando dentro de la misma ASubText en la posición AStart y con la longitud ALength (aunque realmente no inserta sino que es una sustitución del texto que hay en esa misma posición). Veamos un ejemplo:

StuffString( 'INSERTANDO UN TEXTO EN UNA CADENA', 12, 2, 'EL' ) devuelve:

INSERTANDO EL TEXTO EN UNA CADENA

StuffString( 'INSERTANDO UN TEXTO EN UNA CADENA', 12, 15, 'UNA' ) devuelve:

INSERTANDO UNA CADENA

Si nos fijamos en el segundo ejemplo hemos sustituido UN TEXTO EN UNA por UNA (15 caracteres).

function Trim( const S: string ): string; overload;
function Trim( const S: WideString ): WideString; overload;


La función Trim devuelve la cadena S eliminando los espacios en blanco y los caracteres de control que halla a la izquierda y derecha (pero no en medio). Por ejemplo:

Trim( ' ESTO ES UNA PRUEBA ' ) devuelve 'ESTO ES UNA PRUEBA'

function TrimLeft( const S: string ): string; overload;
function TrimLeft( const S: WideString ): WideString; overload;


Esta función es similar a Trim salvo que sólo elimina los espacios en blanco y caracteres de control por la izquierda. Por ejemplo:

TrimLeft( ' ESTO ES UNA PRUEBA ' ) devuelve 'ESTO ES UNA PRUEBA '

function TrimRight( const S: string ): string; overload;
function TrimRight( const S: WideString ): WideString; overload;


Esta función es igual a Trim salvo que sólo elimina los espacios en blanco y caracteres de control por la derecha. Por ejemplo:

TrimRight( ' ESTO ES UNA PRUEBA ' ) devuelve ' ESTO ES UNA PRUEBA'

function UpCase( Ch: Char ): Char;

Convierte un carácter a mayúsculas. Por ejemplo:

UpCase( 'a' ) devuelve A
UpCase( 'ú' ) devuelve ú

Como vemos en el segundo ejemplo no funciona correctamente con caracteres Ansi. Mejor utilizar en su lugar la función AnsiUpperCase.

function UpperCase( const S: string ): string;

Convierte la cadena S a mayúsculas. Por ejemplo:

UpperCase( 'Hola' ) devuelve HOLA
UpperCase( 'Programación en Delphi' ) devuelve PROGRAMACIóN EN DELPHI

El segundo ejemplo no respeta las vocales con tilde. Mejor utilizar AnsiUpperCase.

function WrapText( const Line, BreakStr: string; nBreakChars: TSysCharSet; MaxCol: Integer ):string; overload;
function WrapText( const Line, MaxCol: Integer = 45 ):string; overload;


La función WrapText parte la cadena Line en múltiples líneas de texto separadas por defecto con los caracteres de control #13 y #10, en tamaños máximos de palabra definidos por MaxCol. Veamos algunos ejemplos:

WrapText( 'Partiendo una cadena de texto', 15 ) devuelve

Partiendo una
cadena de texto

WrapText( 'Partiendo una cadena de texto', 10 ) devuelve:

Partiendo
una
cadena de
texto

WrapText( 'Partiendo una cadena de texto', 5 ) devuelve:

Partiendo
una
cadena
de
texto

Es decir, parte la cadena en frases cuyo tamaño máximo es definido por MaxCol pero respetando en ancho de cada palabra (aunque se pase del límite). También podemos definir con que caracteres vamos a separar las palabras así como que deseamos de separador. Si quisieramos separar la frase mediante por punto sería de la siguiente manera:

WrapText( 'Partiendo.una.cadena.de.texto', '-', ['.'], 5 ) devuelve

Partiendo.-una.-cadena.-de.-texto

Hemos definido el separador (quitando #13 y #10) y también le hemos indicado el carácter que divide cada palabra (el punto).

Con esto finalizan las funciones de manipulación de texto.

Pruebas realizadas en Delphi 7.

10 agosto 2007

Operaciones con cadenas de texto (IV)

Vamos a seguir con las funciones para tratamiento de cadenas de texto:

procedure Delete( var S: string; Index, Count:Integer );

Este procedimiento elimina de la cadena S (pasada por variable) un número de caracteres situados en la posición Index y cuya longitud viene determinada por Count. El primer elemento comienza por 1. Por ejemplo:

var
sTexto: String;
begin
sTexto := 'CORTANDO UNA CADENA DE TEXTO';
Delete( sTexto, 10, 14 );
end;

Ahora la variable sTexto contiene:

CORTANDO TEXTO

porque hemos eliminado UNA CADENA DE.

function DupeString( const AText: string; ACount: Integer ): string;

Esta función devuelve la cadena AText repetida tantas veces como se indique en ACount. Por ejemplo:

DupeString( 'HOLA ', 3 ) devuelve HOLA HOLA HOLA

function High( X );

Devuelve el valor más alto de un tipo de variable. Vamos a ver un ejemplo que utiliza esta función con distintos tipos de variable y muestra el resultado en un Memo:

var
Numeros: array[1..4] of Integer;
begin
Memo.Lines.Add( 'High( Char )=' + IntToStr( Ord( High( Char ) ) ) );
Memo.Lines.Add( 'High( Integer )=' + IntToStr( High( Integer ) ) );
Memo.Lines.Add( 'High( Word )=' + IntToStr( High( Word ) ) );
Memo.Lines.Add( 'High( DWord )=' + IntToStr( High( DWord ) ) );
Memo.Lines.Add( 'High( Boolean )=' + BoolToStr( High( Boolean ), True ) );
Numeros[1] := 13;
Numeros[2] := 6;
Numeros[3] := 16;
Numeros[4] := 5;
Memo.Lines.Add( 'High( Numeros )=' + IntToStr( High( Numeros ) ) );
end;

El resultado que muestra sería:

High( Char )=255
High( Integer )=2147483647
High( Word )=65535
High( DWord )=4294967295
High( Boolean )=True
High( Numeros )=4

Como vemos en el último caso, nos devuelve el último índice del del array Numeros.

procedure Insert( Source: string; var S: string; Index: Integer );

Este procedimiento inserta dentro de la cadena S (pasada como variable) la cadena Source en la posición Index. Por ejemplo:

var
sTexto: String;
begin
sTexto := 'AMPLIANDO TEXTO';
Insert( ' UNA CADENA DE', sTexto, 10 );
end;

Ahora sTexto contiene:

AMPLIANDO UNA CADENA DE TEXTO

function LastDelimiter( const Delimiters, S: string ): Integer;

Esta función nos devuelve la posición del último delimitador determinado por Delimiters que encuentre en la cadena S. Con unos ejemplos se ve mas claro:

LastDelimiter( ',', 'PABLO,JUAN,MARIA,MARCOS' ) devuelve 17
LastDelimiter( '.', '1.2.3.4.5' ) devuelve 8
LastDelimiter( '-:', 'A:1-B:2-C:3' ) devuelve 10

En el último ejemplo hemos utilizado dos delimitadores. Esta función sería muy interesante para crear un parser de código fuente HTML buscando delimitadores <>.

function Length( S ): Integer;

Devuelve la longitud máxima de una cadena de texto o de un array. Por ejemplo:

var
Valores: array of Integer;
begin
ShowMessage( Length( 'HOLA' ) );
SetLength( Valores, 3 );
ShowMessage( IntToStr( Length( Valores ) );
end;

Los comandos ShowMessage nos mostraría los valores 4 para la cadena HOLA y el valor 3, que es la longitud del array dinámico.

function LowerCase( const S: string ): string;

Convierte los caracteres de la cadena S a minúsculas. Se recomenda utilizar en su lugar la función AnsiLowerCase para caracteres internacionales de 8 bits. Por ejemplo:

LowerCase( 'Programando con Delphi' ) devuelve:

programando con delphi

LowerCase( 'MANIPULANDO CADA CARÁCTER DE LA CADENA' )

manipulando cada carÁcter de la cadena

Como vemos en este último ejemplo la palabra CARÁCTER ha dejado la Á en mayúsculas. Por ello se recomienda utilizar la función AnsiLowerCase para evitar estas incidencias.

procedure Move( const Source; var Dest; Count: Integer );

Este procedimiento (que debería llamarse quizás Copy) copia la cantidad de bytes Count de Source a la variable Dest. Por ejemplo:

var
sOrigen, sDestino: String;
begin
sOrigen := 'COPIANDO TEXTO';
sDestino := '--------------';
Move( sOrigen[10], sDestino[3], 5 );
end;

El valor de la variable destino sería:

--TEXTO-------

function Pos( Substr: string; S: string ): Integer;

Devuelve la posición de la cadena Substr dentro de la cadena S (Distingue mayúsculas de minúsculas). Si no la encuentra nos devuelve un cero (el primer elemento es el 1). Por ejemplo:

Pos( 'PALABRA', 'BUSCANDO UNA PALABRA' ) devuelve 14
Pos( 'palabra', 'BUSCANDO UNA PALABRA' ) devuelve 0

procedure ProcessPath( const EditText: string; var Drive: Char; var DirPart: string; var FilePart: string );

Este interesante procedimiento divide la cadena EditText la cual se supone que es una ruta como:

C:\Archivos de programa\MSN Messenger\msnmsgr.exe

en unidad, directorio y archivo. Vamos un ejemplo volcando la información en un Memo:

var
sRuta, sDirectorio, sArchivo: String;
cUnidad: Char;
begin
sRuta := 'C:\Archivos de programa\MSN Messenger\msnmsgr.exe';
ProcessPath( sRuta, cUnidad, sDirectorio, sArchivo );
Memo.Lines.Add( 'sUnidad=' + cUnidad );
Memo.Lines.Add( 'sDirectorio=' + sDirectorio );
Memo.Lines.Add( 'sArchivo=' + sArchivo );
end;

El resultado sería:

sUnidad=C
sDirectorio=\Archivos de programa\MSN Messenger
sArchivo=msnmsgr.exe

Nota: para que funcione esta función la ruta que le pasemos debe ser real, en caso contrario da un error.

En el próximo artículo terminaremos con este tipo de funciones.

Pruebas realizadas en Delphi 7.

09 agosto 2007

Operaciones con cadenas de texto (III)

Seguimos con funciones para manejo de cadenas de caracteres:

function AnsiReverseString( const AText: AnsiString ): AnsiString;

Esta función devuelve la cadena AText con los caracteres invertidos (de derecha a izquierda). Por ejemplo:

AnsiReverseString( 'Al revés' ) devuelve:

séver lA

AnsiReverseString( 'INVIRTIENDO UNA CADENA DE TEXTO' ) devuelve:

OTXET ED ANEDAC ANU ODNEITRIVNI

function AnsiRightStr( const AText: AnsiString; const ACount: Integer ): AnsiString;

Esta función devuelve la parte derecha de la cadena AText según el número de caracteres que le indiquemos en ACount. Por ejemplo:

AnsiRightStr( 'PROGRAMANDO CON DELPHI', 6 ) devuelve DELPHI
AnsiRightStr( 'PROGRAMANDO CON DELPHI', 11 ) devuelve CON DELPHI
AnsiRightStr( 'PROGRAMANDO CON DELPHI', 18 ) devuelve RAMANDO CON DELPHI

function AnsiStartsStr( const ASubText, AText: string ): Boolean;

Devuelve True si la cadena ASubText está al comienzo de la cadena AText (distingue mayúsculas y minúsculas). Veamos como funciona:

AnsiStartsStr( 'C:\', 'C:\Mis documentos\' ) devuelve True
AnsiStartsStr( 'c:\', 'C:\Mis documentos\' ) devuelve False

function AnsiStartsText( const ASubText, AText: string ): Boolean;

Esta función es igual a AnsiStartsStr salvo que no distingue mayúsculas de minúsculas. En el caso anterior:

AnsiStartsText( 'C:\', 'C:\Mis documentos\' ) devuelve True
AnsiStartsText( 'c:\', 'C:\Mis documentos\' ) devuelve True

function AnsiUpperCase( const S: string ): string;

Devuelve la cadena S convertida toda en mayúsculas. Veamos un par de ejemplos:

AnsiUpperCase( 'Programando con Delphi' ) devuelve:

PROGRAMANDO CON DELPHI

AnsiUpperCase( 'manipulando cada carácter de la cadena' ) devuelve:

MANIPULANDO CADA CARÁCTER DE LA CADENA

procedure AppendStr( var Dest: string; const S: string ); deprecated;

Este procedimiento esta obsoleto. Añade a la cadena Dest el contenido dela cadena S. Utilizar en su lugar el operador + o la función Contat.

function CompareStr( const S1, S2: string ): Integer;

Compara las cadenas de texto S1 y S2 y devuelve cero si son iguales. Si son distintas devuelve un número positivo o negativo según la diferencia de caracteres. La operación de diferencia de caracteres se realiza a nivel de cada carácter utilizando su valor equivalente en ASCII. Por ejemplo:

CompareStr( 'HOLA', 'HOLA' ) devuelve 0
CompareStr( 'HOLA', 'HOLa' ) devuelve -32
CompareStr( 'HOLa', 'HOLA' ) devuelve 32
CompareStr( 'HOLA', 'HOYA' ) devuelve -13

Esta función esta obsoleta. Se recomienda utilizar AnsiCompareStr en su lugar.

function CompareText( const S1, S2: string ): Integer;

Similar a la función CompareStr pero sin tener en cuenta mayúsculas y minúsculas. Según el ejemplo anterior:

CompareText( 'HOLA', 'HOLA' ) devuelve 0
CompareText( 'HOLA', 'HOLa' ) devuelve 0
CompareText( 'HOLa', 'HOLA' ) devuelve 0
CompareText( 'HOLA', 'HOYA' ) devuelve -13

Esta función esta obsoleta. Se recomienda utilizar AnsiCompareText en su lugar.

function Concat( s1 [, s2,..., sn]: string ): string;

Esta función devuelve concatenadas un número indeterminado de cadenas de texto que le pasemos como parámetro, aunque se recomienda utilizar el operador + en su lugar. Veamos un ejemplo:

Concat( '12', '34', '56', '78' ) devuelve 12345678
Concat( 'COMO', ' ESTÁN', ' USTEDES' ) devuelve COMO ESTÁN USTEDES

function Copy( S; Index, Count: Integer ): string;
function Copy( S; Index, Count: Integer ): array;


Ambas funciones devuelven una subcadena de la cadena S en la posición Index (el primer elemento es 1) y con una longitud de Count. Por ejemplo:

Copy( '123456', 1, 3 ) devuelve 123
Copy( '123456', 4, 2 ) devuelve 45
Copy( 'CAPTURANDO UNA PALABRA', 16, 7 ) devuelve PALABRA

También puede utilizarse para copiar elementos entre arrays de cualquier tipo. Por ejemplo vamos a crear un array de enteros y vamos a copiar una parte del mismo a otro:

var Origen, Destino: array of Integer;
i: Integer;
begin
SetLength( Origen, 6 );
Origen[0] := 10;
Origen[1] := 20;
Origen[2] := 30;
Origen[3] := 40;
Origen[4] := 50;
Origen[5] := 60;
Destino := Copy( Origen, 2, 4 );
for i := 0 to Length( Destino ) - 1 do
Memo.Lines.Add( Format( 'Destino[%d]=%d', [i,Destino[i]] ) );
end;

El resultado lo hemos volcado a pantalla dentro de un campo Memo mostrando lo siguiente:

Destino[0]=30
Destino[1]=40
Destino[2]=50
Destino[3]=60

En el próximo artículo seguiremos con más funciones.

Pruebas realizadas en Delphi 7.

08 agosto 2007

Operaciones con cadenas de texto (II)

Continuamos viendo funciones para manejo de cadenas de caracteres:

function AnsiIndexStr( const AText: string; const AValues: array of string ): Integer;

Esta función comprueba si alguna de las cadenas contenidas en el array AValues coincide extactamente con la cadena AText (distingue mayúsculas y minúsculas). Si la encuentra nos devuelve el número de índice del array donde se encuentra (empieza por cero), en caso contrario nos devuelve -1. Por ejemplo:

AnsiIndexStr( 'JUAN', ['CARLOS','PABLO','JUAN','ROSA'] ) devuelve 2
AnsiIndexStr( 'JUAN', ['CARLOS','PABLO','Juan','ROSA'] ) devuelve -1

Como vemos en el primer ejemplo nos dice que JUAN se encuentra en la posición 2 del array. Sin embargo en el sedungo ejemplo devuelve -1 porque JUAN es distinto de Juan.

function AnsiIndexText( const AText: string; const AValues: array of string ): Integer;

Funciona exactamente igual que la función AnsiIndexStr salvo que no distingue mayúsculas y minúsculas. Según el ejemplo anterior ambas llamadas a la función devuelven idéntico resultado:

AnsiIndexText( 'JUAN', ['CARLOS','PABLO','JUAN','ROSA'] ) devuelve 2
AnsiIndexText( 'JUAN', ['CARLOS','PABLO','Juan','ROSA'] ) devuelve 2

function AnsiLeftStr( const AText: AnsiString; const ACount: Integer ): AnsiString;

Esta función devuelve la parte izquierda de la cadena AText según el número de caracteres que le indiquemos en ACount. Sería algo así:

AnsiLeftStr( 'PROGRAMANDO CON DELPHI', 6 ) devuelve PROGRA
AnsiLeftStr( 'PROGRAMANDO CON DELPHI', 11 ) devuelve PROGRAMANDO
AnsiLeftStr( 'PROGRAMANDO CON DELPHI', 18 ) devuelve PROGRAMANDO CON DE

function AnsiLowerCase( const S: string ): string;

Devuelve la cadena S convertida toda en minúsculas (si tiene letras, naturalmente). Veamos un par de ejemplos:

AnsiLowerCase( 'Programando con Delphi' ) devuelve:

programando con delphi

AnsiLowerCase( 'MANIPULANDO CADA CARÁCTER DE LA CADENA' ) devuelve:

manipulando cada carácter de la cadena

Como vemos nos ha respetado la tílde en la palabra carácter.

function AnsiMatchStr( const AText: string; const AValues: array of string ): Boolean;

Esta función nos dice si alguna de las cadenas contenidas en el array AValues coincide exactamente con la cadena AText (comprobando mayúsculas y minúsculas). Aunque esta función pueda parecer igual a AnsiIndexStr se diferencia en que sólo responde True o False si la encontrado o no, al contrario que AnsiIndexStr que nos devuelve que la posición donde la ha encontrado. Con un ejemplo se ve mas claro:

AnsiMatchStr( 'JUAN', ['CARLOS','PABLO','JUAN','ROSA'] ) devuelve True
AnsiMatchStr( 'JUAN', ['CARLOS','PABLO','Juan','ROSA'] ) devuelve False

Nota: La ayuda de Delphi 7 dice que esta función devuelve un Integer y realmente devuelve un Boolean, será un error de documentación (ya estamos acostumbrados a la 'magnífica' documentación de Borland). En cambio si está corregido en la función:

function AnsiMatchText( const AText: string; const AValues: array of string ): Boolean;

Similar a la función anterior AnsiMatchStr pero sin diferenciar mayúsculas y minúsculas. Siguiendo el mismo ejemplo:

AnsiMatchText( 'JUAN', ['CARLOS','PABLO','JUAN','ROSA'] ) devuelve True
AnsiMatchText( 'JUAN', ['CARLOS','PABLO','Juan','ROSA'] ) devuelve True

function AnsiMidStr( const AText: AnsiString; const AStart, ACount: Integer ): AnsiString;

Devuelve un trozo de la cadena AText cuya posición comienza en AStart (el primer elemento es el 1) y cuyo número de caracteres viene determinado por ACount. Por ejemplo:

AnsiMidStr( 'PROGRAMANDO CON DELPHI', 7, 5 ) devuelve MANDO
AnsiMidStr( 'PROGRAMANDO CON DELPHI', 17, 6 ) devuelve DELPHI

function AnsiPos( const Substr, S: string ): Integer;

Devuelve la posición de la cadena Substr que está dentro de la cadena S. Si no la encuentra devuelve un cero (el primer elemento comienza por 1). También distingue entre mayúsculas y minúsculas. Veamos como funciona:

AnsiPos( 'PALABRA', 'BUSCANDO UNA PALABRA' ) devuelve 14
AnsiPos( 'Palabra', 'BUSCANDO UNA PALABRA' ) devuelve 0

function AnsiReplaceStr( const AText, AFromText, AToText: string ): string;

Esta función nos devuelve la cadena AText reemplazando las palabras que contenga según la variable AFromText sustituyéndolas por AToText. Tiene encuentra mayúsculas y minúsculas:

AnsiReplaceStr( 'CORRIGIENDO TEXTO DENTRO DE UNA FRASE', 'TEXTO', 'UNA PALABRA' ) devuelve:

CORRIGIENDO UNA PALABRA DENTRO DE UNA FRASE

AnsiReplaceStr( 'CORRIGIENDO TEXTO DENTRO DE UNA FRASE', 'Texto', 'UNA PALABRA' ) devuelve:

CORRIGIENDO TEXTO DENTRO DE UNA FRASE

Como vemos en el segundo ejemplo al no encontrar Texto por contener minúsculas ha dejado la frase como estaba.

function AnsiReplaceText( const AText, AFromText, AToText: string ): string;

Igual a la función AnsiReplaceStr sin distinguir mayúsculas y minúsculas:

AnsireplaceText( 'CORRIGIENDO TEXTO DENTRO DE UNA FRASE', 'TEXTO', 'UNA PALABRA' ) devuelve:

CORRIGIENDO UNA PALABRA DENTRO DE UNA FRASE

AnsireplaceText( 'CORRIGIENDO TEXTO DENTRO DE UNA FRASE', 'Texto', 'UNA PALABRA' ) devuelve:

CORRIGIENDO UNA PALABRA DENTRO DE UNA FRASE


El próximo artículo continuará con más funciones de manipulación de texto.

Pruebas realizadas en Delphi 7.

07 agosto 2007

Operaciones con cadenas de texto (I)

Delphi posee un amplio repertorio de funciones para el análisis y manipulación de cadenas de texto que nos harán la vida mucho más fácil una vez las conozcamos. Comencemos con ellas:

function AnsiCompareStr( const S1, S2: string ): Integer;

Esta función compara dos cadenas de texto carácter a carácter y nos dice si son iguales (diferencia mayúsculas y minúsculas). Si ambas cadenas son iguales devuelve un cero. Devolverá un 1 si la cadena S1 es superior a la cadena S2 y devuelve un -1 si la cadena S1 es inferior a la cadena S2. Veamos un ejemplo:

AnsiCompareStr( 'HOLA', 'HOLA' ) devuelve 0
AnsiCompareStr( 'HOLA', 'HOLa' ) devuelve 1
AnsiCompareStr( 'HOLa', 'HOLA' ) devuelve -1

¿Cuando se considera una cadena de texto superior a otra? Pues el orden es el siguiente:

Letras mayúsculas > Letras minúsculas
Letras minúsculas > Números

function AnsiCompareText( const S1, S2: string ): Integer;

Esta función es similar a AnsiCompareStr a diferencia de que no distingue entre mayúsculas y minúsculas. En el caso anterior:

AnsiCompareText( 'HOLA', 'HOLA' ) devuelve 0
AnsiCompareText( 'HOLA', 'HOLa' ) devuelve 0
AnsiCompareText( 'HOLa', 'HOLA' ) devuelve 0
AnsiCompareText( 'HOLA', 'HOLLA' ) devuelve -1
AnsiCompareText( 'HOLLA', 'HOLA' ) devuelve 1

El orden entre cadenas se define por:

Letras > Números

function AnsiContainsStr( const AText, ASubText: string ): Boolean;

Comprueba si la cadena ASubText esta dentro de la cadena AText. Por ejemplo:

AnsiContainsStr( 'DELPHI AL LIMITE', 'LIMITE' ) devuelve True
AnsiContainsStr( 'DELPHI AL LIMITE', 'LIMITe' ) devuelve False

function AnsiContainsText( const AText, ASubText: string ): Boolean;

Esta función es igual a AnsiConstainsStr salvo que no diferencia mayúsculas de minúsculas. Veamos un ejemplo:

AnsiContainsText( 'DELPHI AL LIMITE', 'LIMITE' ) devuelve True
AnsiContainsText( 'DELPHI AL LIMITE', 'LIMITe' ) devuelve True
AnsiContainsText( 'DELPHI AL LIMITE', 'LIMITES' ) devuelve False

function AnsiEndsStr( const ASubText, AText: string ): Boolean;

La función nos devuelve True si la cadena AText termina en la cadena ASubText. He aquí un ejemplo:

AnsiEndsStr( '.avi', 'C:\Archivos de programa\Emule\Incoming\pelicula.avi' ) devuelve True
AnsiEndsStr( '.AVI', 'C:\Archivos de programa\Emule\Incoming\pelicula.avi' ) devuelve False

Para este caso es mejor utilizar la función:

function AnsiEndsText( const ASubText, AText: string ): Boolean;

Esta función obtiene el mismo resultado que AnsiEndsStr pero sin diferenciar mayúsculas de minúsculas. En el caso anterior:

AnsiEndsText( '.avi', 'C:\Archivos de programa\Emule\Incoming\pelicula.avi' ) devuelve True
AnsiEndsText( '.AVI', 'C:\Archivos de programa\Emule\Incoming\pelicula.avi' ) devuelve True

Como vemos es ideal para comprobar extensiones de archivos.

En el próximo artículo seguiremos con muchas más funciones.

Pruebas realizadas en Delphi 7.

06 agosto 2007

Dando formato a los números reales

La unidad SysUtils dispone del tipo TFloatFormat siguiente:

type TFloatFormat = (ffGeneral, ffExponent, ffFixed, ffNumber, ffCurrency);

Este tipo es utilizado por las funciones CurrToStrF, FloatToStrF y FloatToText para dar formato a los números reales que pasen a formato texto. Como hemos visto anteriormente, los posibles valores de TFloatFormat son:

ffGeneral

Define el formato general de un número real acercando el valor resultante tanto como sea posible. Quita los ceros que se arrastran y la coma cuando sea necesario. No muestra ningún separador de miles y utiliza el formato exponencial cuando la mantisa es demasiado grande para el valor especificado según el formato. El formato de la coma es determinado por la variable DecimalSeparator.

Veamos un ejemplo mostrando el resultado en un campo Memo:

var rCantidad: Extended; // Número real para hacer pruebas
begin
rCantidad := 1234.56;

Memo.Lines.Add( 'General 4,0 = ' + FloatToStrF( rCantidad, ffGeneral, 4, 0 ) );
Memo.Lines.Add( 'General 6,0 = ' + FloatToStrF( rCantidad, ffGeneral, 6, 0 ) );
Memo.Lines.Add( 'General 6,2 = ' + FloatToStrF( rCantidad, ffGeneral, 6, 2 ) );
Memo.Lines.Add( 'General 3,2 = ' + FloatToStrF( rCantidad, ffGeneral, 3, 2 ) );

El resultado que nos muestra es el siguiente:

General 4,0 = 1235
General 6,0 = 1234,56
General 6,2 = 1234,56
General 3,2 = 1,23E03

Como vemos la función FloatToStrF toma los siguientes parámetros:

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

Value es el número real que vamos a pasar a texto.
Format es el tipo de formato en coma flotante que vamos a utilizar (en este caso ffGeneral).
Precision es el número máximo de dígitos enteros que soporta el formato.
Digits es el número máximo de decimales que soporta el formato.

En el caso anterior cuando forzamos a mostrar menos dígitos de precisión de lo que el número real tiene lo que hace la función es recortar o bien decimales o si la cantidad entera es superior al formato lo pasa al formato exponencial.

Veamos el resto de formatos:

ffExponent

Muestra el número real en formato científico cuyo exponente viene representado con la letra E con base 10. Por ejemplo E+15 significa 1015. El carácter de la coma es representado por la variable DecimalSeparator.

Aquí vemos como queda el mismo número en distintos formatos exponenciales:

Memo.Lines.Add( 'Exponencial 4,0 = ' + FloatToStrF( rCantidad, ffExponent, 4, 0 ) );
Memo.Lines.Add( 'Exponencial 6,0 = ' + FloatToStrF( rCantidad, ffExponent, 6, 0 ) );
Memo.Lines.Add( 'Exponencial 6,2 = ' + FloatToStrF( rCantidad, ffExponent, 6, 2 ) );
Memo.Lines.Add( 'Exponencial 3,2 = ' + FloatToStrF( rCantidad, ffExponent, 3, 2 ) );

cuyo resultado es:

Exponencial 4,0 = 1,235E+3
Exponencial 6,0 = 1,23456E+3
Exponencial 6,2 = 1,23456E+03
Exponencial 3,2 = 1,23E+03

El formato ffFixed

Este formato no utiliza ningún separador de unidades de millar. Al igual que los formatos anteriores si la precisión del número real es superior al formato entonces muestra el resultado en notación científica. Quedaría de la siguiente manera:

Memo.Lines.Add( 'Fijo 4,0 = ' + FloatToStrF( rCantidad, ffFixed, 4, 0 ) );
Memo.Lines.Add( 'Fijo 6,0 = ' + FloatToStrF( rCantidad, ffFixed, 6, 0 ) );
Memo.Lines.Add( 'Fijo 6,2 = ' + FloatToStrF( rCantidad, ffFixed, 6, 2 ) );
Memo.Lines.Add( 'Fijo 3,2 = ' + FloatToStrF( rCantidad, ffFixed, 3, 2 ) );

dando los valores:

Fijo 4,0 = 1235
Fijo 6,0 = 1235
Fijo 6,2 = 1234,56
Fijo 3,2 = 1,23E03

El formato ffNumber

Es igual al formato ffFixed salvo que también incluye el separador de unidades de millar, el cual viene representado por la variable ThousandSeparator. En nuestro ejemplo:

Memo.Lines.Add( 'Número 4,0 = ' + FloatToStrF( rCantidad, ffNumber, 4, 0 ) );
Memo.Lines.Add( 'Número 6,0 = ' + FloatToStrF( rCantidad, ffNumber, 6, 0 ) );
Memo.Lines.Add( 'Número 6,2 = ' + FloatToStrF( rCantidad, ffNumber, 6, 2 ) );
Memo.Lines.Add( 'Número 3,2 = ' + FloatToStrF( rCantidad, ffNumber, 3, 2 ) );

mostraría:

Número 4,0 = 1.235
Número 6,0 = 1.235
Número 6,2 = 1.234,56
Número 3,2 = 1,23E03

El formato ffCurrency

Es similar al formato ffNumber pero con un símbolo de secuencia agregado, según se haya definido en la variable CurrencyString. Este formato también esta influenciado por las variables CurrencyFloat y NegCurrFloat. Sigamos el ejemplo:

Memo.Lines.Add( 'Moneda 4,0 = ' + FloatToStrF( rCantidad, ffCurrency, 4, 0 ) );
Memo.Lines.Add( 'Moneda 6,0 = ' + FloatToStrF( rCantidad, ffCurrency, 6, 0 ) );
Memo.Lines.Add( 'Moneda 6,2 = ' + FloatToStrF( rCantidad, ffCurrency, 6, 2 ) );
Memo.Lines.Add( 'Moneda 3,2 = ' + FloatToStrF( rCantidad, ffCurrency, 3, 2 ) );

Da como resultado:

Moneda 4,0 = 1.235 €
Moneda 6,0 = 1.235 €
Moneda 6,2 = 1.234,56 €
Moneda 3,2 = 1,23E03

según las variables mencionadas anteriormente que recogen el formato moneda por defecto configurado en Windows.

Pruebas realizadas en Delphi 7.

03 agosto 2007

El tipo Variant

Las variables de tipo Variant dan una gran flexibilidad a Delphi permitiendo el almacenamiento de cualquier tipo de dato básico dentro de las mismas.

Estas variables son muy útiles cuando se necesitan procesar muchos datos en tiempo de ejecución antes de saber su tipo, pero debido a los errores que puede causar hay que tener cuidado al utilizarlas.

Lo que no pueden contener es ninguna estructura de datos, objetos ni punteros. Por ejemplo, no se puede hacer:

var
Variable: Variant;
begin
Variable := nil;

Ya que provocaría un error al compilar. Pero si se puede hacer:

Variable := NULL;

Y mucho ojo porque no es lo mismo una variable Nula que una variable Vacia. Cuando se crea una variable Variant y no se hace nada con ella sigue siendo una variable Vacia, pero no nula. Será Nula si nosotros se lo decimos como hemos visto anteriormente.

¿Pero como sabemos que tipo tipo de dato contiene una variable Variant? Pues para eso tenemos la función VarType que nos devuelve el tipo de dato que contiene una variable Variant. Aquí tenemos una función que nos devuelve el tipo de dato de un Variant:

function TipoVariable( Variable: Variant ): String;
var iTipo: Integer;
begin
iTipo := VarType( Variable );

case iTipo of
varEmpty : Result := 'Vacía';
varNull : Result := 'Nula';
varSmallInt : Result := 'SmallInt';
varInteger : Result := 'Integer';
varSingle : Result := 'Single';
varDouble : Result := 'Double';
varCurrency : Result := 'Currency';
varDate : Result := 'Date';
varOleStr : Result := 'OleStr';
varDispatch : Result := 'Dispatch';
varError : Result := 'Error';
varBoolean : Result := 'Boolean';
varVariant : Result := 'Variant';
varUnknown : Result := 'Desconocida';
varByte : Result := 'Byte';
varWord : Result := 'Word';
varLongWord : Result := 'LongWord';
varInt64 : Result := 'Int64';
varStrArg : Result := 'StrArg';
varString : Result := 'String';
varAny : Result := 'Any';
end;

Result := 'Tipo ' + Result;
end;

Veamos un ejemplo de su utilización volcando el contenido en un Memo:

var
Variable: Variant;
begin
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := NULL;
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := 125;
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := 1250;
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := 125000;
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := -1800;
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := -1900000;
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := 'Cadena de texto';
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );
Variable := True;
Memo.Lines.Add( 'Variable=' + VarToStr( Variable ) );
Memo.Lines.Add( TipoVariable( Variable ) );

El resultado que mostrará en pantalla será el siguiente:

Variable=
Tipo Vacía
Variable=
Tipo Nula
Variable=125
Tipo Byte
Variable=1250
Tipo Word
Variable=125000
Tipo LongWord
Variable=-1800
Tipo SmallInt
Variable=-1900000
Tipo Integer
Variable=Cadena de texto
Tipo String
Variable=True
Tipo Boolean

Como vemos según el dato que guardamos en la variable va adaptándose de un tipo a otro.

Pruebas realizadas en Delphi 7.

02 agosto 2007

Conversiones entre unidades de medida

Delphi incorpora una librería interesante encargada de realizar conversiones entre unidades de tiempo, volumen, distancia, etc. Para ello hay que añadir las unidades ConvUtils y StdConvs:

uses
Windows, Messages, ..., ConvUtils, StdConvs;

La función encargada de realizar conversiones es la siguiente:

Convert( ValorAConvertir: Double; DesdeUnidad, HastaUnidad: TConvType): Double;

Veamos algunos ejemplos mostrando el resultado en un campo Memo.
Para convertir de millas a kilómetros:

var dMillas, dKilometros: Double;
begin
dMillas := 15;
dKilometros := Convert( dMillas, duMiles, duKilometers );
Memo.Lines.Add( Format( '%8.4f Millas = %8.4f Kilometros', [dMillas, dKilometros] ) );

Esta otra convierte de pulgadas de área a centímetros de area:

var dPulgadas, dCentimetros: Double;
begin
dPulgadas := 21;
dCentimetros := Convert( dPulgadas, auSquareInches, auSquareCentimeters );
Memo.Lines.Add( Format( '%8.4f Pulgadas de área = %8.4f Centímetros de área', [dPulgadas, dCentimetros] ) );

Y si queremos convertir libras en kilogramos:

var dLibras, dKilos: Double;
begin
dLibras := 60;
dKilos := Convert( dLibras, muPounds, muKilograms );
Memo.Lines.Add( Format( '%8.4f Libras = %8.4f Kilos', [dLibras, dKilos] ) );

También podemos convertir unidades de temperatura:

var dFahrenheit, dCelsius: Double;
begin
dFahrenheit := 84;
dCelsius := Convert( dFahrenheit, tuFahrenheit, tuCelsius );
Memo.Lines.Add( Format( '%8.4fº Fahrenheit = %8.4fº Celsius', [dFahrenheit, dCelsius] ) );

Así como conversión entre unidades de volumen:

var dMetrosCubicos, dLitros: Double;
begin
dMetrosCubicos := 43;
dLitros := Convert( dMetrosCubicos, vuCubicMeters, vuLiters );
Memo.Lines.Add( Format( '%8.4f Metros cúbicos = %8.4fº Litros', [dMetrosCubicos, dLitros] ) );

Ahora vamos a ver todos los tipos de conversión según las unidades de medida.
Para convertir entre unidades de área tenemos:

auSquareMillimeters
auSquareCentimeters
auSquareDecimeters
auSquareMeters
auSquareDecameters
auSquareHectometers
auSquareKilometers
auSquareInches
auSquareFeet
auSquareYards
auSquareMiles
auAcres
auCentares
auAres
auHectares
auSquareRods

Convertir entre unidades de distancia:

duMicromicrons
duAngstroms
duMillimicrons
duMicrons
duMillimeters
duCentimeters
duDecimeters
duMeters
duDecameters
duHectometers
duKilometers
duMegameters
duGigameters
duInches
duFeet
duYards
duMiles
duNauticalMiles
duAstronomicalUnits
duLightYears
duParsecs
duCubits
duFathoms
duFurlongs
duHands
duPaces
duRods
duChains
duLinks
duPicas
duPoints

Convertir entre unidades de masa:

muNanograms
muMicrograms
muMilligrams
muCentigrams
muDecigrams
muGrams
muDecagrams
muHectograms
muKilograms
muMetricTons
muDrams
muGrains
muLongTons
muTons
muOunces
muPounds
muStones

Convertir entre unidades de temperatura:

tuCelsius
tuKelvin
tuFahrenheit
tuRankine
tuReamur

Convertir entre unidades de tiempo:

tuMilliSeconds
tuSeconds
tuMinutes
tuHours
tuDays
tuWeeks
tuFortnights
tuMonths
tuYears
tuDecades
tuCenturies
tuMillennia
tuDateTime
tuJulianDate
tuModifiedJulianDate

Convertir entre unidades de volumen:

vuCubicMillimeters
vuCubicCentimeters
vuCubicDecimeters
vuCubicMeters
vuCubicDecameters
vuCubicHectometers
vuCubicKilometers
vuCubicInches
vuCubicFeet
vuCubicYards
vuCubicMiles
vuMilliLiters
vuCentiLiters
vuDeciLiters
vuLiters
vuDecaLiters
vuHectoLiters
vuKiloLiters
vuAcreFeet
vuAcreInches
vuCords
vuCordFeet
vuDecisteres
vuSteres
vuDecasteres
vuFluidGallons
vuFluidQuarts
vuFluidPints
vuFluidCups
vuFluidGills
vuFluidOunces
vuFluidTablespoons
vuFluidTeaspoons
vuDryGallons
vuDryQuarts
vuDryPints
vuDryPecks
vuDryBuckets
vuDryBushels
vuUKGallons
vuUKPottles
vuUKQuarts
vuUKPints
vuUKGills
vuUKOunces
vuUKPecks
vuUKBuckets
vuUKBushels

Pruebas realizadas en Delphi 7.

01 agosto 2007

Tipos de puntero

Pointer:

- Es un tipo general de puntero hacia cualquier objeto o variable en memoria.
- Al no ser de ningún tipo suele ser bastante peligroso si provoca desbordamientos de memoria.

PAnsiChar:

- Es un tipo de puntero hacia un valor AnsiChar.
- También puede ser utilizado para apuntar a caracteres dentro de una cadena AnsiString.
- Al igual que otros punteros permite la aritmética, es decir, los procedimientos Inc y Dec pueden utilizarse para mover el puntero en memoria.

PAnsiString:

- Apunta hacia una cadena AnsiString.
- Debido a que AnsiString ya es un puntero hacia si misma, el puntero PAnsiString no suele utilizarse mucho.

PChar:

- Es un tipo de puntero hacia un valor Char.
- Puede ser utilizado para apuntar a caracteres dentro de una cadena string.
- Permite aritmética de punteros mediante los procedimientos Inc y Dec.
- Suele utilizarse mucho para procesar cadenas de caracteres terminadas en cero, tal como las utilizadas en el lenguaje C/C++.
- Los caracteres Char son idénticos a los de las variables AnsiChar, siendo de 8 bits de tamaño.

PCurrency:

- Apunta hacia un valor Currency.
- Permite aritmética de punteros mediante los procedimientos Inc y Dec.

PDateTime:

- Apunta hacia un valor TDateTime.
- Permite aritmética de punteros mediante los procedimientos Inc y Dec.

PExtended:

- Apunta hacia un valor Extended.
- Permite aritmética de punteros mediante los procedimientos Inc y Dec.

PInt64:

- Apunta hacia un valor Int64.
- Permite aritmética de punteros mediante los procedimientos Inc y Dec.

PShortString:

- Apunta hacia una cadena ShortString.
- Debido a que las variables ShortString difieren de las variables string,
para apuntar a una variable ShortString es necesario utilizar la función Addr.

PString:

- Apunta hacia una cadena String.
- Al ser la cadena String un puntero en si misma no suele utilizarse mucho este puntero.

PVariant:

- Apunta hacia un valor Variant.
- Al ser Variant un tipo genérico y variable hay que extremar la precaución en el manejo de este puntero.

PWideChar:

- Apunta hacia un valor WideChar.
- Puede ser utilizado para apuntar a caracteres dentro de una cadena WideString.
- Permite aritmética de punteros mediante los procedimientos Inc y Dec.

PWideString:

- Apunta hacia una cadena WideString.
- Al ser ya cadena WideString un puntero en si misma no suele utilizarse mucho.

Pruebas realizadas en Delphi 7.

31 julio 2007

Tipos de variable para el manejo de caracteres

AnsiChar:

- Permite el almacenamiento de un carácter o un número entero.
- Valores comprendidos entre 0 y 255.
- Gasta un byte de memoria (8 bits).

AnsiString:

- Un tipo de dato que almacena una cadena de caracteres tipo AnsiChar.
- Cada carácter gasta un byte de memoria (8 bits), aunque la longitud de la cadena puede ser ilimitada.
- Al ser su contenido dinámico gastará tantos bytes como caracteres tenga la cadena en ese momento.
- El primer elemento comienza por [1].

ShortString:

- Variable para guardar un simple carácter o un número entero.
- Valores comprendidos entre 0 y 255.
- Al ser su contenido dinámico gastará tantos bytes como caracteres tenga la cadena en ese momento.
- Permite un máximo de 255 caracteres.
- El primer elemento comienza por [1].

String:

- Un tipo de dato que almacena una cadena de caracteres.
- Cuando se crea es tratado como AnsiString.
- La longitud es ilimitada siendo cada carácter de un byte (8 bits).
- El primer elemento comienza por [1].

WideChar:

- Almacena un carácter internacional.
- Puede guardar un carácter o un número entero entre 0 y 65535.
- Gasta 2 bytes de memoria (16 bits).

WideString:

- Un tipo de dato que almacena una cadena de caracteres tipo WideChar.
- Cada carácter gasta 2 bytes de memoria (16 bits), aunque la longitud de la cadena puede ser ilimitada.
- Al ser su contenido dinámico gastará tantos bytes como caracteres tenga la cadena en ese momento.
- El primer elemento comienza por [1].

Pruebas realizadas en Delphi 7.

30 julio 2007

Los tipos reales en Delphi

Currency:

- Tipo flotante de punto fijo con 4 decimales usado para valores financieros, minimizando así los errores de redondeo (recomendado para programas de gestión).
- Gasta 8 bytes de memoria (64 bits).

Double:

- Tipo flotante de uso general.
- Soporta hasta 15 dígitos de precisión.
- Gasta 8 bytes de memoria (64 bits).

Extended:

- Tipo flotante con alta capacidad y precisión.
- Soporta hasta 19 dígitos de precisión.
- Gasta 10 bytes de memoria (80 bits).

Real:

- Tipo flotante general obsoleto mantenido por compatibilidad hacia atrás (usar Double en su lugar).
- Soporta hasta 15 dígitos de precisión.
- Gasta 8 bytes de memoria (64 bits).

Real48:

- Tipo flotante general obsoleto mantenido por compatibilidad hacia atrás (usar Extended en su lugar).
- Soporta hasta 11 dígitos de precisión.
- Gasta 6 bytes de memoria (48 bits).

Single:

- El tipo flotante que tiene menos capacidad y precisión, aunque es el más rápido de procesar.
- Soporta hasta 7 dígitos de precisión.
- Gasta 4 bytes de memoria (32 bits).

Pruebas realizadas en Delphi 7.

27 julio 2007

Los tipos enteros en Delphi

Byte:

- Tipo entero que soporta valores entre 0 y 255.
- Gasta un byte de memoria (8 bits).

Cardinal:

- Tipo entero básico sin signo.
- Soporta valores entre 0 y 4294967295.
- Gasta 4 bytes de memoria (32 bits).

Integer:

- Tipo entero con signo.
- Soporta valores entre -2147483648 y 2147483647.
- Gasta 4 bytes de memoria (32 bits)

Int64:

- Tipo entero con signo.
- Alcanza valores desde -9223372036854775808 hasta 9223372036854775807.
- Gasta 8 bytes de memoria (64 bits).

LongInt:

- Tipo entero con signo.
- Valores comprendidos entre -2147483648 y 2147483647.
- Gasta 4 bytes de memoria (32 bits).
- Es un tipo fijo que no cambiará en futuras versiones de Delphi.

LongWord:

- Tipo entero sin signo.
- Valores comprendidos entre 0 y 4294967295.
- Gasta 4 bytes de memoria (32 bits).

DWord:

- Igual que LongWord;

ShortInt:

- Tipo entero con signo.
- Valores comprendidos entre -128 y 127.
- Gasta 1 byte de memoria (8 bits).

SmallInt:

- Tipo entero con signo.
- Valores comprendidos entre -32768 y 32767.
- Gasta 2 bytes de memoria (16 bits).

Word:

- Tipo entero sin signo.
- Valores comprendidos entre 0 y 65535.
- Gasta 2 bytes de memoria (16 bits).

Pruebas realizadas en Delphi 7.

26 julio 2007

Trabajando con arrays dinámicos

Una de las ventajas de los arrays dinámicos respecto a los estáticos es que pueden modificar su tamaño en tiempo de ejecución y no es necesaria la gestión de memoria para los mismos, ya que se liberan automáticamente al terminar el procedimiento, función o clase donde están alojados. También son ideales para enviar un número indeterminado de parámetros a funciones o procedimientos.

Para crear una array dinámico lo que hacemos es declarar el array pero sin especificar su tamaño:

public
Clientes: array of string;

Antes de meter un elemento al array hay que especificar su tamaño con la función SetLength. En este ejemplo creamos tres elementos:

SetLength( Clientes, 3 );

Y ahora introducimos los datos en el mismo:

Clientes[0] := 'JUAN';
Clientes[1] := 'CARLOS';
Clientes[2] := 'MARIA';

A diferencia de los arrays estáticos el primer elemento es el cero y no
el uno. Como he dicho antes no es necesario liberarlos de memoria, pero
si aún así deseamos hacerlo sólo es necesario hacer lo siguiente:

Clientes := nil;

con lo cual el array queda inicializado para que pueda volver a ser utilizado.

Pruebas realizadas en Delphi 7.

25 julio 2007

Clonar las propiedades de un control

Cuantas veces hemos deseado en tiempo de ejecución copiar las características de un control a otro según las acciones del usuario. Por ejemplo si tenemos nuestro propio editor de informes se podrían copiar las carácterísticas de las etiquetas seleccionas por el usuario al resto del formulario (font, color, width, etc.)

Para ello tenemos que añadir la unidad TypInfo:


uses
Windows, Dialogs, ..., TypInfo;

A esta función que voy a mostrar hay que pasarle el control origen y el destino (la copia) así como que propiedades deseamos copiar:

function ClonarPropiedades( Origen, Destino: TObject;
Propiedades: array of string ): Boolean;
var
i: Integer;
begin
Result := True;
try
for i := Low( Propiedades ) to High( Propiedades ) do
begin
// ¿Existe la propiedad en el control origen?
if not IsPublishedProp( Origen, Propiedades[I] ) then
Continue;

// ¿Existe la propiedad en el control destino?
if not IsPublishedProp( Destino, Propiedades[I] ) then
Continue;

// ¿Son del mismo tipo las dos propiedades?
if PropType( Origen, Propiedades[I]) <>
PropType( Destino, Propiedades[I] ) then
Continue;

// Copiamos la propiedad según si es variable o método
case PropType(Origen, Propiedades[i]) of
tkClass:
SetObjectProp( Destino, Propiedades[i],
GetObjectProp( Origen, Propiedades[i] ) );

tkMethod:
SetMethodProp( Destino, Propiedades[I],
GetMethodProp( Origen, Propiedades[I] ) );
else
SetPropValue( Destino, Propiedades[i],
GetPropValue( Origen, Propiedades[i] ) );
end;
end;
except
Result := False;
end;
end;

Para copiar las características principales de una etiqueta habría que llamar a la función de la siguiente manera:

ClonarPropiedades( Label1, Label2, ['Font', 'Color', 'Alignment',
'Width', 'Height', 'Layout'] );

También se pueden copiar eventos tales como OnClick, OnMouseDown, etc. permitiendo así abarcar muchos controles con un solo evento.

Pruebas realizadas en Delphi 7.

24 julio 2007

Aplicar antialiasing a una imagen

El algoritmo antialiasing se suele aplicar a una imagen para evitar los bordes dentados y los degradados bruscos de color. Lo que hace es suavizar toda la imagen.

En este caso el siguiente procedimiento toma un objeto TImage como primer parámetro y como segundo el porcentaje de antialiasing deseado, siendo normal no aplicar más del 10 o 20%:

procedure Antialiasing( Imagen: TImage; iPorcentaje: Integer );
type
TRGBTripleArray = array[0..32767] of TRGBTriple;
PRGBTripleArray = ^TRGBTripleArray;
var
SL, SL2: PRGBTripleArray;
l, m, p: Integer;
R, G, B: TColor;
R1, R2, G1, G2, B1, B2: Byte;
begin
with Imagen.Canvas do
begin
Brush.Style := bsClear;
Pixels[1, 1] := Pixels[1, 1];

for l := 0 to Imagen.Height - 1 do
begin
SL := Imagen.Picture.Bitmap.ScanLine[l];

for p := 1 to Imagen.Width - 1 do
begin
R1 := SL[p].rgbtRed;
G1 := SL[p].rgbtGreen;
B1 := SL[p].rgbtBlue;

if (p < 1) then
m := Imagen.Width
else
m := p - 1;

R2 := SL[m].rgbtRed;
G2 := SL[m].rgbtGreen;
B2 := SL[m].rgbtBlue;

if ( R1 <> R2 ) or ( G1 <> G2 ) or ( B1 <> B2 ) then
begin
R := Round( R1 + ( R2 - R1 ) * 50 / ( iPorcentaje + 50 ) );
G := Round( G1 + ( G2 - G1 ) * 50 / ( iPorcentaje + 50 ) );
B := Round( B1 + ( B2 - B1 ) * 50 / ( iPorcentaje + 50 ) );
SL[m].rgbtRed := R;
SL[m].rgbtGreen := G;
SL[m].rgbtBlue := B;
end;

if ( p > Imagen.Width - 2 ) then
m := 0
else
m := p + 1;

R2 := SL[m].rgbtRed;
G2 := SL[m].rgbtGreen;
B2 := SL[m].rgbtBlue;

if ( R1 <> R2 ) or ( G1 <> G2 ) or ( B1 <> B2 ) then
begin
R := Round( R1 + ( R2 - R1 ) * 50 / ( iPorcentaje + 50 ) );
G := Round( G1 + ( G2 - G1 ) * 50 / ( iPorcentaje + 50 ) );
B := Round( B1 + ( B2 - B1 ) * 50 / ( iPorcentaje + 50 ) );
SL[m].rgbtRed := R;
SL[m].rgbtGreen := G;
SL[m].rgbtBlue := B;
end;

if ( l < 1 ) then
m := Imagen.Height - 1
else
m := l - 1;

SL2 := Imagen.Picture.Bitmap.ScanLine[m];
R2 := SL2[p].rgbtRed;
G2 := SL2[p].rgbtGreen;
B2 := SL2[p].rgbtBlue;

if ( R1 <> R2 ) or ( G1 <> G2 ) or ( B1 <> B2 ) then
begin
R := Round( R1 + ( R2 - R1 ) * 50 / ( iPorcentaje + 50 ) );
G := Round( G1 + ( G2 - G1 ) * 50 / ( iPorcentaje + 50 ) );
B := Round( B1 + ( B2 - B1 ) * 50 / ( iPorcentaje + 50 ) );
SL2[p].rgbtRed := R;
SL2[p].rgbtGreen := G;
SL2[p].rgbtBlue := B;
end;

if ( l > Imagen.Height - 2 ) then
m := 0
else
m := l + 1;

SL2 := Imagen.Picture.Bitmap.ScanLine[m];
R2 := SL2[p].rgbtRed;
G2 := SL2[p].rgbtGreen;
B2 := SL2[p].rgbtBlue;

if ( R1 <> R2 ) or ( G1 <> G2 ) or ( B1 <> B2 ) then
begin
R := Round( R1 + ( R2 - R1 ) * 50 / ( iPorcentaje + 50 ) );
G := Round( G1 + ( G2 - G1 ) * 50 / ( iPorcentaje + 50 ) );
B := Round( B1 + ( B2 - B1 ) * 50 / ( iPorcentaje + 50 ) );
SL2[p].rgbtRed := R;
SL2[p].rgbtGreen := G;
SL2[p].rgbtBlue := B;
end;
end;
end;
end;
end;

Suponiendo que tengamos en un formulario un objeto TImage llamado Image1 llamaríamos a este procedimiento de la siguiente manera:

Antialiasing( Image1, 10 );

Pruebas realizadas en Delphi 7.

23 julio 2007

Dibujar varias columnas en un ComboBox

Vamos a ver un ejemplo de dibujar tres columnas al desplegar un ComboBox. El truco está en guardar el valor de las tres columnas en el mismo item pero separado por un punto y coma.

Después implementamos nuestra propia función de dibujado de columnas para que muestre las tres columnas. Para ello metemos en el evento OnDrawItem del ComboBox:

procedure TFormulario.ComboBoxDrawItem( Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState );
var
sValor, sTodo: string;
i, iPos: Integer;
rc: TRect;
AnchoColumna: array[0..3] of Integer;
begin
ComboBox.Canvas.Brush.Style := bsSolid;
ComboBox.Canvas.FillRect( Rect );

// Las columnas deben ir separadas por un ;
sTodo := ComboBox.Items[Index];

// Establecemos el ancho de las columnas
AnchoColumna[0] := 0;
AnchoColumna[1] := 100; // Ancho de la columna 1
AnchoColumna[2] := 200; // Ancho de la columna 2
AnchoColumna[3] := 300; // Ancho de la columna 3

// Leemos el texto de la primera columna
iPos := Pos( ';', sTodo );
sValor := Copy( sTodo, 1, iPos - 1 );

for i := 0 to 3 do
begin
// Dibujamos la primera columna
rc.Left := Rect.Left + AnchoColumna[i] + 2;
rc.Right := Rect.Left + AnchoColumna[i+1] - 2;
rc.Top := Rect.Top;
rc.Bottom := Rect.Bottom;

// Escribimos el texto
Combobox.Canvas.TextRect( rc, rc.Left, rc.Top, sValor );

// Dibujamos las líneas que separan las columnas
if i < 3 then
begin
Combobox.Canvas.MoveTo( rc.Right, rc.Top );
Combobox.Canvas.LineTo( rc.Right, rc.Bottom );
end;

// Leemos el texto de la segunda columna
sTodo := Copy( sTodo, iPos + 1, Length( sTodo ) - iPos );
iPos := Pos( ';', sTodo );
sValor := Copy( sTodo, 1, iPos - 1 );
end;
end;

Modificando el bucle y el array de enteros AnchoColumna podemos crear el número de columnas que queramos. Ahora sólo hay que meter los items en el ComboBox separados por punto y coma:

with Combobox.Items do
begin
Add( 'JOSE;SANCHEZ;GARCIA;' );
Add( 'MARIA;PEREZ;GOMEZ;' );
Add( 'ANDRES;MARTINEZ;RUIZ;' );
end;

Por último hay que decirle al ComboBox que la rutina de pintar los items corre por nuestra cuenta:

procedure TFormulario.FormCreate(Sender: TObject);
begin
// Le decimos al ComboBox que lo vamos a pintar nosotros
Combobox.Style := csOwnerDrawFixed;
end;

Pruebas realizadas en Delphi 7.

20 julio 2007

Generar claves aleatorias

El siguiente procedimiento que voy a mostrar genera una clave aleatoria según el número de sílabas y números que le indiquemos. Por ejemplo:

GenerarClave( 3, 2 )

puede devolver:

catamo56
dilema12
catoye97
...

Aquí tenemos el generador de claves:

function GenerarClave( iSilabas, iNumeros: Byte ): string;
const
Consonante: array [0..19] of Char = ( 'b', 'c', 'd', 'f', 'g', 'h', 'j',
'k', 'l', 'm', 'n', 'p', 'r', 's',
't', 'v', 'w', 'x', 'y', 'z' );

Vocal: array [0..4] of Char = ( 'a', 'e', 'i', 'o', 'u' );

function Repetir( sCaracter: String; iVeces: Integer ): string;
var
i: Integer;
begin
Result := '';
for i := 1 to iVeces do
Result := Result + sCaracter;
end;

var
i: Integer;
si, sf: Longint;
n: string;
begin
Result := '';
Randomize;

if iSilabas <> 0 then
for i := 1 to iSilabas do
begin
Result := Result + Consonante[Random(19)];
Result := Result + Vocal[Random(4)];
end;

if iNumeros = 1 then
Result := Result + IntToStr(Random(9))
else
if iNumeros >= 2 then
begin
if iNumeros > 9 then
iNumeros := 9;

si := StrToInt('1' + Repetir( '0', iNumeros - 1 ) );
sf := StrToInt( Repetir( '9', iNumeros ) );
n := FloatToStr( si + Random( sf ) );
Result := Result + Copy( n, 0, iNumeros );
end;
end;

Suele utilizarse en la creación de cuentas de usuario y departamentos en programas de gestión, dejando que posteriormente el usuario pueda cambiarse la clave.

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.

18 julio 2007

Dibujar un gradiente en un formulario

Con un sencillo procedimiento podemos hacer que el fondo de nuestros formularios quede con un aspecto profesional con un degradado de color. Para ello escribimos en el evento OnPaint del formulario:

procedure TFormulario.FormPaint( Sender: TObject );
var
wFila, wVertical: Word;
iRojo: Integer;
begin
iRojo := 200;
wVertical := ( ClientHeight + 512 ) div 256;

for wFila := 0 to 512 do
begin
with Canvas do
begin
Brush.Color := RGB( iRojo, 0, wFila );
FillRect( Rect( 0, wFila * wVertical, ClientWidth, ( wFila + 1 ) * wVertical ) );
Dec( iRojo );
end;
end;
end;

Lo que hace es crear un barrido vertical según el ancho y alto de nuestro formulario y va restando de la paleta RGB el componente rojo. Si queremos cambiar el color sólo hay que jugar con los componentes RGB hasta conseguir el efecto deseado.

Al maximizar o cambiar el ancho y alto de la ventana se quedará el degradado cortado. Para evitar esto le decimos en el evento OnResize que vuelva a pintar la ventana:

procedure TFormulario.FormResize( Sender: TObject );
begin
Repaint;
end;

Este efecto suele utilizarse mucho instalaciones o en CD-ROM interactivos, catálogos, etc.

Pruebas realizadas en Delphi 7.

17 julio 2007

Trocear y unir archivos

Una de las utilidades más famosas que se han asociado a la descarga de archivos es el programa Hacha, el cual trocea archivos a un cierto tamaño para luego poder unirlos de nuevo.

El siguiente procedimiento parte un archivo a la longitud en bytes que le pasemos:

procedure TrocearArchivo( sArchivo: TFileName; iLongitudTrozo: Integer );
var
i: Word;
FS, Stream: TFileStream;
sArchivoPartido: String;
begin
FS := TFileStream.Create( sArchivo, fmOpenRead or fmShareDenyWrite );

try
for i := 1 to Trunc( FS.Size / iLongitudTrozo ) + 1 do
begin
sArchivoPartido := ChangeFileExt( sArchivo, '.' + FormatFloat( '000', i ) );
Stream := TFileStream.Create( sArchivoPartido, fmCreate or fmShareExclusive );

try
if fs.Size - fs.Position < iLongitudTrozo then
iLongitudTrozo := FS.Size - FS.Position;

Stream.CopyFrom( FS, iLongitudTrozo );
finally
Stream.Free;
end;
end;
finally
FS.Free;
end;
end;

Si por ejemplo le pasamos el archivo Documentos.zip creará los archivos:

Documentos.001
Documentos.002
....

Para volver a unirlo tenemos otro procedimiento donde le pasamos el primer trozo y el nombre del archivo original:

procedure UnirArchivo( sTrozo, sArchivoOriginal: TFileName );
var
i: integer;
FS, Stream: TFileStream;
begin
i := 1;
FS := TFileStream.Create( sArchivoOriginal, fmCreate or fmShareExclusive );

try
while FileExists( sTrozo ) do
begin
Stream := TFileStream.Create( sTrozo, fmOpenRead or fmShareDenyWrite );

try
FS.CopyFrom( Stream, 0 );
finally
Stream.Free;
end;

Inc(i);
sTrozo := ChangeFileExt( sTrozo, '.' + FormatFloat( '000', i ) );
end;
finally
FS.Free;
end;
end;

Una ampliación interesante a estos procedimientos sería meter el nombre del archivo original en el primer o último trozo, así como un hash (MD4, MD5, SHA, etc.) para saber si algún trozo está defectuoso.

Pruebas realizadas en Delphi 7.

16 julio 2007

Mover componentes en tiempo de ejecución

Para darle un toque profesional a un programa no esta mal añadir un editor que permita al usuario personalizar sus formularios, informes o listados. Por ejemplo en un programa de facturación sería interesante que el usuario pudiera personalizar el formato de su factura.

Antes de nada hay que crear en la sección private del formulario tres variables encargadas de guardar las coordenadas del componente que se esta moviendo así como una variable booleana que nos dice si en este momento se esta moviendo un componente:

private
{ Private declarations }
iComponenteX, iComponenteY: Integer;
bMoviendo: Boolean;

Creamos dentro de type la definición de un control movible:

type
TMovible = class( TControl );

Los siguientes procedimientos hay que asignarlos a un componente para que puedan ser movidos por todo el formulario al ejecutar el programa:

procedure TFPrincipal.ControlMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
iComponenteX := X;
iComponenteY := Y;
bMoviendo := True;
TMovible( Sender ).MouseCapture := True;
end;

procedure TFPrincipal.ControlMouseMove( Sender: TObject; Shift: TShiftState; X, Y: Integer );
begin
if bMoviendo then
with Sender as TControl do
begin
Left := X - iComponenteX + Left;
Top := Y - iComponenteY + Top;
end;
end;

procedure TFPrincipal.ControlMouseUp( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
if bMoviendo then
begin
bMoviendo := False;
TMovible( Sender ).MouseCapture := False;
end;
end;

Por ejemplo, si queremos mover una etiqueta por el formulario habría que asignar los eventos:

Label1.OnMouseDown := ControlMouseDown;
Label1.OnMouseUp := ControlMouseUp;
Label1.OnMouseMove := ControlMouseMove;

Con esto ya podemos crear nuestro propio editor de formularios sin tener que utilizar las propiedades DragKing y DragMode de los formularios que resultan algo engorrosas.

Pruebas realizadas en Delphi 7.

13 julio 2007

Averiguar los datos del usuario de Windows

Una de las mejores cosas que se pueden hacer en un programa cuando da un error es que nos envíe automáticamente los datos por correo electrónico. Pero es importante saber que usuario ha enviado el error dentro de la red local.

A continuación vamos a ver cuatro procedimientos que nos van a dar el nombre del usuario de Windows, el nombre de su PC en la red, su IP local y su IP pública.

Lo primero como siempre es añadir las unidades:

uses
Windows, Messages, ..., WinSock, IdHttp, WinInet;

Esta función nos devuelve el nombre del usuario:

function LeerUsuarioWindows: string;
var
sNombreUsuario: String;
dwLongitudNombre: DWord;
begin
dwLongitudNombre := 255;
SetLength( sNombreUsuario, dwLongitudNombre );

if GetUserName( PChar( sNombreUsuario ), dwLongitudNombre ) Then
Result := Copy( sNombreUsuario, 1, dwLongitudNombre - 1 )
else
Result := 'Desconocido';
end;

Y esta otra nos da el nombre del PC en la red:

function LeerNombrePC: string;
var
Buffer: array[0..255] of char;
dwLongitud: DWord;
begin
dwLongitud := 256;

if GetComputerName( Buffer, dwLongitud ) then
Result := Buffer
else
Result := ''
end;

La siguiente nos da la IP Local en la red:

function IPLocal: String;
var
p: PHostEnt;
s: array[0..128] of char;
p2: pchar;
wVersionRequested: WORD;
wsaData: TWSAData;
begin
// Arranca la librería WinSock
wVersionRequested := MAKEWORD( 1, 1 );
WSAStartup( wVersionRequested, wsaData );

// Obtiene el nombre del PC
GetHostName( @s, 128 );
p := GetHostByName( @s );

// Obtiene la dirección IP y libera la librería WinSock
p2 := iNet_ntoa( PInAddr( p^.h_addr_list^ )^ );
Result := Result + p2;
WSACleanup;
end;

Y esta última lo que hace es decirnos nuestra IP pública conectando con el servidor dyndns.org y utiliza el componente Indy HTTP el cual leer el contenido del HTML:

function IP_Publica: string;

function EsNumerico( S: string ): Boolean;
begin
Result := false;
if ( Length( S ) > 0 ) then
case S[1] of
'0'..'9': Result := True;
end;
end;
var
HTMLBody: string;
i: Integer;
IdHTTP: TIdHTTP;
begin
Result := '';

// ¿Estamos conectados a Internet?
if WinInet.InternetGetConnectedState( nil, 0 ) then
begin
IdHTTP := TIdHTTP.Create( Application );

try
HTMLBody := IdHTTP.Get( 'http://checkip.dyndns.org/' );

for i := 0 to Length( HTMLBody ) - 1 do
begin
if EsNumerico( HTMLBody[i] ) or ( HTMLBody[i] = '.' ) then
Result := Result + HTMLBody[i];
end;

finally
IdHTTP.Free;
end;
end;
end;

Pruebas realizadas en Delphi 7.

12 julio 2007

Leer la cabecera PE de un programa

¿Queréis verle las tripas a un archivo EXE? El siguiente procedimiento que voy a mostrar lee la cabecera PE de los archivos ejecutables y nos informa del punto de entrada del programa, el estado de los registros, la pila, etc.

Un archivo ejecutable se compone de distintas cabeceras dentro del mismo, ya sea si se va a ejecutar dentro del antiguo sistema operativo MS-DOS o en cualquier versión de Windows.

El siguiente procedimiento toma como parámetro un archivo ejecutable y lo guarda en un supuesto campo memo llamado INFORMACION que se encuentra en el formulario FPrincipal:

procedure TFPrincipal.ExaminarEXE( sArchivo: String );
var
FS: TFilestream;
Firma: DWORD;
Cabecera_dos: IMAGE_DOS_HEADER;
Cabecera_pe: IMAGE_FILE_HEADER;
Cabecera_opc: IMAGE_OPTIONAL_HEADER;
begin
INFORMACION.Clear;

FS := TFilestream.Create( sArchivo, fmOpenread or fmShareDenyNone );

try
FS.Read( Cabecera_dos, SizeOf( Cabecera_dos ) );

if Cabecera_dos.e_magic <> IMAGE_DOS_SIGNATURE then
begin
INFORMACION.Lines.Add( 'Cabecera DOS inválida' );
Exit;
end;

LeerCabeceraDOS( Cabecera_dos, INFORMACION.Lines );

FS.Seek( Cabecera_dos._lfanew, soFromBeginning );
FS.Read( Firma, SizeOf( Firma ) );

if Firma <> IMAGE_NT_SIGNATURE then
begin
INFORMACION.Lines.Add( 'Cabecera PE inválida' );
Exit;
end;

FS.Read( Cabecera_pe, SizeOf( Cabecera_pe ) );
LeerCabeceraPE( Cabecera_pe, INFORMACION.Lines );

if Cabecera_pe.SizeOfOptionalHeader > 0 then
begin
FS.Read( Cabecera_opc, SizeOf( Cabecera_opc ) );
LeerCabeceraOpcional( Cabecera_opc, INFORMACION.Lines );
end;
finally
FS.Free;
end;
end;

Éste a su vez llama a cada uno de los procedimientos que leen las cabeceras DOS, PE y opcional dentro del mismo EXE:

procedure LeerCabeceraDOS( const h: IMAGE_DOS_HEADER; Memo: TStrings );
begin
Memo.Add( 'Cabecera DOS del archivo' );
Memo.Add( Format( 'Número mágico: %d', [h.e_magic] ) );
Memo.Add( Format( 'Byes de la última página del archivo: %d', [h.e_cblp] ) );
Memo.Add( Format( 'Páginas en archivo: %d', [h.e_cp] ) );
Memo.Add( Format( 'Relocalizaciones: %d', [h.e_crlc] ) );
Memo.Add( Format( 'Tamaño de la cabecera en párrafos: %d', [h.e_cparhdr] ) );
Memo.Add( Format( 'Mínimo número de párrafos que necesita: %d', [h.e_minalloc] ) );
Memo.Add( Format( 'Máximo número de párrafos que necesita: %d', [h.e_maxalloc] ) );
Memo.Add( Format( 'Valor inicial (relativo) SS: %d', [h.e_ss] ) );
Memo.Add( Format( 'Valor inicial SP: %d', [h.e_sp] ) );
Memo.Add( Format( 'Checksum: %d', [h.e_csum]));
Memo.Add( Format( 'Valor inicial IP: %d', [h.e_ip] ) );
Memo.Add( Format( 'Valor inicial (relativo) CS: %d', [h.e_cs] ) );
Memo.Add( Format( 'Dirección del archivo de la tabla de relocalización: %d', [h.e_lfarlc] ) );
Memo.Add( Format( 'Número overlay: %d', [h.e_ovno]));
Memo.Add( Format( 'Identificador OEM (para e_oeminfo): %d', [h.e_oemid] ) );
Memo.Add( Format( 'Información OEM; específico e_oemid: %d', [h.e_oeminfo] ) );
Memo.Add( Format( 'Dirección de la nueva cabecera exe: %d', [h._lfanew] ) );
Memo.Add( '' );
end;

procedure LeerCabeceraPE( const h: IMAGE_FILE_HEADER; Memo: TStrings );
var
Fecha: TDateTime;
begin
Memo.Add( 'Cabecera PE del archivo' );
Memo.Add( Format( 'Máquina: %4x', [h.Machine]));

case h.Machine of
IMAGE_FILE_MACHINE_UNKNOWN : Memo.Add(' Máquina desconocida ' );
IMAGE_FILE_MACHINE_I386: Memo.Add( ' Intel 386. ' );
IMAGE_FILE_MACHINE_R3000: Memo.Add( ' MIPS little-endian, 0x160 big-endian ' );
IMAGE_FILE_MACHINE_R4000: Memo.Add( ' MIPS little-endian ' );
IMAGE_FILE_MACHINE_R10000: Memo.Add( ' MIPS little-endian ' );
IMAGE_FILE_MACHINE_ALPHA: Memo.Add( ' Alpha_AXP ' );
IMAGE_FILE_MACHINE_POWERPC: Memo.Add( ' IBM PowerPC Little-Endian ' );
$14D: Memo.Add( ' Intel i860' );
$268: Memo.Add( ' Motorola 68000' );
$290: Memo.Add( ' PA RISC' );
else
Memo.Add( ' tipo de máquina desconocida' );
end;

Memo.Add( Format( 'Número de secciones: %d', [h.NumberOfSections] ) );
Memo.Add( Format( 'Fecha y hora: %d', [h.TimeDateStamp] ) );
Fecha := EncodeDate( 1970, 1, 1 ) + h.Timedatestamp / SecsPerDay;
Memo.Add( FormatDateTime( ' c', Fecha ) );

Memo.Add( Format( 'Puntero a la tabla de símbolos: %d', [h.PointerToSymbolTable] ) );
Memo.Add( Format( 'Número de símbolos: %d', [h.NumberOfSymbols] ) );
Memo.Add( Format( 'Tamaño de la cabecera opcional: %d', [h.SizeOfOptionalHeader] ) );
Memo.Add( Format( 'Características: %d', [h.Characteristics] ) );

if ( IMAGE_FILE_DLL and h.Characteristics ) <> 0 then
Memo.Add(' el archivo es una' )
else
if (IMAGE_FILE_EXECUTABLE_IMAGE and h.Characteristics) <> 0 then
Memo.Add(' el archivo es un programa' );

Memo.Add('');
end;

procedure LeerCabeceraOpcional( const h: IMAGE_OPTIONAL_HEADER; Memo: TStrings );
begin
Memo.Add( 'Información sobre la cabecera PE de un archivo ejecutable EXE' );
Memo.Add( Format( 'Magic: %d', [h.Magic] ) );

case h.Magic of
$107: Memo.Add( ' Imagen de ROM' );
$10b: Memo.Add( ' Imagen de ejecutable' );
else
Memo.Add( ' Tipo de imagen desconocido' );
end;

Memo.Add( Format( 'Versión mayor del enlazador: %d', [h.MajorLinkerVersion] ) );
Memo.Add( Format( 'Versión menor del enlazador: %d', [h.MinorLinkerVersion]));
Memo.Add( Format( 'Tamaño del código: %d', [h.SizeOfCode]));
Memo.Add( Format( 'Tamaño de los datos inicializados: %d', [h.SizeOfInitializedData]));
Memo.Add( Format( 'Tamaño de los datos sin inicializar: %d', [h.SizeOfUninitializedData]));
Memo.Add( Format( 'Dirección del punto de entrada: %d', [h.AddressOfEntryPoint]));
Memo.Add( Format( 'Base de código: %d', [h.BaseOfCode]));
Memo.Add( Format( 'Base de datos: %d', [h.BaseOfData]));
Memo.Add( Format( 'Imagen base: %d', [h.ImageBase]));
Memo.Add( Format( 'Alineamiento de la sección: %d', [h.SectionAlignment]));
Memo.Add( Format( 'Alineamiento del archivo: %d', [h.FileAlignment]));
Memo.Add( Format( 'Versión mayor del sistema operativo: %d', [h.MajorOperatingSystemVersion]));
Memo.Add( Format( 'Versión mayor del sistema operativo: %d', [h.MinorOperatingSystemVersion]));
Memo.Add( Format( 'Versión mayor de la imagen: %d', [h.MajorImageVersion]));
Memo.Add( Format( 'Versión menor de la imagen: %d', [h.MinorImageVersion]));
Memo.Add( Format( 'Versión mayor del subsistema: %d', [h.MajorSubsystemVersion]));
Memo.Add( Format( 'Versión menor del subsistema: %d', [h.MinorSubsystemVersion]));
Memo.Add( Format( 'Valor de la versión Win32: %d', [h.Win32VersionValue]));
Memo.Add( Format( 'Tamaño de la imagen: %d', [h.SizeOfImage]));
Memo.Add( Format( 'Tamaño de las cabeceras: %d', [h.SizeOfHeaders]));
Memo.Add( Format( 'CheckSum: %d', [h.CheckSum]));
Memo.Add( Format( 'Subsistema: %d', [h.Subsystem]));

case h.Subsystem of
IMAGE_SUBSYSTEM_NATIVE:
Memo.Add( ' La imagen no requiere un subsistema. ' );

IMAGE_SUBSYSTEM_WINDOWS_GUI:
Memo.Add( ' La imagen se corre en un subsistema GUI de Windows. ' );

IMAGE_SUBSYSTEM_WINDOWS_CUI:
Memo.Add( ' La imagen corre en un subsistema terminal de Windows. ' );

IMAGE_SUBSYSTEM_OS2_CUI:
Memo.Add( ' La imagen corre sobre un subsistema terminal de OS/2. ' );

IMAGE_SUBSYSTEM_POSIX_CUI:
Memo.Add( ' La imagen corre sobre un subsistema terminal Posix. ' );
else
Memo.Add( ' Subsistema desconocido.' )
end;

Memo.Add( Format( 'Características DLL: %d', [h.DllCharacteristics]) );
Memo.Add( Format( 'Tamaño de reserva de la pila: %d', [h.SizeOfStackReserve]) );
Memo.Add( Format( 'Tamaño de trabajo de la pila: %d', [h.SizeOfStackCommit]) );
Memo.Add( Format( 'Tamaño del Heap de reserva: %d', [h.SizeOfHeapReserve]) );
Memo.Add( Format( 'Tamaño de trabajo del Heap: %d', [h.SizeOfHeapCommit]) );
Memo.Add( Format( 'Banderas de carga: %d', [h.LoaderFlags] ) );
Memo.Add( Format( 'Numeros RVA y tamaño: %d', [h.NumberOfRvaAndSizes] ) );
end;

Espero que os sea de utilidad si os gusta programar herramientas de administración de sistemas operativos Windows.

Pruebas realizadas en Delphi 7.

Publicidad