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

HelperConverte para
MMToUnits(mm)unidade interna (0,1 mm) — o que você usa ao montar
MMToPx, MMToPt, MMToTwips, MMToEMUpixels, pontos, twips, EMU (uso interno dos exports)

Ex.: MMToUnits(190) = 1900 (= 190 mm da largura útil do A4).

2. Instalação

  1. Instale os pacotes ReportsHowieRT (runtime) e ReportsHowieDT (design-time). Só o TrhReport cai na paleta.
  2. 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
rhbtReportTitleuma vez, no início do relatório
rhbtPageHeadertopo de cada página
rhbtPageFooterrodapé de cada página
rhbtGroupHeaderao iniciar um grupo (GroupExpression)
rhbtMasterDatauma vez por registro do dataset mestre
rhbtDetailDatauma vez por registro do dataset detalhe (master-detail)
rhbtGroupFooterao fechar um grupo (subtotais)
rhbtSummaryuma vez, no fim (totais gerais)
rhbtChildbanda 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. Use clNavy, clSilver ou RGB(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

CategoriaFunções
TextoUPPER, LOWER, TRIM, LEN, COPY(s,ini,qtd), POS(sub,s)
LógicaIIF(cond,a,b), COALESCE(a,b,...)
NúmeroROUND, TRUNC, INT, ABS
FormataçãoFORMATFLOAT(masc,x), FORMATDATETIME(masc,dt), DATETOSTR, STR
Data/horaNOW, DATE/TODAY, TIME
AgregadosSUM, AVG, COUNT, MIN, MAX, FIRST, LAST
ConstantesTRUE, 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, use ShowDataPreview.

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.

Relatório hierárquico Cliente › Categoria › Produtos com subtotais
[ Print sugerido: img/relatorio-aninhado.png — preview do relatório Cliente › Categoria › Produtos, com subtotais por categoria e total do cliente ]
Saída da hierarquia Cliente › Categoria › Produtos › Subtotal categoria › 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.Wait ao uses (registra o TFDGUIxWaitCursor) e garanta que a libpq.dll bate 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

TrhPreviewControl embutido num form
[ Print sugerido: img/preview-embutida.png — o controle de preview embutido no form, com a barra de navegação de páginas e zoom ]
Preview embutida (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;
FormatoClasseObservação
HTMLTrhHtmlExporterdivs absolutos em mm; imagens em data-URI
PDFTrhPdfExporterPDF 1.4 puro-Pascal; Helvetica; JPEG via DCTDecode
XLSXTrhXlsxExportergrade reconstruída por posição das células
DOCXTrhDocxExporterpará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;

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.

Designer visual do ReportsHowie
[ Print sugerido: img/designer.png — o designer aberto: ribbon no topo, painel de dados à esquerda, superfície central com bandas, inspetor à direita ]
Designer: ribbon (Arquivo/Zoom/Inserir/Banda/Alinhar/Ver), painel de dados, superfície e inspetor.

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.

Painel de dados do designer
[ Print sugerido: img/painel-dados.png — o painel "Dados" à esquerda, com a árvore dataset → campos ]
Painel de dados: árvore 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

#ObjetivoTrecho
R1Data/hora de emissãoEmitido em [FORMATDATETIME('dd"/"mm"/"yyyy hh":"nn', NOW)]
R2Numeração de páginaPagina [PAGE] de [TOTALPAGES]
R3Moeda BRR$ [FORMATFLOAT('#,##0.00', [valor])]
R4Percentual[FORMATFLOAT('0.0"%"', [taxa] * 100)]
R5Texto condicional[IIF([estoque] < [minimo], 'REPOR', 'OK')]
R6Default p/ nulos[COALESCE([email], 'sem e-mail')]
R7Maiúsculas/trim[UPPER(TRIM([nome]))]
R8Subtotal de grupoSubtotal: R$ [FORMATFLOAT('#,##0.00', SUM([total]))]
R9Contagem e médiaItens: [COUNT([total])] Media: [FORMATFLOAT('#,##0.00', AVG([total]))]
R10Maior/menor[FORMATFLOAT('#,##0.00', MAX([total]))] / [FORMATFLOAT('#,##0.00', MIN([total]))]
R11Bind simplesT.DataField := 'cliente';
R12Abreviação[UPPER(COPY([nome], 1, 3))]
R18Título por períodoMovimento 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

SintomaCausaSolução
PG-314 ... unsupported architecture [x64]. Required [x86]app Win32 carregando libpq.dll x64compile o app como Win64 (ou use libpq de 32 bits)
Connection refused (10061)servidor parado ou porta erradaconfirme o serviço e a porta (PG às vezes usa 5433)
Object factory ... TFDGUIxWaitCursor is missingfalta o provider de UI do FireDACadicione FireDAC.VCLUI.Wait ao uses
DS-206. Cannot open dataset (TFDMemTable)Active=True sem estrutura/dadosuse campos persistentes + Active=False, ou CreateDataSet
PG-310. Cannot execute command returning result setsExecSQL num comando que retorna linhasuse Open/ExecSQLScalar
Acentos saem como é/â€"literal não-ASCII em .pas lido como ANSIsalve em UTF-8 com BOM ou use escapes #$XXXX
Grupo repetido/quebradodataset não ordenado pela GroupExpressionadicione ORDER BY na ordem dos grupos
Componente não aparece na paletapacote DT não instalado / form fechadoinstale ReportsHowieDT e abra um form VCL

ReportsHowie — github.com/howardroatti/ReportsHowie — LGPL-3.0. Contribuições são bem-vindas; veja CONTRIBUTING.md.