Para aqueles momentos que quero varrer todos os componetes que estão no FORM para pegar quem são e aplicar um operador   AS que tal usar um metódo anônimo para isto….

Forma tradicional

A forma tradicional de se varrer todos os componentes que estão sobre responsabilidade do FORM é fazer um loop com FOR e avaliar um-a-um até encontrar o que se deseja.

[code lang=”pascal”]

var  i:integer;
begin
for i := 0 to FormX.ComponentCount-1 do
begin
if FormX.Components[i] is TQuery then // quero pegar so as TQuery
with FormX.Components[i] as TQuery do
begin
close; // fechar a TQuery
end;
end;

end;
[/code]

 

Loop de Componentes com Anonimous Methods

Utilizando “Anonimous Methods” é um modelo não tradicional de olhar a mesma questão. Entender a estrutura de um “Anonimous Method” pode simplificar muitas operações de codifição do dia-a-dia e como exemplo de fundo vamos varrer os componentes dos formulários…

  • Criando um HELPER para separar a lógica (o LOOP)

    [code lang=”pascal”]
    type
    TComponentHelper = class Helper for TComponent

    public
    procedure ComponentsLoop(AProc: TProc<TComponent>; AChild: boolean = false);
    end;

    procedure TComponentHelper.ComponentsLoop(AProc: TProc<TComponent>; AChild: boolean = false);
    var
    i: integer;
    begin
    if assigned(AProc) then
    for i := 0 to ComponentCount – 1 do
    begin
    AProc(Components[i]);
    if AChild and (Components[i].ComponentCount > 0) then
    Components[i].ComponentsLoop(AProc, AChild);
    end;
    end;
    [/code]

  • Executando o LOOP utilizando o método anônimo do HELPER

    [code lang=”pascal”]

    procedure TForm39.Button1Click(Sender: TObject);
    begin
    Memo1.Lines.Clear;
    ComponentsLoop(
    Procedure(AComp: TComponent)
    begin
    // fecha todos os TDataset (TQuery)
    if AComp is TDataset then
    with AComp as TDataset do
    close;

    end, false);

    end;

    [/code]

 

Usando  Anonimous LOOP para um TList (genérico)

Quando vezes precisou fazer um loop em um TList….

  • Implementando um LOOP “anonimous” para o TList na classe HELPER

    [code lang=”pascal”]
    procedure TComponentHelper.ListLoop(AList: TList; AProc: TProc<TObject>);
    var
    i: integer;
    begin
    for i := 0 to AList.Count – 1 do
    AProc(AList[i]);
    end;

    [/code]

  • Aplicando o LOOP “anonimous” em um TList

    [code lang=”pascal”]
    procedure TForm39.Button3Click(Sender: TObject);
    var
    FList: TList;
    begin
    memo1.Lines.Clear;
    FList := TList.create;
    try
    PopularALista(FList);

    // fazer um LOOP usando Anonimous
    ListLoop(FList,
    procedure(AObj: TObject)
    begin
    memo1.Lines.Add( AObj.ClassName );
    end);

    finally
    FList.Free;
    end;
    end;

    [/code]

Parece Simples !!! Quantas vezes precisou escrever um LOOP em um TDataset (aka TQuery, TClientDataset)???

Código Exemplo  Helper para TDataset

 

 

Parte 1 – Criando o Host | Parte 2 – Criando o Plugin | Parte 3 – Item de Menu

Os artigos anteriores mostrei como o plugin assina à um item de menu do aplicativo host.  Agora é a vez de um plugin que assina ao serviço de janela, permitindo que seja embutido (embedded) como um componente da janela.

embedded

Criar o Plugin

  1. Criar um projeto DLL em branco;
  2. Adicionar ao projeto um FORM a ser inserido no formulário do aplicativo HOST;
  3. Incluir as UNITs: plugin.Service, plugin.Control no FORM;
  4. na Seção Initialization registrar o Plugin na lista de plugins disponíveis na DLL:
    RegisterPlugin(TPluginControlService.create(TForm1,1,0,'my control embedded'));
  5. Copiar a DLL do projeto para a pasta de “Plugins” do aplicativo HOST;

Exemplo:

[code lang=”pascal”]

{$R *.dfm}

uses plugin.Service, plugin.Control;

……..

initialization

RegisterPlugin(TPluginControlService.create(TForm1,9999,1,’my control embedded’));

finalization

end.
[/code]

 

