Parcourez les cellules d'un modèle de rapport fraîchement ouvert et un titre fusionné se comporte comme une fuite. Vous lisez A1 et obtenez « Quarterly Statement » ; vous lisez B1 jusqu'à F1, qui se trouvent visiblement sous la même bannière, et vous n'obtenez rien. Écrivez une valeur dans C1 pour corriger l'en-tête et elle n'apparaît jamais à l'écran. La grille n'a pas perdu vos données. Elle fait exactement ce que signifie une fusion : en XLS comme en XLSX, un rectangle fusionné affiche le contenu d'une seule cellule, l'ancre en haut à gauche, et traite le reste comme de l'espace couvert qui contient des valeurs sans jamais les montrer. Les utilisateurs d'Excel intègrent cela par tâtonnement. Un générateur de rapports doit l'encoder comme une règle, car dans du code généré le symptôme est une zone vide sans exception à laquelle le rattacher. HotXLS, bibliothèque native en Object Pascal qui lit et écrit les deux formats Excel depuis Delphi et C++Builder, expose la table des fusions assez explicitement pour que vous programmiez selon la règle au lieu de la redécouvrir dans un ticket de support
Une valeur, une ancre
Une fusion est une instruction d'affichage posée sur une grille dont la forme ne change pas. Chaque cellule couverte existe encore dans le fichier comme son propre emplacement ; l'enregistrement de fusion se contente d'indiquer au lecteur de peindre le contenu de l'ancre sur tout le rectangle. Cette distinction gouverne trois comportements à intégrer avant d'écrire la moindre ligne de mise en page. Lire une cellule couverte renvoie sa propre valeur stockée, qui pour une bannière que vous avez construite est généralement vide, si bien que tout code qui inspecte un titre fusionné doit résoudre l'ancre et la lire. Écrire dans une cellule couverte réussit au niveau du fichier et n'apparaît nulle part, ce qui est le piège de l'en-tête invisible décrit en ouverture. Et défusionner une région expose ce qui était posé dessous depuis le début, si bien qu'une valeur égarée écrite dans l'espace couvert devient un défaut visible le jour où quelqu'un dissout la fusion
Côté XLSX, cette table est un objet de première classe. Sheet.MergedCells porte Add('A1:C1'), FindAt(Row, Col), DeleteAt et Items, et l'appel auquel vous recourez le plus est FindAt : passez-lui n'importe quelle coordonnée et il renvoie la région fusionnée qui couvre cette cellule, ou nil quand la cellule est isolée. Cette seule recherche est la fondation des deux moitiés d'une gestion correcte des fusions, la lecture sûre et la garde en écriture, et les deux reviennent plus loin
Deux façades, deux idiomes de fusion
HotXLS garde le moteur BIFF8 classique .xls et le moteur OOXML .xlsx comme des modèles objet distincts, et ils écrivent la fusion différemment parce qu'ils descendent de conventions différentes. La façade XLS suit l'idiome COM d'Excel : vous prenez une plage sur une propriété indexée à deux arguments et vous appelez Merge avec un OleVariant dont la valeur décide de la géométrie obtenue
var
Book: IXLSWorkbook; // compté par interface : pas de Free manuel
Sh: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
Sh := Book.Sheets[1]; // la collection de feuilles XLS est en base 1
Sh.Range['A1', 'F1'].Merge(False); // False = un seul bloc fusionné
Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
Sh.Range['A3', 'F4'].Merge(True); // True = fusion horizontale : une fusion par ligne
Book.SaveAs('layout.xls');
end;
L'argument de Merge est la partie que l'on se trompe le plus souvent. Sur une plage de deux lignes, Merge(True) produit deux fusions indépendantes d'une ligne chacune, ce qui correspond à la « Fusion horizontale » d'Excel et à exactement ce que vous voulez pour un bandeau d'en-tête empilé dont les lignes doivent rester séparables. Merge(False) fond tout le rectangle en un bloc unique. La plage rapporte aussi MergeCells comme indicateur d'état, renvoie la région englobante par MergeArea et se dissout avec Unmerge. La façade XLSX expose les mêmes opérations sous d'autres noms : Sheet.MergeCells(Row1, Col1, Row2, Col2) prend des bornes entières, TXLSXRange.Merge accepte le variant Across équivalent, et la collection MergedCells contient le résultat
Un modèle qui grandit avec ses données
Un vrai modèle de rapport n'est pas une grille figée. L'en-tête et les totaux sont fixes, mais la section de détail entre les deux s'étire à la mesure de ce que renvoie la requête. Le motif qui tient garde dans le modèle une ligne de détail entièrement mise en forme, la clone une fois par enregistrement, puis ouvre un espace devant le bloc de totaux pour que tout ce qui est ancré en dessous glisse vers le bas sans perdre sa mise en forme
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611'; // la valeur va à l'ancre, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1; // index du pool en base 0, côté cellule en base 1
// la ligne 5 est la ligne de détail modèle mise en forme
for I := 0 to ItemCount - 1 do
Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1); // styles et formules voyagent avec elle
// ouvrir un espace au-dessus du bloc de totaux ; le contenu en dessous descend
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);
Deux lignes méritent un second regard. L'affectation de police porte un décalage d'une unité qui mord en silence : Fonts.Add rend une position de pool en base 0, alors qu'une cellule range une référence de police en base 1 où 0 désigne la police par défaut ; oublier le + 1 ne lève rien, cela met simplement votre titre dans la mauvaise typographie. L'autre ligne est CopyRange, qui déplace la mise en forme et les formules avec les valeurs. C'est toute la raison de cloner une ligne de modèle construite à la main plutôt que de reconstruire son apparence par du code. Un concepteur possède l'apparence une fois pour toutes, dans le modèle ; le générateur ne fait jamais que verser des données dans ses copies
Cette séparation va plus loin encore quand la mise en page réutilisable vit dans son propre classeur, disons une feuille de bandeaux d'en-tête et de pied partagés entre rapports. CopyRangeTo effectue le même clonage par-dessus les frontières de feuilles, en prenant une feuille cible et des coordonnées de destination, si bien qu'un générateur peut garder une feuille modèle intacte et estamper ses régions dans autant de feuilles de sortie que le travail en demande. L'alternative, muter le modèle sur place et tenter de le restaurer ensuite, est le genre de chose qui marche jusqu'au jour où une exécution s'interrompt à mi-parcours
Ce que InsertRows déplace, et ce qu'il ne déplace pas
Le motif du modèle qui grandit ne fonctionne que parce que InsertRows côté XLSX est une modification structurelle et non un simple brassage de cellules. Quand il ouvre un espace, il relocalise les régions fusionnées, les hauteurs de lignes, les liens hypertexte, les commentaires, les volets figés, les plages de filtre automatique, les mises en forme conditionnelles, les validations de données, les tableaux, les noms définis, les ancrages d'images et les ancrages de graphiques situés sous le point d'insertion, pas seulement les valeurs des cellules. C'est ce qui permet au bloc de totaux d'arriver à sa nouvelle ligne avec ses fusions et ses formats de nombre intacts au lieu d'arriver dépouillé
Ses deux limites documentées sont celles autour desquelles concevoir. L'ajustement des formules est cantonné à la feuille en cours de modification : les références internes à cette feuille sont réécrites, et une formule d'une autre feuille qui pointe dans la zone décalée est réécrite aussi, mais l'ajustement ne suit que les références visant la feuille modifiée, si bien que tout schéma de références entre classeurs mérite son propre audit plutôt qu'une confiance aveugle. La seconde limite est plus tranchée, et elle est côté XLS. Les tableaux croisés dynamiques survivent aux cycles ouverture-sauvegarde comme des enregistrements bruts préservés, non comme des objets modélisés que HotXLS peut déplacer, si bien que l'insertion de lignes ne relocalise pas l'empreinte d'un tableau croisé. Tout modèle que vous bâtissez pour le format .xls devrait garer ses régions de tableau croisé loin de tout bandeau qui grandit
Refuser d'écrire des données dans l'espace de mise en page
La défaillance des cellules fusionnées qui atteint réellement la production n'est pas la défaillance cosmétique. Elle est structurelle : une ligne de détail dérive dans un bandeau de mise en page fusionné, ses valeurs atterrissent dans des cellules couvertes et deviennent invisibles, et les totaux de colonnes cessent discrètement de correspondre à ce que voit quiconque lit la feuille. Comme FindAt répond à la question de la région couvrante pour n'importe quelle coordonnée, le générateur peut refuser cette écriture au moment où elle se produirait plutôt que de livrer un rapport qui sous-compte en silence
// refuser d'écrire des données de détail dans une région de mise en page fusionnée
if Sheet.MergedCells.FindAt(Row, 1) <> nil then
raise Exception.CreateFmt('row %d overlaps a merged layout region', [Row]);
Sheet.Cells[Row, 1].Value := Detail.Description;
Le même contrôle de limites a sa place partout où un utilisateur triera ou filtrera plus tard la sortie. Une plage contenant des fusions ne peut pas être triée proprement, car le tri déplace les lignes indépendamment et une fusion qui s'étend sur plusieurs lignes n'a pas de ligne unique avec laquelle voyager ; Excel répond par une erreur ou par une mise en page brouillée. La discipline qui garde les rapports corrects est géographique. Cantonnez les fusions aux bandeaux de titre, aux séparateurs de sections et aux blocs de signature, et gardez plat le milieu tabulaire de la feuille. L'article sur la génération de rapports par modèle développe cette séparation entre mise en page et données en un flux complet piloté par des marqueurs, et l'article sur la mise en forme conditionnelle et le texte enrichi traite la mise en forme de ce bandeau de données plat
Comment les fusions se dégradent à la sortie
Une fusion est un concept de classeur, et chaque format d'export orienté texte l'honore à un degré différent. Connaître les trois comportements d'avance épargne un cycle de recette. L'export HTML reproduit fidèlement les fusions, en émettant colspan et rowspan sur un seul tableau, si bien qu'un rapport destiné au navigateur garde son allure en bandeaux. L'export RTF n'étend pas du tout les colonnes : le texte de l'ancre atterrit dans sa propre cellule et le reste de la largeur de la fusion ressort en cellules vides, ce qui laisse un titre large visuellement collé à gauche dans un traitement de texte. Le CSV n'a aucune notion de fusion, si bien que la valeur de l'ancre occupe un champ et que chaque cellule couverte sort en champ vide. La leçon, pour un classeur qui alimente aussi des exports délimités, est de garder tout ce qui porte du sens hors de la géométrie fusionnée ; l'article sur l'export CSV, TSV et HTML passe chaque format en revue en détail
Une réassurance pour qui met cela en balance avec la taille du fichier : les fusions ne coûtent presque rien à l'échelle d'un rapport. La table des fusions est minuscule face aux données de cellules, et lire une cellule couverte passe toujours par FindAt plutôt que par un balayage. La pression sur les performances des grands classeurs vient d'ailleurs, surtout de la croissance du pool de styles et de la mémoire que retient le chemin de sauvegarde, ce que l'article sur les performances des grands classeurs aborde directement. Les deux API de fusion, les opérations d'édition structurelle et les démonstrations de modèles sont livrées avec le HotXLS Delphi Component