viernes, 15 de diciembre de 2017

Comprobar el contenido de un string

Muchas veces es necesario validar que una cadena de texto contenga solo ciertos caracteres, por ejemplo solo letras mayúsculas, minúsculas y números. Como todo hay mil formas de hacerlo, en este caso usaremos conjuntos, un ciclo FOR .. DO para recorrer el string, que de paso recordamos que su índice comienza con 1, y un condicional IF.. THEN. Todo esto, en una función que retornará True si el string contiene los caracteres "válidos" o False si alguno de ellos no cumple con la regla.

function ChequearContenido (const c:String) : Boolean;
var
  i:Integer;
begin
  for i:=1 to Length(c) do
    if not ( c[i] in ['A'..'Z', 'a'..'z', '0'..'9'] ) then Exit (False);
ChequearContenido:=True;
end;


Y todo sin siquiera la necesidad de recurrir a la definición de conjuntos.

miércoles, 13 de diciembre de 2017

Detener un programa mediante Halt

Halt es un procedimiento que al ser llamado, detiene el programa, "a lo bestia" podría decirse, sin vueltas, el programa finaliza. No es necesario un ejemplo, basta con llamar a Halt desde cualquier parte de nuestro código y listo.

Halt devuelve el control al programa que lo llamó, si el que lo llamó fue el sistema operativo, volverá a él, si fue otro programa, retornará a ese otro programa, por eso el procedimiento Halt permite un argumento (un integer) como valor de salida. Si este argumento no se especifica, su valor por default es cero.

Documentación oficial de Halt en FreePascal.org

domingo, 26 de noviembre de 2017

Recorrer un DataSet y actualizar registros

Si queremos actualizar un campo de una fila o registro mientras recorremos el TDataSet por ejemplo mediante un while, obtendremos un error en tiempo de ejecución. Con un ejemplo será más sencillo:

WHILE NOT ZQuery1.EOF DO
begin
  ZQuery1.Edit;
  ZQuery1.FieldByName('alguncampo').AsInteger:=0;   
  ZQuery1.Post
  ZQuery1..Next;
end;


El código precendente nos dará el error y es correcto que lo haga, porque no situamos el puntero al comienzo del DataSet que sería con ZQuery1.First.

ZQuery1.First;
WHILE NOT ZQuery1.EOF DO
begin
  ZQuery1.Edit;
  ZQuery1.FieldByName('alguncampo').AsInteger:=0;   
  ZQuery1.Post
  ZQuery1..Next;
end;


Otra manera de resolver esto es mediante el uso de ExecuteDirect (y más rápido si se trata de un gran volumen), si tenemos un identificador único, mejor:

