O uso de “Template” na IDE do Delphi proporciona melhoria de produtividade a medida que reduz digitação de rotinas repetitivas.
Ainda a pouco tempo abordei sobre o uso de Template de MVCBr e ORMBr aqui no blog – hoje vamos ver alguns “templates” disponíveis para uso com MVCBr.

base
Os “templates” de MVCBr encontram-se na pasta .\templates; A instalação dos templates é basicamente copiar os arquivos XML para a pasta padrão de templates do Delphi como já foi tratado no “artigo anterior sobre templates“.

elementar meu caro
Copiando os arquivo XML para a pasta padrão, o Delphi irá disponibilizar as macros de chamada para os templates utilizando o atalho: CTRL+J ou ainda pelo uso da sequência de palavras que identificam um template em particular.

base para acesso aos templates
mv – macros para views
mc – macros para controller
mm – macros para model
mo – macros para Observables

vamos a lista de macros para os templates (CTRL+J)

VIEWS
mv.conv – utilizado para converter um FORM já existente em um componente para MVCBr (criar interfaces para View, Controller e Classe Controller);
mv.docommand – criar método DoCommand (um override) a ser adicionado para VIEWs que queira receber notificação de EVENTS do Observer de controle interno ao framework;
mv.form – criar as assinaturas de classe e interfaces que permite converte um FORM para VIEW (mais enxuto em relação ao mv.conv);
mv.update – criar método UPDATE (um override) para views que receberam eventos UPDATE enviado pelo observer genérico de usuário;
mv.uses – criar uma lista de dependências de uses para VIEWs (auxiliar de conversão de FORMs que já existem);

Controlles
mc.addmodel – adiciona modelo de exemplo para adicionar um MODEL ao controller – utilizado no Create do Controller ou CreateModules;
mc.factory – cria a assinatura para um controller, útil para criar controllers independentes ou para conversão de código legado;
mc.fdconnection – cria uma sequência de código para o controller que gerar conexão de banco dados FireDAC;
mc.init – cria uma sequência de geração de INIT que permite inserir um TFormFactory em um controller;
mc.initialization – cria a chamada de registro do controller para a lista de controllers disponível ao aplicativo;
mc.interface – cria uma sequência para gerar uma interface para o controller;

Models
mm.creator – cria uma UNIT exemplo de geração de um MODEL genérico;
mm.ormbr.fd – cria uma sequência de integração de FireDAC Modelo do ORMBr com Model MVCBr;
mm.ormbr.sqlite – cria uma sequência de integração com SQLite deo ORMBr com Model MVCBr;
mm.ormbr.createGetTable – cria uma sequência para função GetTable para ORMBr;
mm.ormbrfb.table – cria conjunto para gerar acesso Firebird de integração de ORMBr com Model MVCBr.

Observable
mo.subscribe – criar um serviço de Observer assinando um determinado evento – como se fosse um “callback”;
mo.unsubscribe – remove o serviço do Observer;

GIT com a lista completa dos XML utilizados

Impressionante o número de vezes que precisamos copiar os valores de atributos de um objeto para outro ou simplesmente criar um novo com as mesmas propriedades.
Conhecendo a classe TMVCBrPrototype notamos o poder de RTTI combinado com Generics nos permite facilitar o trabalho sem precisar digitar todos aquelas propriedades envolvendo os dois objetos.

base
Usando Generics para passar o tipo de classe a ser manipulado e com RTTI descobrimos quais os atributos desta classe… agora é só os valores de uma para o outro.

elementar meu caro
Uso de Generics tem por finalidade facilitar o RTTI pegar quais os atributos a serem copiados do objeto alvo. Para que a cópia ocorra, o objeto de origem e de destino precisam possuir o mesmo nome para o atributo. O ideal é que ambos possuam a mesma tipagem, ainda que não seja obrigatório.

[code]
TMVCBrPrototype = Class(TInterfacedObject, IMVCBrPrototype)
public
class procedure Copy<T: Class>(ASource: T; ATarget: T); static;
class function Clone<T: Class>(ASource: T): T; static;
class function New<T: Class>: T; static;
end;

