Preparando para gerar imagem QRCode no gerador de relatórios saí procurando o que havia disponível.
Não foi difícil chegar em: ZXing

O código ZXing para Delphi é o mesmo utilizado no ACBr, razão que preferi utilizar o código distribuído junto com o ACBr, visto que já esta incorporado ao nosso código (você pode preferir utilizar o código original – sem problema).

Gerando QRCode para qualquer imagem

Procedimento para gerar o codigo QRCode com base no ZXing:

[code lang=”pascal”]

// usando ACBR
uses ACBrDelphiZXingQRCode;
// se nao usa ACBR, pode usar o componente original :
// uses DelphiZXingQRCode;
// https://github.com/debenu/DelphiZXingQRCode/

procedure QrCodeToCanvas(AWidth, AHeight: Integer; ATexto:String; ACanvas: TCanvas);
var
bitmap: TBitmap;
qr: TDelphiZXingQRCode;
r: Integer;
c: Integer;
scala: Double;
begin
bitmap := TBitmap.create;
try
qr := TDelphiZXingQRCode.create;
try
qr.Data := ATexto;

// ajuta o tamanho do bitmap para o tamanho do qrcode
bitmap.SetSize(qr.Rows, qr.Columns);

// copia o qrcode para o bitmap
for r := 0 to qr.Rows – 1 do
for c := 0 to qr.Columns – 1 do
if qr.IsBlack[r, c] then
bitmap.Canvas.Pixels[c, r] := clBlack
else
bitmap.Canvas.Pixels[c, r] := clWhite;

// prepara para redimensionar o qrcode para o tamanho do canvas
if (AWidth < bitmap.Height) then
begin
scala := AWidth / bitmap.Width;
end
else
begin
scala := AHeight / bitmap.Height;
end;

// transfere o bitmap para a imagem
ACanvas.StretchDraw(Rect(0, 0, Trunc(scala * bitmap.Width),
Trunc(scala * bitmap.Height)), bitmap);

finally
qr.Free;
end;
finally
bitmap.Free;
end;
end;

[/code]

Utilizando o código para gerar a imagem:

[code lang=”pascal”]

type
TForm10 = class(TForm)
Image1: TImage;
LabeledEdit1: TLabeledEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form10: TForm10;

implementation

{$R *.dfm}

// transferindo o QRCode para a imagem (Canvas)
procedure TForm10.Button1Click(Sender: TObject);
begin
QrCodeToCanvas(Image1.Width, Image1.Height,LabeledEdit1.Text, Image1.Canvas);
end;

[/code]

Exemplo

 

Componente para QuickReport

Para integrar o QrCode com QuickReport deve registrar componente que herde a estrutura quickreport no IDE.

Criar uma Package nova ou adicionar a uma já existe o código:

[code lang=”pascal”]

procedure Register;
begin
RegisterComponents(‘QReport’, [TqrQRCode, TqrDBQRCode]);
end;

[/code]

Ver Código completo

 

 

Cenário

Tenho um aplicativo que roda como executável windows executando atividades que não requerem intervenção do usuário para o seu funcionamento. Se manter o aplicativo minimizado fica muito fácil de fecha-lo por engano, parando o serviço que se encontra em execução. Uma opções é deixar o aplicativo na bandeja (aqueles ICON no canto direito do windows) – a desvantagem neste caso é que depende que seja feito login para  “INICIAR” a execução do aplicativo.
Nestas situações criar um serviço que sobe junto com o windows pode ser uma alternativa interessante – ponto que irei focar neste POST.

Considerações sobre  um formulário preexistente

Considerações sobre as diferenças em executar um APP com formulário X Serviço que não possui um formulário:
1) Considere um formulário padrão de execução para um aplicativo que mostra o formulário; Quando se usa como aplicativo ele inicia a execução pelo evento onFormShow, mas quando inicia como serviço não é aconselhável gerar o evento FormShow, já que executando como serviço não há um terminal/janela para mostrar as mensagens.
2) Outra consideração importante é não permitir chamadas para funções que mostram mensagens para o usuário (ex: showmessage); Uma alternativa é criar um FAKE para a função ShowMessage (… e outras) direcionando a mensagem para uma saída em disco (um LOG);

[code lang=”pascal”]
implementation

{$R *.dfm}

procedure TForm10.Execute;
begin // procedure a ser executa quando estiver chamando como serviço
// nao executa o SHOW do formulário
FServico := true;
Init;
end;

procedure TForm10.FormCreate(Sender: TObject);
begin
FContador := 0;
FServico := false;
end;

procedure TForm10.FormShow(Sender: TObject);
begin
// quando é executa como Aplicativo, usa o show para dar inicio ao funcinamento
FServico := false;
init;
end;

procedure TForm10.Init;
begin
Timer1.Enabled := true;
end;

procedure TForm10.Log(texto: string);
begin
TThread.Queue(nil,
procedure // sincroniza a escrita no memo1 – previne chamada multi-thread
begin
Memo1.Lines.Add(texto); // mostra o texto em um memo.
end);
end;

