Mar 28

Convertir boolean a string

Si bien cualquier programador es capaz de hacer una función que reciba una variable del tipo boolean y devuelva una cadena de caracteres (string) en pocos minutos, ¿para que reinventar la rueda?

El título de esta entrada, en sí está mal, pues sabemos que en Pascal una variable no puede cambiar de type, pero se entiende.

BoolToStr se puede usar de dos formas:

BoolToStr ( variableBoolean )

De este modo retorna un ‘-1’ en caso de que la variable sea True o ‘0’ (cero) si es False.

BoolToStr ( variableBoolean, 'Verdadero', 'Falso')

En cambio de esta forma podemos pasar los strings para cada valor, correspondiendo el primero para True y el siguiente para False. Estos parámetros son recibidos como const (constantes) por la función, como puede apreciarse en la wiki de FreePascal: BoolToStr.

 

Mar 26

Arrastrar y soltar entre listas (Drag and Drop)

Lo primero a tener en cuenta es que lo que se arrastra de un contenedor tiene que ser compatible con el contenedor receptor. Lo habitual es que se arrastren y suelten strings (cadenas) entre listas de strings, es lo más básico. En el ejemplo se arrastrará y soltará desde una lista del tipo TFileListBox a otra del tipo TListBox.

Hay que preparar ambos objetos, el TFileListBox para que permita arrastrar sus ítems y a TListBox para que acepte lo que le llega de TFileListBox.

En un Form crear ambos componentes y dejarles su nombre por default (TFileListBox1 y ListBox1).

Desde el inspector de objetos seleccionamos FileListBox1 y establecemos dmAutomatic en la propiedad DragMode. Con esto solo conseguimos que los elementos contenidos en esa lista se puedan arrastrar. Y con este elemento no es necesario hacer más nada.

Ahora seleccionamos también desde el inspector de objetos ListBox1, en Eventos, definimos OnDragDrop y OnDragOver. (definimos = hacer click en el botón con los ‘…’).

procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  if (Source is TFileListBox) then ListBox1.AddItem(FileListBox1.items[FileListBox1.ItemIndex],ListBox1);
end;

ListBox1 agregará ítems que provengan de objetos del tipo TFileListBox.

 

procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
		State: TDragState; var Accept: Boolean);
begin
  Accept:=(Source is TFileListBox);
end;  

ListBox1 aceptará que objetos del tipo TFileListBox le suelten ítems.

Arrastrar y soltar entre listas – Lazarus from lazarus.elsigno.com on Vimeo.

Mar 21

TTimer

El componente TTimer es un temporizador con un intervalo mínimo de un milisegundo aproximado aunque es recomendable establecer un intervalo mínimo de 10 milisegundos para que se aproxime más a la realidad, si el intervalo se establece en 100 o 1000 entonces se obtendrá un mejor resultado. Free Pascal cuenta con varios temporizadores, siendo TTimer el más simple y limitado. Para medir con exactitud es recomendable crear un cronómetro propio en base a la hora provista por el sistema operativo y calcular en base a la diferencia entre finalización e inicio. Pero si lo que buscamos no requiere de mayor precisión que la de un segundo o décima de segundo, entonces Timer es el indicado.

Los ingredientes necesarios para seguir el ejemplo son, además del Form:

  • 1 TTimer
  • 1 TLabel
  • 3 TButton
  • 1 variable integer (privada o pública)

TTimer

PaletaSystem

El componente TTimer se encuentra en la paleta System.

Por defecto el Timer viene activado, para este ejemplo, desde el inspector de objetos lo desactivamos.

Timer1

De paso definimos el intervalo en 100 para obtener una medición lo más real posible, claro que después se puede jugar y poner en 1 a ver qué pasa. Pasará que en 10 segundos medirá 6 o como mucho 7; si lo ponemos en 10, en 10 segundos medirá 8-9 aprox. y en 1000 el margen de “error” será de aprox. 1% o incluso menos.

Form-Timer-Desing

Sí, el contador en vede, bold y agrandado porque todo en escala de grises es simplemente aburrido y la programación es divertida, es arte (aunque este ejemplo precisamente no merezca tal distinción).

Pasemos al código:

unit Unit1;

{$mode objfpc}{$H+}

interface

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

