Technisch artikel

Een XLSX-werkblad dupliceren in Delphi met HotXLS

Je hebt één werkblad precies goed opgebouwd. De kopband is samengevoegd, de kolombreedtes passen bij de data, de bovenste twee rijen zijn vastgezet, het afdrukbereik en de marges zijn ingesteld voor een nette A4-uitvoer, en het tabblad is gekleurd zodat finance het kan vinden. Nu heeft het rapport er twaalf van nodig, één per regio, allemaal met dezelfde lay-out. Dat werkblad twaalf keer opnieuw in code opbouwen is precies hoe subtiele afwijkingen ontstaan: regio 7 krijgt een kolom die een punt smaller is, regio 11 verliest de freeze, en niemand merkt het tot de PDF op het bureau van een manager belandt. Wat je echt wilt is de programmeerversie van Excel's rechtermuisknop, Verplaatsen of kopiëren, Een kopie maken: neem het afgebouwde werkblad en stamp er onafhankelijke duplicaten van

De XLSX-engine in HotXLS, een native Delphi- en C++Builder-bibliotheek die Excel-bestanden leest en schrijft zonder Excel zelf te automatiseren, kon al werkbladen verplaatsen, werkbladen verwijderen en celbereiken tussen werkbladen kopiëren. Wat hij tot v2.91.0 nog niet kon, was een heel werkblad in één aanroep klonen. Die release voegt twee ingangen toe: TXLSXWorksheet.CopyFrom, dat werkbladstatus van de ene worksheet naar de andere kopieert, en TXLSXSheets.Duplicate, dat een nieuw werkblad toevoegt en CopyFrom voor je uitvoert. Het interessante deel is niet dat het dingen kopieert. Het is de bewuste scheidslijn tussen wat deep-copied wordt en wat niet, en waarom die grens precies daar ligt

Eén aanroep om een afgebouwd werkblad te klonen

De hoog-niveau operatie is Duplicate. Geef de bronworksheet-index, die in XLSX-terminologie vanaf 1 telt, en je krijgt een gloednieuw werkblad terug dat de lay-out en data van het origineel spiegelt. De indexconventie sluit aan op Items[] aan de XLSX-kant, dus werkblad één heeft index 1, niet 0; geef je een index buiten bereik door, dan krijg je nil terug in plaats van een exception, hetzelfde faalcontract dat de rest van de worksheetcollectie gebruikt

Twee dingen in dat fragment verdienen vertraging. Ten eerste neemt FreezePanes zijn argumenten rij-eerst, FreezePanes(ARow, ACol), zodat het aansluit op de indexering van Cells[Row, Col]; het duplicaat erft exact die freeze-split. Ten tweede heet de methode Duplicate en niet het voor de hand liggende Copy, en dat is geen stijlkeuze. Copy is een standaardroutine in de System-unit, die voortdurend wordt gebruikt voor strings en dynamische arrays. Een methode met de naam Copy op een class zou die routine binnen method bodies overschaduwen en precies de soort naamresolutie-ambiguïteit creëren die je zes maanden later bijt. Duplicate omzeilt dat hele probleem en leest op de call site ook beter

De standaardnaam volgt Excel's eigen regel

Wanneer je de overload met één argument aanroept, of een lege naamstring doorgeeft, krijgt het nieuwe werkblad de naam van de bron met een (2)-achtervoegsel, en dat achtervoegsel telt op tot de naam uniek is. Dupliceer het Template-werkblad één keer en je krijgt Template (2); dupliceer het nog eens en je krijgt Template (3), omdat Template (2) al bezet is. Dat spiegelt de namen die Excel zelf uit zijn opdracht Een kopie maken genereert, zodat een workbook dat je code produceert eruitziet zoals een gebruiker een handmatig gedupliceerd bestand zou verwachten. De uniciteitscontrole loopt over de live worksheetcollectie, dus ze stapt ook over namen die je handmatig hebt gemaakt, niet alleen over eerdere duplicaties

