viernes, 31 de enero de 2020

Crear un formulario en tiempo de ejecución.

Muchas veces cuando programamos queremos ver los datos de las variables, ya sean arrays, stringlist o lo que sea, y nos valemos de uno o más TMemo para mostrar allí los datos. También para mostrar al usuario los logs o alguna otra información. Hay muchas formas de hacer esto, una de ellas creando un TForm con un Tmemo, de esta manera tendremos en la unidad más de un formulario, claro que solo uno será el creado por Lazarus con su correspondiente archivo .lfm.

Para el caso agregar en el Form 3 TButtons, solo eso.

Veamos el código.

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
  Button1: TButton;
  Button2: TButton;
  Button3: TButton;
  procedure Button1Click(Sender: TObject);
  procedure Button2Click(Sender: TObject);
  procedure Button3Click(Sender: TObject);
  procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  procedure FormCreate(Sender: TObject);
  private
  sl1:TStringList;
  sl2:TStringList;
  sl3:TStringList;
  procedure VerLista(sl:TStringList);
public

end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  i:Integer;
begin
  sl1:=TStringList.Create;
  sl2:=TStringList.Create;
  sl3:=TStringList.Create;
  for i:=0 to 99 do
  begin
    sl1.Add(IntToStr(i));
    sl2.Add(IntToStr(i*10));
    sl3.Add(IntToStr(i*100));
   end;
end;

procedure TForm1.VerLista(sl: TStringList);
var
  F:TForm;
  mm:TMemo;
begin
  F:=TForm.Create(nil);
  F.Height:=380;
  F.Width:=500;
  F.Position:=poMainFormCenter;
  mm:=TMemo.Create(nil);
  mm.Text:=sl.Text;
  mm.Parent:=F;
  mm.Align:=alClient;
  mm.ScrollBars:=ssAutoBoth;
  F.ShowModal;
  FreeAndNil(mm);
  FreeAndNil(F);
end;

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  CloseAction:=caFree;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  VerLista(sl1);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  VerLista(sl2);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  VerLista(sl3);
end;

end.


Lo más importante es esto
mm.Parent:=F;
que establece el parentesco, sitúa el TMemo dentro del formulario F. El resto es simplemente asignar valores a las propiedades tanto del formulario como del memo.

viernes, 24 de enero de 2020

TStringGrid: borrar filas.

¿Por qué no hay un método (procedimiento o función) para eliminar todas la filas de un StringGrid excepto las fijas? Porque con StringGrid1.RowCount:=1; alcanza, dejando la primera fila únicamente, siendo ésta la fija (los títulos de las columnas), si se tratara de una grilla sin fila fija, entonces sería StringGrid1.RowCount:=0; casi siempre se utiliza la primer fila para los títulos de las columnas, la fila 0 (cero), de esta forma se conservan las propiedades de las columnas.

Otras formas de borrar filas:

DeleteRow(nroFila) donde nroFila es un entero, simplemente borra dicha fila.
Clear: Borra todo, no queda nada.

Borrar ciertas filas en base a una condición.

Lo primero a tener en cuenta es saber que utilizando un for to no vamos a ningún lado, por ejemplo:

for i:=0 to StingGrid1.RowCount-1 do
  if condicion then StringGrid.DeleteRow(i);

nos dará un error en timepo de ejecución; veamos por qué:

Supongamos que tenemos una grilla con 3 filas sin filas fijas, de la 0 a la 2 y sin ninguna condición usamos el método anterior. La propiedad RowCount-1 será 3-1=2 entonces el ciclo for será de 0 a 2, hasta ahí vamos bien.

En la primera iteración:
for i:=0 to StingGrid1.RowCount-1 do //i=0 y RowCount=2 es decir de 0 a 2
  StringGrid.DeleteRow(i); //Borra la fila 0

en la segunda iteración:
for i:=0 to StingGrid1.RowCount-1 do //i=1 y RowCount=1 es decir de 1 a 1
  StringGrid.DeleteRow(i); //Borra la fila 1

en la tercera iteración:
for i:=0 to StingGrid1.RowCount-1 do //i=2 y RowCount=1 es decir de 2 a 1 //---->ERROR<----
  StringGrid.DeleteRow(i); //Borra la fila 0

La forma correcta es mediante for downto. Supongamos una grilla de n filas con la primera de ellas fija la cual no borraremos, entonces:

for i:=StringGrid.RowCount-1 downto 1 do //el ciclo no llega a la fila 0.
  StringGrid.DeleteRow(i);

y con un condicional sería lo mismo pero agregando la condición:

