Tekninen artikkeli

XLSX-laskentataulukon monistaminen Delphissä HotXLS-komponentilla

Olet rakentanut yhden taulukon täydelliseksi. Ylätunnisteen alue on yhdistetty, sarakkeiden leveydet sopivat tietoon, kaksi ylintä riviä on kiinnitetty (frozen), tulostusalue ja marginaalit on asetettu siistiä A4-vientiä varten, ja välilehti on väritetty, jotta talousosasto löytää sen helposti. Nyt raporttiin tarvitaan kaksitoista tällaista taulukkoa, yksi kullekin alueelle, jokainen alkaen samasta asettelusta. Tämän taulukon rakentaminen koodissa kaksitoista kertaa uudelleen on tapa, jolla hienovaraiset erot hiipivät mukaan: alue 7 saa pisteen verran kapeamman sarakkeen, alue 11 menettää rivien kiinnityksen, eikä kukaan huomaa sitä ennen kuin PDF laskeutuu esimiehen työpöydälle. Se mitä todella haluat, on ohjelmallinen versio Excelin oikean klikkauksen "Siirrä tai kopioi, Luo kopio" -toiminnosta: ota valmis taulukko ja monista siitä riippumattomia kopioita

HotXLS:n XLSX-moottori, joka on alkuperäinen Delphi- ja C++Builder-kirjasto Excel-tiedostojen lukemiseen ja kirjoittamiseen ilman itse Excelin automatisointia, osasi jo siirtää taulukoita, poistaa niitä ja kopioida solualueita taulukosta toiseen. Se, mitä se ei pystynyt tekemään ennen versiota v2.91.0, oli koko laskentataulukon kloonaaminen yhdellä kutsulla. Tämä julkaisu lisää kaksi sisääntulopistettä: TXLSXWorksheet.CopyFrom, joka kopioi taulukkotason tilan laskentataulukosta toiseen, ja TXLSXSheets.Duplicate, joka lisää uuden taulukon ja suorittaa CopyFrom-metodin puolestasi. Mielenkiintoista ei ole se, että se kopioi asioita. Se on se tietoinen raja, joka on vedetty syväkopioitavan ja matalakopioitavan välille, ja miksi tuo raja on siinä missä se on

Yksi kutsu valmiin taulukon kloonaamiseksi

Korkean tason operaatio on Duplicate. Anna sille lähdetaulukon 1-pohjainen indeksi, ja se palauttaa upouuden laskentataulukon, joka peilaa alkuperäisen asettelua ja tietoja. Indeksikäytäntö vastaa XLSX-puolen Items[]-kokoelmaa, joten ensimmäinen taulukko on indeksi 1, ei 0; välitä alueen ulkopuolella oleva indeksi, ja saat tulokseksi arvon nil poikkeuksen sijaan. Tämä on sama epäonnistumissopimus, jota loputkin XLSX-taulukkokokoelmasta noudattavat

var
  Book: TXLSXWorkbook;
  Template, Copy: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Template := Book.Sheets.Add('Template');
    Template.Cells[1, 1].Value := 'Quarterly Statement';
    Template.Range['A1:C1'].Merge;
    Template.ColWidth[1] := 18;
    Template.FreezePanes(2, 1);          // freeze top row + first column
    Template.TabColorIsAuto := False;
    Template.TabColor := $FF1F4E79;

    // Clone with an explicit name...
    Copy := Book.Sheets.Duplicate(1, 'Region-North');
    // ...or let it pick the Excel-style default name.
    Copy := Book.Sheets.Duplicate(1);    // -> "Template (2)"

    Book.SaveAs('regions.xlsx');
  finally
    Book.Free;
  end;
end;

