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:
// 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:
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á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:
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.