10 septiembre 2007

El componente TTreeView (I)

Cuando queremos representar una información en forma de árbol incluyendo nodos padres e hijos el componente ideal para ello es el TreeView, el cual funciona exactamente igual que la parte izquierda del Explorador de Windows.

Veamos las peculiaridades que aporta este componente. Todas las funciones las voy a aplicar sobre esta pantalla:


AÑADIENDO ELEMENTOS AL ARBOL

Cuando se añaden elementos a un árbol pueden pertenecer al elemento raiz del mismo (sin padre) o pertenecer a un elemento ya creado. Los elementos del árbol se llaman nodos (Node) pudiendose crear tantos niveles de nodos como se deseen.

Vamos a crear un procedimiento asociado al botón Nuevo que va a añadir un nuevo nodo al árbol. El elemento insertado será un nodo raiz, a menos que el usuario seleccione un nodo antes de ello, que hará que sea su hijo:

procedure TFTreeView.BNuevoClick( Sender: TObject );
var
sNombre: string;
begin
sNombre := InputBox( 'Crear un nodo', 'Nombre:', '' );

if sNombre <> '' then
begin
// ¿Hay un nodo seleccionado?
if TreeView.Selected <> nil then
begin
TreeView.Items.AddChild( TreeView.Selected, sNombre );
TreeView.Selected.Expanded := True;
end
else
TreeView.Items.Add( nil, sNombre );
end;
end;

Lo que hace este procedimiento es preguntarnos el nombre del nodo y si el usuario ha seleccionado uno anteriormente lo mete como hijo (AddChild) expandiendo además el nodo padre (Expanded) como si el usuario hubiese pulsado el botón + del elemento padre.

MODIFICANDO LOS ELEMENTOS DEL ARBOL

Una vez creado un árbol de elementos se puede modificar el texto de cada uno de ellos mediante la propiedad Text. Vamos a hacer que si el usuario pulsa el botón Modificar y hay un nodo seleccionado el programa nos pregunte el nuevo nombre del nodo y lo cambie:

procedure TFTreeView.BModificarClick( Sender: TObject );
var
sNombre: string;
begin
if TreeView.Selected <> nil then
begin
sNombre := InputBox( 'Crear un nodo', 'Nombre:', TreeView.Selected.Text );

if sNombre <> '' then
TreeView.Selected.Text := sNombre;
end;
end;

Cuando se van a hacer operaciones con los nodos hay que asegurarse siempre que el nodo al que vayamos a acceder no sea nil, para evitar Access Violations.

ELIMINANDO LOS ELEMENTOS DEL ARBOL

Para eliminar un nodo del árbol se utiliza el método Delete:

procedure TFTreeView.BEliminarClick( Sender: TObject );
begin
if TreeView.Selected <> nil then
TreeView.Selected.Delete;
end;

Y si queremos eliminar todos los nodos se utiliza el método Clear de la propiedad Items:

procedure TFTreeView.BBorrarTodosClick( Sender: TObject );
begin
TreeView.Items.Clear;
end;

ORDENANDO LOS NODOS ALFABETICAMENTE

El modo de ordenar los elementos de un componente TreeView es igual al de ordenar los elementos de un componente ListView. Hay dos métodos: CustomSort y AlphaSort. El método CustomSort no voy a explicarlo ya que lo mencioné anteriormente en el artículo dedicado al componente ListView y es un poco más primitivo que utilizar AlphaSort.

Para ordenar todos los elementos de un árbol TreeView se utiliza el método:

procedure TFTreeView.BOrdenarClick( Sender: TObject );
begin
TreeView.AlphaSort( True );
end;

El parámetro booleano especifica si queremos que ordene tanto los elementos padres como los hijos (True) o sólo los elementos padres. Si no se especifica nada se asume que ordene todos los elementos.

Ahora hay que programar el evento OnCompare del TreeView para que ordene alfabéticamente los elementos:

