Share Button

Um dia vi um programador tentando usar um componente VCL comum do Delphi que não tinha um correspondente DB para ele. Olhando a ginástica necessária para o componente responder a movimentações do Dataset, então lembrei quando estava aprendendo Delphi a uns 20 anos. O mais sensato é criar um novo componente DB para o mesmo.

base
Um componente DB é um componente de uso geral que responde a eventos existentes no TDatasource. O TDatasource emite uma chamada para o evento – no lado do componente, ele trata o evento e atualiza a visualização para o usuário.

elementar meu caro
Para receber evento, um componente DB deve responder aos eventos emitidos pelo TDatasource e fazer o ligação com a coluna da tabela que ao qual esta associado. Assim sendo, o componente DB irá expor uma ligação para o TDatasource (Datasource) e outro para o coluna desejada (Field);

o exemplo
Sabe aquele componente encontrados nos celulares ON/OFF, que tal criar um componente VCL DB para ele. O componente que executa esta funcionalidade na VCL é o TToggleSwitch, então vamos torná-lo um “dataware”.

usando unit: MVCBr.DataControl
Para facilitar a criação dos diversos componentes DB existentes no MVCBr – é feito uso do componente TMVCBrDataControl que entrega o mecanismo básico a todo componente DB. Ele que faz a ligação com evento “OnChange” do Datasource com a base do novo componente, vejamos como ficou…


  TDBToggleSwitch = class(TToggleSwitch)
  private
    ...
  published
    property ValueTrue: string read FValueTrue write SetValueTrue;
    property ValueFalse: string read FValueFalse write SetValueFalse;
    property DataField: string read GetDataField write SetDataField;
    property Datasource: TDatasource read GetDatasource write SetDatasource;
  end;


as propriedades
Datasource e DataField são comuns aos componentes DB para ligar TDatasource e coluna da tabela;
ValueTrue: indicar qual o valor encontrado na tabela que indica ON;
ValueFalse: indicar qual o valor encontrado na tabela que indica OFF;

Unit completa no GIT

Share Button

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

Share Button

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.

  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

TMVCBrPrototype

Share Button

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:

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

....

var LMinhaClasse : IMinhaClasse;
...
   LMinhaClasse := TMinhaClasse.create;

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

Share Button

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;


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


Código de Exemplo

Share Button

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;

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;

Share Button

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:

    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;
    
  • Unit: Data.DB.Helper
    Trocando informações entre um JSON e um TDataset:

    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;
    

    Para obter um JSON do TDataset:

       // gera um array para todas as linhas
       FDMemTable1.ToJson;
    
    
       // gera o JSON somente para a linha corrente
       FDMemTable1.Fields.ToJson;
    
    
  • Unit: System.Classes.Helper;
    Lidando JSON com Objetos:

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

Share Button

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:

    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;
    
  • Unit: Data.DB.Helper
    ForEach em um objeto TDataset:

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

Share Button

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:

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

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.

Share Button

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:

     
     TComponentX = class(TComponent)
      public
      published
     end;

(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:

    TComponentHelper = Class Helper TComponent
      public
         Ligado:Boolean;
    end;

(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:

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.

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