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.

Share Button

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:

   <?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>

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” ….

Share Button

Trocar informações entre RECORDs com estrutura diferentes pode ser um trabalho relativamente cansativa. Uma opção é converter o RECORD de origem em JSON e em seguida carregar o JSON no RECORD de destino.
Executar esta atividade é possível fazendo uso das classes RTTI do delphi. Então a idéia mais básica é criar métodos para gerar JSON e carga do JSON com o RECORD desejado.

Uma proposta utilizando generics para lidar com JSON fontes:


 TJsonRecord<T: Record > = class
  public
     /// gerar JSON
    class function ToJson(O: T; const AIgnoreEmpty: Boolean = true;
      const AProcBefore: TProc < TJsonObject >= nil): string;
     /// carregar um JSON
    class procedure FromJson(O: T; AJson: string);
  end;

Considerando uma estrutura de RECORD (origem e destino):

     TRecordOrigem = record
       nome:string;
       email:string;
     end;


     TRecordDestino = record
       nome:string;
       email:string;
       outros:integer;
     end;

A estrutura de identificação do RECORD por generics tem por finalidade aplicar RTTI para descobrir quais os FIELDS disponíveis no RECORD a ser transformado. Assim quando queremos gerar o JSON, fazemos:

   var sJson:string;
       dados : TRecordOrigem;
   ...
   dados.nome := 'xxxx';
   dados.email := 'yyyy';

   sJson := TJsonRecord<TRecordOrigem>.ToJson(dados);   // retorna o JSON

No RECORD de destino, fazendo a carga do JSON:

   var destino:TRecordDestino;
   ...

   TJsonRecord<TRecordDestino>.FromJson(destino,sJson);

Share Button

“Observer” é inigmático. Você nem esperava e chega aquela notícia que alguma coisa aconteceu. A janela estava lá mostrando uma fila de clientes em atendimento, mas de repente alguém gritou que chegou um cliente com preferência no atendimento e a fila precisa ser reorganizada para acomodar aquele cliente com preferência… Certamente você já viu um cenário deste ou participou de uma fila destas.
Vejamos os atores para o cenário: de uma lado existe alguém pronto para receber a informação de necessidade de reorganizar a fila, ele fica lá só esperando alguém gritar – de outro tem um lá na porta da instituição com sede de saber novidades para sair falando para todo mundo.

Trabalhar com “Observer” é em outras palavras montar uma rede de informação de notícias. Existe um ator que assina o serviço de notícia e outro que distribui as notícias – é isto que um “patterns observer” faz.

Olhando para o MVCBr, o primeiro passo é assinar o serviço de notícias (aquele que fica esperando uma notícia para tomar uma ação):

   uses MVCBr.Observable;

   ... on form show
   // assinar o serviço de notícias
   TMVCBrObservable.subscribe( form1, 'reorganizar.fila',  
                               procedure (json:TJsonValue)
                               var nQtde:integer;
                               begin
                                   nQtde = json.getvalue<integer>('qtde');
                                   abrirVagaNaFila(nQtde);
                               end);

   .... on destroy
   // retirar a assinatura do serviço
   TMVCBrObservable.UnSubscribe(form1);

Do outro lado esta o distribuidor de notícias, aquele que grita que precisa entrar um cliente preferêncial na fila:

   uses MVCBr.Observable;
   ..
    
   // em algum lugar em que a notícia nasce
   TMVCBrObservable.send(  'reorganizar.fila',   TJsonObject.parse(   '{"qtde":1}' ) );


Quando o “observer” recebe a notícia, ele com sede de espalhar a notícia, sai procurando todo mundo que se candidatou para receber aquele serviço… e pá… manda a notícia uma-a-uma para cada assinante.

Aproveitem: git

Share Button

Quando trabalho com MVCBr o “model” é responsável em executar ações do aplicativo. Um aplicativo, mesmo os mais simples, poderá consumir um número considerável de Models para levar a cabo a execução de todas as ações.
Não será raro que sua aplicação requeira carregar uma dezena de “model” em um único “controller” – nem sempre irá utilizá-los de imediato ou todos ao mesmo tempo. Nada mais sensato postergar a criação do objeto para o momento que for necessário sua execução e concluído a ação liberá-lo da memória sem perder o acesso a classe para posterior execução de novas ações.

Postergar a criação do objeto é chamado de “Lazy Load” – carga tardia do objeto.

Estratégia

A estratégia para criar o objeto tardiamente é criar um “Patterns Adapter” para que faça o controle da classe a ser criada no momento que for necessária, ou seja, ao instânciar o “adapter” ele deve guardar qual a classe que irá criar somente quando for chamado para executar a ação requerida.