type

  { TForm1 }

  TForm1 = class(TForm)
    btnComenzar: TButton;
    btnDetener: TButton;
    btnVolverACero: TButton;
    lblContador: TLabel;
    Timer1: TTimer;
    procedure btnComenzarClick(Sender: TObject);
    procedure btnDetenerClick(Sender: TObject);
    procedure btnVolverACeroClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    private
      contador:Integer;
    { private declarations }
    public
    { public declarations }
 end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  contador:=contador+1;
  lblContador.Caption:=IntToStr(contador);
end;

procedure TForm1.btnComenzarClick(Sender: TObject);
begin
  Timer1.Enabled:=True;
end;

procedure TForm1.btnDetenerClick(Sender: TObject);
begin
  Timer1.Enabled:=False;
end;

procedure TForm1.btnVolverACeroClick(Sender: TObject);
begin
  contador:=0;
  lblContador.Caption:=IntToStr(contador);
end;

end. 

El resultado:

Timer-Run

 

Mar 20

Obtener la lista de tablas de una base de datos SQL

ZConnectionEs algo muy simple desde consola o en tiempo de diseño, pero en tiempo de ejecución? También es sencillo, lo difícil fue encontrar cómo hacerlo. Resulta que Zeos (ZeosLib), más precisamente su principal componente, ZConnection, posee un procedimiento llamado GetTableNames que nos devuelve un parámetro del tipo TStrings pasado como referencia (lógico, si no fuese pasado como referencia no devolvería nada) con la lista de todas las tablas de la base de datos conectada. Se le puede pasar un TStringList o un ListBox.items por ejemplo.

 

Primer ejemplo con TStringList y un TMemo:

// Definir procedimiento o función o agregar el código donde sea necesario
var
  listatablas:TStringList;
  i:Integer;          
begin
  listatablas:=TStringList.Create;   
  ZConnection1.GetTableNames('',listatablas);   // La base de datos debe estar conectada...
  for i:=0 to listatablas.Count-1 do
    Memo1.Lines.Add(listatablas[i]);     // Memo1 debe estar en el Form
  ...
end;
   

El primer parámetro pasado podría haber sido por ejemplo ‘c*’ en cuyo caso hubiésemos obtenido la lista de tablas cuyos nombres comiencen con c. Al pasarlo vacío le indicamos que llene el StringList con todas las tablas.

Este ejemplo, con muy poco código, alcanza y sobra para mostrar los nombres de las tablas, pero si queremos que además el usuario pueda elegir una (o varias) tablas podemos valernos de un ListBox. Para ello, no le pasaremos como parámetro el ListBox sino ListBox.Items que es del tipo TStringList.

Segundo ejemplo con ListBox:

ZConnection1.GetTableNames('',ListBox1.Items) // Requiere un ListBox en el Form
   

No puede ser más fácil, y con solo una línea de código.

 

Mar 17

DBGrid: seleccionar una columna

El componente DBGrid carece de una opción para marcar una columna de la forma en que lo hacemos con una fila con dgRowSelect, es decir, no tenemos la opción dgColSelect. Lo cual no significa que no podamos hacerlo, de hecho el código es bastante simple:

procedure TForm1.DBGrid1TitleClick(Column: TColumn);
begin
  DBGrid1.Columns[Column.Index].Color := clHighlight;
  DBGrid1.Columns[column.Index].Title.Color := clHighlight;
end;   

Basta con dos líneas de código para el evento TitleClick que nos envía parámetro Column del tipo TColumn, esto nos facilita todo, pues usamos la propiedad Index para saber sobre que columna (el itulo  de la columna) se hizo el click y le cambiamos el color tanto al título como a las celdas, según se desee.

columnclick

Mar 16

Como simular un click

click

Por ejemplo, un click en un botón que tiene definido el evento OnClick que a su vez pertenece a una clase que generalmente es un formulario. No puede llamarse como si fuese un procedimiento o una función, si bien está declarado como un procedimiento, es un evento.

Si tengo declarado este evento en el típico Form1:

procedure btnCerrarDBClick (Sender: TObject);

se puede invocar de la siguiente  manera:

btnCerrarDBClick (self);

Formas incorrectas y que por lo tanto arrojarán errores de compilación:

Form1.btnCerrarDBClick;

btnCerrarDBClick;

 

Mar 08

