sábado, 28 de noviembre de 2020

Ejecutar programas externos con TProcess

Para empezar hay que tener claro algo: TProcess no es un emulador de terminal.

TProcess es una clase utilizada para ejecutar y controlar otros procesos ajenos a nuestro programa.

La mayoría de la veces se usa para ejecutar comandos (que son programas) que se ejecutan desde un emulador de terminal, pero se pueden ejecutar también programas con entorno gráfico, es decir, cualquier clase de programas, incluso aquellos que requieren permiso de administrador, como veremos en el ejemplo.

TProcess es un componente "no visual" que está en la pestaña o lengüeta "System" de la barra de componentes de Lazarus. Esta clase se halla definida en la unidad process y forma parte de la FCL (Free Component Library).

function TFmain.LoadList: Boolean;
var
  proc:TProcess;
begin
  if Assigned(dmiList) then dmiList.Free;
  dmiList:=TStringList.Create;
  proc:=TProcess.Create(nil);
  proc.CommandLine:='pkexec dmidecode';
  proc.Options:=proc.Options+[poWaitOnExit, poUsePipes];
  proc.Execute;
  dmiList.LoadFromStream(proc.Output);
  proc.Free;
  Result:=dmiList.Count>0;
end;

Esta función es del programa LazDMIDecode y carga una lista con el resultado del comando dmidecode, que requiere contraseña de administrador y es solicitada al usuario anteponiendo el comando pkexec al comando dmidecode. De esta manera el programa nunca sabrá la password y el cuadro de diálogo que la solicita es el nativo del sistema operativo.

Las opciones, desde ya, deben definirse antes de llamar al proceso Execute o seleccionarlas desde el inspector de objetos si se utilizó como componente en un formulario o similar.

La opción poWaitOnExit indica que debe esperar a que finalice el proceso antes de pasar a la instrucción siguiente a proc.Execute.

La opción poUsePipes es para capturar el resultado que se almacena en la propiedad Output de TProcess en un stream. La propiedad Output solo debe utilizarse conjuntamente con la opción poUsePipes y solo si el comando invocado devuelve un resultado, caso contrario ocurrirá un error.

Para finalizar se asigna el stream de proc.Output al stringlist y luego se libera la instancia de TProcess si utilizó como en este ejemplo creándola mediante el constructor de la clase.

Documentación de TProcess:

martes, 24 de noviembre de 2020

TActionList

Es una clase muy útil para manejar y asociar manejadores de eventos; de hecho, debería utilizarse siempre, aunque no tengamos dos componentes que ejecuten la misma acción y utilizar el famoso evento OnClik solo para referenciar a la acción de la lista e incluso que la acción de la lista sea llamar a un procedimiento o función que pueda ser miembro público o privado del Form o incluso de otra unidad.

Veamos el famoso ejemplo de cuando se tienen un menú, una barra y unos botones para realizar lo mismo.


Ingredientes:

  • TMainMenu
  • 3 TMenuItem
  • TToolBar
  • 3 TToolButton
  • 3 TButton
  • TActionList

Los ítems del menú Archivos serán: Abrir, Guardar y Cerrar, lo mismo para los botones de la barra y los botones del formulario.

Para abrir el editor de la lista de acciones TActionList1 se hace doble click sobre el mismo. Con [Insert] o haciendo click en "+" y en "Nueva acción", no en "Nueva acción estándar" que es otra cosa. Escribimos el nombre "Abrir" y lo mismo para las otras dos acciones: Guardar y Cerrar.


Debe de quedar así:


Ahora desde el inspector de objetos definimos los eventos de cada acción:


Haciendo click en los "..." se define solo.

Recordemos que estamos hablando de eventos, no podemos asignar a OnClick (por ejemplo) un procedimiento regular, debe ser un evento, por eso usamos la lista de acciones.

Además nuestro programa queda bien factorizado, mejor lectura y mantenimiento.

A cada TAction le definimos Caption y Name.



Todavía no escribimos nada de código, sigamos.

Desde el inspector de objetos, asociamos el evento OnClick de cada componente con la acción respectiva.


Otra opción es asignar la TAction justamente en el evento Action, o solo en OnClick o también en ambas. Para el ejemplo con OnClick solamente está bien.

En este caso la acción será solo un showmessage, pero en lugar de incluir el mismo en la acción, creamos un procedimiento privado en el formulario y en la acción llamamos al mismo. Siempre es mejor tener el menor código posible en los manejadores de eventos, además de esta forma podemos utilizar el procedimiento, llegado el caso, desde cualquier lado, incluso tenerlos en otra unidad.