Para executar uma carga tardia da classe o MVCBr utiliza a classe TModelAdapterFactory como mostra a declaração a seguir:


  TModelAdapterFactory<T: Class> = class(TModelFactory, IModel,
    IModelAdapter<T>)
  private
    FInstanceClass: TComponentClass;
    FInstance: T;
    function GetInstance: T; virtual;
    constructor CreateInternal; overload; virtual;
  public
    constructor Create; overload;
    class function New(AController: IController)
      : TModelAdapterFactory<T>; static;
    destructor Destroy; override;
    procedure Release; override;
    property Instance: T read GetInstance;
    procedure FreeInstance; virtual;
  end;

Criando o Adapter

Explorando o código do TModelAdapterFactory notará que a inicialização é permitida através da “class function New” que guarda as informações da sub-classe a ser criada tardiamente.

Durante a criação do adapter não haverá inicialização da sub-classe, que só poderá ser criada ao chamar a propriedade “Instance” – momento criar e retorna uma instância da sub-classe;

Exemplo:


  /// declarando a variável para o Adapter - passando tipo da sub-classe 
  var LMeuAdapter : TModelAdapterFactory<TDataModuleXXX>; 

.....
  /// criando o adapter 
  LMeuAdpater := TModelAdapterFactory<TDataModuleXXX>.new(   FController );

  /// consumindo a sub-classe
  LMeuAdapter.Instance.ExecutarMeuProcedimento;  // exemplo de chamada de um método da sub-classe

Sendo o adapter uma herança de “IModel”, em geral será utilizado como uma instância ligada a um controller no MVCBr – neste caso dispensa a declaração da variável e permite tratar o MODEL como outro MODEL comum ligado ao seu controller.

    /// adicionando o model para o controller no CreateModels do Controller
    add(     TModelAdapterFactory<TDataModuleXXX>.New(FController)   );

Quando usar um Adapter

Há duas razões para querer utilizar um Adapter como um MODEL.
1. O primeiro deles é quando você quer importar uma classe para o MVCBr sem fazer as alterações da classe existente – irá permitir consumir a sub-classe como um MODEL mesmo quando a classe não implementa a interface de um MODEL – a sub-classe será acessível pela propriedade “Instance” ;
2. Quando um objeto requeira “instânciar” e “liberar” de forma formal sem interferência da contagem de referência. Casos práticos foram detectado necessidade da utilização do Adapter quando se trabalha com TDataModule – para melhorar o controle e liberação da instância;

Liberação temporária da Instância

Um bom motivo para utilizar carga tardia de um classe pode ser justificada para manter carga de memória somente dos objetos que sejam necessário para a ação e liberá-los tão logo termine sua utilização – para liberar a instância utilizar “FreeInstance” que libera somente a instância da sub-classe mantendo a Adapter ativo.

Git: Projeto MVCBr (quando deste artigo o código corrente Branch DEV); Localizar UNIT: MVCBr.Model para ver o código;

Share Button

Os seguintes arquivos que devem ser configurados:
MVCBrServer.config
oData.ServiceModel.json
As configurações são as mesmas para o Servidor em Standalone ou em Serviço do Windows.
1º vamos configura o arquivo MVCBrServer.config, ele é responsável por informar ao servidor qual o drive de banco de dados a ser usado e o caminho da base de dados, que no exemplo vou mostrar usando uma base em Firebird.

{ "Config":
    { "WSPort":"8080",
      "driverid":"FB",
      "Server":"localhost",
      "Database":"C:\\Diretorio_BD\\BD_NOME.FDB",
      "user_name":"sysdba",
      "Password":"masterkey",
      "VendorLib":""
    }
}