procedure TFTreeView.TreeViewCompare( Sender: TObject; Node1,
Node2: TTreeNode; Data: Integer; var Compare: Integer );
begin
Compare := CompareText( Node1.Text, Node2.Text );
end;

Si queremos que la ordenación sea descendente entonces sólo hay que cambiar el signo:

Compare := -CompareText( Node1.Text, Node2.Text );

GUARDANDO LA INFORMACION DEL ARBOL EN UN ARCHIVO DE TEXTO

La clase TTreeView dispone del método SaveToFile para volcar todos los nodos en un archivo de texto para su posterior utilización. Voy a hacer un procedimiento que pregunte al usuario que nombre deseamos dar al archivo y lo guardará en el mismo directorio del programa con la extensión .TXT:

procedure TFTreeView.BGuardarClick( Sender: TObject );
var sNombre: string;
begin
sNombre := InputBox( 'Crear un nodo', 'Nombre:', '' );

if sNombre <> '' then
TreeView.SaveToFile( ExtractFilePath( Application.ExeName ) + sNombre + '.txt' );
end;

La información la guarda por líneas donde cada elemento hijo va separado por tabuladores:

documentos
ventas
contactos
Pablo
Ana
Maria
claves
Terra
Hotmail
GMail

CARGANDO LA INFORMACION DEL ARBOL GUARDADA ANTERIORMENTE

Para cargar el archivo de texto vamos a crear en tiempo real el componente TOpenDialog para que le pregunte al usuario el archivo de texto a cargar:

procedure TFTreeView.BCargarClick( Sender: TObject );
var
Abrir: TOpenDialog;
begin
Abrir := TOpenDialog.Create( Self );
Abrir.InitialDir := ExtractFilePath( Application.ExeName );
Abrir.Filter := 'TreeView|*.txt';

if Abrir.Execute then
TreeView.LoadFromFile( Abrir.FileName );

Abrir.Free;
end;

Hemos modificado el filtro de la carga para que sólo se vean archivos TXT.

En el próximo artículo seguiremos viendo más propiedades interesantes del componente TreeView.

Pruebas realizadas en Delphi 7.

07 septiembre 2007

Mostrando información en un ListView (y III)

En esta última parte referida al componente ListView vamos a ver como modificar los colores de las filas y las columnas según nuestro propio criterio. Para ello vamos a utilizar el evento OnCustomDrawSubItem.

En el primer ejemplo voy a mostrar como cambiar la columna 2 (la del nombre del artículo) poniendo la fuente azul oscuro y negrita:

procedure TFormulario.ListViewCustomDrawSubItem( Sender: TCustomListView;
Item: TListItem; SubItem: Integer; State: TCustomDrawState;
var DefaultDraw: Boolean );
begin
// ¿Va pintar la segunda columna?
if SubItem = 2 then
begin
Sender.Canvas.Font.Color := clNavy;
Sender.Canvas.Font.Style := [fsBold];
end
else
Sender.Canvas.Font.Color := clBlack;
end;

Por el contrario, si en vez de modificar las características de una columna queremos modificar las de una fila entonces sería de la siguiente manera:

procedure TFormulario.ListViewCustomDrawSubItem( Sender: TCustomListView;
Item: TListItem; SubItem: Integer; State: TCustomDrawState;
var DefaultDraw: Boolean );
begin
// ¿Es la primera fila?
if Item.Index = 1 then
Sender.Canvas.Font.Color := clRed
else
Sender.Canvas.Font.Color := clBlack;
end;

En este ejemplo mostrado hemos puesto la segunda fila con la fuente de color rojo. También se podría dibujar una fila según los datos contenidos en ella. Supongamos que deseo que se ponga la fuente de color rojo con un fondo amarillo en aquellos artículos cuyas unidades sean superiores a 3:

