Tehnični članak

Spojene celice HotXLS in postavitev predlog v Delphiju

Prehodite celice sveže odprte predloge poročila in spojen naslov se obnaša kot puščanje. Preberete A1 in dobite »Quarterly Statement«; preberete B1 do F1, ki vidno ležijo pod istim trakom, in ne dobite ničesar. V C1 zapišete vrednost, da bi popravili glavo, in ta se nikoli ne pokaže na zaslonu. Mreža vaših podatkov ni izgubila. Počne natanko to, kar spojitev pomeni: tako v XLS kot v XLSX spojen pravokotnik izriše vsebino ene celice, torej zgornjega levega sidra, preostanek pa obravnava kot pokrit prostor, ki vrednosti hrani, a jih nikoli ne pokaže. Uporabniki Excela to vsrkajo s poskusi in napakami. Generator poročil pa mora to zapisati kot pravilo, saj je v ustvarjeni kodi simptom prazno območje brez izjeme, ki bi vodila do vzroka. HotXLS, izvorna knjižnica v Object Pascalu, ki iz Delphija in C++Builderja bere ter zapisuje oba zapisa Excela, tabelo spojitev razkrije dovolj izrecno, da lahko programirate po pravilu, namesto da ga znova odkrivate v zahtevku za podporo

Ena vrednost, eno sidro

Spojitev je navodilo za prikaz, položeno čez mrežo, ki oblike ne spremeni. Vsaka pokrita celica v datoteki še naprej obstaja kot lastno mesto; zapis spojitve odjemalcu le pove, naj vsebino sidra nariše čez pravokotnik. Ta razlika poganja tri načine obnašanja, ki jih je vredno ponotranjiti, preden napišete kakršno koli kodo za postavitev. Branje pokrite celice vrne njeno lastno shranjeno vrednost, ki je pri traku, ki ste ga zgradili sami, običajno prazna, zato mora vsaka koda, ki pregleduje spojen naslov, razrešiti in prebrati sidro. Pisanje v pokrito celico na ravni datoteke uspe in se ne pokaže nikjer, kar je past nevidne glave iz uvoda. Razdružitev območja pa razgali vse, kar je ves čas ležalo pod njim, tako da se zablodela vrednost, zapisana v pokrit prostor, spremeni v viden hrošč tistega dne, ko kdo spojitev razveže

Diagram spojenega traku v HotXLS, kjer pokrite celice obdržijo lastna mesta, medtem ko se branja razrešijo na sidro A1 v preglednicah v Delphiju
HotXLS ohrani vsako pokrito celico kot resnično mesto in znova nariše le sidro, tako da se branja razrešijo prek A1, pisanja v pokrit prostor pa ostanejo nevidna do razdružitve

Na strani XLSX je ta tabela prvorazreden objekt. Sheet.MergedCells nosi Add('A1:C1'), FindAt(Row, Col), DeleteAt in Items, klic, po katerem posežete najpogosteje, pa je FindAt: izročite mu poljubno koordinato in vrne spojeno območje, ki to celico pokriva, ali nil, kadar celica stoji sama. To eno samo iskanje je temelj obeh polovic pravilnega ravnanja s spojitvami, torej varnega branja in varovala pri pisanju, in obe se pojavita pozneje

Dve fasadi, dva idioma spajanja

HotXLS ohranja klasičen pogon BIFF8 za .xls in pogon OOXML za .xlsx kot ločena objektna modela, spajanje pa zapisujeta različno, ker izhajata iz različnih izročil. Fasada XLS sledi idiomu Excel COM: območje vzamete iz indeksirane lastnosti z dvema argumentoma in pokličete Merge z OleVariant, katerega vrednost določi geometrijo, ki jo na koncu dobite

var
  Book: IXLSWorkbook;   // šteto prek vmesnika: brez ročnega Free
  Sh: IXLSWorksheet;
begin
  Book := TXLSWorkbook.Create;
  Sh := Book.Sheets[1];                 // zbirka listov XLS se šteje od 1
  Sh.Range['A1', 'F1'].Merge(False);    // False = en sam spojen blok
  Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
  Sh.Range['A3', 'F4'].Merge(True);     // True = spajanje počez: ena spojitev na vrstico
  Book.SaveAs('layout.xls');
end;