Als je één werkblad per regio of per maand genereert, gebruik dan liever de overload met expliciete naam. Een voorspelbaar Region-North, Region-South-schema is later makkelijker aan te spreken dan een reeks (2), (3)-achtervoegsels, en het houdt je gedefinieerde namen en formules over meerdere werkbladen leesbaar

Wat CopyFrom deep-copied

Onder de motorkap voegt Duplicate het werkblad toe en roept daarna CopyFrom(ASource) aan, dat je ook direct kunt gebruiken wanneer je wilt klonen naar een werkblad dat je al hebt aangemaakt. CopyFrom bewaakt meteen de twee degeneratieve gevallen: kopiëren vanaf nil of een werkblad op zichzelf kopiëren, beide keren keert de methode direct terug zonder iets te doen. Alles daarna is de kopie zelf, en die is bewust breed

De celdata komt eerst. CopyFrom vraagt het bronwerkblad om zijn UsedRange, de strakke begrenzing van gevulde cellen en samengevoegde bereiken, en hergebruikt de bestaande CopyRangeTo-mechaniek om elke waarde, formule en per-cel stijlindex naar het doel te brengen, beginnend bij A1. Bovenop de cellen speelt het de volledige laag werkbladstatus af die een sjabloon afgebouwd doet lijken:

  • Samengevoegde bereiken, opnieuw opgebouwd op coördinaten zodat de banner dezelfde rechthoek beslaat
  • Kolombreedtes en rijhoogtes, plus de lijsten voor verborgen, samengevouwen en outline-niveau, letterlijk gekopieerd zodat niet-standaard rijen en kolommen exact uitlijnen
  • Freeze panes en de view-state: zoomniveau, gridline- en zero-value-weergave, right-to-left-richting en het viewtype
  • Protectiestatus met de bijbehorende permissieopties per actie, zodat een vergrendeld sjabloon op dezelfde manier vergrendeld blijft
  • Het volledige page-setup-blok: marges, oriëntatie, papierformaat, scaling en fit-to-page, afdrukbereik, afdruktitels, headers en footers, en de print-gridlines- en print-headings-vlaggen
  • Het AutoFilter-bereik, de tabkleur en de zichtbaarheid van het werkblad

Het resultaat is een werkblad dat identiek print, filtert en presenteert als zijn bron. En omdat de cellen, samenvoegingen en dimensielijsten fysiek opnieuw worden opgebouwd op het nieuwe werkblad in plaats van gealiased, is het duplicaat volledig onafhankelijk. Schrijf 999 in een cel op de kopie en de bron behoudt zijn oorspronkelijke waarde; die onafhankelijkheid is het belangrijkste eigenschap van een kloon voor parallelle regionale rapporten, en de meegeleverde SheetCopy-demo controleert dat expliciet

Wat het oppervlakkig laat, en waarom

Nu het eerlijke deel. Grafieken, ingesloten afbeeldingen, XLSX-tabellen, gegevensvalidaties en voorwaardelijke opmaakregels worden niet gekopieerd. Dat is een gedocumenteerde, bewuste grens, en het is de moeite waard om de reden daarachter te begrijpen zodat je eromheen kunt plannen in plaats van ervan te schrikken

Elk van die verzamelingen draagt identiteit en verwijzingen mee die een naïeve veldkopie niet overleven. Een grafiek wijst naar een bronbereik en bezit een drawing-relationship in het OOXML-pakket; het object klonen zonder de relationship en seriereferenties opnieuw te mappen levert een grafiek op die tegen de verkeerde data rendert, of een pakket dat Excel als herstelbehoeftig markeert. Een tabel heeft een naam die binnen de workbook uniek moet zijn, een headerregel die aan specifieke kolommen is gekoppeld en een eigen automatisch gegenereerde relationship. Voorwaardelijke opmaak en gegevensvalidaties hangen aan coördinatenbereiken en kunnen, in het geval van validatie, via een formule naar andere bereiken verwijzen. Een van deze correct deep-copiëren betekent verwijzingen herschrijven en frisse identiteiten uitgeven, en dat is echt werk met echte faalmodi. Het half doen, het object kopiëren maar niet zijn verwijzingen, is nog erger dan niet kopiëren: je krijgt een bestand dat opent met een herstelprompt en inhoud stilletjes laat vallen. De engine kopieert dus de dingen die schoon gekopieerd kunnen worden en laat de referentie-dragende verzamelingen over aan de aanroeper, die weet waar het doel naar moet wijzen