Tässä koodinpätkässä on kaksi huomionarvoista seikkaa. Ensinnäkin FreezePanes ottaa argumenttinsa rivi ensin -järjestyksessä, FreezePanes(ARow, ACol), jotta se vastaa Cells[Row, Col]-indeksointia; moniste perii tarkan kiinnitysrajauksen. Toiseksi metodin nimi on Duplicate eikä ilmeisempi Copy, mikä ei johdu tyylillisistä mieltymyksistä. Copy on System-yksikön standardi rutiini, jota käytetään jatkuvasti merkkijonoille ja dynaamisille taulukoille. Luokan Copy-metodi varjostaisi sitä metodien rungoissa ja loisi juuri sellaisen tulkinnanvaraisuuden, joka aiheuttaa ongelmia myöhemmin. Duplicate ohittaa tämän ongelman kokonaan ja näyttää selkeältä kutsupaikassa

Oletusnimi noudattaa Excelin omaa sääntöä

Kun kutsut yhden argumentin ylikuormitusta tai välität tyhjän nimimerkkijonon, uusi taulukko nimetään lähteen mukaan (2)-päätteellä, ja pääte kasvaa, kunnes nimi on yksilöllinen. Monista Template-taulukko kerran ja saat nimen Template (2); monista se uudelleen ja saat nimen Template (3), koska Template (2) on jo varattu. Tämä peilaa nimiä, joita Excel itse luo "Luo kopio" -komennollaan, joten koodisi tuottama työkirja näyttää siltä, mitä käyttäjä odottaisi käsin monistetulta taulukolta. Yksilöllisyystarkistus ajetaan reaaliaikaista taulukkokokoelmaa vasten, mikä tarkoittaa, että se huomioi myös käsin luomasi nimet, ei vain aiemmista monistuksista tulleita

Jos olet luomassa yhden taulukon alueelle tai kuukaudelle, käytä sen sijaan nimenomaista nimen ylikuormitusta. Ennalta arvattava Region-North- ja Region-South-kaava on helpompi käsitellä myöhemmin kuin jono (2)- ja (3)-päätteitä, ja se pitää määritellyt nimet sekä taulukoiden väliset kaavat luettavina

Mitä CopyFrom syväkopioi

Konepellin alla Duplicate lisää taulukon ja kutsuu sitten CopyFrom(ASource)-metodia, jota voit myös kutsua suoraan, kun haluat kloonata taulukon jo aiemmin luomasi taulukon päälle. CopyFrom suojautuu kahdelta degeneroituneelta tapaukselta heti alussa: kopiointi nil-arvosta tai taulukon kopiointi itseensä palauttavat heti tekemättä mitään. Kaikki tämän jälkeen on itse kopiointia, ja se on tarkoituksellisen laajaa

Solutieto tulee ensin. CopyFrom pyytää lähteeltä sen UsedRange-alueen eli solujen ja yhdistettyjen alueiden tiukan rajoituslaatikon, ja käyttää olemassa olevaa CopyRangeTo-koneistoa viedäkseen jokaisen arvon, kaavan ja solukohtaisen tyyli-indeksin kohteeseen alkaen solusta A1. Solujen lisäksi se toistaa koko taulukkotason tilan kerroksen, joka tekee pohjasta valmiin näköisen:

  • Yhdistetyt alueet, jotka luodaan uudelleen koordinaattien mukaan, jotta otsikkoalue kattaa saman suorakulmion
  • Sarakkeiden leveydet ja rivien korkeudet, sekä piilotettujen, suljettujen ja jäsennystason (outline-level) luettelot, jotka kopioidaan sellaisenaan niin, että ei-oletusrivit ja -sarakkeet linjautuvat täsmälleen
  • Kiinnitetyt ruudut ja näkymätila: zoomaustaso, ruudukon ja nolla-arvojen näyttö, oikealta vasemmalle -suunta ja näkymätyyppi
  • Suojaustila sen sarakkeilla ja solujen käyttöoikeuksilla, jotta lukittu pohja pysyy lukittuna samalla tavalla
  • Koko sivunasetusten lohko: marginaalit, suunta, paperikoko, skaalaus ja sivulle sovittaminen, tulostusalue, tulostusotsikot, ylä- ja alatunnisteet sekä tulostusruudukko- ja tulostusotsikkoliput
  • AutoFilter-alue, välilehden väri ja taulukon näkyvyys