ZQuery1.First;
WHILE NOT ZQuery1.EOF DO
begin
  ZConnection1.ExecuteDirect('UPDATE tabla SET alguncampo=0 WHERE    identificaorunico='+ZQuery1.FieldByName('id').AsString'+' ;');
  ZQuery1..Next;
end;


Se recomienda hacer esto cuando no podamos hacerlo mediante una sentencia SQL UPDATE, ya que puede ser lento dependiendo del volumen de datos.

viernes, 24 de noviembre de 2017

Borrar registros de un dataset mediante While

El problema aquí es que pasa con el puntero cuando se elimina un registro y el next. Pues bien, cuando se elimina el registro, pasa al siguiente, por lo tanto hay que evitar el next cuando se borra.

While not (DMF.ZQReg.EOF ) do
begin 

  if algunacondicion then
    begin
      nEmple:=DMF.ZQReg.FieldByName('regemple').AsInteger;
      dFechaHora:=DMF.ZQReg.FieldByName('regfechahora').AsDateTime;
      DMF.ZQReg.Edit;
      DMF.ZQReg.Delete;
      Inc(nContador);
    end
  else
    begin
      nEmple:=DMF.ZQReg.FieldByName('regemple').AsInteger;
      dFechaHora:=DMF.ZQReg.FieldByName('regfechahora').AsDateTime;
      DMF.ZQReg.Next;
    end;
end; 


En este caso debo eliminar registros duplicados de lector biométrico, en algunacondicion evaluo si el registro es duplicado y tomo los datos antes de borrarlo, porque a veces los datos están hasta quintuplicados.
Como puede verse, el Next lo realizo solo si no se elimina el registro (fila).

Si el volumen de datos a procesar es grande, conviene utilizar una conexión sin autocommit y utilizar BEGIN y COMMIT tal como se explica aquí.

martes, 10 de octubre de 2017

Lazarus: guardar la disposición de las ventanas

Prescindiendo de algún paquete tipo anchor docking porque simplemente en Linux con GTK2 al menos no me funciona y nunca le di mucha importancia al asunto. Hasta que descubrí, leyendo en el foro de Lazarus, donde se aprende muchísimo, dicho sea de paso, que podemos ubicar y dimensionar las ventanas como más nos guste y simplemente guardar el escritorio (desktop). No solo eso, podemos guardar todos los escritorio que queramos.

Primero abrimos todas las ventanas (Explorador del proyecto, Inspector del código, etc.) que queremos, las distribuimos convenientemente, fijamos su tamaño y cuando está todo como nos resulte más cómodo, guardamos el escritorio.


Ítem Desktops en el menú Herramientas.


Presionando el botón del diskette guardamos un nuevo escritorio, el famoso "guardar como" o "Save as...".

Diferencia entre Free y FreeAndNil

Básicamente Free libera el objeto, pero no el puntero, es decir, el puntero sigue apuntando a una dirección de memoria (inutilizable ya). Para evitar esto está FreeAndNil que además de liberar el objeto le asigna nil al puntero, por ende, si preguntamos si esta asignado nos devolverá False, de la otra forma, Assigned nos devolvería True.
Hay varias discusiones en torno a FreeAndNil, argumentos a favor y en contra. Depende la situación, a veces en un escenario particular conviene no utilizarlo.

Resumiendo, Free libera al objeto y FreeAndNil además de liberarlo le asigna Nil al puntero.

Ejemplo:

var
  objeto : TBitmap;
begin
  objeto := TBitmap.Create;
  //... Se hace algo con el objeto ...
  objeto.Free;
  if Assigned (objeto) then
    ShowMessage ('Sí, está asignado.')
  else ShowMessage ('No está asignado');
end;


Esto mostrará 'Sí, está asignado'.

Con FreeAndNil:

var
  objeto : TBitmap;
begin
  objeto := TBitmap.Create;
  //... Se hace algo con el objeto ...
  FreeAndNil (objeto);
  if Assigned (objeto) then
    ShowMessage ('Sí, está asignado.')
  else ShowMessage ('No está asignado');
end;


Esto mostrará 'No está asignado'.

Conclusión final: en la mayoría de los casos usar solo Free es correcto y también lo es usar FreeAndNil.

Este es el procedimiento FreeAndNil:

procedure FreeAndNil(var obj);
var
  temp: tobject;
begin
  temp:=tobject(obj);
  pointer(obj):=nil;
  temp.free;
end;
     

Como curiosidad aunque algo lógico, primero le asigna nil y luego hace el Free por razones más que obvias, digo curioso porque podría llamarse NilAndFree.

domingo, 8 de octubre de 2017

RecNo: obtener el registro actual de un DataSet

Un error muy común es intentar obtener y establecer el puntero de un DataSet desde la propiedad del mismo, ¿por qué? porque no está implementada y siempre retornará -1 o 0 (cero), se necesita acceder a ella a través de una clase descendiente que la implemente, por ejemplo, una query de SQL.

También es importante no confundir RecNo con RowID ni con la clave primaria ni con nada, es un puntero al registro actual y se manifiesta en forma de un número entero, un LongInt para ser más precisos, por ende puede almacenarse este valor en una variable del tipo Integer.

Ejemplo de forma incorrecta de leer la propiedad RecNo:

ZQuery.DataSource.DataSet.RecNo

La forma correcta es:

ZQuery.RecNo

Por ejemplo:

var
  RegistroActual:Integer;
begin
  RegistroActual:=ZQuery.RecNo;
  ZQuery.Close;
  //...Se hace algo....
  ZQuery.Open;
  ZQuery.RecNo:=RegistroActual;
end;

SQLiteStudio: mostrar más de 1.000 filas

El valor predeterminado (por default) de filas o registros a mostrar en una consulta (query) en SQLiteStudio es 1.000 (mil). Por eso cuando hacemos una consulta que arroja un resultado de más de mil registros, solo se muestran los primeros mil únicamente, como si se hiciera un LIMIT 1000;

Para modificar esto y cambiar ese valor por otro, ya sea mayor o menor, solo debemos acceder a la configuración a través del menú Tools, ítem Open configuration dialog. O presionando F2.


Ahora en la barra izquierda seleccionamos Data browsing.


En Number of data rows per page establecemos el número deseado de filas a mostrar y finalmente presionamos OK.

viernes, 22 de septiembre de 2017

Traducir diálogos y botones

¿Cómo hacer que el programa creado con Lazarus muestre los diálogos y botones en español?

Utilizando la unidad Translations y lafunción TranslateUnitResourceStrings.

En nuestra unidad principal del proyecto debemos incluir la unidad Translations de la manera habitual:

uses Translations

Luego podemos definir un procedimiento privado dentro del formulario de la misma unidad principal de esta forma:

private
    procedure Traducir;


Lo implementamos:

procedure TForm1.Traducir;
var
  Lang, FallbackLang: string;
begin
  Lang := 'es';
  FallBackLang := '';
  TranslateUnitResourceStrings('LclStrConsts','lclstrconsts.es.po', Lang, FallbackLang);
  TranslateUnitResourceStrings('lr_const','lr_const.es.po', Lang, FallbackLang);
  TranslateUnitResourceStrings('printer4lazstrconst','printer4lazstrconst.es.po', Lang, FallbackLang);
end;


Aclaraciones:

lclstrconsts.es.po: ya viene incluído con Lazarus, traduce los diálogos y botones.

lr_const.es.po: Solo si se utiliza LazReports, el archivo está incluido en el paquete.

printer4lazstrconst.es.po: es para traducir los diálogos de impresión, que no están traducidos en Lazarus 1.6, por lo que lo traduje y lo posteé en el foro de Lazarus para que lo agreguen, desconozco si está incuído en versiones posteriores a la 1.6, por ende es probable que si no se posee el archivo printer4lazstrconst.es.po o bien puede tirar un error o simplemente no traducir.

Finalmente debemos hacer la llamada al procedimiento, sino, no pasa nada:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Traducir;
end;


Si esto no funciona de la forma esperada hay dos opciones: una es estudiar a fondo el tema empezando por la wiki: http://wiki.freepascal.org/Translations_/_i18n_/_localizations_for_programs/es . La otra es copiando esos 3 archivos en la carpeta de nuestro proyecto y listo.

Los dos primeros archivos, como ya dije, vienen incluídos, por lo tanto solo hay que encontrarlos y copiarlos.

En esta carpeta están los 3 archivos, si se está usando la versión 1.6 de Lazarus se pueden usar sin problemas todos. Para el caso de los diálogos de impresora, pueden usarse de manera segura en versiones 1.6 y anteriores.

Exit, el procedimiento que devuelve un valor.

Caso curioso el del procedure exit que devuelve opcionalmente un valor, como si fuese una función. Claro que esto es muy práctico cuando se realizan funciones de validación de datos. Veamos la definición oficial de Exit:

procedure Exit(

const X: TAnyType

);


Exit sale de la subrutina y retorna el control a la rutina que lo llamó. Si se le llama desde la unidad principal, finaliza la ejecución del programa. El argumento opcional X permite especificar un valor de retorno, en el caso de que se invoque desde una función, siendo entonces result = X.

Ejemplo:

Function Validar ( a, b, c: integer ) : boolean;
begin
  if a=b then Exit (True);
  if b>c then Exit (True);
  Exit (False);
end;


Si a=b o b>c pudo haber ido en una sola sentencia pero lo separé para que quede más claro el ejemplo.
Si a=b retorna True y el resto no se ejecuta, lo mismo si b>c. Si no se cumplen las condiciones previas entonces retorna False.

domingo, 17 de septiembre de 2017

ZQuery y el bug en valores del tipo boolean en SQLite.

TZQuery es un componente de ZeosLib que tiene un error, al guardar un valor del tipo boolean, lo hace como Y/N en lugar 0/1.

Por ejemplo:

ZQuery1.FieldByName('puntual').asBoolean:=True;

Almacena en la columna puntual el valor Y en lugar de un 1.

Luego si se realiza una consulta ... WHERE puntual ... o WHERE NOT(puntual) no se obtendrán los resultados deseados, habrá que cambiar la consulta por WHERE puntual='Y' o WHERE puntual='N'.

Por qué SQLite lo acepta? Porque SQLite tiene la famosa particularidad de trabajar los tipos de datos por afinidad, para SQLite en definitiva es un caracter, ni siquiera le importa que no sea númerico, acepta Y/N y también una M. Error de SQLite? No, guste o no, con ventajas y desventajas, SQLite es así.

Error de ZeosLib? Sí, de hecho el mismo fue reportado (ver ticket) y solucionado para versiones 7.2 y obviamente, posteriores.

Soluciones, varias:

Conviviendo con el bug, podría usarse asInteger, por ejemplo:

ZQuery1.FieldByName('puntual').asInteger:=1;

No lo he chequeado pero debería funcionar.

Actualizar ZeosLib a una versión 7.2 o posterior.

Poner esta opción en el componente, comunmente ZQuery:

ZQuery.Properties.Values['BindOrdinalBoolValues'] := 'True';

Esto se puede hacer también desde el inspector de objetos, en Properties. 
Solución que obtuve desde el ticket del bug, aportada por uno de los desarrolladores de Zeos. Y teniendo en cuenta que hoy, la última versión estable es la 7.1.4 es una buena alternativa para quienes optamos siempre por versiones estables.

jueves, 7 de septiembre de 2017

SQLite – Triggers: explicación y ejemplos

Un trigger es un evento que se lanza cuando ocurre algo determinado en una base de datos y se ejecuta una sentencia SQL. Ese “algo” que ocurre puede ser que se borre un registro (fila) de una determinada tabla, que se agregue un registro o que se modifique uno o más campos de una tabla, es decir, un INSERT, DELETE o UPDATE.

¿Cuándo se declara el trigger? Puede declararse en cualquier momento y siempre de acuerdo a las necesidades del caso, siendo lo normal hacerlo al momento de crear las tablas, como los índices y constraints.

No es necesario que las tablas actuantes en el trigger estén relacionadas mediante una Foreign Key.

Existe una IDE para SQLite muy práctica para este tipo de cosas, especialmente hasta que le tomemos la mano, nos es de gran ayuda, que es SQLiteStudio (está en inglés) y es de código abierto.

Uno puede, no obstante, prescindir de los triggers de SQLite y hacerlo a mano, es decir, mediante programación, en mi caso, con Free Pascal desde Lazarus, de hecho lo estuve haciendo hasta ahora, por “seguridad”, por preferir tener el control total especialmente en cuanto a la validación de por ejemplo valores duplicados, todavía lo hago, no me llevo bien con el manejo de excepciones, además SQLite carece de Stored Procedures, de momento son excusas válidas. Pero para borrar un registro en otra tabla, actualizar un dato, o agregar, casos simples, empecé a utilizar triggers, más precisamente hace dos días y por suerte además de funcionar correctamente, no es algo difícil de implementar.

Los ejemplos son casos reales de un programa de control de proveedores e insumos.


INSERT:

Un trigger que cree un registro en otra tabla cada vez que el usuario da de alta un insumo.

CREATE TRIGGER agregoinsu
         AFTER INSERT
            ON prod
BEGIN
    INSERT INTO movinsu (
                            idcompra,
                            idprod,
                            fecha,
                            cantidad,
                            precio,
                            total,
                            saldo
                        )
                        VALUES (
                            0,
                            new.id,
                            '2016-01-01',
                            new.ini,
                            0,
                            0,
                            0
                        );
END;


Al trigger lo nombré “agregoinsu”, luego hay que indicar cuando se dispara el trigger, antes o después y antes o después de ¿qué?, en este caso después (AFTER) de que se produjo el INSERT. ¿en qué tabla?, ON prod (en la tabla prod). Nota: la tabla prod debería llamarse insumos, pero se llama prod por motivos que no vale la pena aclarar. Hasta acá definimos cuando se lanza el trigger, ahora debemos indicar que se hace y eso lo hacemos mediante sentencias SQL comprendidas entre BEGIN y END; En este caso se indica que se inserte un registro en la tabla “movinsu” que posee un campo autoincremental “id” que se omite porque se encarga SQLite, el resto de las columnas o campos los especifico. Ahora debo definir los valores de esa nueva fila en “movinsu” y acá aparece la palabra new. Para triggers de inserción, se utiliza new, para triggers de eliminación se utiliza old y para triggers de actualización se pueden utilizar ambos (new y old). “new.id” significa que el nuevo registro en movinsu, en la columna idprod, irá el valor del nuevo (new) id (prod.id) y en la columna cantidad ira el valor “new.ini” que viene de prod.ini. Resumiendo, cada vez que se agregue, o dé de alta, un insumo (prod) se insertará una fila en la tabla movinsu con los valores especificados.

Este trigger debe definirse sobre la tabla que lanza el evento, en este caso, la tabla prod.

DELETE:

Lo contrario al ejemplo anterior, un trigger que elimine un registro en la tabla movinsu cuando se elimina un insumo de la tabla prod.

CREATE TRIGGER borroinsu
         AFTER DELETE
            ON prod
BEGIN
    DELETE FROM movinsu
          WHERE (idprod = old.id) AND
                (idcompra = 0);
END;


Este trigger es muy simple, AFTER (después) de DELETE (borrar) un registro ON (en) la tabla prod se ejecuta la sentencia SQL comprendida entre BEGIN y END, borrar de la tabla movinsu donde “old.id” sea igual a idprod (de la tabla movinsu). La otra condición idcompra=0 es porque con ese ID identifico el stock inicial; en realidad podría obviar esto, ya que antes de permitir borrar un insumos chequeo primero que no tenga movimientos registrados, si los tiene no permito su eliminación, pero por las dudas prefiero comprobarlo dos veces y no dejar que un trigger se dispare y borre alegremente todos los movimientos de un insumo, no nos olvidemos que cualquiera con un poco de conocimientos puede abrir la tabla y borrar un insumo y el trigger se dispara igual, por ejemplo, un usuario intenta borrar un insumo y el programa no lo deja porque tiene 20 registraciones, el usuario debe primero borrar todas las registraciones y luego borrar el insumo, puede tentarse de meter mano a la base de datos y borrarlo, pues bien, en este caso, el trigger con el condicional (idcompra=0) solo borrará el registro correspondiente al stock inicial dejando “vivas” las otras registraciones. Claro que si lo que se pretende es que el trigger arrase con todo quedaría así:

CREATE TRIGGER borroinsu
         AFTER DELETE
            ON prod
BEGIN
    DELETE FROM movinsu
          WHERE (idprod = old.id);
END;



“old.id” es el campo “id” del registro que se borró. Como es un trigger del tipo delete, solo se puede utilizar old para referenciar un campo.

UPDATE:

En update se puede usar tanto old como new para referenciar campos. Este trigger se lanza cuando se modifica el stock inicial de un insumo (campo “ini”) en la tabla prod y modifica el campo “cantidad” en la tabla movinsu.

CREATE TRIGGER modificoinsu
         AFTER UPDATE OF ini
            ON prod
BEGIN
    UPDATE movinsu
       SET cantidad = new.ini
     WHERE (idprod = new.id) AND
           (idcompra = 0);
END;


Traduciendo un poco, se lanza después (AFTER) de una modificación (UPDATE) del campo “ini” de la tabla prod (ON prod) y se ejecuta la actualización (UPDATE) de la tabla movinsu, se establece (SET) el campo “cantidad” con el valor de “new.ini” (el nuevo stock inicial) y la claúsula WHERE es la misma que el ejemplo anterior. Si bien el campo “id” no cambia, se debe referenciarlo con new o con old.

Son tres ejemplos sencillos para iniciarse en el tema triggers.

Documentación consultada: (en inglés)

https://sqlite.org/lang_createtrigger.html

https://www.tutorialspoint.com/sqlite/sqlite_triggers.htm

Cambiar color de varios TEdit OnEnter OnExit

Si tenemos pocos (2 o 3) Tedit en un formulario, podemos rápidamente hacer uso de los eventos OnEnter y OnExit de cada edit y listo, total, son pocos. Pero si tenemos muchos TEdit cómo crear un evento para OnEnter y otro para OnExit que sirva para todos los Edits, sean 1, 2, 10 o 50. Simple, creamos dos procedimiento en la clase del formulario, no aparte del Form, sino como miembros de la clase Form.

Ejemplo:

procedure EntraEdit(Sender: TObject);
procedure SaleEdit(Sender: TObject);


Luego implementamos dichos eventos:

procedure TForm1.EntraEdit(Sender: TObject);
begin
 TDBEdit(Sender).Color:=clMoneyGreen;
end;

procedure TForm1.SaleEdit(Sender: TObject);
begin
  TDBEdit(Sender).Color:=clDefault;
end;


Ahora, desde el inspector de objetos, en la parte Eventos, en cada TEdit deberemos seleccionar en todos el evento OnEnter y OnExit asignando EntraEdit y SaleEdit respectivamente. Con esto logramos reducir la cantidad de código. Con esta optimización, otra ventaja es que si deseamos cambiar el color y tenemos 20 edits, solo necesitamos modificar una sola línea de código.

miércoles, 6 de septiembre de 2017

DBGrid: Formato de la columna según el tipo de campo

En tiempo de diseño y conociendo la tabla es una cosa, pero en tiempo de ejecución y sin saber nada de la tabla, ya es distinto. Para empezar nos encontramos con tipos de datos enumerados y por ende, debemos conocerlos. Luego hay que recorrer las columnas o campos y definir el formato según lo que necesitemos, siendo lo normal los tipos de dato numéricos y de fecha. Para el ejemplo se recorrerán las columnas de una tabla SQL y se cambiará el formato del DBGird si el campo es numérico decimal.

procedure TForm1.FormatoColumnasGrid;
var
  ind:Integer;
begin
  FormatSettings.DecimalSeparator:='.';
  for ind:=0 to ZQ.FieldCount-1 do
    if (ZQ.FieldDefs.Items[ind].DataType=ftFloat) or (ZQ.FieldDefs.Items[ind].DataType=ftCurrency)
    or (ZQ.FieldDefs.Items[ind].DataType=ftBCD) then
      DBGrid1.Columns[ind].DisplayFormat:='#0.00';
end;


Este procedimiento se llama luego de habilitarse el DBGrid. Se establece el separador decimal para establecer correctamente el formato aunque también se podría hacer de forma automática o preguntarle al usuario.

Se utiliza FieldCount – 1 porque la indexación del ZQuery (de ZeosLib) comienza por cero. DataType es del tipo enumerado, para SQL los valores de numéricos con decimales son: ftFloat, ftCurrency y ftBCD,

Listado de tipos de datos completo en español 

Documentación de TFieldType

Obtener el string de un enumerado

No existe algo así como EnumToStr pero desde luego hay formas sencillas de obtener o “convertir” (entre comillas) el valor en cadena de caracteres de un enumerado.

¿Cómo? Con el procedimiento Str.

Ejemplo:

Str ( ZQuery1.FieldDefs.Items[i].DataType, s );
showmessage(s)
;

A Str le pasamos el primer parámetro que es el enumerado y el segundo que es una variable del tipo string, ambos por referencia y el procedimiento asignará a la variable, en este caso “s” el valor string del enumerated.

procedure Str(var X: TNumericType[:NumPlaces[:Decimals]];var S: String)

Opcionalmente, se puede establecer el formato numérico, ya que Str también convierte tipos de datos numéricos, sean o no enumerados, aunque para los no enumerados hay funciones más completas y con la comodidad de ser funciones y no procedimientos.

Documentación de Str en Lazarus

Documentación de Str en Free Pascal

Insert masivo y rápido en SQLite

El que sabe, sabe; y el que no, se pasa horas buscando, por eso este simple post para algo tan simple, para los que estamos eternamente aprendiendo.

No hay ningún problema con la ejecución directa de sentencias SQL cuando se trata de pocos registros a insertar, haríamos algo así: (pseudo código)

While not Eof do
  ExecuteDitect('lo que sea');


Si nuestro conector con la base de datos tiene la propiedad autocommit en True y son pocos registros, el usuario no lo notará. El problema es que cada vez que se completa una transacción, SQLite requiere dos completas rotaciones del plato del disco, tendiendo en cuenta unas 7.200 rotaciones por minuto, con suerte, viento a favor y sin usar progressbar, podríamos insertar 60 registros por segundo, es decir, 1.200 registros tomaría 20 segundos, entonces debemos incluir una barra de progreso para que el usuario no piense que el programa dejó de funcionar, la querida barra de progreso relentizará aún más el proceso. Hasta aquí la explicación de por qué demora tanto.

Solución: BEGIN …. COMMIT es decir, encerrar las transacciones entre un BEGIN y un COMMIT.

Ejemplo con el componente ZConnection de ZeosLib:

if ZConnection1.Connected then ZConnection1.Disconnect;
ZConnection1.AutoCommit:=False;
Zconnection1.Connect;
ZConnection1.ExecuteDirect('BEGIN; ');
while not EOF(f) do
begin
  ReadLn(f,s);
  ZConnection1.ExecuteDirect(s);
end;
CloseFile(f);
ZConnection1.ExecuteDirect('COMMIT; ');
ZConnection1.Disconnect;


En este caso, f es un archivo de texto plano que contiene lenguaje SQL. La velocidad es increíble, un archivo de 7,7 MB  en menos de 2 segundos, para más de 45.000 registros de 12 campos.

La propiedad autocommit de ZConnection1 debe estar en False, podemos hacerlo en el inspector de objetos de Lazarus o por código.

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.

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 https://lazarus-freepascal.blogspot.com on Vimeo.

Temporizador 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)

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.


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.


