Teknisk artikel

HotXLS: merged cells and layout-driven report templates

Iterera cellerna i en nyöppnad rapportmall och en sammanslagen rubrik beter sig som en läcka. Du läser A1 och får "Quarterly Statement"; du läser B1 till F1, som synligt sitter under samma banderoll, och du får ingenting. Skriv ett värde i C1 för att lappa rubriken och det syns aldrig på skärmen. Rutnätet har inte tappat din data. Det gör precis vad en sammanslagning innebär: i både XLS och XLSX renderar en sammanslagen rektangel innehållet i en cell, ankaret uppe till vänster, och behandlar resten som täckt utrymme som håller värden men aldrig visar dem. Excel-användare tar sig an detta genom försök och misstag. En rapportgenerator måste koda in det som en regel, för i genererad kod är symptomet ett tomt område utan något undantag att spåra till. HotXLS, ett nativt Object Pascal-bibliotek som läser och skriver båda Excel-formaten från Delphi och C++Builder, exponerar sammanslagningstabellen tillräckligt tydligt för att du ska kunna programmera mot regeln i stället för att återupptäcka den i ett supportärende

Ett värde, ett ankare

En sammanslagning är en visningsinstruktion lagd ovanpå ett rutnät som inte ändrar form. Varje täckt cell existerar fortfarande i filen som sin egen plats; sammanslagningsposten talar bara om för en mottagare att måla ankarcellens innehåll över rektangeln. Den distinktionen driver tre beteenden värda att internalisera innan du skriver någon layoutkod. Att läsa en täckt cell returnerar dess eget lagrade värde, vilket för en banderoll du byggt oftast är tomt, så all kod som inspekterar en sammanslagen rubrik måste slå upp och läsa ankaret. Att skriva till en täckt cell lyckas på filnivå och syns ingenstans, vilket är den osynliga-rubrik-fällan från inledningen. Och att dela upp ett område igen exponerar vad som än legat under det hela tiden, så ett kringspritt värde skrivet i täckt utrymme förvandlas till en synlig defekt den dag någon löser upp sammanslagningen

Diagram över en HotXLS sammanslagen banderoll där täckta celler behåller sina egna platser medan läsningar löses till ankaret A1 i Delphi-kalkylblad
HotXLS behåller varje täckt cell som en verklig plats och ritar bara om ankaret, så läsningar löses via A1 medan skrivningar in i täckt yta förblir osynliga tills en avfogning

På XLSX-sidan är den tabellen ett förstklassigt objekt. Sheet.MergedCells bär Add('A1:C1'), FindAt(Row, Col), DeleteAt och Items, och det anrop du oftast tar till är FindAt: mata den med vilken koordinat som helst och den returnerar det sammanslagna området som täcker den cellen, eller nil när cellen står för sig själv. Den enda uppslagningen är grunden för båda halvorna av korrekt sammanslagningshantering, den säkra läsningen och skrivvakten, och båda dyker upp senare

Två fasader, två sammanslagningsidiom

HotXLS behåller den klassiska BIFF8-motorn för .xls och OOXML-motorn för .xlsx som separata objektmodeller, och de stavar sammanslagning olika eftersom de härstammar från olika konventioner. XLS-fasaden följer Excels COM-idiom: du tar ett område från en tvåargumentsindexerad egenskap och anropar Merge med en OleVariant vars värde avgör vilken geometri du hamnar med

var
  Book: IXLSWorkbook;   // gränssnittsräknat: inget manuellt Free
  Sh: IXLSWorksheet;
begin
  Book := TXLSWorkbook.Create;
  Sh := Book.Sheets[1];                 // XLS-bladsamlingen är 1-baserad
  Sh.Range['A1', 'F1'].Merge(False);    // False = ett sammanslaget block
  Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
  Sh.Range['A3', 'F4'].Merge(True);     // True = slå samman rad för rad: en sammanslagning per rad
  Book.SaveAs('layout.xls');