Argument metode Merge je tisti del, ki ga ljudje pogosto zgrešijo. Čez dvovrstično območje Merge(True) ustvari dve neodvisni enovrstični spojitvi, kar je Excelovo »Merge Across« in natanko to, kar želite pri zloženem pasu glave, ki naj ostane ločljiv po vrsticah. Merge(False) celoten pravokotnik zlije v en sam blok. Območje tudi sporoča MergeCells kot zastavico stanja, vsebujoče območje vrne prek MergeArea, razveže pa se z Unmerge. Fasada XLSX iste operacije izpostavlja pod drugimi imeni: Sheet.MergeCells(Row1, Col1, Row2, Col2) sprejme celoštevilske meje, TXLSXRange.Merge sprejme enakovredno varianto Across, zbirka MergedCells pa hrani rezultat

Predloga, ki raste s svojimi podatki

Resnična predloga poročila ni nespremenljiva mreža. Glava in vsote so nespremenljive, razdelek s podrobnostmi med njima pa se raztegne na toliko, kolikor vrne poizvedba. Vzorec, ki zdrži, v predlogi ohrani eno v celoti oblikovano vrstico s podrobnostmi, jo klonira enkrat na zapis in nato pred blokom vsot odpre vrzel, tako da vse, kar je zasidrano spodaj, zdrsne navzdol brez izgube oblikovanja

Predloga poročila HotXLS, ki raste v Delphiju: oblikovana vrstica s podrobnostmi se klonira za vsak zapis, InsertRows pa odpre vrzel, tako da blok vsot zdrsne navzdol z nedotaknjenimi spojitvami
Kloniranje oblikovane vrstice s podrobnostmi prenese njene sloge in formule v vsako kopijo, InsertRows pa nato pas vsot zdrsne navzdol z nedotaknjenimi spojitvami in oblikami
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611';    // vrednost gre v sidro, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1;        // indeks zaloge se šteje od 0, na strani celice od 1

// vrstica 5 je oblikovana vrstica predloge za podrobnosti
for I := 0 to ItemCount - 1 do
  Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1);             // slogi in formule potujejo z njo

// odpri vrzel nad blokom vsot; vsebina spodaj se zamakne navzdol
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);

Dve vrstici se izplača pogledati še enkrat. Dodelitev pisave nosi zamik za ena, ki tiho ugrizne: Fonts.Add vrne položaj v zalogi, ki se šteje od 0, celica pa hrani referenco pisave, ki se šteje od 1, kjer 0 pomeni privzeto pisavo, zato izpuščeni + 1 ne sproži ničesar, le naslov oblikuje v napačni pisavi. Druga vrstica je CopyRange, ki poleg vrednosti premakne tudi oblikovanje in formule. To je celoten razlog za kloniranje ročno zgrajene vrstice predloge namesto obnavljanja njenega videza v kodi. Oblikovalec je lastnik videza enkrat, v predlogi; generator pa v njene kopije le zliva podatke

Ta delitev se razteza še dlje, kadar večkrat uporabljena postavitev živi v svojem delovnem zvezku, denimo v listu s pasovi glave in noge, ki si jih delijo poročila. CopyRangeTo opravi isti klon čez meje delovnih listov, saj sprejme ciljni list in ciljne koordinate, tako da lahko generator ohrani en sam neomadeževan list predloge in njegova območja odtisne v toliko izhodnih listov, kolikor jih naloga potrebuje. Druga možnost, spreminjanje predloge na mestu in poznejši poskus obnovitve, je vrsta rešitve, ki deluje do dneva, ko se izvajanje prekine na pol poti

Kaj InsertRows premakne in česa ne

Vzorec rastoče predloge deluje le zato, ker je InsertRows v XLSX strukturno urejanje in ne premetavanje celic. Ko odpre vrzel, prestavi spojena območja, višine vrstic, hiperpovezave, komentarje, zamrznjene podokvire, območja samodejnega filtra, pogojna oblikovanja, preverjanja podatkov, tabele, definirana imena, sidra slik in sidra grafikonov, ki ležijo pod točko vstavljanja, ne le vrednosti celic. To je tisto, zaradi česar blok vsot v svojo novo vrstico prispe z nedotaknjenimi spojitvami in oblikami števil, namesto da bi prispel oskubljen