// Como copiar de B para A

var B,A : TMinhaClasse;


se: B := TMinhaClasse.create;
B.nome := ‘x’;

clone:
A := TMVCBrPrototype.Clone<TMinhaClasse>(B); /// Cria A e copia B para A

para copiar (requer que A e B já estejam criados):
TMVCBrPrototype.Copy<TMinhaClasse>(B,A); /// copia B para A

[/code]

TMVCBrPrototype

Inspecionando o código MVCBr irá notar que o uso de interface é predominante às diversas classes do framework.

base
Uma variável tipada por uma interface é diferente de uma variável “object” por estabelecer um independência entre o objeto e a implementação de classe do objeto. Como é comum, se diz que a interface é um contrato estabelecido entre o cliente e o fornecedor – No delphi pascal, o objeto representado por uma instância não requer o tradicional FREE para liberar o objeto. A linguagem estabelece um contador incremental de referência para a instância a cada chamada e o decrementa a medida que não seja necessária, quando alcança um “count=0” o objeto é liberado da memória.

elementar meu caro
Se o objeto é liberado de forma automática “gerenciada pelo gerenciador”, então não é necessário FREE ou DisposeOf. Em alguns trechos de código é comum encontrar uma atribuição NIL para forçar o decremento do contador de referência – com isto força a liberação da instância da memória mesmo antes de terminar o execução do trecho de código.

Quando usar Create()
O uso do “Create(..)” permite criação da classe pascal nativa “SEM” fazer uso da interface. Nestes casos a liberação do objeto da memória se dará fazendo uma chamada FREE ou DisposeOf para o mesmo;

Usando New(..)
Em geral, o uso do NEW é implícito o desejo em obter uma instância representado pela interface que a classe implementa. Como uma interface, a instância dispensa o Free ou DisposeOf, fica a cargo do contador de referência retirar o objeto da memória.

Posso converter um Objeto em uma representação Interface ?

Se o objeto em questão implementa uma interface a resposta será SIM. Toda classe que implementa uma interface pode ser criada com CREATE e atribuído a uma variável tipada para a interface que implementa. Neste caso o gerenciamento de memória é feito pela representação “Interface” e não requer Free ou DisposeOf;

Exemplo:

[code]
type
IMinhaClasse = interface
[…]
end;
TMinhaClasse = class(TInterfacedOjbect, IMinhaClasse)
end;

….

var LMinhaClasse : IMinhaClasse;

LMinhaClasse := TMinhaClasse.create;

[/code]

No caso “LMinhaClasse” não requer Free nem mesmo DisposeOf, já que foi tipada por uma interface.

Ver também – Um Adapter Interfaced para qualquer objeto

Precisa notificar o usuário sobre algum evento?
Então você precisa de Windows Notification na sua aplicação.

base
No Windows 10 está disponível o envio de mensagens para a bandeja do windows alertando sobre alguma ocorrência.

elementar meu caro
Usar a notificação do windows é bastante simples, com a ajuda de um model do MVCBr que se encontra na pasta \AdIN\Windows base instanciar o “model” e sair utilizando – se a utilização é eventual, nem mesmo instanciar será necessário;

Unit: WinNotification.Model, WinNotification.Model.Interf;
[code]

/// declaração da classe
TWinNotificationModel

class function Notify(const AName: String; const ASubject: String;
const AMessage: String): boolean;
end;

/// enviando mensagens para o windows
TWinNotificationModel.Notify(‘Ola Windows’,
‘Horário Visita se aproximando’,’Não esqueça de sair para a visita’);

[/code]

Código de Exemplo

Tenho um objeto implementado utilizando uma herança nativa da DELPHI e gostaria de fazer uso de INTERFACE para este objeto sem precisar implementar uma nova classe.

base
Um patterns adapter objetiva gerar um adaptador que recebe um objeto externo e implementa novas funcionalidade.

elementar meu caro
Não estamos falando de herança, mas de um adaptador ao objeto que adiciona funcionalidade para o qual não foi desenhado originalmente.
Destaco como o adapter responde como se fosse o próprio objeto expondo procedures, funções e atributos diretos sem fazer CAST dos mesmos.