end;

Argumentet till Merge är det folk gör fel. Över ett tvåradsområde ger Merge(True) två oberoende enradiga sammanslagningar, vilket är Excels "Merge Across" och precis vad du vill ha för ett staplat rubrikband som ska hålla sina rader separerbara. Merge(False) smälter samman hela rektangeln till ett enda block. Området rapporterar också MergeCells som en tillståndsflagga, returnerar det omslutande området via MergeArea, och löser upp sig själv med Unmerge. XLSX-fasaden exponerar samma operationer under andra namn: Sheet.MergeCells(Row1, Col1, Row2, Col2) tar heltalsgränser, TXLSXRange.Merge accepterar motsvarande Across-variant, och samlingen MergedCells håller resultatet

En mall som växer med sin data

En verklig rapportmall är inte ett fast rutnät. Rubriken och summeringarna är fasta, men detaljsektionen mellan dem sträcker ut sig till vad frågan än returnerar. Mönstret som håller behåller en fullt stiliserad detaljrad i mallen, klonar den en gång per post, och öppnar sedan ett mellanrum framför summeringsblocket så att allt förankrat under glider ner utan att förlora sin formatering

HotXLS-rapportmall som växer i Delphi: den stilade detaljraden klonas per post och InsertRows öppnar ett gap så att totalsblocket glider ner med sammanslagningar intakta
Att klona den formaterade detaljraden bär dess stilar och formler in i varje kopia, och InsertRows skjuter sedan ned summbandet med fogar och format intakta
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611';    // värdet går till ankaret, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1;        // poolindex 0-baserat, cellsidan 1-baserad

// rad 5 är den stiliserade detaljmallraden
for I := 0 to ItemCount - 1 do
  Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1);             // stilar och formler följer med

// öppna ett mellanrum ovanför summeringsblocket; innehåll nedanför flyttas ner
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);

Två rader förtjänar en andra titt. Typsnittstilldelningen bär ett fel-med-en-fel som biter tyst: Fonts.Add lämnar tillbaka en 0-baserad poolposition, medan en cell lagrar en 1-baserad typsnittsreferens där 0 betyder standardtypsnittet, så att utelämna + 1 ger inget fel, det stilar bara din rubrik i fel typsnitt. Den andra raden är CopyRange, som flyttar formatering och formler tillsammans med värdena. Det är hela anledningen till att klona en handbyggd mallrad i stället för att återskapa dess utseende i kod. En designer äger utseendet en gång, i mallen; generatorn häller bara data i kopior av den

Den uppdelningen skalar vidare när den återanvändbara layouten bor i sin egen arbetsbok, säg ett blad med rubrik- och sidfotsband delade mellan rapporter. CopyRangeTo utför samma klon över bladgränser, och tar ett målblad plus destinationskoordinater, så att en generator kan behålla ett orört mallblad och stämpla in dess områden i så många utdatablad som ett jobb behöver. Alternativet, att mutera mallen på plats och försöka återställa den efteråt, är det slags sak som fungerar tills dagen en körning avbryts halvvägs

Vad InsertRows flyttar, och vad den inte gör

Mönstret att låta en mall växa fungerar bara för att XLSX InsertRows är en strukturell redigering snarare än en cellomskakning. När den öppnar ett mellanrum flyttar den de sammanslagna områdena, radhöjder, hyperlänkar, kommentarer, frysta rutor, autofilterintervall, villkorlig formatering, datavalideringar, tabeller, definierade namn, bildankare och diagramankare som sitter under infogningspunkten, inte bara cellvärdena. Det är vad som låter summeringsblocket anlända till sin nya rad med sina sammanslagningar och talformat intakta i stället för att anlända avskalat