procedure TFormulario.ListViewCustomDrawSubItem( Sender: TCustomListView;
Item: TListItem; SubItem: Integer; State: TCustomDrawState;
var DefaultDraw: Boolean );
begin
// Si el número de unidades es superior a tres iluminamos la fila de rojo con fondo amarillo
if StrToInt( Item.SubItems[2] ) > 3 then
begin
Sender.Canvas.Brush.Color := clYellow;
Sender.Canvas.Font.Color := clRed;
end
else
begin
Sender.Canvas.Brush.Color := clWhite;
Sender.Canvas.Font.Color := clBlack;
end;
end;

Las posibilidades para realizar combinaciones de este tipo son enormes tanto para columnas como para filas.

PINTANDO FILAS Y COLUMNAS SEGÚN SU ESTADO

Hasta ahora lo que hemos dibujado ha sido para todas las filas y columnas pero se podría modificar sólo la fila que tiene el foco en azul:

procedure TFormulario.ListViewCustomDrawSubItem( Sender: TCustomListView;
Item: TListItem; SubItem: Integer; State: TCustomDrawState;
var DefaultDraw: Boolean );
begin
// La fuente de la fila seleccionada en negrita
if cdsFocused in State then
Sender.Canvas.Font.Style := [fsBold]
else
Sender.Canvas.Font.Style := [];
end;

Las posibilidades de la variable State son:

cdsSelected -> La columna o fila ha sido seleccionada
cdsGrayed -> La columna o fila esta grisacea
cdsDisabled -> La columna o fila esta deshabilitada
cdsChecked -> La fila aparece con el CheckBox activado
cdsFocused -> La columna o fila esta enfocada
cdsDefault -> Por defecto
cdsHot -> Se ha activado el HotTrack y esta enfocado
cdsMarked -> La fila esta marcada
cdsIndeterminate -> La fila no esta seleccionada ni deseleccionada

Con lo que hemos visto ya podemos utilizar el componente ListView como una rejilla de datos sin tener que utilizar tablas temporales ni un DBGrid.

Pruebas realizadas en Delphi 7.

06 septiembre 2007

Mostrando información en un ListView (II)

Veamos que más le podemos hacer con el componente TListView.

ORDENANDO LOS ELEMENTOS DE LA LISTA

El objeto ListView dispone de dos tipos de ordenación parecidos. Por un lado tenemos la ordenación mediante la función CustomSort:

function CustomSort( SortProc: TLVCompare; lParam: Longint ): Boolean;

Esta función toma como primer parámetro la dirección de una función CALLBACK encargada de establecer los parámetros de la ordenación. Veamos un ejemplo de ordenación ascendente por la columna ARTÍCULO (la segunda):

ListView.CustomSort( @Ordenacion, 0 );

Donde la función de ordenación sería la siguiente:

function Ordenacion( Item1, Item2: TListItem; ParamSort: Integer ): integer; stdcall;
begin
Result := CompareText( Item1.SubItems[1], Item2.SubItems[1] );
end;

Esta función compara los items 1 y 2 y le devuelve el resultado a CustomSort. Si queremos ordenarla descendentemente sería así:

function Ordenacion( Item1, Item2: TListItem; ParamSort: Integer ): integer; stdcall;
begin
Result := -CompareText( Item1.SubItems[1], Item2.SubItems[1] );
end;

Por otro lado tenemos la función AlphaSort la cual no necesita una función CALLBACK porque para eso tenemos el evento OnCompare. Se haría de la siguiente manera:

ListView.AlphaSort;

y en el evento OnCompare del ListView:

procedure TFormulario.ListViewCompare( Sender: TObject; Item1, Item2: TListItem;
Data: Integer; var Compare: Integer );
begin
Compare := CompareText( Item1.SubItems[1], Item2.SubItems[1] );
end;

Prácticamente es parecido a CustomSort (ya que un ListView desciende del componente CustomListView).

UTILIZANDO LA PRIMERA COLUMNA PARA CASOS ESPECIALES