El código:

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Menus, ComCtrls,
  StdCtrls, ActnList;

type

  { TForm1 }

  TForm1 = class(TForm)
    aAbrir: TAction;
    aCerrar: TAction;
    aGaurdar: TAction;
    ActionList1: TActionList;
    bAbrir: TButton;
    bGuardar: TButton;
    bCerrar: TButton;
    mnAbrir: TMenuItem;
    mnGuardar: TMenuItem;
    mnCerrar: TMenuItem;
    mnMenu: TMainMenu;
    mnArchivos: TMenuItem;
    tbBarrar: TToolBar;
    tbAbrir: TToolButton;
    tbGuardar: TToolButton;
    tbCerrar: TToolButton;
    procedure aAbrirExecute(Sender: TObject);
    procedure aCerrarExecute(Sender: TObject);
    procedure aGaurdarExecute(Sender: TObject);
  private
    procedure Abrir;
    procedure Guardar;
    procedure Cerrar;
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.aAbrirExecute(Sender: TObject);
begin
  Abrir;
end;

procedure TForm1.aCerrarExecute(Sender: TObject);
begin
  Cerrar;
end;

procedure TForm1.aGaurdarExecute(Sender: TObject);
begin
  Guardar;
end;

procedure TForm1.Abrir;
begin
  ShowMessage('Procedimiento Abrir archivo');
end;

procedure TForm1.Guardar;
begin
  ShowMessage('Procedimiento Guardar archivo');
end;

procedure TForm1.Cerrar;
begin
  ShowMessage('Procedimiento Cerrar archivo');
end;

end. 

Este es el uso clásico de la clase TActionList, pero la misma posee muchas más opciones. Además nos ahorramos tener en el código los eventos OnClick de cada componente y nos pudimos ahorrar también los procedimientos colocando ese código en el evento Execute de cada TAction.

Descargar proyecto.

domingo, 8 de noviembre de 2020

TZQuery.ExecSQL

Actualización 26-12-2020: downgrade de Zeos 7.2.6 (también probé la 7.2.8) a Zeos 7.1.3.a stable del año del jopo.

No hace mucho actualicé tanto el IDE como el compilador, algunos componentes que forman parte de Lazarus se actualizan y otros, como ZeosLib no. Años usando la versión estable 7.1.3 (si no me equivoco) ya que las primeras versiones de la 7.2 me tiraba errores por todos lados. Finalmente decidí ir por la 7.2.6 y al principio iba todo bien, pero un sistema de los primeros que hice, hace ya más de 3 años, y sin las mejores técnicas de programación precisamente, me pidieron una modificación, nada del otro mundo y ahí empezaron los problemas con el querido Zeos. La documentación de los cambios de Zeos no es la mejor del mundo.

En dicho programa, tenía mucho código que actualizaba la base de datos con TZQuery en lugar de TZConnection.ExecuteDirect. Motivo: se pueden utilizar parámetros, me resulta más cómodo. Por ejemplo tenía códigos de este tipo:

ZQTHab.SQL.Text:='DELETE FROM tashaber;';
ZQTHab.Open;
ZQTHab.Close; 

Funcionaba sin problemas.

Ahora arroja un error: "Can not open a Resultset." 

 



Se soluciona con TZQuery.ExecSQL que la verdad no sé si es un método nuevo o si siempre existió.

ZQTHab.SQL.Text:='DELETE FROM tashaber;';
ZQTHab.ExecSQL; 

Lo bueno es que no se necesita ni abrir ni cerrar la consulta, más legible.

También me saltaron errores de base de datos bloqueada y tuve problemas con la edición e inserción de registros. Pues bien, hoy salió una nueva versión de ZeosLib, la 7.2.8 que no corrige esos bugs, pero anuncia que los mismos serán corregidos en la versión 8.0.

"When using Cached Updates, it is not possible to add a row and then edit
that row before posting to the database. This bug cannot be fixed in Zeos 7.2.
It has been fixed in the upcoming Zeos 8.0. Please use database transactions
instead.".

Respecto de la base de datos bloqueada, era cuando usaba una segunda conexión a la base de datos, tuve que eliminar esa segunda conexión. El tema sin solución es que el programa se usa en red y cuando dos o más usuarios se conectan a la base de datos se bloquea. Además era una de las gracias de utilizar el conector de Zeos con la propiedad Autocommit en true y funcionaba de maravilla.

Deberé, otra vez, regresar a la versión 7.1.3 donde todo funcionaba bien.