Unit: MVCBr.Patterns.Adapter;

[code]
uses MVCBr.Patterns.Adapter;

Type
// objeto base a ser adaptado
TMeuObjeto = Class(TObject)
public
texto:string;
End;

procedure TForm85.FormCreate(Sender: TObject);
var
LAdapter : IMVCBrAdapter<TMeuObjeto>;
begin

LAdapter := TMVCBrAdapter<TMeuObjeto>.create(nil);
// atribuir valor
LAdapter.texto := ‘texto de exemplo’;

// pegando o valor do objeto
ShowMessage( LAdapter.texto );

end;
[/code]

Depois do XML, JSON é a mais produtiva ferramenta para trocar de informações entre diferentes objetos ou diferentes plataformas.

base
JSON é uma notação para estrutura Chave/Valor criada originalmente em JavaScript. Comparativamente ao XML, JSON é mais compacto e totalmente transparente em objetos JavaScript o que o popularizou como a estrutura mais adequada de troca de informações entre servidor e cliente.

elementar meu caro
Quando um objeto possui um estrutura de dados e se deseja enviar ou converter em outra classe ou objeto, o JSON deve ser a primeira opção para facilitar a troca de dados entre os atores da operação.
Usar uma interface tem como foco delegar ao aplicativo decidir o momento adequado para retirar a instância da memória, não precisando se preocupar com o FREE do objeto implementando algo mais próximo de um coletor de lixo existente e outras linguagens.

como usar

TInterfacedJSON representa uma implementação da interface IJSONObject para um objeto TJSONObject.

  • Unit: System.JSON.Helper;

    Gerando o IJSONObject para o dado:

    [code]
    uses System.JSON.Helper;
    procedure TForm84.FormCreate(Sender: TObject);
    var
    AJson, ANome: string;
    LJson: IJsonObject;
    ACliente: Integer;
    begin

    AJson := ‘{ "cliente":1,"nome":"jose de maria" }’;

    LJson := TInterfacedJSON.new(AJson);
    with LJson.JsonObject do
    begin
    /// pegando nome do cliente
    ANome := s(‘nome’);
    ACliente := I(‘cliente’);
    end;

    // adicionando novo key/value
    LJson.addPair(‘fone’:’9999-9999′);

    //….
    end;
    [/code]

  • Unit: Data.DB.Helper
    Trocando informações entre um JSON e um TDataset:
    [code]
    procedure TForm84.Button1Click(Sender: TObject);
    var LJson:IJSONObject;
    begin

    LJson := TInterfacedJSON.new;
    LJson.addPair(‘cliente’,1);
    LJson.addPair(‘nome’,’jose de maria’);

    FDMemTable1.open;
    FDMemTable1.edit;

    // carrega os fields com o valor do JSON
    // se o JSON for um ARRAY será carrega cada posição no array como uma nova linha na tabela.
    FDMemTable1.FromJsonObject(LJson.JsonObject,True);

    FDMemTable1.post;
    end;
    [/code]

    Para obter um JSON do TDataset:

    [code]
    // gera um array para todas as linhas
    FDMemTable1.ToJson;

    // gera o JSON somente para a linha corrente
    FDMemTable1.Fields.ToJson;

    [/code]

  • Unit: System.Classes.Helper;
    Lidando JSON com Objetos:
    [code]
    // gera um JSON de um objeto
    MeuObjeto.ToJson;

    // carregando o objecto com os valor de um JSON
    MeuObjeto.FromJson( … );

    [/code]

ForEach faz uso de funções anônimas para realizar a tarefa.

base
Funções e/ou Procedures anônimas são úteis para gerar mecanismo de eventos sem necessidade de criar um evento para objeto. O uso de um evento é obrigatório a existência de uma classe para injetar o evento a ser chamado. Com anônimos é possível injetar um evento a qualquer chamada, não demandando a obrigação da criação de uma classe.