Inserindo o Plugin em uma janela do HOST

  1.  Se o projeto HOST ainda não tem recurso para suportar plugin – arrastar o componente  TPluginManager para o formulário principal;TPluginManager
  2. Escolher um formulário onde deseja inserir o plugin controle (embedded) – arrastar um TPanel para o local onde o plugin será inserido, para estabelecer a área a ser ocupada pelo plugin (interno no plugins ele executa Align=alClient no formulário);
  3. Incluir na uses a UNIT no formulário: plugin.Manager (a mesma utilizada pelo component TPluginManager);
  4. No evento OnShow do formulário fazer chamada ao Manager do plugin para encontrar o plugin ser incorporado:
      GetPluginManager.EmbedControl(Panel1.Handle,9999,1);

Observando os parâmetros da procedure

TPluginManager.EmbedControl(AParentHandle: THandle;
AControlID, AControlType: Int64);

cabe considerar:

  • AParentHandle -> é o handle do TPanel onde o plugin será incorporado;
  • AControlID -> é um identificador para carregar um determinado plugin em específico – identifica o ID da janela que irá consumir o plugin (valor fixo nos dois lados);
  • AControlType -> um ID para separar os vários plugins que uma mesma janela pode consumir;

Quando chama

GetPluginManager.EmbedControl(Panel1.Handle,9999,1); 
deve inserir no Panel1 um plugin de controle identificado por 9999 que contenha serviços do tipo 1;

<a href="https://github.com/amarildolacerda/helpers/tree/master/plugin">Ver código de exemplo</a>

 

Parte 1 – Criando o Host | Parte 2 – Criando o Plugin

Registrando MenuItem no Host

No artigo anterior foi apresentado o exemplo de um plugin que assina o serviço de Menu do Host chamando o método:

[code]
PluginApplication.RegisterMenuItem(‘mnPlugins’, ‘Meu menu que adiciona um plugin’, self as IPluginMenuItem);
[/code]

 

Ao receber a chamada ao método do PluginApplication.RegisterMenuItem o host deverá coletar informações sobre o plugin bem como executar ações para disponibilizar o recurso solicitado.

Tratando-se de MenuItem, a implementação do host será criar o item de menu no local adequado para que seja visível ao usuário.

Um exemplo de como criar o item de menu no host:

 

[code lang=”pascal”]

// um item de menu para guardar a interface que o plugin assina
TPluginMenuItemInterf = class(TMenuItem)
protected
FProc: TProc<TObject>; // anonimous
procedure DoClick(Sender: TObject);
public
PluginMenuItem: IPluginMenuItem;
constructor Create(AOwner: TComponent; AProc: TProc<TObject>); overload;
end;

// implementação no TPluginManager -> para criar um item de menu no host
procedure TPluginManager.NewMenuItem(AMainMenu: TMainMenu;
ADefaultMenu: TMenuItem; const APath, ACaption: string;
ADoExecute: IPluginMenuItem; AProc: TProc<TObject>);
var
it: TPluginMenuItemInterf;
itClient: TMenuItem;
begin
inc(itCount);
// procura o menu para mostrar
itClient := AMainMenu.FindItem(APath);
if itClient = nil then
itClient := ADefaultMenu; // se nao encontrou pega um padrao

if not assigned(AProc) then
AProc := (
procedure(Sender: TObject)
begin
with TPluginMenuItemInterf(Sender) do
PluginMenuItem.DoClick(0);
end);

// cria o menu
it := TPluginMenuItemInterf.Create(AMainMenu, AProc);
it.Name := ‘mnPlugin_’ + formatDatetime
(‘hhmmsszzz_’ + intToStr(itCount), now);
it.PluginMenuItem := ADoExecute;
it.Caption := (it.PluginMenuItem as IPluginMenuItem).GetCaption;
// adiciona o menu na lista
itClient.Add(it);

end;

[/code]

 

Pensando em facilitar a integração no HOST é possível registrar o componente na aba do Delphi – Arrastar e soltar o componente TPluginManager no formulário principal do HOST.

PluginManager

Explorando código padrão para o evento do TPluginManager.RegisterMenuItem  irá notar que já foi implementado as funcionalidades para inserir o plugin no menu do aplicativo. Caso deseje mudar o comportamento do evento padrão, basta implementar o evento do componente com funcionalidade personalizadas…

 

Fontes: Código base para o Plugin

Parte 1 – Criando o Host

Na parte 1 deste artigo tracei as linhas básicas para a implementação do Aplicativo Principal HOST – se ainda não o leu, será mais produtivo fazê-lo antes de continuar.

Criando um Plugin
O Plugin em si consiste em uma DLL que “exports” 2 entradas:

  1. function LoadPlugin(AAplication: IPluginApplication): IPluginItems;
  2. procedure UnloadPlugin;

A base para a criação do plugin é a unit: plugin.Service