for i:=StringGrid.RowCount-1 downto 1 do //el ciclo no llega a la fila 0.
  if condicion then
    StringGrid.DeleteRow(i);

jueves, 23 de enero de 2020

Clase para medir tiempo de ejecución.

Si bien esta entrada que escribí hace exactamente dos años (increíble, juro que es coincidencia) acerca de medir el tiempo de ejecución de un proceso es totalmente válida y funcional, mejor aún es hacer una clase (class) que haga lo mismo. Las ventajas son muchas, desde la legibilidad del código hasta la manera más simple se su reutilización.

La clase es simple:

type
  TMedirTiempo=class(TObject)
private
  FInicio:TTime;
  FFinaliza:TTime;
  FTranscurrido:TTime;
public
  constructor Create (Owner:TComponent);
  function Iniciar:TTime;
  function Finalizar:TTime;
  function Transcurrido:TTime;
end;


Cómo estoy comenzando a implementar clases, luego de leer bastante, voy a explicar un poco algunas cosas que la mayoría de los colegas programadores ya saben, pero los que retomamos la programación luego de muchos años, no.

Por ejemplo, la clase la definimos como class(TObject) porque es una clase que no hereda de ninguna otra clase y la más "primitiva" es TObject. Ahora:
TMedirTiempo=class así sin TObject también es válido, pero no recomendado.
FInicio es un campo (Field) por eso se acostumbra a empezar con la letra F; los campos son variables que pertenecen a una clase. Si se declaran en la "sección" (en realidad no es sección aunque lo aparenta muy bien, private es un identificador) private solo podrá se accedido por otros miembros de la clase y clases heredadas pero no por la instancia de la clase, por ejemplo:

var
  Medidor:TMedirTiempo;
begin
  Medir:=TMedirTiempo.Create(nil);
  Medir.Finicio:= // esto no se puede.

¿Por qué usamos un constructor? Porque en él inicializamos los campos, pero muchas veces no es necesario definirlo, en tal caso se utiliza el heredado de TObject. Y sí, el constructor es público, lo mismo que las funciones, lo que implica que se pueden invocar en la unidad donde instanciamos la clase.

Hasta aquí la interfaz, vamos ahora a la implementación de la clase.

constructor TMedirTiempo.Create(Owner: TComponent);
begin
  FInicio:=StrToTime('0');
  FFinaliza:=StrToTime('0');
  FTranscurrido:=StrToTime('0');
end;

function TMedirTiempo.Iniciar: TTime;
begin
  FInicio:=Now;
end;

function TMedirTiempo.Finalizar: TTime;
begin
  FFinaliza:=Now;
end;

function TMedirTiempo.Transcurrido: TTime;
begin
  FTranscurrido:=FFinaliza-FInicio;
  Result:=FTranscurrido;
end;


Y para utilizarla es muy simple, por ejemplo:

Procedure CargarSList;
var
  MedirTiempo:TMedirTiempo;
  sl:TStringList;
  i:Integer;
begin
  sl:=TStringList.Create;
  MedirTiempo:=TMedirTiempo.Create(nil);
  MedirTiempo.Iniciar;
  for i:=1 to 100000 do
    sl.Add(IntToStr(i));
  MedirTiempo.Finalizar;
  ShowMessage('Tiempo transcurrido: '+TimeToStr(MedirTiempo.Transcurrido));
  sl.Free;
  MedirTiempo.Free;
end;


También podríamos mostrar la hora de inicio y fin de la medición, porque ambas son funciones que devuelven el tiempo.

Puede pasar (y pasa) que el tiempo es menor a un segundo, entonces se muestra 0 (cero). Una opción para agregar los milisegundos es utilizar la misma función TimeToStr (que se ubica en la unidad DateUtils) con la opción FormatSettings.
Sería algo así:

Procedure CargarSList;
var
  MedirTiempo:TMedirTiempo;
  sl:TStringList;
  i:Integer;
  fs:TFormatSettings;
begin
  fs:=DefaultFormatSettings;
  fs.LongTimeFormat:='hh:nn:ss.zzz';
  sl:=TStringList.Create;
  MedirTiempo:=TMedirTiempo.Create(nil);
  MedirTiempo.Iniciar;
  for i:=1 to 100000 do
    sl.Add(IntToStr(i));
  MedirTiempo.Finalizar;
  ShowMessage('Tiempo transcurrido: '+TimeToStr(MedirTiempo.Transcurrido,fs));
  sl.Free;
  MedirTiempo.Free;
end;


TFormatSettings es un registro (record) por lo tanto ni se crea ni se libera.