Pausar un programa con Delay y Sleep

Delay es un procedimiento de Free Pascal disponible en la unidad Crt que permite pausar el programa en x milisegundos.

Es sumamente útil cuando queremos mostrar un mensaje al usuario y que lo lea antes de hacer click casi por acto reflejo sin leer.

Para utilizarla simplemente escribimos

Delay (5000); //Pausa de 5 segundos

El parámetro que recibe el procedimiento es del tipo Word, por ende los valores deben estar dentro del rango 0 .. 65535. Esto nos da aproximadamente un minuto, ya que el tiempo es aproximado. Si se necesita más tiempo, no es necesario hacer varias llamadas a Delay dentro de un loop, sino que contamos con un procedimiento similar llamado Sleep que recibe como parámetro por valor del tipo Cardinal que es a su vez del tipo LongWord cuyo rango es 0 .. 4.294.967.295 lo cual es suficiente como para poner en coma el programa por casi 50 días.

Sleep (4294967000); // lo duerme por 50 días

Sin olvidarse de agregar Crt en uses para el caso de utilizar Delay o sysutils en caso de hacer uso de Sleep.

Documentación oficial de Delay.

Documentación oficial de Sleep.

 

Mar 07

Volver a cero el rowid de SQLite

Como reiniciar o “resetear” el rowid de una tabla en SQLite? Muchas veces definimos el famoso campo ID como integer, primary key, autoincrement, unique para que SQLite se encargue de él y lo hace, guardando el valor del último registro y cada vez que agregamos uno toma esa valor y lo incrementa en uno. Es por eso que si tenemos 10 registros con ID del 1 al 10 y borramos 5 y luego insertamos uno, el valor de este último será 11 y no 6. Si tememos una tabla con 500 filas y las borramos todas, el próximo registro, el ID será 501, si lo que buscamos es que sea 1 y no 501, hay una solución, luego de borrar toda la tabla ejecutamos el siguiente comando:

DELETE FROM SQLITE_SEQUENCE WHERE name='tutabla';

Ejemplo: borrar la tabla cuentas

DELETE FROM cuentas; DELETE FROM SQLITE_SEQUENCE WHERE name='cuentas';

Si devuelve el siguiente error: No such table ‘SQLITE_SEQUENCE’ se debe a que en el schema no está definido, sqlite_sequence se crea al definir una columna como integer, primary key, autoincremental, unique.

Es muy útil para tablas que usamos como temporales, no del tipo temporal que es distinto.

Otro ejemplo con código Free Pascal:

dmrc.ZQtemp.SQL.Text:='DELETE FROM tasiento; DELETE FROM SQLITE_SEQUENCE WHERE name=''tasiento'';';

 

Mar 07

Lazarus problema con acentos: solución.

A veces puede pasar que de un día para el otro y sin recordar haber hecho ningún cambio en nuestro IDE ni en el sistema operativo, Lazarus no acentúa, dicho de otra manera, se come los acentos o los duplica en el inspector de objetos. Y el resto de los programas que tenemos instalados no reproducen este error, es solo Lazarus. Nada grave, se puede convivir con ello y seguir programando sin ningún problema, por ejemplo, escribiendo el caracter acentuado en un editor de texto plano como Gedit y copiándolo y pegándolo en el editor de código fuente del RAD Lazarus. Nada cómodo pero sirve mientras buscamos “algo” que nos devuelva los acentos.

La solución que describo está comprobada exitosamente en Lazarus 1.6 FPC 3.0.0 sobre Linux Mint 17.2 x64 MATE Gtk-2.

Hay que hacer un script, con un editor de texto plano:

#!/bin/sh
export GTK_IM_MODULE=gtk-im-context-simple
export QT_IM_MODULE=simple
export XMODIFIERS=@im=none
startlazarus %f

Lo guardamos donde nos resulte cómodo con la extensión .sh y lo marcamos como ejecutable.
Y de ahora en más, lanzaremos Lazarus desde ese script. Este método está documentado en la wiki de Free Pascal.

Pantallazo

 

Mar 07

Crear tablas en SQLite con código Free Pascal

Como casi todo en programación, hay varias formas de hacer una misma tarea y ésta no es la excepción. Crearemos una base de datos y una tabla. También le agregaremos un registro a la tabla, todo con código Free Pascal desde Lazarus y utilizando un solo componente de Zeos Lib que no facilita las tareas con las bases de datos.

