miércoles, 20 de junio de 2018
LazReport EVariantError
Resulta que si bien Free Pascal no distingue entre mayúsculas y minúsculas, el componente LazReport sí lo hace con las variables del reporte, motivo que está fuera del alcance de esta entrada. En resumen, el string que utilizamos en dicho evento o cualquier otro evento de LazReport que utilice el par de valores parname y parvalue, en parname debemos respetar las minúsculas.
Ejemplo del error:
Motivo del error:
En LazReport la variable está escrita toda en minúscula.
Mientras que en el evento está escrita combinando mayúsculas y minúsculas.
Solución: simplemente escribir correctamente la variable definida en el reporte, es decir todo en minúsculas, en este caso se cambia 'FechaReporte' por 'fechareporte'.
domingo, 17 de junio de 2018
TDBGrid: ordenar por columnas.
En el inspector de objetos, en DBGrid, eventos, buscar: OnTitleClick
y generar el evento (procedimiento):
procedure TFdCtas.DBGridCtasTitleClick(Column: TColumn);
var
i:Integer;
begin
for i:=0 to DBGridCtas.Columns.Count-1 do DBGridCtas.Columns.Items[i].Title.Font.Style:=[];
ZQCtas.Close;
ZQCtas.SQL.Text:='SELECT * FROM dcuentas ORDER BY '+Column.FieldName+';';
ZQCtas.Open;
Column.Title.Font.Style:=[fsBold];
end;En el ciclo for quitamos "Bold" de todas las columnas y al final del código lo establecemos para la actual (que viene como parámetro).
Quedando así:
miércoles, 23 de mayo de 2018
TDataSource cuándo usarlo?
El problema, lo dije desde un principio cuando retomé la programación, motivado por la existencia del IDE Lazarus, es la documentación, muchas veces inexistente y otras veces hay que dedicar mucho tiempo para dar con ella.
Los tutoriales para trabajar con bases de datos son pocos, algunos desactualizados y casi siempre los mismo ejemplos.
Es así como aprendí que para conectar y trabajar con una BD se necesitan 3 componentes: el conector (TZConnection), la consulta (TZQuery) y la fuente de datos (TDataSource). Pues no es así. Me di cuenta leyendo y participando del foro, cuando un usuario planteó una duda y detecté que en su código no utilizaba ningún TDataSource, le pregunté y me respondió si era necesario. Me sembró la duda y de hecho la respuesta es: No. Eso es cambia mucho mi panorama, para empezar no necesitaré tantos data modules si solo tengo un para de consultas, puedo declarar las conexiones y consultas sin necesidad de utilizar el data aware y que no molesten en el Form. Ahora entiendo por qué una vez me dijeron que cuando comienzas con esto, usas todo data aware y luego vas directamente por el código, y así es.
El data aware TDataSource solo es necesario cuando necesitamos enlazar los datos con un componente como puede ser un DBGrid, DBComboBox, etc. caso contrario no es necesario.
El ícono es muy claro, el componente envía los datos de un TDataSet hacia otros componentes, es indispensable para un TDBGrid por ejemplo, por eso es que el DataSource se debe vincular con un DataSet (y éste con un conector) y lo que alimenta al DBGrid es el DataSource. Ahora si no necesitamos alimentar ningún control, entonces no es necesario utilizar ningún DataSource.
jueves, 10 de mayo de 2018
SQL: Insertar registros en una tabla con campo auto increment.
Por ejemplo, una tabla (tabla1) con 4 campos: id, nombre, apellido y edad.
CREATE TABLE tabla1 (id INTEGER UNIQUE NOT NULL PRIMARY KEY AUTOINCREMENT, nombre VARCHAR(50), apellido VARCHAR(50), edad INTEGER);Error común:
INSERT INTO tabla1 VALUES ('Juan', 'Pérez', 25);Esto arrojará un error del tipo "la tabla tiene 4 columnas pero solo se proporcionan 3 valores" y es cierto, pero claro, no se puede pasar el valor id porque el mismo debe establecerlo SQL.
La solución es simplemente especificar los campos:
INSERT INTO tabla1 (nombre, apellido, edad) VALUES ('Juan', 'Pérez', 25);En caso de utilizar Zeos esto se realiza mediante ZConnection1.ExecuteDirect o también puede hacerse mediante el dataset ZQuery que sería algo así: (con la tabla ya creada)
ZQuery1.SQL.Text('SELECT * FROM tabla1;');
ZQuery1.Insert;
ZQuery1.FieldByName('nombre').AsString:='Juan';
ZQuery1.FieldByName('apellido').AsString:='Pérez';
ZQuery1.FieldByName('age').AsInteger:=25;
ZQuery1.Post
miércoles, 2 de mayo de 2018
Abrir documentos y URLs.
OpenDocument: se le debe enviar el string con el nombre del archivo, retornará True si tuvo éxito o False en caso contrario. Esta función deriva la tarea al sistema operativo, el cual intentará abrir el archivo con el programa asociado según el tipo de archivo que sea. Ejemplo: OpenDocument('ayuda.pdf') abrirá el archivo ayuda.pdf que debe ubicarse en el directorio donde se encuentra el ejecutable. También se le puede pasar el path completo.
OpenURL: función similar que intentará que el navegador predeterminado se ejecute y vaya a la URL especificada. El string debe comenzar con 'http://' o 'https://'. Ejemplo: OpenURL('http://duckduckgo.com')
viernes, 13 de abril de 2018
Publicar proyecto
¿Para qué sirve esto? Para varias cosas, por ejemplo, para duplicar un proyecto o precisamente para publicar un proyecto. Lo de duplicar un proyecto se entiende, en cuanto a publicar, un ejemplo simple es para subirlo a un foro o ponerlo como descarga (primero publicar, luego comprimir).
Nota: el proceso de publicar proyecto no afecta en lo más mínimo al proyecto original.
Si usted participa de un foro de programación y le piden que adjunte el proyecto, pues bien, esto es lo que debe hacer:
Primero prepare un directorio vacío donde Lazarus publicará el proyecto.
Luego acceda a la opción desde el menú Proyecto.
En directorio de destino debemos establecer el que creamos para tal fin. Presionando sobre el botón con los tres puntos es la forma más práctica.
Si el proyecto tiene por ejemplo archivo de LazReport y queremos que se publiquen, los agregamos en los filtros a incluir "|lfr" y listo, aceptar.
Una ventana de diálogo nos advierte que si el directorio no está vacío se vaciará. Luego no habrá ningún mensaje de proyecto publicado ni nada, pero en el directorio especificado estarán todos los archivos. Se comprimen a formato 7z o zip y se publica. Es importante utilizar siempre 7z o zip en los foros de programación, especialmente el zip.
También sirve esto como copia de seguridad
domingo, 11 de marzo de 2018
Guardar y leer registros en archivos binarios.
En este ejemplo guardaremos registros con nombres de empresas, número ID y nombre de la base de datos, algo simple. Como buffer usaremos un array (vector o matriz unidimensional) de registros.
Código:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics,
Dialogs, StdCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
BGuardar: TButton;
BLeer: TButton;
BAgregar: TButton;
edID: TEdit;
EdEmpresa: TEdit;
EdBD: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Memo1: TMemo;
procedure BAgregarClick(Sender: TObject);
procedure BGuardarClick(Sender: TObject);
procedure BLeerClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
type
TReg=record
ID:Integer;
Empresa:string[100];
BD:string[100];
end;
var
Form1: TForm1;
archivo:String;
aReg:array[0..99] of TReg;
cantReg:Integer;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
archivo:=GetCurrentDir+PathDelim+'datareg.bin';
cantReg:=0;
if FileExists(archivo) then BLeerClick(Sender);
//Si el archivo existe lo carga al array
end;
procedure TForm1.BGuardarClick(Sender: TObject);
var
FReg:File of TReg; //Archivo que contendrá registros tipo TReg
i:Integer;
begin
AssignFile(FReg,archivo); //Vinculamos el archivo
Rewrite(FReg); //Lo vamos a sobreescribir
for i:=0 to cantReg-1 do //Recorremos el array y lo escribimos
//con Write
begin
Write(FReg,aReg[i]);
end;
CloseFile(FReg); //Cerramos el archivo
end;
procedure TForm1.BLeerClick(Sender: TObject);
var
FReg:File of TReg; //Archivo que contendrá registros tipo TReg
i:Integer;
begin
AssignFile(FReg,archivo); //Vinculamos el archivo
Reset(FReg); //Lo abrimos en modo solo lectura
i:=0;
while not (EOF(FReg)) do //Lo cargamos al array
begin
Read(FReg,aReg[i]);
Memo1.Lines.Add(IntToStr(aReg[i].ID)+' '+aReg[i].Empresa+' '+
aReg[i].BD);
Inc(i);
Inc(cantReg);
end;
CloseFile(FReg); //Cerramos el archivo
end;
procedure TForm1.BAgregarClick(Sender: TObject);
begin
aReg[cantReg].ID:=StrToInt(edID.Text);//Agregamos solo al array,
//no al archivo.
aReg[cantReg].Empresa:=EdEmpresa.Text;
aReg[cantReg].BD:=EdBD.Text;
Inc(cantReg);
Memo1.Lines.Add('aReg['+IntToStr(cantReg-1)+']: '+EdEmpresa.Text);
end;
end.
En el registro debemos definir la longitud de los strings.
El primer registro de un archivo binario está en la posición 0 (cero).
No hay forma de agregar un registro a un archivo binario, como sí podemos hacerlo con archivos de texto plano, siempre ha que sobreescribir todo el archivo, de ahí ReWrite.
Al utilizar AssignFile podemos acceder mediante Seek a un determinado registro conociendo su posición y leerlo.
Código fuente: archivosbinarios.7z (incluye e archivo binario con 5 registros).
o en GitLab
viernes, 9 de marzo de 2018
Cifrar y guardar en archivo binario.
En este ejemplo, para que se entienda bien, porque de eso tratan los ejemplos, cargaremos la llave en una variable. También cabe destacar que los nombres de variables y funciones que se ven en este ejemplo, son para aprender, en la práctica hay que esconder los datos, no usar para cifrar una función llamada cifrar, etc. También hay que validar datos y utilizar try al leer y escribir archivos.
Veamos el código:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics,
Dialogs, StdCtrls, BlowFish;
type
{ TForm1 }
TForm1 = class(TForm)
BGuardar: TButton;
BLeer: TButton;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Edit5: TEdit;
Edit6: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
procedure BGuardarClick(Sender: TObject);
procedure BLeerClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
function Cifrar (const texto:String):RawByteString;
function DesCifrar (const texto:String):RawByteString;
{ private declarations }
public
{ public declarations }
end;
type
TRegistro=Record
Servidor:String[100];
Usuario:String[100];
Clave:String[100];
end;
var
Form1: TForm1;
archivo:String;
llave:String;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
archivo:=GetCurrentDir+PathDelim+'prueba.dat';
llave:='La llave';
end;
function TForm1.Cifrar(const texto: String): RawByteString;
var
str_Cifrar:TBlowFishEncryptStream;
streamTexto:TStringStream;
begin
streamTexto:=TStringStream.Create('');
str_Cifrar:=TBlowFishEncryptStream.Create(llave,streamTexto);
str_Cifrar.WriteAnsiString(texto);
str_Cifrar.Free;
Result:=streamTexto.DataString;
streamTexto.Free;
end;
function TForm1.DesCifrar(const texto: String): RawByteString;
var
str_DesCifrar:TBlowFishDeCryptStream;
unstream:TStringStream;
temp:RawByteString;
begin
unstream:=TStringStream.Create(texto);
unstream.Position:=0;
str_DesCifrar:=TBlowFishDeCryptStream.Create(llave,unstream);
temp:=str_DesCifrar.ReadAnsiString;
str_DesCifrar.Free;
unstream.Free;
Result:=temp;
end;
procedure TForm1.BGuardarClick(Sender: TObject);
var
Registro:TRegistro;
FReg:File of TRegistro;
begin
Registro.Servidor:=Cifrar(Edit1.text);
Registro.Usuario:=Cifrar(Edit2.text);
Registro.Clave:=Cifrar(Edit3.text);
AssignFile(FReg,archivo);
Rewrite(FReg);
Write(FReg,Registro);
CloseFile(FReg);
end;
procedure TForm1.BLeerClick(Sender: TObject);
var
Registro:TRegistro;
FReg:File of TRegistro;
begin
AssignFile(FReg,archivo);
Reset(FReg);
Read(FReg,Registro);
CloseFile(FReg);
edit4.Text:=DesCifrar(Registro.Servidor);
edit5.Text:=DesCifrar(Registro.Usuario);
Edit6.Text:=DesCifrar(Registro.Clave);
end;
end.
Lo primero que debemos hacer es incluir la unidad BlowFish en uses.
Si trabajamos con archivos binarios, definimos un registro para guardar y leer los datos.
En el método Create simplemente definimos el archivo y la llave, que como ven, puede contener espacios.
Luego 2 funciones para encriptar y desencriptar y 2 procedimientos para guardar y leer, para un ejemplo, alcanza y sobra.
Veamos la función Cifrar:
function TForm1.Cifrar(const texto: String): RawByteString;
var
str_Cifrar:TBlowFishEncryptStream;
streamTexto:TStringStream;
begin
streamTexto:=TStringStream.Create('');
str_Cifrar:=TBlowFishEncryptStream.Create(llave,streamTexto);
str_Cifrar.WriteAnsiString(texto);
str_Cifrar.Free;
Result:=streamTexto.DataString;
streamTexto.Free;
end;
Nótese que no devuelve un string, sino un RawByteString que es una cadena de caracteres (string) sin ningún CodePage asociado, ideal e indispensable para que esto funcione.
Necesitamos dos variables, una para el stream de cifrado de Blow Fish y otro un stream común, donde volcaremos el texto cifrado.
Creamos el stream común (streamTexto) vacío.
Creamos el stream de cifrado de BF y le pasamos como parámetros la llave (establecida en FormCreate) y el stream de texto vacío. Es importante hacer todo en este orden.
Ahora ciframos con WriteAnsiString, como parámetro le pasamos la constante de la función llamada texto.
Liberamos el stream de cifrado, el texto cifrado está en el stream de texto (streamtexto).
Finalmente asignamos al resultado de la función el DataString del stream de texto y liberamos el mismo.
Ahora la función DesCifrar:
function TForm1.DesCifrar(const texto: String): RawByteString; var str_DesCifrar:TBlowFishDeCryptStream; unstream:TStringStream; temp:RawByteString; begin unstream:=TStringStream.Create(texto); unstream.Position:=0; str_DesCifrar:=TBlowFishDeCryptStream.Create(llave,unstream); temp:=str_DesCifrar.ReadAnsiString; str_DesCifrar.Free; unstream.Free; Result:=temp; end;
También usamos dos variables para los streams y una tercera del tipo RawByteString que contendrá el valor que retornará la función. Podría obviarse esta variable supuestamente, pero por algo está ahí, la verdad no me acuerdo, algún error me habrá hecho intentar con una variable temporal, funcionó y ahí está.
Aquí cuando creamos el stream de texto, no lo hacemos vacío sino con el valor de la constante texto y a su vez, volvemos a cero su posición para que BF la lea desde el comienzo.
Creamos el stream de descifrado de BF y le pasamos también la llave y el stream de texto.
Ahora sí desciframos usando el método ReadAnsiString perteciente a TBlowFishDeCryptStream y lo asignamos a la variable temp; liberamos los streams y como resultado enviamos el valor de temp.
El resto del código no lo voy a explicar porque simplemente es usar estas dos funciones, escribir y leer el archivo binario de la manera habitual.
Descargar el código fuente: BlowFish.7z
o en GitLab
viernes, 9 de febrero de 2018
Guardar y leer imágenes en bases de datos.
Logotipo: TImage;Al TImage del Form1 lo llamamos Logotipo.
Para leer la imagen y mostrarla en un TImage en un Form:
procedure TFom1.CargoDatos;
var
unstream:TMemoryStream;
begin
//Se cargan la campos "normales"...
if ZQuery1.FieldByName('logo').IsNull then
begin
Logotipo.Picture.Clear;
Exit;
end;
unstream:=TMemoryStream.Create;
unstream.Position:=0;
TBlobField(ZQuery1.FieldByName('logo')).SaveToStream(unstream);
unstream.Position:=0;
Logotipo.Picture.LoadFromStream(unstream);
unstream.Free;
end;
Declaramos una variable del tipo TMemoryStream donde almacenaremos el contenido de la imagen que se encuentra en el campo "logo" de una tabla.Para no tener problemas, averiguamos si existe tal imagen, lo hacemos con FieldByName('logo').IsNull. Si esto devuelve True entonces borramos la imagen de LogoTipo, si no hacemos esto quedará la imagen cargada anteriormente si la hubiere. Si IsNull devuelve False quiere decir que hay una imagen (o debería haberla), entonces creamos el stream, lo posicionamos en 0 (cero) y lo cargamos con TBlobField(el campo).SaveToStream y cabe aclarar que no es necesario definir ninguna variable del tipo TBlobField, este procedimiento se encarga de todo. Ahora ya tenemos la imagen del campo logo de una tabla de una base de datos cargada en un stream de memoria, el paso final es mostrarla en el formulario, para eso posicionamos nuevamente a 0 (cero) el stream, y lo mandamos al TImage nombrado Logotipo y no olvidarse de liberar el stream utilizando el método Free.
Ahora lo inverso, leer la imagen y guardarla en la base de datos. Se omite la carga desde archivo en este ejemplo.
procedure TForm1.GuardarDatos(Sender: TObject); var ms:TMemoryStream; begin //Se pone el dataset en modo edit o insert y se graban los
//campos "normales"...
if (logotipo.Picture.Width>0) then
begin
ms:=TMemoryStream.Create;
Logotipo.Picture.SaveToStream(ms);
ms.Position:=0;
TBlobField(ZQuery1.FieldByName('logo')).LoadFromStream(ms);
ms.Free;
end
else
begin
ZQuery1.FieldByName('logo').AsString:='';
end;
ZQuery1.Post;
end;
Como en el código anterior, necesitamos una variable para el stream en memoria. Y nuevamente para no tener problemas, mediante Logotipo.Picture.Width>0 determinamos si hay alguna imagen que guardar, caso contrario se guarda NULL, AsString:='' hace eso.Si hay imagen, creamos el stream y le asignamos la imagen que está contenida en la propiedad Picture de Logotipo (TImage). Posicionamos en 0 (cero) el stream que ya contiene la imagen y nuevamente nos valemos de TBlobField que asignará el stream al campo "logo" y liberamos con Free el stream.
Desde ya es un código orientativo, pero testeado que este método funciona, al menos para SQlite utilizando ZeosLib.
Hay muchas formas de hacer esto, pero esta forma es la que menos problemas me trajo y es bastante simple y "limpia". He probado antes con DBImage, pero a veces ejecutando el código desde la IDE me tiraba un error EReadError que podía ignorar y todo seguía bien, de hecho ejecutando el programa (fuera de la IDE) esos errores no se mostraban, hasta que detecté que si la imagen que leía DBImage no era JPG entonces largaba ese error, eso me motivo a deshacerme de TDBImage y hacerlo como lo muestro, básicamente con un stream, el procedimiento TBlobField y un TImage.
sábado, 3 de febrero de 2018
TDBLookUpComboBox validar OnExit
O esto otro:
que de paso, no puede pasar en el combo de bancos, porque es del tipo lista y de manera predeterminada ya seleccionamos el primer elemento, ahí no hay que validar nada, el usuario no puede hacer de las suyas. Pues bien, en cualquiera de los dos casos, si presiona imprimir y no se valida, el programa mostrará un mensaje de error. En cambio con una simple validación, obligamos al usuario a seleccionar un ítem del combo, mediante el evento OnExit:
procedure TFRepCuentas.cmbCtaExit(Sender: TObject);
begin
if cmbCta.ItemIndex=-1 then
begin
ShowMessage('Debe seleccionar una cuenta.');
cmbCta.SetFocus;
end;
end;
Entonces si el usuario lo deja en blanco o escribe xx (no coincidiendo xx con ningún elemento del combo) le aparecerá el siguiente mensaje y le enviará el cursor nuevamente al combo y de ahí no sale hasta que seleccione un ítem.
Y tendrá que seleccionar una cuenta sí o sí o cerrar la ventana (lo cual también podríamos evitar si quisiéramos).