Sí, el contador en verde, bold y agrandado, porque todo en escala de grises es simplemente aburrido.

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:

Obtener la lista de tablas de una base de datos SQL

Es 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.

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

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.

Como simular un 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;

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.

martes, 5 de septiembre de 2017

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'';';

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.


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. También se puede usar la función QuotedStr para evitar tantos apóstrofes.


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 para cerrar la conexión con la base de datos.

Unidad sin formulario en Lazarus

Es algo tan simple que no figura en los resultados de búsqueda de los pocos buscadores de Internet que hay en la actualidad a nivel global.

Desde la IDE Lazarus en el menú Archivo, Nueva unidad.





Y nos aparecerá algo así:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
    Classes, SysUtils;

implementation

end.


En definitiva lo único que falta es el Form y el resto es igual, como no podía ser de otra manera.

Luego de uses declaramos las funciones y procedimientos y debajo de implementation, las escribimos.

Ideal para tener todas las funciones de, por ejemplo, validación, en una unidad a la cual luego podremos acceder desde otras unidades. Otra utilidad práctica es para declarar variables globales.



Crear, leer y guardar un archivo de texto plano.

Caso: crear un archivo de texto donde se guarde el directorio o carpeta seleccionada por el usuario. Consultar dicho archivo. El método funciona tanto en GNU/Linux como en Windows.