In de praktijk betekent dat voor een rijk sjabloon: dupliceer eerst het werkblad zodat je de cellen, lay-out en afdrukinstellingen hebt, en bouw daarna de grafiek, tabel, validaties of voorwaardelijke opmaak opnieuw op de kopie met dezelfde API die je de eerste keer gebruikte. Omdat je ze opnieuw aanmaakt tegen de eigen bereiken van het duplicaat, komen de verwijzingen per constructie goed uit. Voor een grafiek die A1:C10 leest, voeg je op de kopie een nieuwe grafiek toe die naar de kopie van A1:C10 wijst; voor een AutoFilter die live moet blijven, noteer dat het filterbereik wel mee overgaat, dus je hoeft alleen de kolomcriteria opnieuw toe te passen. De voorwaardelijke-opmaak- en validatieregels voeg je opnieuw toe via dezelfde aanroepen als beschreven in het artikel over samengevoegde cellen en rapporttemplate-lay-out, dat de merge table en het bereikmodel behandelt dat de kopie erft

Waar duplicatie in een rapportpipeline past

Werkbladduplicatie is de natuurlijke partner van generatie op basis van placeholders. De token-gedreven aanpak in de gids voor sjabloon-gedreven rapportgeneratie in Delphi lost het probleem op van data schrijven in een lay-out die anderen bewerken; duplicatie lost het probleem op dat je die lay-out vele keren in één workbook nodig hebt. Combineer ze en het patroon is schoon: houd één ongerept Template-werkblad met zijn tokens, samenvoegingen en afdrukinstellingen, en voor elke regio of periode roep je Duplicate aan, vul je de tokens van de kloon met die dataslice en ga je verder. Het ongerepte sjabloon wordt nooit aangepast, dus het blijft een betrouwbare bron voor de volgende kloon, en elk uitvoerwerkblad begint vanuit een byte-for-byte identieke lay-out

Eén volgorde-instructie voorkomt een hele klasse verwarring. Dupliceer het werkblad vóór je er data in giet, niet erna. Een sjabloon hoort structuur en opmaak te dragen, geen cijfers van vorig kwartaal, en een lege gestylede sheet klonen betekent dat elke duplicaat schoon begint. Als je een werkblad dupliceert dat al data bevat, komt die data mee, omdat CopyFrom het gebruikte bereik trouw kopieert; dat is soms precies wat je wilt, maar voor een fan-out-rapport meestal niet

Een korte verificatiegewoonte

Omdat de deep-copy-versus-shallow-copy-scheiding onzichtbaar is tot je ernaar zoekt, bouw je beter een controle van vijf regels in de job in dan erop te vertrouwen dat alles is overgekomen. Na het dupliceren lees je de structurele signalen terug waarvan de kloon geacht wordt ze te erven en assert je dat ze overeenkomen met de bron

Het aantal samengevoegde bereiken, een kolombreedte, de freeze-rij en de tabkleurvlag vertellen je dat de laag die wel gekopieerd moet worden ook echt is meegekomen. Behandel afzonderlijk elk werkblad dat een grafiek, tabel, validaties of voorwaardelijke opmaak bevat als een lijst die op de kopie opnieuw moet worden opgebouwd: hun afwezigheid is bewust, en de oplossing zijn een paar aanroepen, geen bugreport. Dat mentale model, diep waar het veilig is en oppervlakkig waar verwijzingen zouden breken, is het hele verhaal van hoe je deze functie goed gebruikt

Werkbladduplicatie en de CopyFrom-kopie van werkbladstatus die hier is beschreven, worden meegeleverd in v2.91.0 van de native HotXLS Delphi spreadsheet component, samen met een uitvoerbare SheetCopy-sample die de clone-and-mutate-cyclus van begin tot eind oefent

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;
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.