Tulos on taulukko, joka tulostuu, suodattaa ja esittäytyy identtisesti lähteensä kanssa. Ja koska solut, yhdistykset ja mittaluettelot luodaan fyysisesti uudelleen uudelle sivulle sen sijaan, että ne olisivat aliaksia, moniste on täysin itsenäinen. Kirjoita arvo 999 kopion soluun, ja lähde säilyttää alkuperäisen arvonsa. Tämä itsenäisyys on kloonin tärkein ominaisuus rinnakkaisia alueraportteja varten, ja mukana toimitettu SheetCopy-demo vahvistaa tämän nimenomaisesti

Mitä se jättää matalaksi (shallow) ja miksi

Sitten se rehellinen osuus. Kaavioita, upotettuja kuvia, XLSX-taulukoita, tiedon oikeellisuustarkistuksia (data validations) ja ehdollisen muotoilun sääntöjä ei kopioida. Tämä on dokumentoitu, tietoinen raja eikä unohdus, ja syy on hyvä ymmärtää, jotta voit suunnitella asiat sen ympärille sen sijaan, että yllättyisit siitä

Jokainen näistä kokoelmista sisältää identiteettejä ja viitteitä, jotka eivät selviä yksinkertaisesta kenttien kopioinnista. Kaavio osoittaa lähdetietoalueeseen ja omistaa piirustusyhteyden (drawing relationship) OOXML-paketissa; objektin kloonaaminen ilman yhteyden ja sarjaviitteiden uudelleenkartoitusta tuottaa kaavion, joka renderöityy väärää tietoa vasten, tai paketin, jonka Excel ilmoittaa vaativan korjausta. Taulukolla on nimi, jonka on oltava yksilöllinen työkirjassa, tiettyihin sarakkeisiin sidottu otsikkorivi ja oma automaattisesti luotu yhteys. Ehdolliset muotoilut ja oikeellisuustarkistukset kiinnittyvät koordinaattialueisiin ja voivat oikeellisuustarkistuksen tapauksessa viitata muihin alueisiin kaavalla. Minkä tahansa näistä syväkopiointi oikein tarkoittaa viitteiden uudelleenkirjoittamista ja uusien identiteettien luomista, mikä on todellista työtä todellisilla virhemahdollisuuksilla. Sen tekeminen puolitiessä, kopioimalla objekti mutta ei sen viitteitä, on huonompi vaihtoehto kuin kopioimatta jättäminen: se tuottaa tiedoston, joka avautuu korjauskehotteella ja kadottaa hiljaisesti sisältöä. Siksi moottori kopioi asiat, jotka se voi kopioida siististi, ja jättää viitteitä sisältävät kokoelmat kutsujalle, joka tietää, mihin kohteen tulisi osoittaa

Käytännössä tämä tarkoittaa sitä, että monipuolisemman pohjan työnkulku on seuraava: monista taulukko solujen, asettelun ja tulostusasetusten saamiseksi, ja rakenna sitten kaavio, taulukko, tarkistukset tai ehdolliset muotoilut uudelleen kopioon samalla API:lla, jolla loit ne ensimmäisen kerran. Koska luot ne uudelleen kopion omia alueita vasten, viitteet muodostuvat rakenteellisesti oikein. Jos kaavio lukee aluetta A1:C10, lisää kopioon uusi kaavio, joka osoittaa kopion alueeseen A1:C10; jos haluat suodattimen (AutoFilter) olevan toiminnassa, huomaa että suodattimen alue siirtyy kopioinnissa, joten sinun tarvitsee vain soveltaa sarakekriteerit uudelleen. Ehdollisen muotoilun ja tiedon oikeellisuustarkistuksen säännöt lisäisit uudelleen samoilla kutsulla, jotka on kuvattu artikkelissa yhdistetyistä soluista ja raporttipohjien asettelusta, joka käy läpi yhdistelmätaulukon ja aluemallin, jonka kopio perii