Dess två dokumenterade gränser är de att designa kring. Formeljustering är begränsad till bladet som redigeras: referenser inom det bladet skrivs om, och en formel på ett annat blad som pekar in i det förskjutna området skrivs också om, men justeringen följer bara referenser som pekar mot det redigerade bladet, så alla scheman för referenser mellan arbetsböcker förtjänar en egen granskning snarare än blint förtroende. Den andra gränsen är skarpare, och den finns på XLS-sidan. Pivottabeller överlever öppna-spara-cykler som råa bevarade poster, inte som modellerade objekt HotXLS kan flytta, så att infoga rader flyttar inte en pivottabells fotavtryck. Alla mallar du bygger för formatet .xls bör parkera sina pivotområden väl bortom alla band som växer

Att vägra skriva data i layoututrymme

Det sammanslagningsfel som faktiskt når produktion är inte det kosmetiska. Det är strukturellt: en detaljrad driver in i ett sammanslaget layoutband, dess värden hamnar i täckta celler och blir osynliga, och kolumnsummorna slutar tyst stämma med vad någon som läser bladet kan se. Eftersom FindAt besvarar frågan om täckande område för vilken koordinat som helst, kan generatorn vägra den skrivningen i det ögonblick den skulle inträffa i stället för att leverera en rapport som tyst underräknar

// vägra skriva detaljdata i ett sammanslaget layoutområde
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;

Samma gränskontroll hör hemma överallt där en användare senare kommer att sortera eller filtrera utdatan. Ett område med sammanslagningar inuti kan inte sorteras rent, eftersom sorteringen flyttar rader oberoende av varandra och en sammanslagning som spänner över rader inte har någon enskild rad att följa med. Excel svarar med ett fel eller en förvrängd layout. Disciplinen som håller rapporter korrekta är geografisk. Begränsa sammanslagningar till rubrikband, sektionsavdelare och signaturblock, och håll den tabellariska mitten av bladet platt. Artikeln om mallbaserad rapportgenerering utvecklar den här layout-mot-data-uppdelningen till ett fullständigt platshållardrivet arbetsflöde, och artikeln om villkorlig formatering och rich text täcker stilarna för det platta databandet

Hur sammanslagningar degraderas på vägen ut

En sammanslagning är ett arbetsboksbegrepp, och varje textorienterat exportformat respekterar det i olika grad. Att känna till de tre beteendena i förväg sparar en QA-cykel. HTML-export återger sammanslagningar troget, och skriver ut colspan och rowspan på en enda tabell, så en webbläsarbunden rapport behåller sitt bandade utseende. RTF-export spänner inte över kolumner alls: ankartexten hamnar i sin egen cell och den återstående bredden av sammanslagningen kommer ut som tomma celler, vilket lämnar en bred rubrik visuellt tryckt åt vänster i en ordbehandlare. CSV har inget begrepp om sammanslagning, så ankarvärdet upptar ett fält och varje täckt cell skrivs ut som ett tomt fält. Slutsatsen för en arbetsbok som också matar avgränsade exporter är att hålla allt bärande utanför sammanslagen geometri; artikeln om CSV-, TSV- och HTML-export går igenom varje format i detalj

HotXLS sammanslagen titel exporterad från Delphi till HTML med colspan och rowspan, till RTF utan spann, och till CSV som utplattade fält
Samma sammanfogade rubrik överlever HTML-export via colspan och rowspan, degraderas i RTF till en ensam vänsterlåst cell och plattas ut till ett värde plus tomma fält i CSV

En försäkran för den som väger detta mot filstorlek: sammanslagningar kostar nästan ingenting i rapportskala. Sammanslagningstabellen är liten jämfört med celldatan, och att läsa en täckt cell går fortfarande via FindAt i stället för att skanna. Prestandapressen på stora arbetsböcker kommer från annat håll, huvudsakligen tillväxten av stilpoolen och minnet som sparvägen håller, vilket artikeln om prestanda för stora arbetsböcker tar upp direkt. Båda sammanslagnings-API:erna, de strukturella redigeringsoperationerna och malldemonstrationerna levereras med HotXLS Delphi Component