Technický článek

Sloučené buňky a šablony reportů v Delphi s HotXLS

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 B1F1, 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

Diagram sloučeného banneru HotXLS, kde překryté buňky si drží vlastní sloty, zatímco čtení se rozřeší na kotvu A1 v tabulkách Delphi
HotXLS drží každou zakrytou buňku jako skutečný slot a překresluje jen kotvu, takže čtení se rozřeší přes A1, zatímco zápisy do zakrytého prostoru zůstávají neviditelné až do unmerge

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í

Rostoucí šablona sestavy HotXLS v Delphi: stylizovaný detailní řádek se klonuje na záznam a InsertRows otevře mezeru, takže blok součtů sjede dolů s neporušenými merges
Klonování stylizovaného řádku detailu nese jeho styly i vzorce do každé kopie a InsertRows pak sjede pás součtů dolů s mergi i formáty v neporušenosti
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ě

Sloučený titul HotXLS exportovaný z Delphi do HTML s colspan a rowspan, do RTF bez spans a do CSV jako zploštělá pole
Týž sloučený titul přežije HTML export skrze colspan a rowspan, v RTF degraduje na osamocenou buňku zamčenou vlevo a v CSV zplostne na hodnotu plus prázdná pole

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