Una de las cosas que se pueden meter en primera columna es un CheckBox para que el usuario marque filas. Antes de eso vamos a volver a mostrar la primera columna (Nada) a 70 de ancho y le cambiamos el nombre a Seleccionado. Por último activamos en las propiedades del ListView el campo CheckBoxes.

Al ejecutar el programa y dar de alta filas en el listado veremos que por cada elemento aparece un CheckBox a la izquierda para poder marcarlo. ¿Cómo sabemos metiante código los elementos que han sido marcados? Con la propiedad Checked de cada Item.

Veamos un ejemplo que comprueba los artículos marcados con CheckBox, los vuelca a un StringList y los saca a pantalla:

var
Seleccionados: TStringList;
i: Integer;
begin
Seleccionados := TStringList.Create;

for i := 0 to ListView.Items.Count - 1 do
if ListView.Items[i].Checked then
Seleccionados.Add( ListView.Items[i].SubItems[1] );

ShowMessage( Seleccionados.Text );

Seleccionados.Free;
end;

Otra utilidad muy interesante que se le puede dar a la primera columna es asociarle una imagen a cada fila. Para ello hay que añadir al formulario el componente ImageList que encuentra en la pestaña Win32.

Después añadimos imágenes a la lista y asociamos las propiedades LargeImages, StateImages y SmallImages con el componente ImageList. Con sólo hacer eso todas las filas tendrán a la izquierda la primera imagen de la lista de imágenes. Para cambiar la imagen en una fila determinada se hace:

ListView.Items[2].ImageIndex := 2;

o bien cambiamos sólo la fila seleccionada por el usuario:

if ListView.Selected <> nil then
ListView.Selected.ImageIndex := 1;

ACTIVANDO LA SELECCIÓN AUTOMÁTICA

Si activamos en el componente ListView la propiedad HotTrack y pasamos el puntero del ratón por las filas veremos como se iluminan en otro color, quedando seleccionadas fijamente si permanecemos con el ratón sobre las mismas.

Esto puede ser de utilidad para crear programas visualizadores de imágenes que muestren fotografías con sólo posicionarse con el ratón encima del nombre de la foto JPG. O para crear un listado de URL para nuestras páginas web preferidas.

También se pueden activar las propiedades:

htHandPoint -> Cambia el puntero del ratón a una mano cuando se para por encima de las filas.
htUnderlineCold -> Subraya la fila que se va a seleccionar
htUnderlineHold -> Subraya la fila que hay seleccionada

En el próximo artículo vamos a ver como reprogramar el dibujado de las filas en tiempo real.

Pruebas realizadas en Delphi 7.

05 septiembre 2007

Mostrando información en un ListView (I)

Cuando nos conectamos a una base de datos estamos acostumbrados a mostrar la información en un componente TDBGrid, pero hay ocasiones en las que hay que mostrar información temporal que no va conectada a la base de datos. Un ejemplo sería el listar ciertos campos de los albaranes antes de facturar o procesar la información de varias tablas en una sola.

DEFINIENDO COMO SE MUESTRA LA INFORMACIÓN EN UN LISTVIEW

El componente TListView permite mostrar la información de varias maneras diferentes lo cual se especifica en su propiedad ViewStyle cuyas posibilidades son:

vsIcon -> Muestra sus elementos como iconos de tamaño normal
vsList -> Muestra sus elementos como una lista a varias columnas
vsReport -> Muestra sus elementos según las filas y columnas al estilo DBGrid.
vsSmallIcon -> Muestra sus elementos como iconos pequeños

En esta ocasión vamos a poner la propiedad ViewStyle a vsReport para que tenga un comportamiento similar a una rejilla de datos al estilo TDBGrid.

A continuación vamos a ver como definir las columnas a mostrar dentro de un ListView. Para ello lo que hacemos es hacer doble clic sobre el ListView y se abrirá una ventana para añadir columnas. Pulsando el botón Add New vamos a añadir las siguientes columnas:

Nº columna Caption Width Alignment
---------- ------------- ------ ----------------
0 Nada 0 taLeft
1 ID 50 taRightJustify
2 Artículo 150 taLeftJustify
3 Unidades 50 taRightJustify
4 Precio 50 taRightJustify
5 Total 50 taRightJustify

La primera columna de un ListView es especialmente utilizada para guardar el título de una fila o una imagen asociada como veremos más adelante. Como la primera columna que quiero mostrar es el ID y la quiero alinear a la derecha entonces no me sirve. Lo que hago es llamar a la primera columna Nada y le doy un ancho de 0 para que no se vea. Mas adelante le daremos utilidad a la misma.

Una vez definidas las columnas vamos a ver como añadir filas.

AÑADIENDO ELEMENTOS AL LISTADO

Todas las columnas que se dan de alta en un objeto ListView son de tipo String, con lo cual si deseamos mostrar otro tipo de información debemos utilizar las típicas rutinas de conversión FloatToStr, BoolToStr, etc.

Veamos un ejemplo de como dar de alta tres artículos:

procedure TFormulario.NuevoElemento;
begin
with ListView.Items.Add do
begin
SubItems.Add( '1' );
SubItems.Add( 'MONITOR LG' );
SubItems.Add( '3' );
SubItems.Add( '230,45' );
SubItems.Add( '691,35' );
end;

with ListView.Items.Add do
begin
SubItems.Add( '2' );
SubItems.Add( 'TECLADO LOGITECH' );
SubItems.Add( '2' );
SubItems.Add( '49,99' );
SubItems.Add( '99,98' );
end;

with ListView.Items.Add do
begin
SubItems.Add( '3' );
SubItems.Add( 'RATÓN OPTICO DELL' );
SubItems.Add( '5' );
SubItems.Add( '15,99' );
SubItems.Add( '79,95' );
end;
end;

Como se puede apreciar primero se crea un Item por cada fila y un SubItem para cada columna, ignorando la columna 0 donde interviene la propiedad Caption como veremos más adelante. De hecho, SubItem[0] es la segunda columna y no la primera.

MODIFICANDO LAS FILAS DEL LISTADO

El primer elemento introducido en un ListView tiene un índice de 0. Lo que vamos a hacer es cambiar el TECLADO LOGITECH por un GENIUS:

procedure TFormulario.ModificarElemento;
begin
// Modificamos el segundo elemento de la lista
with ListView.Items[1] do
begin
SubItems[1] := 'TECLADO GENIUS';
SubItems[2] := '7';
SubItems[3] := '31,99';
SubItems[4] := '223,93';
end;
end;

Hemos cambiado todos las columnas menos la primera (el ID). Aquí hay que andarse con ojo y no acceder a un elemento de que no exista porque si no provocaría el error que tanto nos gusta:

Access violation at address ....


ELIMINANDO FILAS DE LA LISTA

Eliminar un registro de la lista es algo tan secillo de hacer como:

ListView.Items[0].Delete;

Eso elimina la primera fila. Y si deseamos eliminar todos los elementos de la lista:

ListView.Items.Clear;

CAMBIANDO EL ASPECTO Y EL COMPORTAMIENTO DEL LISTADO

En la lista que hemos creado no podemos seleccionar ninguna fila, esta algo así como bloqueado. Para poder hacerlo hay que activar la propiedad RowSelect a True.

Otra característica interesante es la de mostrar un separador de filas y columnas al estilo de la rejilla DBGrid. Para ello activa la propiedad GridLines a True.

También ocurre que si tenemos una fila seleccionada y pasamos el foco a otro control parece que se pierde la selección de dicha fila, aunque realmente sigue seleccionada si volvemos al mismo. Para evitar esto lo que debemos hacer es desactivar la propiedad HideSelection de tal manera que cuando pasemos a otro control en vez de estar la fila seleccionada en azul lo hace en color gris pero sigue estando seleccionada para el usuario.