Ya he escrito que para crear una base de datos en SQLite solo hay que establecer la conexión, si la base de datos no existe, entonces SQLite la crea.
Solo necesitamos un TZConnection que lo soltaremos en el Form o en el Data Module.

Nota: al ZConnection1 en la propiedad protocol desde el inspector de objetos le seleccionamos sqlite3.

Este ejemplo consiste en una función que crea una base de datos, una tabla y un registro en la misma. Si todo salió bien se devuelve True, caso contrario, False.

function CrearEmpresas: Boolean;
var
  ret:Boolean;
begin
  ret:=True;
  if not FileExists('/home/programa1/empresas.db') then
  begin
    if ZConnection1.Connected then ZConnection1.Disconnect;
    try
      ZConnection1.Database:='/home/programa1/empresas.db';
      ZConnection1.Connect;
      ZConnection1.ExecuteDirect('CREATE TABLE IF NOT EXISTS empre ( '+
      'idempre INTEGER      PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, '+
      'nombre  VARCHAR (40) NOT NULL, '+
      'cuit    VARCHAR (13) NOT NULL, '+
      'db      VARCHAR (40) NOT NULL, '+
      'dir     VARCHAR (80) NOT NULL );');
      ZConnection1.ExecuteDirect('INSERT INTO empre VALUES '+
      '(1,''DEMO EMPRESA S.A.'',''20-12345678-9'',''demodb.db'','+''''+CurrentDirectory+''');');
      ZConnection1.Disconnect;
    Except
      ret:=False;
    end;
    ZConnection1.Disconnect;
  end;
  CrearEmpresas:=ret;
end;  

Para evitar errores, primero verificamos que el archivo no exista y luego también chequeamos que ZConnection1 no esté conectado, pues si lo está no nos sirve, dado que para estar conectado lo está a una base de datos, y necesitamos crear la base de datos lo cual sucede al momento de efectuar la conexión. Por eso, si Connected devuelve True, lo desconectamos. Lo que sigue lo “envolvemos” en un try / Except y si salta algún error la función retornará False.

Ya asegurándonos de que ZConnection1 está desconectado, le establecemos la base de datos con path completo aunque sin path creará la DB en el directorio de ejecución, por lo tanto puede obviarse el path y poner ‘empresas.db’ por ejemplo. Conectamos y en ese momento es cuando la DB es creada. Acto seguido, valiéndonos del método ExecuteDirect incluiremos el mismo código que escribiríamos en una terminal o consola para SQLite con la diferencia de que necesitamos el uso de comillas dobles en la inserción de datos. Con el primer ExecuteDirect creamos la tabla empresas.db si no existe, algo que sobra porque acabamos de crear la DB. Con el segundo, agregamos un registro, ahora llamado fila, lo cual es correcto pero no me gusta. Y ahí debemos utilizar las comillas dobles, pulsando dos veces la comilla simple, no utilizar nunca la doble. El 1 al tratase de un entero, no necesita comillas, es un valor numérico, el resto de los campo (ahora columnas) al ser de texto (VARCHAR) si necesita estar entre comillas simples, pero al estar ya dentro de un texto entre comillas, para indicarle al compilador que la comilla no finaliza el texto, en lugar de una comilla simple escribimos dos comillas simples, el dato, y nuevamente dos comillas simples.

Ahora pasemos a explicar esto:

‘ ‘demodb.db’ ‘,  ‘+ ‘ ‘ ‘ ‘ + CurrentDirectory + ‘ ‘ ‘ ) ; ‘ ) ;

corto la cadena de caracteres luego de la coma, la finalizo con la comilla simple y le concateno el directorio actual:

‘ ‘ ‘ ‘  la primer comilla inicia el texto y la última lo cierra, en medio dos comillas imprimen una comilla en la cadena.
+ para agregar el string que me devuelve CurrentDirectory, nuevamente + ‘ ‘ ‘  tres comillas, la primera para iniciar nuevamente un texto, la segunda y tercera para imprimir una comilla, luego ) ; ‘ esa última comilla cierra.

Lo que sigue es un Disconnect que podría obviarse, pero prefiero que sobre y no que falte.

 

Entradas más antiguas «