Projděte buňky čerstvě otevřené šablony reportu a sloučený titulek se chová jako netěsnost. Přečtete A1 a dostanete „Quarterly Statement"; přečtete B1 až F1, které viditelně sedí pod stejným pruhem, a nedostanete nic. Zapíšete hodnotu do C1, abyste opravili záhlaví, a na obrazovce se nikdy neobjeví. Mřížka vaše data neztratila. Dělá přesně to, co sloučení znamená: v XLS i XLSX sloučený obdélník vykreslí obsah jedné buňky, kotvy vlevo nahoře, a se zbytkem zachází jako s pokrytým prostorem, který drží hodnoty, ale nikdy je nezobrazí. Uživatelé Excelu si na to zvyknou metodou pokus-omyl. Generátor reportů to musí zakódovat jako pravidlo, protože v generovaném kódu je symptomem prázdná oblast bez jediné výjimky, ke které by se dalo dopátrat. HotXLS, nativní knihovna Object Pascal, která čte a zapisuje oba formáty Excelu z Delphi a C++Builderu, zpřístupňuje tabulku sloučení dost explicitně na to, abyste mohli programovat proti pravidlu místo toho, abyste jej znovu objevovali v ticketu podpory
Jedna hodnota, jedna kotva
Sloučení je zobrazovací instrukce vrstvená nad mřížkou, která nemění tvar. Každá pokrytá buňka v souboru stále existuje jako svůj vlastní slot; záznam sloučení jen konzumentovi říká, aby obsah kotvy vymaloval přes celý obdélník. Tento rozdíl řídí tři chování, která se vyplatí zvnitřnit dřív, než napíšete jakýkoli kód pro rozvržení. Čtení pokryté buňky vrátí její vlastní uloženou hodnotu, která je u vámi postaveného pruhu obvykle prázdná, takže jakýkoli kód, který zkoumá sloučený titulek, musí kotvu vyřešit a přečíst. Zápis do pokryté buňky uspěje na úrovni souboru a neprojeví se nikde, což je past neviditelného záhlaví z úvodu. A zrušení sloučení odhalí cokoli, co pod ním celou dobu sedělo, takže bludná hodnota zapsaná do pokrytého prostoru se v den, kdy někdo sloučení rozpustí, promění ve viditelnou vadu
Na straně XLSX je tato tabulka plnohodnotný objekt. Sheet.MergedCells nese Add('A1:C1'), FindAt(Row, Col), DeleteAt a Items, a volání, po kterém sáhnete nejčastěji, je FindAt: předáte mu jakoukoli souřadnici a ono vrátí sloučenou oblast pokrývající danou buňku, nebo nil, když buňka stojí samostatně. Toto jediné vyhledání je základem obou polovin správného zacházení se sloučením, bezpečného čtení a hlídače zápisu, a obě se objeví později
Dvě fasády, dva idiomy sloučení
HotXLS udržuje klasický engine BIFF8 .xls a engine OOXML .xlsx jako oddělené objektové modely, a zapisují sloučení jinak, protože pocházejí z odlišných konvencí. Fasáda XLS se řídí idiomem COM Excelu: vezmete rozsah z indexované vlastnosti se dvěma argumenty a zavoláte Merge s OleVariant, jehož hodnota rozhoduje o geometrii, kterou nakonec dostanete
var
Book: IXLSWorkbook; // počítáno referencemi rozhraní: žádný ruční Free
Sh: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
Sh := Book.Sheets[1]; // kolekce listů XLS je 1-based
Sh.Range['A1', 'F1'].Merge(False); // False = jeden sloučený blok
Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
Sh.Range['A3', 'F4'].Merge(True); // True = merge across: jedno sloučení na řádek
Book.SaveAs('layout.xls');
end;
Argument pro Merge je ta část, kterou lidé pletou. Přes rozsah o dvou řádcích vytvoří Merge(True) dvě nezávislá jednořádková sloučení, což je Excelovo „Merge Across" a přesně to, co chcete pro naskládaný pruh záhlaví, jehož řádky mají zůstat oddělitelné. Merge(False) spojí celý obdélník do jediného bloku. Rozsah také hlásí MergeCells jako stavový příznak, vrací obsahující oblast přes MergeArea a sám se rozpouští pomocí Unmerge. Fasáda XLSX zpřístupňuje stejné operace pod jinými jmény: Sheet.MergeCells(Row1, Col1, Row2, Col2) přebírá celočíselné hranice, TXLSXRange.Merge přijímá ekvivalentní variantu Across a kolekce MergedCells drží výsledek
Šablona, která roste se svými daty
Skutečná šablona reportu není pevná mřížka. Záhlaví a součty jsou pevné, ale detailní sekce mezi nimi se natáhne na cokoli, co dotaz vrátí. Vzor, který obstojí, drží v šabloně jeden plně stylovaný detailní řádek, klonuje jej jednou na záznam a pak otevře mezeru před blokem součtů, aby vše zakotvené níže sjelo dolů, aniž by ztratilo formátování
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611'; // hodnota jde do kotvy, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1; // index poolu 0-based, strana buňky 1-based
// řádek 5 je stylovaný detailní řádek šablony
for I := 0 to ItemCount - 1 do
Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1); // styly a vzorce cestují s ním
// otevře mezeru nad blokem součtů; obsah pod ním se posune dolů
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);
Dva řádky se vyplatí prohlédnout podruhé. Přiřazení písma nese chybu o jedna, která kouše potichu: Fonts.Add vrací 0-based pozici v poolu, zatímco buňka ukládá 1-based odkaz na písmo, kde 0 znamená výchozí písmo, takže vynechání + 1 nic nevyvolá, jen ostyluje váš titulek nesprávným písmem. Druhý řádek je CopyRange, který přesouvá formátování a vzorce společně s hodnotami. To je celý důvod, proč klonovat ručně postavený řádek šablony místo rekonstrukce jeho vzhledu v kódu. Návrhář vlastní vzhled jednou, v šabloně; generátor už jen lije data do jeho kopií
Toto rozdělení se dobře škáluje i tehdy, když opakovaně použitelné rozvržení žije ve vlastním sešitu, řekněme list s pruhy záhlaví a zápatí sdílenými napříč reporty. CopyRangeTo provádí stejné klonování přes hranice listů, přičemž přebírá cílový list plus cílové souřadnice, takže generátor může udržovat jeden neposkvrněný list šablony a otiskovat jeho oblasti do tolika výstupních listů, kolik úloha potřebuje. Alternativa, mutovat šablonu na místě a pak se ji pokoušet obnovit, je ten druh věci, která funguje až do dne, kdy běh v půli selže
Co InsertRows přesouvá a co ne
Vzor rostoucí šablony funguje jen proto, že XLSX InsertRows je strukturální úprava, ne pouhé přeskládání buněk. Když otevírá mezeru, přemístí sloučené oblasti, výšky řádků, hypertextové odkazy, komentáře, ukotvené panely, rozsahy autofiltru, podmíněné formáty, validace dat, tabulky, definované názvy, kotvy obrázků a kotvy grafů, které sedí pod bodem vložení, ne jen hodnoty buněk. Právě to umožňuje, aby blok součtů dorazil na svůj nový řádek se sloučeními a formáty čísel nedotčenými místo aby dorazil ošizený
Jeho dvě zdokumentovaná omezení jsou to, kolem čeho je třeba navrhovat. Úprava vzorců je omezená na upravovaný list: odkazy uvnitř tohoto listu se přepíšou, a vzorec na jiném listu, který míří do posunuté oblasti, se přepíše taky, ale úprava sleduje jen odkazy, které cílí na upravovaný list, takže jakékoli schéma odkazů napříč sešity si zaslouží vlastní audit místo slepé důvěry. Druhé omezení je ostřejší a je na straně XLS. Kontingenční tabulky přežívají cykly otevření a uložení jako syrové zachované záznamy, ne jako modelované objekty, se kterými HotXLS umí hýbat, takže vkládání řádků nepřemístí stopu kontingenční tabulky. Jakákoli šablona, kterou stavíte pro formát .xls, by měla mít oblasti kontingenčních tabulek pěkně mimo dosah jakéhokoli pruhu, který roste
Odmítnutí zápisu dat do prostoru rozvržení
Selhání sloučených buněk, které skutečně doputuje do produkce, není to kosmetické. Je strukturální: detailní řádek zabloudí do sloučeného pruhu rozvržení, jeho hodnoty přistanou v pokrytých buňkách a stanou se neviditelnými, a součty sloupců tiše přestanou odpovídat tomu, co vidí kdokoli, kdo list čte. Protože FindAt odpovídá na otázku pokrývající oblasti pro jakoukoli souřadnici, generátor může tento zápis odmítnout přesně ve chvíli, kdy by k němu došlo, místo aby dodal report, který tiše podhodnocuje
// odmítni zapsat detailní data do sloučené oblasti rozvržení
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;
Stejná hraniční kontrola patří kamkoli, kde bude uživatel výstup později třídit nebo filtrovat. Rozsah se sloučeními uvnitř se nedá čistě setřídit, protože třídění přesouvá řádky nezávisle a sloučení táhnoucí se přes řádky nemá jediný řádek, se kterým by cestovalo; Excel odpoví chybou nebo rozházeným rozvržením. Disciplína, která udržuje reporty správné, je geografická. Omezte sloučení na pruhy titulků, oddělovače sekcí a bloky podpisů a tabulkový střed listu udržujte plochý. Článek o generování reportů ze šablon rozvíjí toto rozdělení rozvržení a dat do plného workflow řízeného zástupnými symboly, a článek o podmíněném formátování a rich textu pokrývá stylování onoho plochého datového pruhu
Jak sloučení degradují na cestě ven
Sloučení je koncept sešitu a každý textově orientovaný exportní formát jej respektuje v jiné míře. Znalost těchto tří chování předem ušetří jeden cyklus QA. Export do HTML reprodukuje sloučení věrně, vydává colspan a rowspan na jediné tabulce, takže report vázaný na prohlížeč si udrží svůj pruhovaný vzhled. Export do RTF vůbec nepřeklenuje sloupce: text kotvy přistane ve své vlastní buňce a zbylá šířka sloučení vyjde jako prázdné buňky, což ponechá široký titulek vizuálně nastrčený vlevo v textovém procesoru. CSV nemá koncept sloučení vůbec, takže hodnota kotvy zabere jedno pole a každá pokrytá buňka se vydá jako prázdné pole. Ponaučení pro sešit, který navíc krmí i exporty s oddělovači, je držet cokoli nosného mimo geometrii sloučení; článek o exportu CSV, TSV a HTML prochází každý formát podrobně
Jedno ujištění pro každého, kdo tohle zvažuje proti velikosti souboru: sloučení v měřítku reportu stojí skoro nic. Tabulka sloučení je vedle dat buněk drobná a čtení pokryté buňky pořád jde přes FindAt místo skenování. Tlak na výkon u velkých sešitů přichází odjinud, hlavně z růstu poolu stylů a paměti, kterou drží cesta ukládání, čemuž se přímo věnuje článek o výkonu velkých sešitů. Obě API pro sloučení, strukturální operace úprav i ukázky šablon jsou součástí dodávky HotXLS Delphi Component