Lembrando que o caminho da base deve ser utilizado duas barras “\\” ou barra invertida “/” para separação dos diretórios.
A estrutura do arquivo MVCBrServer.config:

  • "Config" conterá o objeto de parametrização com as seguintes propriedades:
    1. "WSPort" recebe o número da porta que o ServidorMVCBr irá responder;
    2. "driverid" recebe a sigla correspondente ao banco de dados utilizado (“FB”=Firebird, “PG”=PostgreSQL, “MYSQL”=MySQL e “MSSQL”=Microsoft SQL Server);
    3. "Server" recebe o ip ou hostname do servidor de banco de dados;
    4. "Database" recebe o caminho e o nome da sua base de dados;
    5. "user_name" recebe o nome do usuário que se conecta em sua base de dados;
    6. "Password" recebe a senha do usuário que se conecta em sua base de dados;
    7. "VendorLib" recebe o caminho e o nome da DLL-Dynamic-link library caso seu banco de dados exija para a conexão.

    2º vamos configura o arquivo oData.ServiceModel.json, ele é responsável por informar ao servidor qual os recursos serão disponibilizado.

    {
    	"@odata.context": "http://localhost:8080/OData/OData.svc",
    	"__comment": "Services list all resource available to OData.Service",
    	"OData.Services": [
    		{
    			"_comments_":"Products table service",
    			"resource": "produtos",
    			"collection": "produtos",
    			"keyID": "codigo",
    			"maxpagesize": 100,
    			"fields": "produtos.codigo,produtos.descricao,produtos.unidade,produtos.grupo,produtos.preco",
    			"method": "GET,PATCH,POST,PUT,DELETE",
    			"relations": [{
    				"resource": "grupos",
    				"sourceKey": "grupo",
    				"targetKey": "codigo",
    				"join": "left join grupos on (produtos.grupo=grupos.grupo)"
    			}]
    		},
    		{
    			"_comments_":"Goups table service",
    			"resource": "grupos",
    			"collection": "grupos",
    			"keyID": "codigo",
    			"fields": "*",
    			"method": "GET",
    			"maxpagesize": 100,
    			"relations": [{
    				"resource": "produtos",
    				"sourceKey": "codigo",
    				"targetKey": "grupo",
    				"join": "join produtos on (grupos.codigo=produtos.grupo)"
    			}],
    			"expands": [{
    				"resource": "produtos",
    				"filter": "grupos.codigo=:grupo"
    			}]
    		}
    	],
    	"OData.Execute": [
    		{
    			"_comments_":"ExecSQL for exec_procedure",
    			"resource": "exec_procedure",
    			"procedure": "exec_procedure",
    			"params": "filial,dcto,data",
    			"returnRows": true
    		}
    	]
    }
    

    Lembrando que tem um aplicativo para gerar o arquivo oData.ServiceModel.json, o GeradorMetadata, que está disponível junto dos fontes no GitHub no diretório MVCBr/MVCBrServer/GeradorMetadata (Projeto MVCBr).
    As propriedades do arquivo oData.ServiceModel.json:

  • "@odata.context" contem a URL do servidor OData, *obrigatório;
  • "__comment" descrição dos serviços do servidor OData, não é obrigatório;
  • "OData.Services" é um array de objetos que contem os serviços disponibilizados, cada item do array deve conter o objeto com as seguintes propriedades:
    1. "_comments_" uma descrição do serviço, não é obrigatório;
    2. "resource" conterá o nome do serviço que será disponibilizado, não se pode usar caracteres especiais pois esse nome será parte da URL e é case sensitivo, *obrigatório;
    3. "collection" conterá o nome da sua tabela do seu banco de dados, sendo que o nome do serviço pode ser totalmente diferente do nome de sua tabela, *obrigatório;
    4. "keyID" conterá o nome do campo chave da sua tabela, *obrigatório;
    5. "maxpagesize" conterá a quantidade de registro que será disponibilizado na consulta do serviço, não é obrigatório, mas recomendo 100% a utilização, pois se não tiver uma limitação, ao chamar o serviço sem filtro irá trazer todos os registro de sua tabela, com isso deixando seu cliente lento;
    6. "fields" conterá os campos de sua tabela que você quer disponibilizar, *obrigatório pelo menos um campo;
    7. "method" conterá quais método de requisição seu serviço poderá receber, “GET,PATCH,DELETE,PUT,POST”, *obrigatório pelo menos um método;
    8. "relations" é um array de objetos, que cada item terá um objeto com as seguinte propriedades:
      1. "resource" nome do serviço relacionado;
      2. "sourceKey" campo chave para relacionada ao serviço;
      3. "targetKey" nome do serviço com a relação;
      4. "join" sentença SQL da relação;
    9. "expands" é um array de objetos com os serviços disponibilizado, direciona os registros relacionados que devem ser recuperados no registro ou no conjunto que está sendo recuperado, cada item terá um objeto com as seguinte propriedades:
      1. "resource" nome do serviço, *obrigatório;
      2. "filter" Especifica uma expressão ou função que deve ser avaliada como verdadeira para que um registro seja retornado, *obrigatório;
  • "OData.Execute" é um array de objetos com os serviços disponibilizado, de acordo com um procedimento existente no seu banco de dados, cada item terá um objeto com as seguinte propriedades:
    1. "_comments_" uma descrição do serviço, não é obrigatório;
    2. "resource" conterá o nome do serviço que será disponibilizado, não se pode usar caracteres especiais pois esse nome será parte da URL e é case sensitivo, *obrigatório;
    3. "procedure" nome do seu procedimento na sua base de dados, *obrigatório;
    4. "params" parâmetro do seu procedimento, *obrigatório se tiver no seu procedimento;
    5. "returnRows" conterá “true” verdadeiro se o procedimento retorna registros e “false” falso para não retorna registros, *obrigatório;

    Vídeo mostrando como ServidorMVCBr funciona.

    Share Button
    1. Tudo bem, você já mostrou no #1 como um VIEW faz para abrir outro VIEW, mas no meu caso eu tenho um TLayout no form principal e queria mostrar meu segundo form dentro do TLayout, como fazer isto ?
      R. O TLayout é um componente do framework FMX. Dado a complexidade em embutir um formulário dentro de outro formulário quando se usa FMX – o caminho mais rápido para fazer isto é pegar um TLayout do VIEW2 e mostrar ele dentro de um TLayout da VIEW1. O MVCBr tem método pronto para fazer esta mágica com pouco código, mas precisa preparar o terreno antes:

      • No form principal VIEW1
        1. arrastar um TLayout e posicionar onde deseja que a o VIEW2 seja mostrado
        2. no ShowView do VIEW1 fazer a chamada para o VIEW2:

                
          uses meuView2.Controller.interf ;
          ... 
          function TNewMVCAppView.ShowView(const AProc: TProc<IView>): integer;
          var
            LController2: IMeuView2Controller;
          begin
            inherited;
            /// Carrega o controller do VIEW2
            LController2 := resolveController<IMeuView2Controller>;
            /// embute o Layout do VIEW2 dentro do Layout do VIEW1 (aqui vai a mágica)
            LController2.Embedded(Layout1);
          end;
          
      • No form do VIEW2
        1. arrastar um TLayout para dentro do VIEW2 e mudar a propriedade “align=client”, os componentes visuais serão arrastados para dentro do TLayout e posicionados a vontade
        2. Incluir na criação do objeto a interface ILayout (é ele que torna acessível o TLayout do VIEW2)
                  TMeuView2View = class(TFormFactory { TFORM } , IView, IThisAs<TMeuView2View>,
                           IMeuView2View, IViewAs<IMeuView2View>, ILayout)
                
        3. implementar a função do ILayout para que retorne o TLayout a ser mostrado dentro do VIEW1
          function TMeuView2View.GetLayout: TObject;
          begin
            result := Layout1;
          end;
          
          
    2. Como posso retornar para o VIEW1 uma cadeia de parâmetros que foram digitados no VIEW2 ? Exemplo: no VIEW2 eu digito alguns parâmetros que gostaria de formatar uma WHERE no SELECT que esta no VIEW1. Então como posso retornar estes dados do VIEW2 para o VIEW1 ?
      R. Lembrando que tudo no MVCBr é INTERFACE, então é preciso pensar em termos de interface. No VIEW2 você precisa de uma função que retorne os parâmetros formatados para serem utilizados no VIEW1. Exemplo: function GetWhereString: string;

      • No VIEW2 – Gerando os parâmetros

        
        Type
          IParametrosView = interface(IView)
            ['{838227AC-E658-4193-91B9-25790379DF49}']
            // incluir especializacoes aqui
            function GetWhereString: string;
          end;
        .....
        
        procedure TParametrosView.FormShow(Sender: TObject);
        begin
          /// clear
          FWhere := '';
        end;
        
        procedure TParametrosView.Button1Click(Sender: TObject);
        begin
          WhereBuilder;  /// construtor da where
          close;
        end;
        
        /// construtor da where
        procedure TParametrosView.WhereBuilder;
          procedure add( AText:string );
          begin
            if FWhere <> '' then
               FWhere := FWhere + ' and ' ;
            FWhere := FWhere + AText;
          end;
        begin
          FWhere := '';
          if LabeledEdit1.text<>'' then
             add( 'email_from like ('+quotedStr(LabeledEdit1.text+'%')+')');
          if LabeledEdit2.text<>'' then
             add( 'email_to like ('+quotedStr(LabeledEdit2.text+'%')+')');
          if LabeledEdit3.text<>'' then
             add( 'email_subject like ('+quotedStr(LabeledEdit3.text+'%')+')');
        end;
        
        /// function da interface do VIEW2
        function TParametrosView.GetWhereString: string;
        begin
          result := FWhere;  /// retorna a where montada
        end;
        
        
      • No VIEW1, pegando os parâmetros digitados no VIEW2:

        
        /// evento para receber os dados digitados no VIEW2
        procedure TNewMVCAppParametersView.ParametersApply(AView: IView);
        begin
          ///  faz alguma coisa com os dados digitados no VIEW2 
          Memo1.lines.text := (AView as IParametrosView).GetWhereString;
        end;
        
        /// abre a janela do VIEW2
        procedure TNewMVCAppParametersView.Button1Click(Sender: TObject);
        begin
          /// No ShowView chamar com os parametros:
          ///   a interface do controller da VIEW2 - irá procurar na lista de controllers qual a view desejada
          ///   nil para o segundo parâmetro   (OnProcBeforeShow);
          ///   método que irá receber o parâmetro  (OnProcCloseEvent);
          ShowView(IParametrosController, nil, ParametersApply);
        end;