Njegovi dve dokumentirani meji sta tisti, ki jima je treba prilagoditi zasnovo. Prilagajanje formul je omejeno na list, ki se ureja: reference znotraj tega lista se prepišejo, prepiše se tudi formula na drugem listu, ki kaže v premaknjeno območje, vendar prilagajanje sledi le referencam, ki ciljajo na urejani list, zato si vsaka shema referenc med delovnimi zvezki zasluži lastno revizijo namesto slepega zaupanja. Druga meja je ostrejša in je na strani XLS. Vrtilne tabele preživijo cikle odpiranja in shranjevanja kot surovi ohranjeni zapisi in ne kot modelirani objekti, ki bi jih HotXLS lahko premikal, zato vstavljanje vrstic odtisa vrtilne tabele ne prestavi. Vsaka predloga, ki jo zgradite za zapis .xls, naj svoja območja vrtilnih tabel postavi krepko stran od vsakega pasu, ki raste

Zavračanje pisanja podatkov v prostor postavitve

Odpoved spojenih celic, ki dejansko doseže produkcijo, ni kozmetična. Je strukturna: vrstica s podrobnostmi zaide v spojen pas postavitve, njene vrednosti pristanejo v pokritih celicah in postanejo nevidne, vsote stolpcev pa se tiho nehajo ujemati s tem, kar vidi vsakdo, ki list bere. Ker FindAt za poljubno koordinato odgovori na vprašanje o pokrivajočem območju, lahko generator to pisanje zavrne v trenutku, ko bi se zgodilo, namesto da bi odpremil poročilo, ki tiho šteje premalo

// zavrni pisanje podrobnih podatkov v spojeno območje postavitve
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;

Isto preverjanje meja sodi povsod, kjer bo uporabnik pozneje izhod razvrščal ali filtriral. Območja s spojitvami znotraj njega ni mogoče čisto razvrstiti, ker razvrščanje vrstice premika neodvisno, spojitev čez več vrstic pa nima ene same vrstice, s katero bi potovala; Excel odgovori z napako ali z zmešano postavitvijo. Disciplina, ki ohranja poročila pravilna, je geografska. Spojitve omejite na pasove naslovov, ločnice razdelkov in bloke za podpis, tabelarno sredino lista pa ohranite ploščato. Članek o ustvarjanju poročil iz predlog to delitev postavitve in podatkov razvije v celoten potek dela, voden z ogradami, članek o pogojnem oblikovanju in obogatenem besedilu pa pokriva oblikovanje tega ploščatega podatkovnega pasu

Kako se spojitve slabšajo na poti navzven

Spojitev je pojem delovnega zvezka, vsak besedilno usmerjen izvozni zapis pa jo spoštuje v drugačni meri. Če vse tri načine obnašanja poznate vnaprej, prihranite en cikel zagotavljanja kakovosti. Izvoz v HTML spojitve reproducira zvesto, saj v eni sami tabeli odda colspan in rowspan, tako da poročilo, namenjeno brskalniku, ohrani svoj pasasti videz. Izvoz v RTF stolpcev sploh ne razpenja: besedilo sidra pristane v svoji celici, preostala širina spojitve pa izpade kot prazne celice, zaradi česar je širok naslov v urejevalniku besedil vidno potisnjen v levo. CSV pojma spojitve ne pozna, zato vrednost sidra zasede eno polje, vsaka pokrita celica pa se odda kot prazno polje. Nauk za delovni zvezek, ki napaja tudi izvoze z ločili, je, da vse nosilno ostane zunaj spojene geometrije; članek o izvozu CSV, TSV in HTML vsak zapis podrobno predstavi

Spojen naslov HotXLS, izvožen iz Delphija v HTML s colspan in rowspan, v RTF brez razpenjanja ter v CSV kot poravnana polja
Isti spojen naslov preživi izvoz v HTML prek colspan in rowspan, v RTF se zdrsne v osamljeno celico ob levem robu, v CSV pa se splošči v vrednost in prazna polja

Ena pomiritev za vsakogar, ki to tehta proti velikosti datoteke: spojitve pri obsegu poročila stanejo skoraj nič. Tabela spojitev je drobcena ob podatkih celic, branje pokrite celice pa še vedno teče skozi FindAt in ne skozi pregledovanje. Zmogljivostni pritisk pri velikih delovnih zvezkih prihaja od drugod, predvsem od rasti zaloge slogov in pomnilnika, ki ga zadržuje pot shranjevanja, kar neposredno obravnava članek o zmogljivosti velikih delovnih zvezkov. Oba vmesnika za spajanje, operacije strukturnega urejanja in predstavitve predlog so priloženi paketu HotXLS Delphi Component