Y si queremos seleccionar más de una fila activamos la propiedad Multiselect, de tal manera que si hay una fila seleccionada puede saberse de la siguiente manera:

if ListView.Selected <> nil then
...

Siendo Selected el Item de la lista que ha sido seleccionado.

Si hay muchas seleccionadas la propiedad SelCount nos lo dirá el número de filas seleccionadas en todo momento. Para averiguar que filas están seleccionadas lo que hacemos es recorrer la lista y ver aquellos elementos que tienen a True la propiedad Selected:

procedure TFormulario.VerSeleccionados;
var
Seleccionados: TStringList;
i: Integer;
begin
Seleccionados := TStringList.Create;

for i := 0 to ListView.Items.Count - 1 do
if ListView.Items[i].Selected then
Seleccionados.Add( ListView.Items[i].SubItems[1] );

ShowMessage( Seleccionados.Text );

Seleccionados.Free;
end;

Lo que hace este procedimiento es crear un objeto StringList y volcar dentro del mismo el nombre de los artículos seleccionados para después sacarlo por pantalla.

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

Pruebas realizadas en Delphi 7.

04 septiembre 2007

Explorar unidades y directorios

Si importante es controlar el manejo de archivos no menos importante es el saber moverse por las unidades de disco y los directorios.

Veamos que tenemos Delphi para estos menesteres:

function CreateDir( const Dir: string ): Boolean;

Esta función crea un nuevo directorio en la ruta indicada por Dir. Devuelve True o False dependiendo si ha podido crearlo o no. El único inconveniente que tiene esta función es que deben existir los directorios padres. Por ejemplo:

CreateDir( 'C:\prueba' ) devuelve True
CreateDir( 'C:\prueba\documentos' ) devuelve True
CreateDir( 'C:\otraprueba\documentos' ) devuelve False (y no lo crea)

function ForceDirectories( Dir: string ): Boolean;

Esta función es similar a CreateDir salvo que también crea toda la ruta de directorios padres.

ForceDirectories( 'C:\prueba' ) devuelve True
ForceDirectories( 'C:\prueba\documentos' ) devuelve True
ForceDirectories( 'C:\otraprueba\documentos' ) devuelve True


procedure ChDir( const S: string ); overload;

Este procedimiento cambia el directorio actual al indicado por el parámetro S. Por ejemplo:

ChDir( 'C:\Windows\Fonts' );

function GetCurrentDir: string;

Nos devuelve el nombre del directorio actual donde estamos posicionados. Por ejemplo:

GetCurrentDir devuelve C:\Windows\Fonts

function SetCurrentDir( const Dir: string ): Boolean;

Establece el directorio actual devolviendo True si lo ha conseguido. Por ejemplo:

SetCurrentDir( 'C:\Windows\Java' );

procedure GetDir( D: Byte; var S: string );

Devuelve el directorio actual de una unidad y lo mete en la variable S. El parámetro D es el número de la unidad siendo:

D Unidad
--- ------------------
0 Unidad por defecto
1 A:
2 B:
3 C:
...

Por ejemplo para leer el directorio actual de la unidad C:

var
sDirectorio: String;
begin
GetDir( 3, sDirectorio );
ShowMessage( 'El directorio actual de la unidad C: es ' + sDirectorio );
end;

function RemoveDir( const Dir: string ): Boolean;

Elimina un directorio en el caso de que este vacío, devolviendo False si no ha podido hacerlo.

RemoveDir( 'C:\prueba\documentos' ) devuelve True
RemoveDir( 'C:\prueba' ) devuelve True
RemoveDir( 'C:\otraprueba' ) devuelve False porque no esta vacío

function DirectoryExists( const Directory: string ): Boolean;

Comprueba si existe el directorio indicado por el parámetro Directory. Por ejemplo:

DirectoryExists( 'C:\Windows\System32\' ) devuelve True
DirectoryExists( 'C:\Windows\MisDocumentos\' ) devuelve False

function DiskFree( Drive: Byte ): Int64;

Devuelve el número de bytes libres de una unidad de dico indicada por la letra Drive:

Drive Unidad
----------- ------
0 Unidad por defecto
1 A:
2 B:
3 C:
...

Por ejemplo vamos a ver el número de bytes libres de la unidad C:

DiskFree( 3 ) devuelve 5579714560


function DiskSize( Drive: Byte ): Int64;

Nos dice el tamaño total en bytes de una unidad de disco. Por ejemplo:

DiskSize( 3 ) devuelve 20974428160


BUSCANDO ARCHIVOS DENTRO DE UN DIRECTORIO

Para buscar archivos dentro de un directorio disponemos de las funciones:

function FindFirst( const Path: string; Attr: Integer; var F: TSearchRec ): Integer;

Busca el primer archivo, directorio o unidad que se encuentre dentro de una ruta en concreto. Devuelve un cero si ha encontrado algo. El parámetro TSearchRec es una estructura de datos donde se almacena lo encontrado:

type
TSearchRec = record
Time: Integer;
Size: Integer;
Attr: Integer;
Name: TFileName;
ExcludeAttr: Integer;
FindHandle: THandle;
FindData: TWin32FindData;
end;


function FindNext( var F: TSearchRec ): Integer;

Busca el siguiente archivo, directorio o unidad especificado anteriormente por la función FindFirst. Devuelve un cero si ha encontrado algo.

procedure FindClose( var F: TSearchRec );

Este procedimiento cierra la búsqueda comenzada por FindFirst y FindNext.

Veamos un ejemplo donde se utilizan estas funciones. Vamos a hacer un procedimiento que lista sólo los archivos de un directorio que le pasemos y vuelca su contenido en un StringList:

procedure TFPrincipal.Listar( sDirectorio: string; var Resultado: TStringList );
var
Busqueda: TSearchRec;
iResultado: Integer;
begin
// Nos aseguramos que termine en contrabarra
sDirectorio := IncludeTrailingBackslash( sDirectorio );

iResultado := FindFirst( sDirectorio + '*.*', faAnyFile, Busqueda );

while iResultado = 0 do
begin
// ¿Ha encontrado un archivo y no es un directorio?
if ( Busqueda.Attr and faArchive = faArchive ) and
( Busqueda.Attr and faDirectory <> faDirectory ) then
Resultado.Add( Busqueda.Name );

iResultado := FindNext( Busqueda );
end;

FindClose( Busqueda );
end;

Si listamos el raiz de la unidad C:

var
Directorio: TStringList;
begin
Directorio := TStringList.Create;
Listar( 'C:', Directorio );
ShowMessage( Directorio.Text );
Directorio.Free;
end;

El resultado sería:

AUTOEXEC.BAT
Bootfont.bin
CONFIG.SYS
INSTALL.LOG
IO.SYS
MSDOS.SYS
NTDETECT.COM

Con estas tres funciones se pueden hacer cosas tan importantes como eliminar directorios, realizar búsquedas de archivos, calcular lo que ocupa un directorio en bytes, etc.

Pruebas realizadas en Delphi 7.

03 septiembre 2007

Trabajando con archivos de texto y binarios (y V)

Vamos a terminar de ver los recursos de los que dispone Delphi para el tratamiento de archivos.

PARTIENDO UN ARCHIVO EN DOS

En este ejemplo vamos a coger el archivo prueba.dat de 100 Kb y dejamos sólo las primeras 30 Kb eliminando el resto:

procedure TFormulario.PartirArchivo;
var F: File of byte;
Buffer: array[0..1023] of Byte;
i: Integer;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'prueba.dat' );
Reset( F );

// Leemos 30 Kb utilizando un buffer de 1 Kb
for i := 1 to 30 do
BlockRead( F, Buffer, 1024 );

Truncate( F );
CloseFile( F );
end;

Hemos utilizado para ello la función Truncate, la cual parte el archivo que estamos leyendo según donde este el puntero F.


ASEGURANDO QUE SE GUARDE LA INFORMACIÓN EN ARCHIVOS DE TEXTO

Cuando abrimos un archivo de texto para escribir en él no se guarda completamente toda la información hasta que se cierra con el procedimiento CloseFile. Esto puede dar problemas si en algún momento el procedimiento WriteLn provoca un error dejando el archivo abierto. Se perdería la mayor parte de la información que supuestamente debería haberse guardado en el mismo.

Para evitar esto disponemos de la función Flush:

function Flush( var t: TextFile ): Integer;

Esta función lo que hace es vaciar el buffer del archivo de texto en el disco duro. Es algo así como ejecutar CloseFile pero sin cerrar el archivo. Por ejemplo:

var
F: TextFile;
sArchivo: String;
begin
sArchivo := ExtractFilePath( Application.ExeName ) + 'prueba.txt';
AssignFile( F, sArchivo );
Rewrite( F );
WriteLn( F, 'Esto es el contenido del archivo de texto.' );
Flush( F );
// aquí podemos seguir escribiendo en el mismo
CloseFile( F );
end;

GUARDANDO OTROS TIPOS DE DATOS EN LOS ARCHIVOS

Hasta ahora sólo hemos guardado información de tipo texto y binaria en archivos pero se puede guardar cualquier tipo de información utilizando cualquier tipo de dato. Por ejemplo para guardar una serie de números reales se haría de la siguiente manera:

procedure TFormulario.GuardarRecibos;
var
F: File of Real;
r: Real;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'ImporteRecibos.dat' );
Rewrite( F );
r := 120.45;
Write( F, r );
r := 1800.05;
Write( F, r );
r := 66.31;
Write( F, r );
CloseFile( F );
end;