procedure TForm10.Timer1Timer(Sender: TObject);
begin
inc(FContador);
Log(‘Chamada: ‘ + intToStr(FContador));
end;

[/code]

Criando um “Service Application” padrão 

Criar um projeto padrão do DELPHI do tipo “Service Application” que será a base para iniciar o nosso serviço;

Com base no projeto padrão, no evento “onStart” criar uma instância para o formulário padrão do aplicativo. Importante neste ponto é trocar o ShowModal por Execute; ou seja, não vamos mostrar o fomulário – somente inciar sua execução;

[code lang=”pascal”]

var
Service10: TService10;

implementation

{$R *.dfm}
uses UnitApp;

var LAppForm : TForm10;

procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Service10.Controller(CtrlCode);
end;

function TService10.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;

procedure TService10.ServiceStart(Sender: TService; var Started: Boolean);
begin
LAppForm := TForm10.create(nil);
LAppForm.execute;
end;

procedure TService10.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
LAppForm.free;
end;

[/code]

Alteração no formulário para LOG em arquivo

[code lang=”pascal”]

procedure TForm10.Log(texto: string);
procedure LogServico;
begin
System.TMonitor.Enter(FLock); // controle de acesso ao arquivo de LOG
try
WriteLogText( ‘AppService.log’,texto);
finally
System.TMonitor.Exit(FLock);
end;
end;

begin
if FServico then
LogServico
else
TThread.Queue(nil,
procedure // sincroniza a escrita no memo1 – previne chamada multi-thread
begin
Memo1.Lines.Add(texto); // mostra o texto em um memo.
end);
end;
[/code]

Alterando o projeto para selecionar o Formulário  Alternando com Serviço

Como padrão um projeto “Service Application” inicializa o objeto TService e executa chamando RUN.
Quando o windows inicializa o serviço faz uma chamada /start para o projeto, então o primeiro passo é testar se o serviço já foi instalado “/install”. Se foi instalado executa o serviço, se não foi instalado ainda ou utilizar o parâmetro /app passar para executar o formulário.

[code lang=”pascal”]
program ProjAppService;

uses
Vcl.SvcMgr,
uServiceApp,
System.SysUtils,
UnitApp,
UnitService in ‘UnitService.pas’ {AppService: TService};

const
NomeServico = ‘AppService’;

{$R *.RES}

begin

{ parametros
/app -> executa como aplicativo
/install -> instala como serviço
/uninstall -> desinstala o serviço
}

if IsServiceInstalled(NomeServico) and
(not(FindCmdLineSwitch(‘app’, [‘-‘, ‘\’, ‘/’], true))) then
begin // é serviço
try
if not Application.DelayInitialize or Application.Installing then
Application.Initialize;
Application.CreateForm(TAppService, AppService);
AppService.name := NomeServico;
Application.Run;
finally
end;
end
else
begin // é app
Application.CreateForm(TForm10, Form10);
form10.ShowModal;
end;

end.
[/code]

 

 

Projeto de Exemplo

 

 

Trabalhar com RECORD é mais fácil administrar memória quando comparado com classes.

Como cada variável RECORD ocupa um endereço diferente com os dados e controla a sua retirada da memória com mais facilidade, prefiro usar RECORD para fazer CACHE de dados.

Por outro lado, temos vários acessos ao banco de dados que possuem parâmetros que estão nestes RECORD (alguém lembrará um CRUD). Uma solução é utilizar RTTI para ler os valores dos parâmetros que estão armazenados no RECORD e passar os valores para os parâmetros (TParams).

[code lang=”pascal”]
function TALQuery.FillParams<T>(rec: T): TALQuery;
var
LList: TJsonValuesList; // unit System.uJson
LPair:TJsonPair;
i:integer;
prm:TParamBase;
begin
result := self;
LList := TJsonValuesList.Create(); // unit System.uJson
try
TJsonValue.GetRecordList<T>(LList, rec); // carrega os valores do RECORD em um LIST
for I := 0 to params.count-1 do
begin
prm:= params[i];
LPair := LList.names[ prm.Name ];
if assigned(LPair) then
case prm.DataType of
ftSmallint,ftInteger:
prm.AsInteger := LPair.JsonValue.GetValue<integer>;
ftFloat:
prm.AsFloat := LPair.JsonValue.GetValue<double>;
ftCurrency:
prm.AsCurrency := LPair.JsonValue.GetValue<Currency>;
ftDateTime,ftDate,ftTime :
prm.asDateTime := LPair.JsonValue.getValue<TDateTime>;
else
prm.Value := LPair.JsonValue.getValue<string>;
end;
end;
finally
LList.free;
end;
end;

/* aplicando */

Type
TPedidoRecord = record

pedido:integer;
filial:integer;
data:TDatetime;
end;

var qry: TMinhaQuery;
rec : TPedidoRecord;
begin
….
qry.fillParams<TPedidoRecord>(rec);

end;
[/code]

 

Dependência: System.uJson