Crear y guardar:

procedure TForm1.btnSeleccionarClick(Sender: TObject);
var
  f:TFileStream;
  s:String;
begin
  if SelectDirectoryDialog1.Execute then
  begin
    if SelectDirectoryDialog1.FileName<>'' then
      EmpresasDBDir:=SelectDirectoryDialog1.FileName;
  end
  else
  begin
    ShowMessage('No se seleccionó ningún directorio. Se utilizará el actual: '+GetCurrentDir);
    EmpresasDBDir:=GetCurrentDir;
  end;
  f:=TFileStream.Create(GetCurrentDir+PathDelim+'empredir.txt',fmCreate);
  s:=EmpresasDBDir;
  f.Write(s[1],Length(s));
  f.Free;
end;

Este evento es lanzado cuando el usuario presionar el botón seleccionar directorio. Las variables utilizadas son:

f del tipo TFileStream, s del tipo string, ambas locales;

EmpresasDBDir del tipo string, global, definida en otra unidad del proyecto.

Además se utiliza el componente SelectDirectoryDialog disponible en la paleta Dialogs de Lazarus.

Si el usuario no seleccionó ningún directorio, es decir, se escapó haciendo click en Cancelar o cerró el cuadro de diálogo, entonces, en lugar de preguntarle para qué presionó el botón si no seleccionó nada, se le informa amablemente que se utilizará el directorio actual, se le muestra el mismo mediante GetCurrenteDir y utilizando la misma función, se asigna el directorio actual a la variable global EmpresasDBDir.