Mihin monistaminen sopii raportointiputkessa

Laskentataulukon monistaminen on luonnollinen kumppani paikkamerkkipohjaiselle (placeholder-driven) luonnille. Tunnusankkuroitu lähestymistapa oppaassa mallipohjaisesta raporttien luonnista Delphissä ratkaisee ongelman tietojen kirjoittamisesta asetteluun, jota muut muokkaavat; monistaminen ratkaisee ongelman saman asettelun tarvitsemisesta useita kertoja yhdessä työkirjassa. Yhdistä ne, niin kaava on siisti: säilytä yksi koskematon Template-taulukko tunnuksineen, yhdistyksineen ja sivunasetuksineen, ja kutsu sitten jokaiselle alueelle tai ajanjaksolle Duplicate-metodia, täytä kloonin tunnukset kyseisellä data-siivulla ja jatka eteenpäin. Alkuperäinen malli ei koskaan muutu, joten se säilyy luotettavana lähteenä seuraavalle kloonille, ja jokainen tulostetaulukko alkaa tavuittain identtisestä asettelusta

Yksi järjestystä koskeva huomautus säästää monilta sekaannuksilta. Monista taulukko ennen kuin kaadat tietoja siihen, älä sen jälkeen. Mallipohjan tulisi sisältää rakenne ja muotoilu, ei viime neljänneksen lukuja, ja tyhjän muotoillun taulukon kloonaaminen tarkoittaa, että jokainen moniste alkaa puhtaalta pöydältä. Jos monistat taulukon, joka jo sisältää tietoja, kyseiset tiedot tulevat mukana, koska CopyFrom kopioi käytetyn alueen uskollisesti. Tämä on toisinaan sitä mitä halutaan, mutta jakeluraportille (fan-out report) se ei yleensä sovi

Nopea varmistustapa

Koska syvä- ja matalakopion välinen ero on näkymätön, ellet etsi sitä, rakenna viisirivinen tarkistus työhön sen sijaan, että luottaisit kaiken siirtyneen. Monistamisen jälkeen lue takaisin rakenteelliset signaalit, jotka kloonin pitäisi periä, ja varmista niiden täsmäävän lähteen kanssa

Copy := Book.Sheets.Duplicate(1, 'Region-North');
WriteLn(Format('merged=%d  colA=%.1f  freezeRow=%d  tabAuto=%d',
  [Copy.MergedCells.Count, Copy.ColWidth[1],
   Copy.FreezeRow, Integer(Copy.TabColorIsAuto)]));
// Prove independence: mutate the copy, confirm the source is untouched.
Copy.Cells[2, 2].Value := 999;
// Template.Cells[2, 2].Value is still whatever it was.

Yhdistelmien määrä, sarakkeen leveys, kiinnitetty rivi ja välilehden värilippu kertovat, että kopioitava kerros todella siirtyi. Erikseen missä tahansa taulukossa, joka sisälsi kaavion, taulukon, tarkistuksia tai ehdollisia muotoiluja, käsittele näitä uudelleenrakennettavien asioiden listana kopiossa: niiden puuttuminen on suunniteltua, ja korjaus vaatii muutaman kutsun, ei virheraporttia. Tämä ajatusmalli – syvä siellä missä se on turvallista, ja matala siellä missä viitteet rikkoutuisivat – on koko tarina siitä, miten tätä ominaisuutta käytetään hyvin

Laskentataulukon monistaminen ja tässä kuvattu CopyFrom-sivutilan kopiointi toimitetaan alkuperäisen HotXLS-komponentti-tuotteen versiossa v2.91.0 yhdessä ajettavan SheetCopy-esimerkin kanssa, joka testaa kloonaa-ja-muuta-syklin alusta loppuun