LGPL-3.0 Gerador de relatórios banded em Pascal puro, sem dependências externas — alternativa livre ao FastReport/QuickReport. Este manual espelha o MANUAL.md e traz dezenas de exemplos prontos para copiar.
1. Conceitos fundamentais
- Modelo em bandas: um relatório é uma pilha de bandas (título, cabeçalho, dados, rodapé…). A banda de dados repete uma vez por registro; as demais aparecem em pontos específicos.
- Unidade interna = 0,1 mm (inteiro): independente de dispositivo. Você quase nunca digita o número cru — usa os helpers:
| Helper | Converte para |
|---|---|
MMToUnits(mm) | unidade interna (0,1 mm) — o que você usa ao montar |
MMToPx, MMToPt, MMToTwips, MMToEMU | pixels, pontos, twips, EMU (uso interno dos exports) |
Ex.: MMToUnits(190) = 1900 (= 190 mm da largura útil do A4).
- Display list única: o motor renderiza para um
TrhRenderedDocument. Preview, impressão e todos os exports consomem essa mesma lista → WYSIWYG, e[TOTALPAGES]de graça. - Dados genéricos: qualquer
TDataSet(FireDAC, ADO, dbExpress, memtable…). Sem acoplar driver.
2. Instalação
- Instale os pacotes ReportsHowieRT (runtime) e ReportsHowieDT (design-time). Só o
TrhReportcai na paleta. - No projeto consumidor, adicione ao Search Path as pastas usadas:
...\source\core;...\source\model;...\source\render;...\source\preview;...\source\expr;...\source\data;...\source\export\html;...\source\export\pdf;...\source\export\ooxml
uses típico de uma aplicação:
uses
rh.Types, rh.Report, rh.Page, rh.Bands, rh.Objects, rh.Model.Types,
rh.Render.Intf, rh.Expr.Nodes, rh.Data.Pipeline,
rh.Preview.Form, // ShowPreview / ShowDataPreview (janela)
rh.Preview.Control, // TrhPreviewControl (preview embutida)
rh.Export.HTML, rh.Export.PDF, rh.Export.XLSX, rh.Export.DOCX;
Community Edition não compila por linha de comando — o build é pela IDE.
3. Seu primeiro relatório (em código)
Exemplo 1 — relatório mínimo com título e banda de dados:
var
Page: TrhPage;
Band: TrhBand;
T: TrhTextObject;
begin
rhReport1.Clear;
rhReport1.Title := 'Ola ReportsHowie';
Page := rhReport1.EnsurePage; // 1a pagina (A4 retrato)
Band := Page.Bands.AddBand(rhbtReportTitle);
Band.Height := MMToUnits(15);
T := Band.Objects.AddNew<TrhTextObject>;
T.Text := 'Meu primeiro relatorio';
T.Left := 0; T.Top := MMToUnits(2);
T.Width := MMToUnits(190); T.Height := MMToUnits(10);
T.Font.Size := 16;
T.HAlign := rhhaCenter;
rhReport1.ShowPreview; // mostra na tela (sem dados)
end;
Left/Top são relativos à área de conteúdo da banda — a margem já é aplicada pelo motor. Largura útil do A4 retrato = 190 mm.
Um helper usado no resto do manual:
function NovoTexto(Band: TrhBand; const Texto: string; TopMM, HeightMM: Integer;
Align: TrhHAlign = rhhaLeft): TrhTextObject;
begin
Result := Band.Objects.AddNew<TrhTextObject>;
Result.Text := Texto;
Result.Left := 0;
Result.Top := MMToUnits(TopMM);
Result.Width := MMToUnits(190);
Result.Height := MMToUnits(HeightMM);
Result.HAlign := Align;
end;
4. Bandas
Tipo (TrhBandType) | Aparece |
|---|---|
rhbtReportTitle | uma vez, no início do relatório |
rhbtPageHeader | topo de cada página |
rhbtPageFooter | rodapé de cada página |
rhbtGroupHeader | ao iniciar um grupo (GroupExpression) |
rhbtMasterData | uma vez por registro do dataset mestre |
rhbtDetailData | uma vez por registro do dataset detalhe (master-detail) |
rhbtGroupFooter | ao fechar um grupo (subtotais) |
rhbtSummary | uma vez, no fim (totais gerais) |
rhbtChild | banda auxiliar ancorada a outra |
Exemplo 2 — adicionar bandas e definir altura:
Page := rhReport1.EnsurePage;
BTitle := Page.Bands.AddBand(rhbtReportTitle); BTitle.Height := MMToUnits(18);
BData := Page.Bands.AddBand(rhbtMasterData); BData.Height := MMToUnits(6);
BFooter := Page.Bands.AddBand(rhbtPageFooter); BFooter.Height := MMToUnits(6);
Propriedades úteis: DataSetName (dataset que dirige a iteração), GroupExpression (quebra de grupo), Height.
5. Objetos
Todo objeto tem Left/Top/Width/Height (via MMToUnits), Visible e Frame. Adicione com Band.Objects.AddNew<T>.
5.1 Texto — TrhTextObject
T := Band.Objects.AddNew<TrhTextObject>;
T.Text := 'Cliente: [nome]'; // texto fixo + ilhas [expr]
T.Font.Size := 12;
T.Font.Style := [fsBold];
T.HAlign := rhhaRight; // rhhaLeft | rhhaCenter | rhhaRight
T.WordWrap := True;
T.Color := clWhite; // cor de fundo
T.Transparent := True;
Também aceita bind direto a um campo via DataField (seção 8).
5.2 Imagem — TrhImageObject
Exemplo 3 — imagem fixa (logo):
Img := Band.Objects.AddNew<TrhImageObject>;
Img.Left := 0; Img.Top := 0;
Img.Width := MMToUnits(40); Img.Height := MMToUnits(20);
Img.Picture.LoadFromFile('C:\logo.png');
Img.KeepAspect := True;
Img.Center := True;
Exemplo 4 — imagem de um campo (blob):
Img := Band.Objects.AddNew<TrhImageObject>;
Img.DataField := 'foto'; // le o campo blob 'foto'
Img.Stretch := True;
5.3 Linha — TrhLineObject
Lin := Band.Objects.AddNew<TrhLineObject>;
Lin.Left := 0; Lin.Top := MMToUnits(8);
Lin.Width := MMToUnits(190); Lin.Height := 0; // Height=0 => horizontal
Lin.PenColor := clSilver;
Lin.PenWidth := MMToUnits(1);
5.4 Forma — TrhShapeObject
Shp := Band.Objects.AddNew<TrhShapeObject>;
Shp.Kind := rhskRoundRect; // rhskRectangle | rhskRoundRect | rhskEllipse
Shp.Left := 0; Shp.Top := 0;
Shp.Width := MMToUnits(190); Shp.Height := MMToUnits(8);
Shp.PenColor := clGray;
Shp.BrushColor := $00F5F5F5; // BGR
Shp.Transparent := False; // False => preenche
6. Fonte, alinhamento, cores e moldura
Exemplo 5 — cabeçalho com fundo e moldura inferior:
T := NovoTexto(BGH, 'Categoria: [categoria]', 1, 6);
T.Font.Name := 'Segoe UI';
T.Font.Size := 11;
T.Font.Style := [fsBold];
T.Font.Color := clNavy;
T.Color := $00F0F0F0; // fundo (BGR, nao RGB!)
T.Transparent := False;
T.Frame.Sides := [rhfsBottom]; // moldura so embaixo
T.Frame.Color := clSilver;
T.Frame.Width := MMToUnits(1);
Cores em Delphi são BGR (
$00BBGGRR), não RGB. UseclNavy,clSilverouRGB(r,g,b).
Horizontal: rhhaLeft, rhhaCenter, rhhaRight. Vertical: rhvaTop, rhvaCenter, rhvaBottom.
7. Expressões [ilha]
Dentro de Text, tudo entre [ e ] é expressão avaliada; o resto é literal. Pode haver várias ilhas num objeto.
Total: R$ [FORMATFLOAT('#,##0.00', [valor])] -> Total: R$ 1.234,50
7.1 Campos
[nome_do_campo] lê o campo do registro atual (case-insensitive).
7.2 Operadores
Aritméticos + - * /, comparação = <> < <= > >=, lógicos and or not, concatenação com +. Parênteses controlam precedência.
7.3 Funções
| Categoria | Funções |
|---|---|
| Texto | UPPER, LOWER, TRIM, LEN, COPY(s,ini,qtd), POS(sub,s) |
| Lógica | IIF(cond,a,b), COALESCE(a,b,...) |
| Número | ROUND, TRUNC, INT, ABS |
| Formatação | FORMATFLOAT(masc,x), FORMATDATETIME(masc,dt), DATETOSTR, STR |
| Data/hora | NOW, DATE/TODAY, TIME |
| Agregados | SUM, AVG, COUNT, MIN, MAX, FIRST, LAST |
| Constantes | TRUE, FALSE, NULL, PI |
7.4 Pseudo-variáveis
[PAGE] (página atual), [TOTALPAGES] (total — o motor faz 2 passadas).
Exemplo 6:
NovoTexto(B, 'Emitido em [FORMATDATETIME(''dd"/"mm"/"yyyy hh":"nn'', NOW)]', 0, 5);
NovoTexto(B, 'Pagina [PAGE] de [TOTALPAGES]', 0, 5, rhhaRight);
NovoTexto(B, 'Situacao: [IIF([saldo] < 0, ''DEVEDOR'', ''OK'')]', 0, 5);
NovoTexto(B, 'Nome: [UPPER(TRIM([nome]))]', 0, 5);
Dentro de string Pascal, aspas simples dobram:
''#,##0.00''.
8. Ligação com dados (data binding)
Dois modos (híbrido) — use o que preferir, inclusive misturados:
8.1 Simples: DataField (estilo DB-aware)
T := Band.Objects.AddNew<TrhTextObject>;
T.DataField := 'cliente'; // exibe o valor do campo 'cliente'
DataField tem precedência sobre Text e internamente vira [cliente] (mesmo motor).
8.2 Avançado: ilhas [expr]
T.Text := '[cliente] ([uf]) - R$ [FORMATFLOAT(''#,##0.00'', [total])]';
8.3 Ligando o dataset em runtime
BData.DataSetName := 'Pedidos';
// ...
rhReport1.SetDataSet('Pedidos', MinhaQuery); // 'Pedidos' == BData.DataSetName
Exemplo 7 — relatório de dados (memtable):
Mem := TFDMemTable.Create(Self);
Mem.FieldDefs.Add('Cliente', ftString, 40);
Mem.FieldDefs.Add('Valor', ftCurrency);
Mem.CreateDataSet;
Mem.AppendRecord(['ACME Ltda', 1234.50]);
Mem.First;
rhReport1.Clear;
Page := rhReport1.EnsurePage;
BData := Page.Bands.AddBand(rhbtMasterData);
BData.DataSetName := 'Vendas';
NovoTexto(BData, '[Cliente] - R$ [FORMATFLOAT(''#,##0.00'', [Valor])]', 0, 6);
rhReport1.SetDataSet('Vendas', Mem);
rhReport1.ShowDataPreview; // preview COM dados
ShowPreview×ShowDataPreview: o primeiro mostra o layout estático; o segundo roda o pipeline (itera, agrupa, agrega). Para banco, useShowDataPreview.
9. Agrupamento e agregados
Para agrupar, use um par group header / group footer com a mesma GroupExpression. Os registros precisam vir ordenados pela expressão (contíguos). Agregados: SUM/AVG/COUNT/MIN/MAX/FIRST/LAST; o escopo é a banda (group footer → total do grupo; summary → total geral).
Exemplo 8 — vendas por categoria com subtotal e total geral:
BGH := Page.Bands.AddBand(rhbtGroupHeader);
BGH.GroupExpression := '[categoria]';
NovoTexto(BGH, 'Categoria: [categoria]', 1, 6).Font.Style := [fsBold];
BData := Page.Bands.AddBand(rhbtMasterData);
BData.DataSetName := 'Pedidos';
NovoTexto(BData, ' [produto] x[quantidade] = R$ [FORMATFLOAT(''#,##0.00'', [total])]', 0, 6);
BGF := Page.Bands.AddBand(rhbtGroupFooter);
BGF.GroupExpression := '[categoria]';
NovoTexto(BGF, 'Subtotal [categoria]: R$ [FORMATFLOAT(''#,##0.00'', SUM([total]))]', 1, 6, rhhaRight);
BSum := Page.Bands.AddBand(rhbtSummary);
NovoTexto(BSum, 'TOTAL: R$ [FORMATFLOAT(''#,##0.00'', SUM([total]))] | Itens: [COUNT([total])]', 2, 6, rhhaRight);
9.1 Grupos aninhados (multi-nível)
Vários níveis de grupo. A ordem dos cabeçalhos (de cima para baixo) define o aninhamento: o primeiro é o mais externo. Cada nível pode ter rodapé (casado pela GroupExpression). Os subtotais somam o escopo do nível — o total da categoria considera só as linhas daquele cliente e categoria.
img/relatorio-aninhado.png — preview do relatório Cliente › Categoria › Produtos, com subtotais por categoria e total do cliente ]Exemplo 8.1 — hierarquia Cliente › Categoria › Produtos › totais:
// nivel externo: Cliente
BCliH := Page.Bands.AddBand(rhbtGroupHeader);
BCliH.GroupExpression := '[cliente]';
NovoTexto(BCliH, 'Cliente: [cliente] ([uf])', 0, 6).Font.Style := [fsBold];
// nivel interno: Categoria
BCatH := Page.Bands.AddBand(rhbtGroupHeader);
BCatH.GroupExpression := '[categoria]';
NovoTexto(BCatH, ' Categoria: [categoria]', 0, 5).Font.Style := [fsItalic];
// detalhe: um produto por linha
BData := Page.Bands.AddBand(rhbtMasterData);
BData.DataSetName := 'Pedidos';
NovoTexto(BData, ' [produto] x[quantidade] R$ [FORMATFLOAT(''#,##0.00'', [total])]', 0, 5);
// rodape interno: subtotal da categoria (dentro do cliente)
BCatF := Page.Bands.AddBand(rhbtGroupFooter);
BCatF.GroupExpression := '[categoria]';
NovoTexto(BCatF, ' Subtotal [categoria]: R$ [FORMATFLOAT(''#,##0.00'', SUM([total]))]', 0, 5, rhhaRight);
// rodape externo: total do cliente
BCliF := Page.Bands.AddBand(rhbtGroupFooter);
BCliF.GroupExpression := '[cliente]';
NovoTexto(BCliF, 'Total do cliente [cliente]: R$ [FORMATFLOAT(''#,##0.00'', SUM([total]))]', 0, 6, rhhaRight).Font.Style := [fsBold];
Ordem obrigatória: o SQL vem na ordem dos grupos →
ORDER BY cliente, categoria, produto. Sem isso os grupos se fragmentam.
10. Master-detail
Banda rhbtMasterData (mestre) + rhbtDetailData (detalhe) com datasets ligados; o detalhe é filtrado pelo registro corrente do mestre.
BMaster := Page.Bands.AddBand(rhbtMasterData);
BMaster.DataSetName := 'Pedidos';
NovoTexto(BMaster, 'Pedido #[id] - [cliente]', 0, 6);
BDetail := Page.Bands.AddBand(rhbtDetailData);
BDetail.DataSetName := 'Itens';
NovoTexto(BDetail, ' [produto] x[qtd] R$ [FORMATFLOAT(''#,##0.00'', [subtotal])]', 0, 5);
rhReport1.SetDataSet('Pedidos', qryPedidos);
rhReport1.SetDataSet('Itens', qryItens); // qryItens com MasterSource=qryPedidos
11. Conectando a um banco (FireDAC/PostgreSQL)
DB-agnóstico: entregue qualquer TDataSet aberto via SetDataSet.
Exemplo 9 — query com joins:
Conn := TFDConnection.Create(Self);
Conn.LoginPrompt := False;
Conn.Params.Add('DriverID=PG');
Conn.Params.Add('Server=127.0.0.1');
Conn.Params.Add('Port=5433'); // confira a porta da sua instancia
Conn.Params.Add('Database=reportshowie_demo');
Conn.Params.Add('User_Name=' + User); // leia de .env, nunca hardcode
Conn.Params.Add('Password=' + Pass);
Conn.Params.Add('CharacterSet=UTF8');
Conn.Open;
Q := TFDQuery.Create(Conn);
Q.Connection := Conn;
Q.SQL.Text :=
'SELECT c.nome AS cliente, pr.categoria, pr.nome AS produto, p.total ' +
'FROM pedidos p ' +
'JOIN clientes c ON c.id = p.cliente_id ' +
'JOIN produtos pr ON pr.id = p.produto_id ' +
'ORDER BY c.nome, pr.categoria'; // ordem dos grupos!
Q.Open;
rhReport1.SetDataSet('Pedidos', Q);
rhReport1.ShowDataPreview;
Requisitos FireDAC: adicione
FireDAC.VCLUI.Waitaouses(registra oTFDGUIxWaitCursor) e garanta que alibpq.dllbate com a plataforma do app (Win64 → libpq x64). Ver Solução de problemas.
12. Pré-visualização
Exibir na tela é opcional e tem duas formas independentes.
12.1 Janela de preview (externa/modal)
rhReport1.ShowDataPreview; // com dados
rhReport1.ShowPreview; // layout estatico
12.2 Preview embutida no form — TrhPreviewControl
img/preview-embutida.png — o controle de preview embutido no form, com a barra de navegação de páginas e zoom ]TrhPreviewControl) — navegação de páginas e zoom inline.Exemplo 10 — preview sempre visível num painel:
Prev := TrhPreviewControl.Create(Self);
Prev.Parent := PanelDireita;
Prev.Align := alClient;
Doc := TrhDataPipeline.BuildDocument(rhReport1); // display list COM dados
Prev.LoadDocument(Doc, True); // o controle assume a posse do Doc
Layout estático: Prev.ShowReport(rhReport1);. LoadDocument sempre substitui o documento exibido.
13. Exportação
Todos consomem a mesma display list. Monte uma vez e exporte para vários formatos.
Exemplo 11 — 4 formatos:
Doc := TrhDataPipeline.BuildDocument(rhReport1);
try
TrhHtmlExporter.ExportToFile(Doc, 'saida\rel.html', rhReport1.Title);
TrhPdfExporter.ExportToFile(Doc, 'saida\rel.pdf');
TrhXlsxExporter.ExportToFile(Doc, 'saida\rel.xlsx', 'Planilha1');
TrhDocxExporter.ExportToFile(Doc, 'saida\rel.docx');
finally
Doc.Free;
end;
| Formato | Classe | Observação |
|---|---|---|
| HTML | TrhHtmlExporter | divs absolutos em mm; imagens em data-URI |
TrhPdfExporter | PDF 1.4 puro-Pascal; Helvetica; JPEG via DCTDecode | |
| XLSX | TrhXlsxExporter | grade reconstruída por posição das células |
| DOCX | TrhDocxExporter | parágrafos de fluxo |
13.1 Envio por e-mail (SMTP)
TrhMailer (unit rh.Email) renderiza o relatório no formato pedido, grava num arquivo temporário e o anexa a uma mensagem enviada por SMTP (Indy). Ligue os datasets antes (o envio reusa o pipeline).
uses rh.Email;
var Mailer: TrhMailer; Cfg: TrhSMTPSettings;
begin
rhReport1.SetDataSet('Pedidos', FDQuery1);
Cfg := TrhSMTPSettings.Create('smtp.exemplo.com', 587,
'usuario', 'senha', 'remetente@exemplo.com', rssStartTLS, 'Nome Remetente');
Mailer := TrhMailer.Create(nil);
try
Mailer.SendReport(rhReport1, rrfPDF, ['destino@exemplo.com'],
'Relatorio de Pedidos', 'Segue em anexo.', Cfg);
finally
Mailer.Free;
end;
end;
- Formato do anexo:
rrfPDF | rrfHTML | rrfXLSX | rrfDOCX. O nome sai doReport.Title(ou passeAttachmentName). TrhSMTPSettings: host, porta, usuário/senha (vazios = sem autenticação), remetente eSecurity=rssNone(25) /rssStartTLS(587) /rssImplicitTLS(465).
TLS desacoplado (zero dependências): o rh.Email não referencia biblioteca SSL alguma. Para transporte seguro, atribua o IOHandler que preferir (OpenSSL ou SChannel) pelo evento OnConfigureSMTP:
uses IdSMTP, IdSSLOpenSSL, IdExplicitTLSClientServerBase;
procedure TForm1.ConfigTLS(Sender: TObject; SMTP: TIdSMTP);
var SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
if SMTP.UseTLS = utNoTLSSupport then Exit; // sem TLS -> dispensa IOHandler
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(SMTP);
SSL.Host := SMTP.Host; SSL.Port := SMTP.Port;
SSL.Destination := SMTP.Host + ':' + IntToStr(SMTP.Port);
SSL.SSLOptions.SSLVersions := [sslvTLSv1_2];
SSL.SSLOptions.Mode := sslmClient;
SMTP.IOHandler := SSL;
end;
// ... Mailer.OnConfigureSMTP := ConfigTLS;
Se pedir TLS sem atribuir IOHandler, SendReport lança ErhEmail com instrução. OpenSSL exige as DLLs libssl/libcrypto no PATH; no Gmail use senha de app (2FA).
Dica de teste: suba um SMTP local de captura (Papercut-SMTP, smtp4dev, MailHog ou um sink em Python com aiosmtpd) em 127.0.0.1:25 e use rssNone — valida render + anexo + envio sem TLS/DLLs.
14. Persistência
O template (bandas/objetos, sem dados) é salvo em JSON (.rhr).
rhReport1.SaveToFile('modelos\pedidos.rhr');
rhReport1.LoadFromFile('modelos\pedidos.rhr');
S := rhReport1.ToJSONString(True);
rhReport1.LoadFromJSONString(S);
No design-time o mesmo JSON viaja no .dfm (propriedade binária ReportData) → round-trip pelo form da IDE.
15. Designer visual
Com o pacote DT instalado, dê duplo-clique no TrhReport para abrir o designer.
img/designer.png — o designer aberto: ribbon no topo, painel de dados à esquerda, superfície central com bandas, inspetor à direita ]- Arquivo — Abrir/Salvar template
.rhr. - Zoom —
-/+. - Inserir — Texto, Imagem, Linha, Forma, Excluir.
- Banda — escolher tipo +
+ Banda/Excluir. - Alinhar — alinhar/centralizar/distribuir a seleção múltipla.
- Ver —
PrevieweAjuda(abre esta documentação).
Recursos: seleção múltipla (Shift+clique ou retângulo), snap ao grid, guias de alinhamento, Ctrl+Z, duplo-clique no texto para editar / na imagem para carregar arquivo.
img/painel-dados.png — o painel "Dados" à esquerda, com a árvore dataset → campos ]dataset → campos. Duplo-clique num campo insere [campo] na banda selecionada.Drag-to-bind (arrastar campo): arraste um campo da árvore para a superfície — sobre um texto seta o DataField dele; em área vazia cria um texto já vinculado no ponto do drop. Objetos vinculados exibem um triângulo azul no canto superior esquerdo e mostram [campo] no design-time.
Painel Estrutura (à direita, acima de Propriedades): árvore Página → Bandas → Objetos que espelha o relatório. A seleção é sincronizada nos dois sentidos — clicar num nó seleciona a banda/objeto na tela (e atualiza o inspetor); selecionar na superfície realça o nó correspondente. Útil para navegar em relatórios com muitas bandas/objetos sobrepostos; o splitter ajusta a altura entre Estrutura e Propriedades.
Ver um relatório de código no designer: salve com SaveToFile('...\pedidos.rhr') e no designer clique Arquivo → Abrir.
16. Impressão
Pela janela de preview (botão Imprimir) ou em código:
uses rh.Render.VCLCanvas;
// ...
Doc := TrhDataPipeline.BuildDocument(rhReport1);
try
TrhVCLRenderer.PrintDocument(Doc, rhReport1.Title);
finally
Doc.Free;
end;
17. Receitas rápidas
| # | Objetivo | Trecho |
|---|---|---|
| R1 | Data/hora de emissão | Emitido em [FORMATDATETIME('dd"/"mm"/"yyyy hh":"nn', NOW)] |
| R2 | Numeração de página | Pagina [PAGE] de [TOTALPAGES] |
| R3 | Moeda BR | R$ [FORMATFLOAT('#,##0.00', [valor])] |
| R4 | Percentual | [FORMATFLOAT('0.0"%"', [taxa] * 100)] |
| R5 | Texto condicional | [IIF([estoque] < [minimo], 'REPOR', 'OK')] |
| R6 | Default p/ nulos | [COALESCE([email], 'sem e-mail')] |
| R7 | Maiúsculas/trim | [UPPER(TRIM([nome]))] |
| R8 | Subtotal de grupo | Subtotal: R$ [FORMATFLOAT('#,##0.00', SUM([total]))] |
| R9 | Contagem e média | Itens: [COUNT([total])] Media: [FORMATFLOAT('#,##0.00', AVG([total]))] |
| R10 | Maior/menor | [FORMATFLOAT('#,##0.00', MAX([total]))] / [FORMATFLOAT('#,##0.00', MIN([total]))] |
| R11 | Bind simples | T.DataField := 'cliente'; |
| R12 | Abreviação | [UPPER(COPY([nome], 1, 3))] |
| R18 | Título por período | Movimento de [FORMATDATETIME('mmmm"/"yyyy', [data_ini])] |
R19 — Relatório ad-hoc sem perturbar um TrhReport já montado: não use rhReport1.Clear (apaga o layout dele); monte num report próprio:
R := TrhReport.Create(nil);
try
R.Title := 'Ad-hoc';
// ...monta bandas em R...
R.SetDataSet('Dados', MinhaQuery);
Doc := TrhDataPipeline.BuildDocument(R); // Doc independente de R
finally
R.Free; // rhReport1 permanece intacto
end;
Preview.LoadDocument(Doc, True); // substitui a visao atual
18. Solução de problemas
| Sintoma | Causa | Solução |
|---|---|---|
PG-314 ... unsupported architecture [x64]. Required [x86] | app Win32 carregando libpq.dll x64 | compile o app como Win64 (ou use libpq de 32 bits) |
Connection refused (10061) | servidor parado ou porta errada | confirme o serviço e a porta (PG às vezes usa 5433) |
Object factory ... TFDGUIxWaitCursor is missing | falta o provider de UI do FireDAC | adicione FireDAC.VCLUI.Wait ao uses |
DS-206. Cannot open dataset (TFDMemTable) | Active=True sem estrutura/dados | use campos persistentes + Active=False, ou CreateDataSet |
PG-310. Cannot execute command returning result sets | ExecSQL num comando que retorna linhas | use Open/ExecSQLScalar |
Acentos saem como é/â€" | literal não-ASCII em .pas lido como ANSI | salve em UTF-8 com BOM ou use escapes #$XXXX |
| Grupo repetido/quebrado | dataset não ordenado pela GroupExpression | adicione ORDER BY na ordem dos grupos |
| Componente não aparece na paleta | pacote DT não instalado / form fechado | instale ReportsHowieDT e abra um form VCL |
ReportsHowie — github.com/howardroatti/ReportsHowie — LGPL-3.0.
Contribuições são bem-vindas; veja CONTRIBUTING.md.