Si os fijais bien en el archivo resultante vemos que ocupa 24 bytes. Esto es así porque el tipo real ocupa 8 bytes en memoria y como son 3 números reales lo que hemos guardado entonces hace un total de 24 bytes. Hay que asegurarse de que tanto al leer como al guardar información en este tipo de archivos se haga con el mismo tipo de variable. Si guardamos información con File of Real y la leemos con File of Single los resultados podrían ser catastróficos (a parte de quedarse el archivo a medio leer).

También se pueden guardar estructuras de datos almacenadas en registros. En este ejemplo que voy a mostrar vamos a ver como guardar los datos de un cliente en un archivo:

type
TCliente = record
ID: Integer;
sNombre: String[50];
rSaldo: Real;
bPagado: Boolean;
end;

procedure TFPrincipal.CrearArchivoRegistro;
var
Cliente: TCliente;
F: File of TCliente;
begin
AssignFile( F, ExtractFilePath( Application.ExeName ) + 'clientes.dat' );
Rewrite( F );

with Cliente do
begin
ID := 1;
sNombre := 'FRANCISCO MARTINEZ LÓPEZ';
rSaldo := 1200.54;
bPagado := False;
end;

Write( F, Cliente );

with Cliente do
begin
ID := 2;
sNombre := 'MARIA ROJO PALAZÓN';
rSaldo := 622.32;
bPagado := True;
end;

Write( F, Cliente );

CloseFile( F );
end;

Unas de las cosas a tener en cuenta es que cuando se define una estructura de datos no se puede definir una variable de tipo String sin dar su tamaño, ya que cada registro debe tener una longitud fija. Si ponemos String nos da un error al compilar, por eso hemos puesto en el nombre un String[50].

Con esto finalizamos la parte básica de tratamiento de archivos en Delphi.

Pruebas realizadas en Delphi 7.

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.

Publicidad