elementar meu caro
Um anônimo pode figurar como um “procedure” (sem retorno) ou uma “function” (com um valor de retorno). Usar como procedure ou função é dependente do objetivo que o CODER pretende empregar ao seu código, se deseja um valor de retorno a resposta será usar um função nas demais irá preferir usar um procedure.

prática

  • Unit: MVCBr.ApplicationController
    ForEach(….): executa um loop sobre uma lista de itens enquanto existir item ou a Função Anônima retornar FALSE;
    Exemplo:
    [code]
    function TApplicationController.FindModel(AGuid: TGuid): IModel;
    var
    rst: IModel;
    begin
    rst := nil;
    ForEach(
    function(ACtrl: IController): boolean
    begin
    result := false;
    ACtrl.GetModel(AGuid, rst);
    if assigned(rst) then
    result := true;
    end);
    result := rst;
    end;
    [/code]
  • Unit: Data.DB.Helper
    ForEach em um objeto TDataset:
    [code]
    ForEach(function (ds:TDataset):boolean
    begin
    /// executa para cada item do dataset
    result:= false; /// não finalizar o loop e ir para o proximo
    end);
    [/code]

Sabe aqueles momento que você queria ser uma mosquinha para ver o que o usuário está fazendo? Seus problemas estão resolvidos, vamos fazer um monitoramento remoto do aplicativo sem ter IDE instalado lá.

Contexto: O sistema operacional windows possui um mecanismo de propagação de mensagens para o DebugView. É o modelo de monitoramento nativo do windows para comunicação de ocorrências internas ao processo do próprio windows.
Download: DebugView

DebugView is an application that lets you monitor debug output on your local system, or any computer on the network that you can reach via TCP/IP. It is capable of displaying both kernel-mode and Win32 debug output generated by standard debug print APIs, so you don’t need a debugger to catch the debug output your applications or device drivers generate, and you don't need to modify your applications or drivers to use non-Windows debug functions in order to view its debug output.

Instalando o servidor

Se o DebugView for utilizado localmente, basta carregar o aplicativo para a memória que ele já inicia a captura das informações transmitidas pelo windows; Caso deseje monitorar remotamente utilize a opção “dbgview /a” ao chamar o DebugView (consultar o manual para mais opções ou /?); “/a” iniciará o DebugView como um servidor TCP/IP que permite conexão a partir de estações remotas;

Instalando o cliente

Quando o DebugView está rodando em uma máquina remota é possível conectar diretamente através da opção de menu “Computer/Connect” indicando o IP onde está rodando o servidor.

Preparando o aplicativo DELPHI

No aplicativo DELPHI inserir chamadas para a função:

[code language=”pascal”]
// uses Winapi.Windows;
procedure OutputDebug(const txt: string);
var
i: integer;
x: integer;
const
n = 1024;
begin
{$IFDEF MSWINDOWS}
try
i := 0;
x := length(txt);
repeat
OutputDebugString({$IFDEF UNICODE}PWideChar{$ELSE}PAnsiChar{$ENDIF}(ExtractFileName(ParamStr(0)) + ‘:’ + copy(txt, i + 1, n)));
i := i + n;
until i >= x;
except
end;
{$ENDIF}
end;
[/code]

Inserindo a chamada a procedure “OutpuDebug” tudo que for passado como parâmetro será transmitido via DebugView diretamente para o cliente que estiver monitorando o servidor sem necessidade de rodar dentro da IDE do Delphi para receber notificações do aplicativo.

Por algum tempo fiquei pensando em uma solução para Class Helper ganhar poder de adicionar uma nova variável. Confesso que já estava desistindo, parecia não ser possível… mas – pensando bem….

Digamos que você tenha uma classe base que gostaria de fazer um Class Helper dela:

Exemplo de uma classe original:
[code]

TComponentX = class(TComponent)
public
published
end;

[/code]
(Code:A)

O desafio é como incluir uma nova variável a classe TComponentX utilizando de Class Helper…

Razões para usar um "Class Helper" e não uma herança: 
    se houver possibilidade de injetar a variável através de herança, 
    é provável que não irá precisar criar um "Class Helper" 
    - as razões para criar um "Class Helper" 
    é dispensar mudanças nos objetos em units já implementadas.

