Cleverton Bueno
← Voltar para todos os artigos

O Fim da Recompilação:
Como Criei um Motor de Relatórios "Self-Service" em C++ Builder

Se existe uma frase que todo programador odeia ouvir na sexta-feira à tarde, é: "O relatório está ótimo, mas faltou a coluna do CRM do médico." No desenvolvimento tradicional desktop (VCL), isso significa um ciclo de 3 horas para uma mudança de 3 minutos. Eu decidi acabar com esse ciclo.

Eu queria que o meu suporte técnico — ou até o próprio cliente — pudesse criar e modificar relatórios complexos sem eu escrever uma única linha de C++. Para isso, eu não usei um gerador pronto. Eu construí um Ecossistema de Relatórios Baseado em Metadados.

A Arquitetura: O Relatório é um Dado, não Código

A grande virada de chave foi parar de tratar relatórios como "Forms do Delphi/C++" e passar a tratá-los como registros no banco de dados.

Criei duas tabelas fundamentais:

  • Tabela1 (Definição): Guarda o layout, o título, as larguras das colunas, o tamanho da fonte e — o mais importante — as flags de comportamento.
  • Tabela2 (Dados Temporários): Uma tabela genérica que minha engine popula antes de chamar o visualizador.

Com isso, separei o processo em três estágios de engenharia pesada.

1. A Interface Adaptativa (A Tela de Filtros Universal)

Em vez de criar 50 telas de filtro (uma para "Faturamento", uma para "Produção", etc.), eu criei UMA tela mutante: TFRel (relat.cpp). Essa tela contém todos os filtros possíveis (Data, Convênio, Médico, Paciente), mas eles nascem invisíveis.

Quando o usuário chama um relatório, minha aplicação lê os metadados. Se o relatório exige data, uma flag que identifica entrada de data é ativada (L_Peri). Se exige convênio, uma flag que identifica que tem convênio é ativada (L_Conv).

No evento FormActivate, a tela se redesenha sozinha:

C++: A Tela Polimórfica (relat.cpp)
// Verifica se os metadados exigem período
if (L_Peri->Visible) {
    // O relatório exige período? Habilita os calendários.
    E_Dati->Visible = true;
    E_Datf->Visible = true;
    MonthCalendar1->Date = Now();
}
if (L_Conv->Visible) {
    // O relatório exige convênio? Habilita o lookup.
    B_Conv->Visible = true;
    E_Conv->Visible = true;
}

O resultado? Uma única classe C++ (TFRel) capaz de servir de interface para mil relatórios diferentes.

2. O "SQL Parser": Injeção de Variáveis em Tempo de Execução

Aqui é onde a flexibilidade encontra a segurança. O SQL do relatório não fica no código, fica no banco de dados (na definição do relatório). Mas não é um SQL estático. É um template. Eu permito que o suporte escreva queries usando variáveis de sistema que minha engine substitui antes de executar:

SQL Template (Armazenado no Banco)
SELECT ... FROM EXAMES
WHERE DATA BETWEEN '<dataescolhida1>' AND '<dataescolhida2>'
AND IDCLIENTE = '<cliente>'

Quando o usuário clica em "Gerar", minha engine pega o valor dos componentes visuais (E_Dati, E_Datf), sanitiza os dados e injeta no SQL. Isso permite criar queries de complexidade infinita (joins, groups, substrings) sem alterar o executável.

3. O Renderizador "Hardcore" (Manipulação de Pixels na VCL)

Aqui está a parte que separa os "arrastadores de componentes" dos engenheiros de software. A maioria das ferramentas de relatório tenta "adivinhar" a largura das colunas (AutoSize). Isso quase sempre quebra o layout em relatórios complexos.

Eu decidi calcular manualmente a renderização. No meu executável visualizador (rel.cpp), eu leio a largura desejada em caracteres (definida na tabela Tabela1) e converto para pixels em tempo real, baseado no tamanho da fonte escolhida.

Olhe este trecho da minha engine de renderização:

C++: Cálculo manual de pixels (rel.cpp)
// Cálculo manual de pixels baseado na fonte
j = DadosBD->Query1->Fields->Fields[i]->DisplayWidth;
// ... lê limite1 da tabela Tabela1 ...

if (tamfont == 10)
    j = (int)j * 8;   // Multiplicador para fonte 10
else if (tamfont == 8)
    j = (int)j * 6.5; // Ajuste fino para fonte 8
else if (tamfont == 6)
    j = (int)j * 5;   // Ajuste para relatórios condensados

// Posicionamento absoluto do componente na banda
QL->Width = j;
QL->Left = l; // 'l' é o acumulador da posição esquerda
l = l + j + 1;

Eu controlo exatamente onde cada pixel vai cair. Se o relatório precisa ser paisagem (poLandscape), minha engine reposiciona os totalizadores (QRSysData, Total) para o novo limite da página (1047 pixels) automaticamente.

4. Desacoplamento Total (Microsserviços Desktop?)

Para garantir que um erro no relatório nunca trave a aplicação principal, tomei uma decisão arquitetural: o visualizador é um processo separado (rel.exe).

A aplicação principal chama o visualizador via linha de comando, passando a conexão e o contexto como injeção de dependência:

Command Line Injection
rel.exe "ConnectionString" "BancoDados" "IdRelatorio" "IdCliente" "IdFilial"

O rel.exe recebe esses parâmetros no FormActivate, conecta-se independentemente, renderiza o relatório e morre. Isso é robustez.

Conclusão

Com esse sistema:

  • O Menu é Dinâmico: Novos relatórios aparecem automaticamente.
  • A UI é Adaptativa: A tela de filtros se molda à necessidade da query.
  • O Layout é Matemático: Nada de colunas encavaladas; o cálculo de pixels garante a precisão.

Transformei a criação de relatórios de uma tarefa de desenvolvimento em uma tarefa de configuração. Hoje, meu suporte cria relatórios personalizados na frente do cliente, enquanto eu foco em evoluir a engine.