Share Button

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:


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

Utilizando o código para gerar a imagem:


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;



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:


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


Ver Código completo

 

 

Share Button

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

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;

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;


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;

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


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;

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.

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.

 

 

Projeto de Exemplo

 

 

Share Button

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

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;

 

Dependência: System.uJson