Odborný článok

Zlúčené bunky HotXLS a rozloženie šablón reportov v Delphi

Prechádzanie buniek čerstvo otvorenej šablóny reportu so zlúčeným nadpisom sa môže zdať ako hľadanie chýb. Prečítate bunku A1 a získate hodnotu „Quarterly Statement“; prečítate bunky B1F1, ktoré vizuálne patria pod rovnaký nadpis, a nedostanete nič. Zapíšete hodnotu do bunky C1 s cieľom opraviť hlavičku a tá sa na obrazovke nikdy neobjaví. Tabuľková mriežka vaše dáta nestratila. Robí presne to, čo zlúčenie znamená: vo formátoch XLS aj XLSX zlúčená oblasť zobrazuje iba obsah jednej bunky – ľavej hornej kotvy – a so zvyšnými zaobchádza ako so zakrytým priestorom, ktorý síce môže obsahovať hodnoty, ale nikdy ich nezobrazí. Používatelia Excelu sa to učia metódou pokusu a omylu. Generátor reportov to však musí zakódovať ako pravidlo, pretože v generovanom kóde je príznakom prázdna oblasť bez akejkoľvek výnimky, podľa ktorej by sa dala chyba nájsť. HotXLS, natívna knižnica pre Object Pascal na čítanie a zápis oboch formátov Excelu v Delphi a C++Builderi, sprístupňuje tabuľku zlúčených buniek tak explicitne, že môžete programovať priamo na základe tohto pravidla namiesto jeho dodatočného odhaľovania

Jedna hodnota, jedna kotva

Zlúčenie je inštrukcia zobrazenia navrstvená na mriežku, ktorá nemení svoj tvar. Každá prekrytá bunka stále existuje v súbore ako samostatné miesto; záznam o zlúčení iba hovorí programu, aby vykreslil obsah kotvy cez celý vyhradený obdĺžnik. Tento rozdiel riadi tri typy správania, ktoré je dobré si osvojiť pred písaním akéhokoľvek kódu pre rozloženie. Čítanie prekrytej bunky vracia jej vlastnú uloženú hodnotu, ktorá je pri vytvorenom nadpise zvyčajne prázdna, takže každý kód zisťujúci zlúčený nadpis musí vyhľadať a prečítať kotvu. Zápis do prekrytej bunky je na úrovni súboru úspešný, no nikde sa nezobrazí, čo predstavuje spomínanú pascu s neviditeľnou hlavičkou. A zrušenie zlúčenia oblasti odhalí čokoľvek, čo sa pod ňou po celý čas nachádzalo, takže náhodná hodnota zapísaná do prekrytého priestoru sa zmení na viditeľný defekt v deň, keď niekto zlúčenie zruší

Na strane XLSX je táto tabuľka plnohodnotným objektom. Kolekcia Sheet.MergedCells obsahuje metódy Add('A1:C1'), FindAt(Row, Col), DeleteAt a vlastnosť Items. Najčastejšie využijete metódu FindAt: odovzdajte jej ľubovoľné súradnice a vráti zlúčenú oblasť pokrývajúcu danú bunku, prípadne hodnotu nil, ak bunka stojí samostatne. Toto jediné vyhľadanie je základom pre bezpečné čítanie aj ochranu pred zápisom, o čom píšeme nižšie

Dve rozhrania, dva zápisy zlúčenia

HotXLS zachováva klasický engine pre formát BIFF8 .xls aj engine pre formát OOXML .xlsx ako samostatné objektové modely. Zlúčenie zapisujú odlišne, pretože vychádzajú z rôznych konvencií. Rozhranie XLS sa riadi konvenciou Excel COM: vezmete rozsah z indexovanej vlastnosti s dvoma argumentmi a zavoláte metódu Merge s parametrom typu OleVariant, ktorého hodnota určuje výslednú geometriu

var
  Book: IXLSWorkbook;   // interface-counted: no manual Free
  Sh: IXLSWorksheet;
begin
  Book := TXLSWorkbook.Create;
  Sh := Book.Sheets[1];                 // XLS sheet collection is 1-based
  Sh.Range['A1', 'F1'].Merge(False);    // False = one merged block
  Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
  Sh.Range['A3', 'F4'].Merge(True);     // True = merge across: one merge per row
  Book.SaveAs('layout.xls');
end;

