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
  ZQuery.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.
Una manera de resolver esto es mediante el uso de ExecuteDirect, si tenemos un identificador único, mejor:

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


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.