Ao percorrer as células de um modelo de relatório acabado de abrir, um título unido comporta-se de forma intrigante: se ler A1, obtém "Quarterly Statement"; contudo, se ler de B1 a F1 (que visualmente se situam sob a mesma barra), não obterá qualquer valor. Se escrever na célula C1 para tentar alterar o cabeçalho, esse valor nunca surgirá no ecrã. A grelha não perdeu os dados: está simplesmente a aplicar as regras de uma união de células. Tanto no XLS quanto no XLSX, um retângulo unido exibe o conteúdo de apenas uma célula — a âncora superior esquerda —, tratando o restante intervalo como espaço sobreposto que pode conter dados mas nunca os mostra. Os utilizadores do Excel aprendem isto por tentativa e erro. Um gerador de relatórios tem de codificar este comportamento como regra, pois em código gerado o sintoma manifesta-se como uma região em branco sem exceções associadas para depuração. O HotXLS, uma biblioteca nativa Object Pascal que lê e escreve ambos os formatos Excel a partir de Delphi e C++Builder, expõe a tabela de uniões explicitamente para que possa programar em conformidade, em vez de redescobrir o problema através de um pedido de suporte
Um valor, uma âncora
A união de células consiste numa instrução de visualização sobreposta a uma grelha que não sofre alteração física de estrutura. Cada célula coberta continua a existir no ficheiro de forma individual; o registo de união apenas instrui o leitor a desenhar o conteúdo da âncora ao longo de todo o retângulo. Esta particularidade dita três comportamentos que deve compreender antes de desenhar layouts via código: ler uma célula coberta devolve o seu valor guardado individualmente (que na barra de títulos costuma ser vazio), pelo que qualquer código que inspecione um título unido tem de obter e ler a âncora; escrever numa célula coberta conclui-se com sucesso ao nível do ficheiro mas não exibe qualquer resultado (a armadilha do cabeçalho invisível); e anular a união (unmerge) de uma região expõe o conteúdo que esteve sob ela todo o tempo, pelo que dados perdidos escritos no espaço coberto tornar-se-ão visíveis no dia em que alguém anular a união
No lado XLSX, essa tabela é um objeto de primeira classe. A propriedade Sheet.MergedCells disponibiliza os métodos Add('A1:C1'), FindAt(Row, Col), DeleteAt e a coleção Items, sendo a chamada a FindAt a mais utilizada: forneça-lhe qualquer coordenada e esta devolverá o intervalo unido que cobre a célula, ou nil se a célula estiver isolada. Esta consulta constitui a base para gerir uniões corretamente, tanto na leitura segura quanto na proteção de escrita, como veremos adiante
Duas interfaces, duas abordagens de união
A interface XLS adota a convenção COM do Excel: obtém um intervalo a partir de uma propriedade indexada de dois argumentos e invoca Merge com um tipo OleVariant cujo valor determina a geometria resultante
var
Book: IXLSWorkbook; // referências de interface: sem Free manual
Sh: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
Sh := Book.Sheets[1]; // a coleção de folhas XLS baseia-se em 1
Sh.Range['A1', 'F1'].Merge(False); // False = um bloco unido
Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
Sh.Range['A3', 'F4'].Merge(True); // True = união horizontal: uma união por linha
Book.SaveAs('layout.xls');
end;
O parâmetro passado a Merge é frequentemente mal interpretado. Num intervalo de duas linhas, Merge(True) gera duas uniões independentes de uma única linha, que corresponde à funcionalidade "Merge Across" (Unir na Horizontal) do Excel e é o comportamento pretendido para blocos de cabeçalho empilhados onde as linhas devem permanecer separadas. O método Merge(False) funde todo o retângulo num único bloco. O intervalo disponibiliza também o marcador de estado MergeCells, devolve a região abrangida através de MergeArea e desfaz a união com Unmerge. A faceta XLSX expõe as mesmas operações sob assinaturas distintas: Sheet.MergeCells(Row1, Col1, Row2, Col2) recebe limites inteiros; TXLSXRange.Merge aceita o parâmetro equivalente Across; e a coleção MergedCells armazena os resultados
Um modelo que cresce com os dados
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'FATURA #2026-0611'; // o valor é atribuído à âncora, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1; // índice do pool baseado em 0, na célula baseia-se em 1
// a linha 5 é a linha modelo de detalhe formatada
for I := 0 to ItemCount - 1 do
Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1); // os estilos e fórmulas acompanham o deslocamento
// abrir espaço antes do bloco de totais; o conteúdo abaixo desloca-se para baixo
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);
Duas linhas merecem particular atenção. A atribuição do tipo de letra contém um desfasamento de uma unidade que falha de forma silenciosa: o método Fonts.Add devolve uma posição baseada em 0, ao passo que as células armazenam uma referência baseada em 1, na qual o valor 0 representa o tipo de letra padrão; assim, omitir o + 1 não gerará exceções, mas aplicará o tipo de letra incorreto ao título. A outra instrução relevante é CopyRange, que duplica a formatação e as vias fórmulas em conjunto com os valores. Esta é a razão principal para clonar uma linha modelo construída manualmente em vez de tentar recriar o seu aspeto via código: o designer define a estética no modelo uma vez, restando ao gerador apenas preencher os dados nas cópias
Esta separação escala quando o layout reutilizável reside no seu próprio livro de cálculo (como uma folha contendo secções de cabeçalho e rodapé partilhadas entre vários relatórios). O método CopyRangeTo realiza a clonagem entre folhas distintas, recebendo a folha de destino e as coordenadas correspondentes; isto permite ao gerador manter uma folha modelo inalterada e replicar as suas áreas em tantas folhas de saída quantas as necessárias. A alternativa (alterar o modelo no próprio local e tentar repor a estrutura no final) é suscetível a falhas se o processamento for interrompido a meio
O que o InsertRows desloca e o que não desloca
Inserir linhas antes do bloco de totais é o que permite ao intervalo de uma fórmula SUM expandir-se à medida que a secção de detalhe cresce. No lado XLSX, o método InsertRows desloca uma vasta lista de estruturas em conjunto com as células: células unidas, alturas de linhas, hiperligações, comentários, painéis fixos (frozen panes), intervalos de autofiltro, formatação condicional, validação de dados, tabelas, nomes definidos e âncoras de gráficos e imagens que se situem abaixo do ponto de inserção. É isto que garante que o bloco de totais seja deslocado para a sua nova linha mantendo as suas uniões e formatos numéricos sem perdas
As duas limitações documentadas devem ser tidas em conta no desenho da aplicação. O ajuste de fórmulas está limitado à folha em edição: as referências internas são reescritas, e uma fórmula noutra folha que aponte para o intervalo deslocado também é atualizada, mas esta atualização afeta apenas referências direcionadas para a folha em edição; como tal, qualquer referência entre livros distintos requer validação adicional. A segunda limitação é mais rígida e afeta a faceta XLS: as tabelas dinâmicas (pivot tables) são preservadas como blocos de bytes não modelados pelo motor, pelo que a inserção de linhas não altera a sua posição no documento. Qualquer modelo estruturado para o formato .xls deve colocar as áreas de tabelas dinâmicas longe de qualquer intervalo que cresça
Impedir a escrita de dados em espaço de layout
O erro com células unidas que costuma manifestar-se em produção não é meramente estético, mas sim estrutural: uma linha de detalhe sobrepõe-se a uma área de layout unida, os seus valores são gravados em células cobertas (ficando invisíveis) e os totais das colunas deixam de coincidir com a informação visível. Dado que o método FindAt identifica a região de união para qualquer coordenada, o gerador consegue impedir a escrita no exato momento em que esta ocorreria, evitando disponibilizar um relatório com cálculos incorretos
// recusar a escrita de dados de detalhe numa região de layout unida
if Sheet.MergedCells.FindAt(Row, 1) <> nil then
raise Exception.CreateFmt('a linha %d sobrepõe-se a uma região de layout unida', [Row]);
Sheet.Cells[Row, 1].Value := Detail.Description;
Esta validação de limites deve ser integrada em qualquer área onde o utilizador possa vir a aplicar ordenação ou filtros. Um intervalo contendo células unidas não pode ser ordenado corretamente porque a ordenação move as linhas individualmente, e uma união que abranja várias linhas não tem uma linha única à qual associar o deslocamento (o Excel gerará erro ou desestruturará o layout). A boa prática recomenda delimitar a união a áreas geográficas específicas: restrinja as uniões a blocos de títulos, divisores de secções e áreas de assinaturas, mantendo o bloco tabelado central plano. O artigo sobre geração de relatórios com modelos desenvolve esta separação entre layout e dados num fluxo baseado em marcadores, e o artigo sobre formatação condicional e rich text aborda a formatação destas colunas planas
Como as uniões divergem na exportação
A união de células é um conceito específico de livros de cálculo, e cada formato de exportação de texto suporta-o em níveis distintos. Conhecer estes comportamentos antecipadamente evita falhas na validação de qualidade: a exportação para HTML reproduz as uniões fielmente, gerando atributos colspan e rowspan numa única tabela, mantendo o aspeto formatado no browser; a exportação para RTF não une as colunas no resultado, gravando o texto da âncora na sua célula e mantendo a largura restante da união em branco (o que encosta os títulos à esquerda no processador de texto); e o formato CSV não suporta o conceito de união, pelo que o valor da âncora ocupa um campo e as células cobertas são exportadas como campos vazios. A regra para livros que também alimentam exportações delimitadas consiste em evitar colocar dados relevantes em áreas unidas; o artigo sobre exportações para CSV, TSV e HTML detalha o comportamento de cada formato
Uma garantia relevante quanto ao tamanho dos ficheiros: as uniões têm custo negligenciável na escala de relatórios. A tabela de uniões é minúscula em comparação com os dados das células, e a leitura de uma célula coberta recorre ao método FindAt em vez de efetuar varrimentos. A otimização de grandes livros foca-se noutros aspetos (como a consolidação de estilos e a memória do fluxo de gravação), abordados no artigo sobre desempenho com grandes livros de cálculo. Ambas as APIs de união, as operações de edição estrutural e os exemplos de modelos são fornecidos com o HotXLS Component