Argument metódy Merge je časť, v ktorej sa ľudia mýlia. Pri rozsahu zahŕňajúcom dva riadky vytvorí volanie Merge(True) dve nezávislé zlúčenia po jednom riadku, čo zodpovedá funkcii „Merge Across“ v Exceli a je to presne to, čo potrebujete pre skupinu hlavičiek, ktorých riadky majú zostať oddelené. Volanie Merge(False) spojí celý obdĺžnik do jedného bloku. Rozsah tiež hlási stavový príznak MergeCells, vracia obsahujúcu oblasť cez MergeArea a ruší zlúčenie pomocou Unmerge. Rozhranie XLSX poskytuje rovnaké operácie pod inými názvami: metóda Sheet.MergeCells(Row1, Col1, Row2, Col2) prijíma celočíselné hranice, metóda TXLSXRange.Merge akceptuje ekvivalentný variant Across a kolekcia MergedCells uchováva výsledok

Šablóna, ktorá rastie spolu s dátami

Skutočná šablóna reportu nie je pevná mriežka. Hlavička a súčty sú fixné, ale sekcia s podrobnosťami medzi nimi sa rozširuje podľa toho, čo vráti databázový dopyt. Osvedčeným vzorom je ponechať v šablóne jeden kompletne naštýlovaný riadok s podrobnosťami, naklonovať ho pre každý záznam a potom otvoriť priestor pred blokom súčtov, aby sa všetko ukotvené nižšie posunulo nadol bez straty formátovania

Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611';    // value goes to the anchor, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1;        // pool index 0-based, cell side 1-based

// row 5 is the styled detail template line
for I := 0 to ItemCount - 1 do
  Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1);             // styles and formulas travel with it

// open a gap above the totals block; content below shifts down
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);

Dva riadky si zaslúžia bližší pohľad. Priradenie písma obsahuje posun o jedna, ktorý sa môže prejaviť chybou bez varovania: metóda Fonts.Add vracia index fondu začínajúci od 0, zatiaľ čo bunka ukladá odkaz na písmo začínajúci od 1 (kde 0 znamená predvolené písmo), takže vynechanie + 1 nevyvolá žiadnu výnimku, iba naštýluje váš nadpis nesprávnym písmom. Druhým dôležitým riadkom je CopyRange, ktorý prenáša formátovanie a vzorce spolu s hodnotami. To je hlavný dôvod, prečo klonovať ručne pripravený riadok šablóny namiesto pracného vytvárania jeho vzhľadu v kóde. Dizajnér definuje vzhľad raz v šablóne a generátor už len nalieva dáta do jeho kópií

Tento princíp sa dá ďalej škálovať, keď opakovane použiteľné rozloženie existuje vo vlastnom zošite, napríklad hárok so zdieľanými hlavičkami a pätami pre viacero reportov. Metóda CopyRangeTo vykonáva rovnaké klonovanie cez hranice pracovných hárkov, pričom prijíma cieľový hárok a cieľové súradnice, takže generátor si môže ponechať čistú šablónu hárka a kopírovať jej oblasti do ľubovoľného počtu výstupných hárkov. Alternatíva v podobe úpravy šablóny priamo na mieste a jej následnej obnovy funguje len dovtedy, kým sa beh programu v polovici nepreruší

Čo InsertRows presúva a čo nie

Vzor rastúcej šablóny funguje len preto, že metóda InsertRows vo formáte XLSX je štrukturálnou úpravou a nie len obyčajným posunom buniek. Keď otvorí medzeru, premiestni zlúčené oblasti, výšky riadkov, hyperodkazy, komentáre, ukotvené panely, rozsahy automatických filtrov, podmienené formáty, overenia údajov, tabuľky, definované názvy a kotvy obrázkov či grafov, ktoré sa nachádzajú pod bodom vloženia, a nielen samotné hodnoty buniek. Vďaka tomu dorazí blok celkových súčtov na svoj nový riadok so zachovanými zlúčeniami a číselnými formátmi bez toho, aby bol o tieto vlastnosti ukrátený