Agora, vamos usar o “Class Helper” para injetar uma nova variável à classe TComponentX:
[code]
TComponentHelper = Class Helper TComponent
public
Ligado:Boolean;
end;
[/code]
(Code:B)

Se estiver habituado ao uso de Class Helper, já deve ter notado que este exemplo (Code:B) não é compilado no Delphi – reclamando que um “Class Helper” não pode inserir uma nova variável. Não seja apressado, já lembrou que existe um “Class Var” e logo sugere escrever assim: ” class var Ligado:Boolean; “… Se este for o seu caso, não tenho boa notícia – você terá criado uma única referência para a variável, não podendo receber valores diferentes nas várias instâncias que a aplicação desejar manter carregada na memória – ou seja, criou uma simples variável GLOBAL que é compartilhada com todas as instâncias da classe;

No nosso caso, queremos que cada instância tenha o seu próprio valor, portanto não pode utilizar um “Class Var” como solução.

PROPOSTA:
[code]
unit Unit1;

interface

uses System.Classes, System.Generics.Collections;

type

TComponentHelper = Class Helper for TComponent
protected
function GetLigado: boolean;
procedure SetLigado(const AValue: boolean);
public
property Ligado: boolean Read GetLigado write SetLigado;
end;

implementation

var
LComponentLigado: TDictionary<TComponent, boolean>;

function TComponentHelper.GetLigado: boolean;
begin
result := true; // responde um padrão, para quando não existir;
if LComponentLigado.ContainsKey(self) then
result := LComponentLigado.Items[self]; // pega o valor da lista
end;

procedure TComponentHelper.SetLigado(const AValue: boolean);
begin
LComponentLigado.AddOrSetValue(self, AValue); // inclui na lista
end;

initialization

LComponentLigado := TDictionary<TComponent, boolean>.Create;

finalization

LComponentLigado.free;

end.
[/code]

A implementação permite adicionar novas variáveis ao objeto do “Class Helper” sem interferir no funcionamento da classe padrão.

ORMBr é um framework que vejo ser da maior importância para elevar o CRUD a um novo patamar – vamos dar uma mãozinha para torná-lo mais RAD.

Com o uso de Templates é possível automatizar alguma coisa rotineiras no DELPHI e o deixar mais produtivo. Hoje vamos a um exemplo de como fazer isto.

Primeira informação a saber é que um Template é um XML estruturado que pode ser escrito até no EDITOR de NOTAS, como meu amigo IVAN gosta de fazer ;-).

Quando o Delphi é carregado ele lê os XMLs na pasta de usuário:

  // pasta de templates
  C:\Users\USUARIO\Documents\Embarcadero\Studio\code_templates\Delphi

Vamos criar um arquivo com nome: ORMBr.CreateEntity.xml e copiar para a pasta de templates.

Conteúdo do XML:

[code lang=”xml”]
<?xml version="1.0" encoding="utf-8" ?>
<codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
version="1.0.0">
<template name="ormbr.createEntity" invoke="manual">
<description>
Cria modelo para uma tabela
</description>
<author>
amarildo lacerda
</author>
<point name="table">
<text>table</text>
</point>
<point name="description">
<text>description</text>
</point>
<code language="Delphi" delimiter="|"><![CDATA[

interface

uses
DB,
Classes,
SysUtils,
Generics.Collections,
/// orm
ORMBr.types.blob,
ORMBr.types.lazy,
ORMBr.types.mapping,
ORMBr.types.nullable,
ORMBr.mapping.Classes,
ORMBr.mapping.register,
ORMBr.mapping.attributes;

type

[Entity]
[Table(‘|table|’, ‘|description|’)]
T|table| = class
private

public

end;

implementation

initialization

TRegisterClass.RegisterEntity(T|table|);

end.

]]>
</code>
</template>
</codetemplate>
[/code]

Se tudo correr bem, agora quando você digitar no editor de código -> “orm + CTRL+J” deverá paracer na lista de opções de templates uma opção “ormbr.createEntity” ….