LoadPlugin
“LoadPlugin” é utilizado quando o HOST inicia o plugin; O parâmetro IPluginApplication representa os serviços publicados no HOST disponíveis para assinatura pelos plugins. Assim todos os serviços que o HOST implementar deve se publicada para que sejam visíveis aos plugins.

Nos casos em que houver necessidades de estender os serviços publicados pelo HOST, IPluginApplication será a interface base publicada adicionada de novos serviços…   IMyPluginApplication = interface( IPluginInterface) …

O Plugin registra-se à lista de plugins 

IPluginItems – é uma lista de plugins disponíveis na DLL;

Para que IPluginItems seja populada o plugin poderá escolher entre os serviços padrões, aquele a que deseja assinar.

[code lang=”pascal”]
initialization
RegisterPlugin(TPluginMenuItemService.Create(TForm1,”,1, ‘Menu base de sample’));

[/code]

DoStart
Após a inicialização do LoadPlugin, ocorrerá um evento “DoStart” que inicializa cada um dos plugins da lista registrada. É neste momento que o plugin fará o pedido de assinatura  no HOST… Tudo isto é feito internamente pelo objeto “TPluginMenuItemService” do exemplo.

[code lang=”pascal”]
procedure TPluginMenuItemService.DoStart;
begin
inherited;
PluginApplication.RegisterMenuItem(FMenuItemName, GetCaption, self);
end;
[/code]

Um esqueleto para um plugin de MenuItem

[code lang=”pascal”]
unit uMenuItemSimple;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
plugin.Interf, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Vcl.ExtCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
LabeledEdit1: TLabeledEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }

public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

uses plugin.Service, plugin.MenuItem, plugin.Control;

procedure TForm1.Button1Click(Sender: TObject);
begin
close;
end;

initialization

RegisterPlugin(TPluginMenuItemService.Create(TForm1,”,1, ‘Menu base de sample’));

finalization

end.
[/code]

 

Introdução
Tomando emprestado o WIKI “plugin” é um componente computacional que adiciona recursos a um programa existente. Quando um programa suporta “plugins” ele permite ser customizado para responder a necessidades não previstas no projeto original.

Uma interface de “plugin” deve prever a possibilidade de um conjunto de código ou janela permitir ser inserida em partes do programa principal.

Em um primeiro instante – é comum encontrar “plugins” que publicam alguma função ou procedimento a ser chamado pelo aplicativo principal. Este modelo limita as funcionalidade dos “plugins” que em geral ficam mais estáticos a serem um item de menu ou uma ou outra funcionalidade.

O “plugin” que irá assinar algum serviço do aplicativo

A idéia que motiva esta publicação é a construção de um modelo de aplicativo principal HOST que publica serviços implementados em seu código e ficam disponíveis para que os “plugins” possam assinar estes serviços.

Então o HOST se torna um “publisher” é o plugin um “subscriber”, onde o plugin que solicita a assinatura de um determinado serviço do HOST.

Com a mecânica em que o “plugin” assina aos serviços do aplicativo servidor permitirá que o “plugin” tome a decisão sobre qual tipo de serviço ele quer assinar no aplicativo principal . Um exemplo é um “plugin” assinar  para ser um item do menu –  em outros casos poderá assinar funcionalidade de uma “aba” de janela ou adicionar “frames” a alguma interface do usuário – entregando mais poder de escolha ao “plugin”.

Segurança x Flexibilidade

Ainda que “flexibilidade” seja o principal objetivo, segurança segue o mesmo caminho da flexibilidade. Há que se questionar o quanto um HOST pode ser seguro o bastante para não permitir um assinante malicioso. Não tenho resposta para a questão, já que um “plugin” maldoso poderia assinar a serviços para aplicar táticas maliciosas – questão que precisa ser avaliada.

diagrama

Uma interface para publicar serviços

Considerando que o programa principal tem um formulário:

TMeuMenu = class(TForm)

end;

o que precisamos fazer é dotar o formulário principal de uma interface que permita publicar os seus serviços:

[code lang=”pascal”]
IPluginApplication = interface
[‘{6ED989EA-E8B5-4435-A0BC-33685CFE7EEB}’]
procedure RegisterMenuItem(const AParentMenuItemName, ACaption: string; ADoExecute: IPluginMenuItem);
procedure RegisterToolbarItem(const AParentItemName, ACaption: string; ADoExecute: IPluginToolbarItem);
procedure RegisterAttributeControl(const AType,ASubType: Int64; ADoExecute: IPluginControl);
end;
[/code]

Com isto o formulário passa a implementar a interface de serviço de “plugins” da seguinte forma:

TMeuMenu = class(TForm, IPluginApplication)
….
end;

 

Registrando o Plugin para o Aplicativo Principal

Antes de carregar um “plugin” o aplicativo host precisa conhece-lo (uma lista de plugins registrados). Uma forma simplista, é guardar uma lista de “plugins” disponíveis em um arquivo INI do host. A lista é necessária para que o aplicativo possa fazer carga dos seus “plugins”. Uma vez feito a carga do plugin, estes  irão assinar os serviços a que desejam interagir com o host.

O framework utiliza a implementação de DLLs para troca de código com o aplicativo principal, publicando duas chamadas – uma para carga inicial da DLL e outra para quando o aplicativo principal esta encerrando o uso do plugin:

[code]
function LoadPlugin(AAplication: IPluginApplication): IPluginItems;
procedure UnloadPlugin;

exports LoadPlugin, UnloadPlugin;
[/code]

Carregando o Plugin
Sabedor destas duas entradas disponíveis no “plugin”, resta escrever os métodos de carga do plugin (ver TPluginManager nos fontes):

[code lang=”pascal”]
function LoadPluginService(APlugin: string;
AAppliction: IPluginApplication): Integer;
var
F: function(APApplication: IPluginApplication): IPluginItems;
H: THandle;
begin
result := -1;
H := LoadLibrary(PWideChar(APlugin));
if H &gt; 0 then
begin
try
@F := GetProcAddress(H, ‘LoadPlugin’);
if assigned(F) then
result := LPluginManager.FPlugins.Add(H, F(AAppliction))
else
raise Exception.Create(‘Não carregou o plugin’);
except
FreeLibrary(H);
end;
end;
end;
[/code]

Note que a função “LoadPlugin” do plugin espera que seja enviado um IPluginApplication e retorna uma lista de plugins existente na mesma DLL, já que uma DLL pode conter 1 ou mais plugins.

O IPluginApplication, representa a interface do HOST que publica os serviços que os plugins poderão assinar no HOST – Internamente a DLL irá montar uma lista de plugins disponíveis. Cada plugin da DLL se encarrega de assinar os serviços do HOST.

Estendendo os serviços do HOST

A interface que publica os serviços no HOST é o IPluginApplication que já possui três serviços básicos para a assinatura sendo eles:

  1. MenuItem – Assinatura para se registrar em um item de menu do HOST;
  2. ToolbarItem – Assinatura para se registrar em um item da Toolbar;
  3. AttributeControl – Uma assinatura a utilizar o plugin como um “control” ou atributo de um janela.

Caso deseje implementar outros serviços para o HOST, estendendo suas funcionalidades é possível estender a interface IPluginApplication e implementa-la no HOST:

[code]
IMyPluginApplication  =  interface(IPluginAppliction)
procedure RegisterXXX(….);
end;

// no host
TMyMainMenu = class(TForm, IMyPluginApplication )

end;
[/code]

 

Parte 2 – Construindo o Plugin

(No final o código estará disponível no GIT)
….

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

 

Precisa fazer persistência local de configurações ?
Então um dia você ainda vai usar um arquivo JSON ao invés de um INI.

Ver uma classe para TJsonFile     Exemplo

  • Usando RTTI para escrever no TJsonFile

Ver o exemplo com fazer persistência de objeto usando RTTI para descobrir as propriedades a guardar no TJsonFile.WriteObject(…). Do outro lado TJsonFile.ReadObject(…) lê as propriedades no JSONFile e popula o objeto.

O funcionamento do RTTI é o mesmo descrito no post anterior

 

É comum encontrar sistemas que utilizam arquivos TIniFiles para persistir informações locais.
O uso de TIniFiles impõe escrever muitas linhas para gravação e leitura do conteúdo.
No exemplo mostro como utilizar RTTI para gravar as propriedades de um objeto diretamente no arquivo INI / carregando as informações do arquivo INI para o objeto.

  • Para o exemplo considerar a seguinte classe base para gravação no arquivo INI:

[code]
// Classe a ser gravar no INI
TIniSecaoClass = class
private
Fbase_datetime: TDatetime;
Fbase_numerico: Double;
Fbase_string: string;
Fbase_integer: integer;
Fbase_boolean: Boolean;
procedure Setbase_datetime(const Value: TDatetime);
procedure Setbase_numerico(const Value: Double);
procedure Setbase_string(const Value: string);
procedure Setbase_integer(const Value: integer);
procedure Setbase_boolean(const Value: Boolean);
public
// propriedades a serem gravadas ou lidas no INI
property base_string: string read Fbase_string write Setbase_string;
property base_datetime: TDatetime read Fbase_datetime
write Setbase_datetime;
property base_numerico: Double read Fbase_numerico write Setbase_numerico;
property base_integer: integer read Fbase_integer write Setbase_integer;
property base_boolean: Boolean read Fbase_boolean write Setbase_boolean;
end;
[/code]

  • HELPERs para adicionar funcionalidade aos objetos existentes no DELPHI.