Dva zdokumentované limity definujú hranice návrhu. Úprava vzorcov je obmedzená na upravovaný hárok: odkazy v rámci tohto hárka sa prepíšu a prepíše sa aj vzorec na inom hárku smerujúci do posunutej oblasti, no táto úprava sleduje iba odkazy na upravovaný hárok. Akýkoľvek zložitejší systém odkazov naprieč zošitmi si preto zaslúži vlastnú kontrolu namiesto slepej dôvery. Druhý limit je prísnejší a týka sa strany XLS. Kontingenčné tabuľky (pivot tables) prežívajú cykly otvorenia a uloženia ako surové záznamy, a nie ako modelované objekty, ktoré by knižnica HotXLS mohla presúvať, takže vloženie riadkov nepresunie umiestnenie kontingenčnej tabuľky. Akákoľvek šablóna pre formát .xls by preto mala mať oblasti kontingenčných tabuliek umiestnené mimo akýchkoľvek rozširujúcich sa pásov

Odmietnutie zápisu dát do priestoru rozloženia

Chyba zlúčenej bunky, ktorá sa reálne dostane do prevádzky, nie je tá kozmetická. Je štrukturálna: riadok s podrobnosťami sa posunie do zlúčeného pásu rozloženia, jeho hodnoty skončia v prekrytých bunkách a stanú sa neviditeľnými, a celkové súčty stĺpcov ticho prestanú súhlasiť s tým, čo čitateľ hárka vidí. Keďže metóda FindAt odpovedá na otázku o pokrývajúcej oblasti pre ľubovoľné súradnice, generátor môže takýto zápis odmietnuť v okamihu, kedy by k nemu malo dôjsť, namiesto toho, aby vygeneroval report s nesprávnymi súčtami

// refuse to write detail data into a merged layout region
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;

Rovnaká kontrola hraníc patrí všade tam, kde bude používateľ neskôr údaje triediť alebo filtrovať. Rozsah so zlúčenými bunkami nie je možné čisto triediť, pretože triedenie presúva riadky nezávisle a zlúčenie presahujúce viacero riadkov nemá jeden riadok, s ktorým by sa mohlo presunúť. Excel v takom prípade vráti chybu alebo poškodí rozloženie. Správnou disciplínou je priestorové oddelenie. Zlúčenia obmedzte na pásy s nadpismi, rozdeľovače sekcií a bloky s podpismi, a stredovú tabuľkovú časť hárka ponechajte plochú. Článok o generovaní reportov pomocou šablón rozvíja toto rozdelenie rozloženia a dát do kompletného pracovného postupu riadeného zástupnými symbolmi, a článok o podmienenom formátovaní a štýloch bohatého textu sa zaoberá štýlovaním plochého dátového pásu

Ako sa zlúčenia deformujú pri exporte

Zlúčenie buniek je koncept zošita a každý textovo orientovaný formát exportu ho rešpektuje v inej miere. Poznať toto správanie vopred šetrí testovací cyklus. Export do HTML reprodukuje zlúčenia verne pomocou atribútov colspan a rowspan v jednej tabuľke, takže report zobrazený v prehliadači si zachováva svoj štruktúrovaný vzhľad. Export do RTF vôbec nepodporuje spájanie stĺpcov: text z kotvy skončí vo svojej bunke a zvyšná šírka zlúčenia sa exportuje ako prázdne bunky, čo zanechá široký nadpis vizuálne posunutý doľava. Formát CSV nemá žiadny koncept zlúčenia, takže hodnota kotvy obsadí jedno pole a každá prekrytá bunka sa vygeneruje ako prázdne pole. Ponaučením pre zošity, ktoré slúžia aj ako zdroje pre exporty oddelené oddeľovačmi, je neumiestňovať dôležité údaje do zlúčených oblastí. Článok o exporte do formátov CSV, TSV a HTML podrobne rozoberá každý formát

Jedno uistenie pre každého, kto porovnáva toto riešenie s veľkosťou súboru: zlúčenia nestoja pri bežnej mierke takmer nič. Tabuľka zlúčení je v porovnaní s dátami buniek maličká a čítanie prekrytej bunky prebieha cez indexované FindAt bez potreby celkového skenovania. Tlak na výkon pri veľkých zošitoch prichádza odinakiaľ, hlavne z rastu fondu štýlov a z pamäte spotrebovávanej pri ukladaní, čomu sa priamo venuje článok o výkone veľkých zošitov. Obe rozhrania API pre zlúčenie, operácie štrukturálnych úprav a ukážky šablón sa dodávajú s komponentom HotXLS Component