Luego creamos el objeto de la clase TFileStream y le pasamos dos parámetros, el primero de ellos, el nombre del archivo y el segundo la forma mediante la cual accedemos al archivo, en este caso fmCreate que significa que creamos un nuevo archivo, entendiendo que si el archivo ya existe, lo sobrescribe. Respecto del primer parámetro, es un string, el cual formamos con el directorio actual + el delimitador + el nombre del archivo, cuya extensión puede ser otra que no sea .txt. El uso de PathDelim es lo que permite que funcione tanto en Linux como en Windows y seguramente también en MacOS que al igual que GNU/Linux está basado en UNIX, ya que el separador o delimitador entre carpeta y archivo será / o \ según el sistema operativo.

La asignación del contenido de la variable EmpresasDBDir a la variable s no es necesario, se podría prescindir de la misma y utilizar directamente EmpresasDBDir, eso queda a gusto del programador teniendo en cuenta la legibilidad del código.

f.Write(s[1], Length(s)) se guardan desde el primero s[1] al último Length(s) caracter de la cadena de caracteres s y se finaliza liberando el objeto.

Si quisiéramos que el archivo conste de varias lineas, habría que agregar #13#10 a la variable s

s:=EmpresasDBDir+#13#10;

Pero para este caso luego al leer el archivo e intentar conectar una base de datos con ese path, nos dará error, porque el Enter (#13#10) no se ve pero está ahí.

Leer el archivo:

procedure TForm1.LeoDirEmpreDB;
var
  f:TFileStream;
  s:String;
begin
  f:=TFileStream.Create(GetCurrentDir+PathDelim+'empredir.txt', fmOpenRead );
  SetLength(s,f.Size);
  f.Read(s[1],F.Size);
  EmpresasDBDir:=s;
  f.Free;
end;

Para leer el archivo, en este caso no interviene el usuario de manera directa, lo realizo mediante un procedimiento y utlizando 2 variables locales f y s y la misma variable global EmpresasDBDir.

El método es parecido al de escritura, al crear el objeto, el primer parámetro es igual y el segundo no, con fmOpenRead, como su nombre lo indica, establecemos que solamente leeremos el archivo, en otras palabras: modo lectura. Paso siguiente, se debe establece el tamaño de s mediante SetLength le asignamos la misma cantidad de caracteres que contiene el archivo, si no hacemos, no funciona. Luego se realiza la lectura con el método Read del primer s[1] al último f.Size caracter. Lo leído lo asigno a la variable global y luego libero el objeto.

Se pueden hacer más cosas mediante el uso de TFileStream, como agregar datos a un fichero existente. También hay otros métodos para la lectura y escritura de archivos. Para más información recomiendo leer los siguiente enlaces:

Delphi al límite (en el cual me basé para este artículo).

FreePascal.org (Documentación oficial).

CuPas (Otro método, Pascal sin objetos).

Muy breve tutorial para empezar con Lazarus

Es este un sencillo ejemplo de primer programa para los recién llegados a Lazarus y a la programación con objetos.


Iniciamos Lazarus, Archivo–> Nuevo–>Aplicación. Nos mostrará un formulario con su correspondiente unit. Antes de hacer nada seleccionamos Guardar todo, el 5to. icono, seleccionamos la carpeta donde se guardará la aplicación, y guardamos, son 2 la ventanas de diálogo de guardar que aparecerán, dejamos los nombres por defecto ya que es una práctica y guardamos.


Cuando compilemos con éxito el programa, tendremos los siguientes archivos:
  • project1: es el ejecutable
  • project1.lpi: contiene información del proyecto, se recomienda no editar nunca este fichero,
  • project1.ico: como su extensión lo sugiere, es el icono.
  • project1.lps: guarda la configuración del proyecto y tampoco debería editarse nunca.
  • project1.lpr: Es el programa principal, se inicializa, se crea el formulario principal y se lanza el programa. Este archivo sí se puede editar (modificar), de hecho en la práctica se modifica habitualmente, aunque no mucho. Para comenzar, de momento, con algún ejercicio sencillo, no habrá que tocarlo.
  • project1.res: es una archivo binario de los recursos del programa.
  • unit1.pas: es el código fuente de la aplicación que estamos escribiendo.
  • unit1.lfm: sería como el código del formulario, o más bien sus propiedades. Salvo contadas excepciones, no hay que modificar nunca este archivo.
En los casos donde cito “no hay que modificar nunca este archivo” es porque lo hace Lazarus “para Lazarus”, se crear y actualizan automáticamente.
Todos los objetos tienen una propiedad “name” y la mayoría tiene una propiedad “caption”, como el caso del formulario TForm que Lazarus crea por defecto cuando creamos una unidad o aplicación. Estas propiedades, además de poder ser editadas mediante código, muchas veces se accede a ellas desde el inspector de objetos. Vamos a cambiar esas propiedades de nuestro formulario, lo llamaremos frmA y el caption es el texto que mostrará el formulario, le podremos simplemente Formulario A.



Ahora a iremos incorporando objetos a nuestro Form, desde la paleta Standar seleccionamos un TLabel (es el 5to. botón) y lo colocamos en el formulario. Un TLabel es una etiqueta, que básicamente muestra un texto.


Al TLabel le cambiaremos la propiedad (también se le llama atributo) caption. Nótese que la etiqueta se llama Label1, si colocásemos otra etiqueta, Lazarus la llamaría Label2. En caption pondremos: Este texto cambiará cuando presione el botón. Esto lo haremos desde el inspector de objetos, seleccionando Label1.




Para mayor comodidad para escribir el texto es aconsejable presionar sobre los 3 puntos (…) en la propiedad caption, esto nos abrirá una ventana para ingresar es texto que mostrará la etiqueta.



De paso podemos alinear la etiqueta horizontalmente al centro de la ventana, haciendo click derecho sobre la misma y seleccionando la opción “Alinear”.



 Bien, ahora lo ideal sería incluir un botón, desde la paleta Standar el 4to. icono que dice “OK” es un TButton, incorporamos uno al formulario y le damos las dimensiones que queramos valiéndonos del mouse.



Por default, Lazarus le asigna el nombre al componente Button1 y también al caption. Cambiaremos solo el caption y le pondremos “Haga click aquí.” o “Pinche aquí” o lo que más les guste.

Cabe destacar dos cosas, en Free Pascal, los objetos por convención (según tengo entendido) llevan una T al comienzo, por eso verán que todo es TForm, TLabel, TButton, TEdit, etc… Lo otro es que Lazarus define la instanciación de del objeto (variable) quitando la T y agregando un número, Form1, Label1, Button1, etc.

Hasta ahora no hemos escrito una sola línea de código, pero llego la hora de hacerlo, debemos hacer que cuando el usuario haga click en el botón cambie el texto de Label1. Para esto, hay que definir el evento OnClick de Button1. Evento: es un procedimiento asociado a un objeto y para una acción determinada. Esto es una definición breve muy básica, siempre hay que leer (y mucho) la wiki de Lazarus y la de Free Pascal.

Para ello, haremos click en Button1 y en el inspector de objetos y luego click en la pestaña “Eventos”, allí buscaremos el evento OnClick y haremos click en la segunda columna para que nos aparezcan los 3 puntos y haremos click sobre los mismos.



Lazarus nos habrá mandado al editor de código fuente donde ya ha definido el evento.


Lo que sigue ahora sí, habrá que escribirlo, con la ayuda de autocompletar que es fundamental para evitar errores, comenzamos a escribir “lab” y presionamos [CTRL] + Sapce para autocompletar.


Cuando vemos la “var” (variable) Label1 del tipo TLabel le damos Enter, escribimos un punto “.” y nuevamente Control + Espacio y buscamos la propiedad Caption, podemos escribir “ca” para hallarla más rápido. Ahora nos resta asignarle el valor que tomará la propiedad caption, por ejemplo:

procedure TfrmA.Button1Click(Sender: TObject);
begin
  Label1.Caption:='Gracias por presionar el botón.';
end;

Para ir viendo como funciona, vamos a compilar, presionando F9 o haciendo click en el icono play.


Y nos tendría que aparecer algo así:


Lo probamos, hacemos click y el texto debe cambiar. Luego pulsar el botón de Stop o [Crtl] +F2 para finalizar.


A esta altura ya creamos un ejecutable escribiendo solo una linea de código. Si bien el RAD Lazarus reduce mucho la cantidad de código a escribir, es erróneo pensar que todo será soltar componentes y escribir poco, mientras se va avanzando, cada vez se escribe más y, a su vez, iremos viendo que en algunos casos es mejor y a veces indispensable, algunos objetos definirlos “a mano” lo que se llama “en tiempo de diseño”, porque todo lo que arrastramos al formulario, puede escribirse e insisto, a veces solo queda esa opción, por ejemplo si queremos que cuando el usuario presione un botón se cree otro botón, el primer botón lo podemos crear soltando un TButton en el TForm, pero el segundo TButton deberemos crearlo, definirlo, mostrarlo y posiblemente eliminarlo mediante código.

Por último, dotaremos al formulario con un botón que muestre una imagen y una leyenda de “Cerrar” y cuya acción será finalizar la aplicación, es decir, cerrarla. El componente para esto se llama TBitBtn y está disponible en la paleta “Additional”, es un icono muy parecido a TButton.


Un BitBtn es un botón al que se le puede añadir una imagen, también posee imágenes prediseñadas, se encuentran en la propiedad “Kind” que por defecto es “bkCustom” y de momento lo dejamos así para añadir nuestra propia imagen, una apropiada para simbolizar la acción de cerrar. Para esto debemos tener una imagen, preferentemente guardada en nuestro disco, con realizar una búsqueda en Internet de “free icons” encontraremos unas cuantas.


Para cargar la imagen, vamos al inspector de objetos y en la propiedad “Glyph” hacemos click en “…”:


(También en Caption escribimos Cerrar).


Se abrirá una nueva ventana de diálogo donde haremos click en Cargar y seleccionamos la imagen.


Y click en Aceptar. En este caso la imagen la obtuve de aquí (icons8.com).

Y con esto terminamos el diseño de nuestro formulario que debería ser algo similar a esto:


Quizas te estés preguntando “entonces para que existe TButton si TBitBtn es mejor?” y la respuesta es simple: hubo una vez en la que no existía TBitBtn y muchos programas se hicieron con TButton y se debe preservar la compatibilidad; y es correcto, desde ya. De mi parte no veo otro motivo y utilizo siempre TBitBtn que se puede utilizar sin ninguna imagen también, con el beneficio de que si el día de mañana le queremos agregar una imagen no tengo más que agregarla si la necesidad de cambiar el componente.

Solo nos falta hacer que el BitBtn1 cierre la aplicación y para ello recurriremos nuevamente al evento OnClick, tal como hicimos con Button1, buscamos en el inspector de objetos el evento OnClick del BitBtn1 y presionamos sobre los 3 puntos.

Escribimos:

Close;

Y nuestro código fuente tendría que verse así:


Compilamos (F9) y probamos.



Y hasta acá llegamos con este muy breve tutorial o guía de inicio. Para finalizar, algunas anotaciones que nunca están de más:

Este código es el mismo para cualquiera de los sistemas operativos de escritorio soportados por Free Pascal, es decir, tanto en GNU/Linux, Windows o Mac OS se compilar este proyecto y funciona. Para proyectos “en serio” el 99% del código es el mismo y en muchos casos el 100%, es una de las principales ventajas de Free Pascal, además de ser de código abierto y la gran documentación online constantemente actualizada.

En este caso he utilizado la versión 3.0.0 de Free Pascal y 1.6.0 de Lazarus sobre Linux Mint 17.2 x86_64 MATE.

Lamentablemente y a pesar de ser el segundo o tercer lenguaje más utilizado en el planeta, no es mucha la documentación en español. Y si vamos al tema libros, al día de hoy solo he conseguido uno solo de Lazarus: “Iniciar con Lazarus y Free Pascal” el cual me ha servido mucho, el problema que tiene es la pésima traducción al español, por lo tanto, lo recomiendo pero en inglés. De hecho, el autor (Menkaura) me ha respondido muy amablemente un par de consultas que le hice por e-mail. Ojo que hay otro libro que en el título menciona a Lazarus pero solo le dedica un capítulo.

Es ideal antes de empezar con Lazarus, practicar Pascal puro desde la consola, luego practicar la programación orientada a objetos también desde consola, antes de comenzar con la IDE Lazarus.

Hay un libro on line Pascal con Free Pascal con muy buena documentación en español, desde ya. También sus 8 videos donde muestra como hacer un editor de textos con Lazarus, los cuales recomiendo ya que enseña como manejar varios componentes: https://youtu.be/el5sD0_2cZo

Y desde ya los foros, no solamente planteando dudas, sino también leyendo todos los debates se aprende mucho.

La mayoría de lo que encuentres sobre Delphi es aplicable a Lazarus con Free Pascal, siempre teniendo en cuenta que Delphi es solamente Windows.

Foros en español de Lazarus:

El oficial: forum.lazarus.freepascal.org
 
Delphi Access: delphiaccess.com 100% es español con varios subforos, entre ellos, Lazarus.

Club Delphi: clubdelphi.com primo hermano de Delphi Access, también 100% en español.

Información oficial de la wiki de Free Pascal sobre los componentes vistos en este tutorial:
// Fin

SQLite crear una base de datos con código

A diferencia de otras bases de dato SQL, SQLite carece del famoso CREATE DATABASE porque si la base de datos no existe, la crea automáticamente.

En tiempo de diseño solo debemos completar la propiedad Database de un componente Zconnection y activar la propiedad Connected, con eso ya se creo la base de datos.


Con código sería así:
   
ZConnection1.Database:='prueba.db';
ZConnection1.Connect;

Aclaración: Zconnection es un componente de ZeosLib, un conjunto de componentes para manejar bases de datos.