[code lang=”pascal”]

uses IniFiles, System.DateUtils, System.Rtti, System.TypInfo;

type

{
Fragmento de: System.Classes.Helper
https://github.com/amarildolacerda/helpers/blob/master/System.Classes.Helper.pas
}
TMemberVisibilitySet = set of TMemberVisibility;

// RTTI para pegar propriedades do object
TObjectHelper = class helper for TObject
private
procedure GetPropertiesItems(AList: TStrings;
const AVisibility: TMemberVisibilitySet);
end;

// Adiciona Uso de RTTI para o INI
TCustomIniFileHelper = class Helper for TCustomIniFile
private
procedure WriteObject(const ASection: string; AObj: TObject);
procedure ReadObject(const ASection: string; AObj: TObject);
public
end;

// Adiciona funções ao TValue
TValueHelper = record helper for TValue
private
function IsNumeric: Boolean;
function IsFloat: Boolean;
function AsFloat: Extended;
function IsBoolean: Boolean;
function IsDate: Boolean;
function IsDateTime: Boolean;
function IsDouble: Boolean;
function AsDouble: Double;
function IsInteger: Boolean;
end;

[/code]

  • Métodos para gravação e leitura para o arquivo INI utilizando RTTI:

[code]
procedure TCustomIniFileHelper.WriteObject(const ASection: string;
AObj: TObject);
var
aCtx: TRttiContext;
AFld: TRttiProperty;
AValue: TValue;
begin
aCtx := TRttiContext.Create;
try
for AFld in aCtx.GetType(AObj.ClassType).GetProperties do
begin
if AFld.Visibility in [mvPublic] then
begin
AValue := AFld.GetValue(AObj);
if AValue.IsDate or AValue.IsDateTime then
WriteString(ASection, AFld.Name, ISODateTimeToString(AValue.AsDouble))
else if AValue.IsBoolean then
WriteBool(ASection, AFld.Name, AValue.AsBoolean)
else if AValue.IsInteger then
WriteInteger(ASection, AFld.Name, AValue.AsInteger)
else if AValue.IsFloat or AValue.IsNumeric then
WriteFloat(ASection, AFld.Name, AValue.AsFloat)
else
WriteString(ASection, AFld.Name, AValue.ToString);
end;
end;
finally
aCtx.free;
end;
end;

procedure TCustomIniFileHelper.ReadObject(const ASection: string;
AObj: TObject);
var
aCtx: TRttiContext;
AFld: TRttiProperty;
AValue, ABase: TValue;
begin
aCtx := TRttiContext.Create;
try
for AFld in aCtx.GetType(AObj.ClassType).GetProperties do
begin
if AFld.Visibility in [mvPublic] then
begin
ABase := AFld.GetValue(AObj);
AValue := AFld.GetValue(AObj);
if ABase.IsDate or ABase.IsDateTime then
AValue := ISOStrToDateTime(ReadString(ASection, AFld.Name,
ISODateTimeToString(ABase.AsDouble)))
else if ABase.IsBoolean then
AValue := ReadBool(ASection, AFld.Name, ABase.AsBoolean)
else if ABase.IsInteger then
AValue := ReadInteger(ASection, AFld.Name, ABase.AsInteger)
else if ABase.IsFloat or ABase.IsNumeric then
AValue := ReadFloat(ASection, AFld.Name, ABase.AsFloat)
else
AValue := ReadString(ASection, AFld.Name, ABase.asString);
AFld.SetValue(AObj, AValue);
end;
end;
finally
aCtx.free;
end;
end;
[/code]

  • Gravando o objeto no arquivo INI:

[code]

procedure TForm6.Button2Click(Sender: TObject);
begin
// grava o objeto OBJ no INI
// inicializar OBJ antes de executar….
with TIniFile.Create(‘teste.ini’) do
try
WriteObject(‘SecaoClass’, obj);
finally
free;
end;

end;
[/code]

  • Carregando o objeto com os dados do INI:

[code]
procedure TForm6.Button4Click(Sender: TObject);
begin
// Ler os dados do INI para o OBJ
with TIniFile.Create(‘teste.ini’) do
try
ReadObject(‘SecaoClass’, obj);
finally
free;
end;
end;
[/code]

Código Fonte no GIT

 

Para escrever arquivos JSON com as configurações ver o post seguinte