Il modo affidabile per produrre un report Excel stilizzato da Delphi è partire da una cartella di lavoro che un designer ha già costruito. Qualcuno in finance impagina la fattura in Excel: il logo, le intestazioni di colonna, i bordi sulla banda di dettaglio, la riga dei totali in grassetto, i formati valuta. Il tuo codice apre quel file, versa dati reali nelle celle che il designer ha riservato per essi, e salva il risultato. L'aspetto è loro; i numeri sono tuoi. HotXLS, una libreria nativa per Delphi e C++Builder che legge e scrive cartelle di lavoro XLS e XLSX senza pilotare Excel, ti dà le tre operazioni di cui questo approccio ha bisogno: cercare una cella tramite il suo testo, copiare un intervallo con i suoi stili e formule intatti, e inserire righe così che tutto ciò che sta sotto scivoli verso il basso insieme ai dati
L'unica regola che separa un generatore che sopravvive alle modifiche del template da uno che si rompe alla prima è non indirizzare mai le celle tramite numeri di riga e colonna letterali. Un template è un documento che altre persone modificano. Il team finance aggiunge una riga di tasse, alza l'altezza della riga del logo, riordina il blocco indirizzo, e il formato del file non ti aiuta per niente: un salvataggio BIFF o OOXML ha successo indipendentemente dal fatto che la riga 10 significhi ancora ciò che significava il trimestre scorso. Un generatore che scrive la prima riga di dettaglio su una riga 10 hard-coded, la prima volta che qualcuno inserisce un blocco sopra la sezione di dettaglio, timbrerà le voci sulle celle sbagliate e sommerà un intervallo di totali che non copre più i dati. Niente solleva eccezioni, ogni salvataggio restituisce successo, e l'unico segnale è un cliente che nota una fattura sbagliata
Ancora ogni coordinata a un token segnaposto
La soluzione è far sì che il template porti con sé le proprie coordinate. Il designer scrive token come {{CUSTOMER}}, {{DATE}} e {{DETAIL_START}} nelle celle che il generatore deve toccare, e il generatore calcola ogni posizione a runtime a partire da dove trova quei token. Le modifiche al layout non contano più, perché il token si sposta insieme alla cella in cui si trova. La seconda metà del contratto è la regola di fallimento: se manca un token richiesto, il job si ferma prima che qualsiasi dato del cliente raggiunga il file. Un template che è andato alla deriva dovrebbe produrre un ticket di job fallito, non un documento consegnato
Trovare i token: FindText e ReplaceText
Entrambe le famiglie di classi di HotXLS espongono una ricerca a livello di foglio di lavoro. FindText restituisce riga e colonna della prima cella il cui testo corrisponde, con un overload che aggiunge la sensibilità al maiuscolo/minuscolo. ReplaceText sostituisce ogni occorrenza e restituisce quante ne ha cambiate. Le due coprono i due tipi di token che tendi ad avere. Un'ancora singola come il nome del cliente la localizzi una volta e ci scrivi accanto; un token che dovrebbe apparire esattamente una volta, come la data del report, lo sostituisci e ne controlli il conteggio. Sul lato XLSX, un riempimento che si ancora in questo modo si presenta così:
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
R, C: Integer;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('invoice-template.xlsx') <> 1 then
raise Exception.Create('Cannot open invoice template');
Sheet := Book.Sheets[0]; // TXLSXSheets.Items è a base 0
if not Sheet.FindText('{{CUSTOMER}}', R, C) then
raise Exception.Create('Template drift: {{CUSTOMER}} anchor missing');
Sheet.Cells[R, C].Value := 'ACME Corp';
if Sheet.ReplaceText('{{DATE}}',
FormatDateTime('yyyy-mm-dd', Date)) = 0 then
raise Exception.Create('Template drift: {{DATE}} token missing');
// l'espansione del dettaglio e il salvataggio seguono sotto
finally
Book.Free;
end;
end;
Due dettagli contano. Primo, FindText e ReplaceText confrontano il valore testuale di una cella; un token incorporato dentro una stringa di formula è invisibile per loro, quindi i token segnaposto vanno messi in celle semplici, mai dentro le formule. Secondo, il conteggio delle sostituzioni è il tuo rilevatore di deriva. Un template che dovrebbe contenere esattamente un token {{DATE}} ma segnala zero sostituzioni è stato modificato, e sollevare un'eccezione in quel momento è esattamente ciò che trasforma una deriva silenziosa del layout in un fallimento visibile
Clonare la riga di dettaglio senza perdere stili o formule
La sezione di dettaglio di una fattura cresce con i dati. Scrivere valori direttamente in righe vuote sotto la riga campione butta via tutto ciò che il designer ha preparato: i bordi, i formati numerici, le formule per riga. Il pattern che conserva tutto questo è lasciare una riga campione completamente formattata nel template e clonarla per ogni voce. CopyRange duplica stili e formule in un'unica chiamata, dopodiché il generatore sovrascrive solo le celle dei valori
const
DetailRow = 10; // la riga campione formattata nel template
var
I: Integer;
begin
// Apri prima lo spazio davanti al blocco dei totali, così l'intervallo SUM
// sotto la banda di dettaglio si estende insieme ai dati.
if Length(Items) > 1 then
Sheet.InsertRows(DetailRow + 1, Length(Items) - 1);
for I := 0 to High(Items) do
begin
if I > 0 then // clona stili e formule dalla riga campione
Sheet.CopyRange(DetailRow, 1, DetailRow, 5, DetailRow + I, 1);
Sheet.Cells[DetailRow + I, 1].Value := Items[I].Name;
Sheet.Cells[DetailRow + I, 2].Value := Items[I].Qty;
Sheet.Cells[DetailRow + I, 3].Value := Items[I].UnitPrice;
Sheet.Cells[DetailRow + I, 4].Formula :=
Format('B%d*C%d', [DetailRow + I, DetailRow + I]); // nessun prefisso '='
end;
end;
Osserva attentamente l'assegnazione della formula. La proprietà Formula di XLSX prende l'espressione senza segno di uguale iniziale, mentre la facciata XLS si aspetta '=B10*C10' assegnato tramite Value. Mescolare le due convenzioni è l'errore di porting più comune tra le famiglie di classi, e fallisce senza lamentarsi: la cella contiene semplicemente una stringa letterale che Excel mostra come testo. Se il template decora la banda di dettaglio con righe di titolo unite, ricorda che solo la cella in alto a sinistra di un'area unita porta un valore. Le regole di layout nell'articolo di approfondimento sulle celle unite nei template di report guidati dal layout spiegano perché le regioni unite vanno tenute del tutto fuori dalla banda dati
Cosa sposta InsertRows, e cosa lascia indietro
Inserire righe davanti al blocco dei totali è ciò che mantiene un intervallo SUM che si estende man mano che cresce la sezione di dettaglio. Sul lato XLSX, InsertRows porta con sé una lunga lista di strutture dipendenti insieme alle celle: intervalli uniti, altezze di riga, collegamenti ipertestuali, commenti, riquadri bloccati, intervalli di autofiltro, formattazioni condizionali, validazioni dei dati, tabelle, nomi definiti, e ancore di immagini e grafici. C'è un confine in quella lista che vale la pena memorizzare. La riscrittura delle formule raggiunge solo i riferimenti all'interno dello stesso foglio. Una formula su un foglio di riepilogo che punta nella regione spostata mantiene le sue vecchie coordinate e legge silenziosamente le celle sbagliate, motivo per cui i totali estratti tra fogli sono più sicuri se espressi tramite nomi a livello di cartella di lavoro. L'articolo di approfondimento sui nomi definiti e le formule tra fogli approfondisce questo pattern
Il formato XLS legacy traccia il confine in un punto più rigido. HotXLS mantiene le tabelle pivot, le tabelle di query e le connessioni a dati esterni nei file BIFF come blocchi di byte grezzi. Sopravvivono ad apertura e salvataggio senza modifiche, ma non sono modellate, quindi l'inserimento di righe non le tocca mai. Un template che colloca una tabella pivot sotto un blocco di dettaglio in espansione si salva senza alcun avviso mentre il rettangolo sorgente della pivot si allontana dai dati. La via d'uscita è strutturale, non difensiva: mantieni il contenuto pivot e query su fogli in cui il generatore non inserisce mai righe, e l'obsolescenza non potrà accadere
Ricalcola prima della consegna, oppure sappi perché l'hai saltato
HotXLS non valuta le formule durante SaveAs. Quando una persona apre il file, Excel ricalcola tutto (la facciata XLS espone CalculationMode e RecalcOnSave se hai bisogno di governare questo comportamento), quindi un report destinato a una casella di posta umana non richiede altro da te. Il quadro cambia nel momento in cui la cartella di lavoro alimenta un altro programma. L'esportazione CSV scrive le formule come loro testo letterale e non le calcola mai, e qualsiasi parser a valle che si fida dei valori in cache leggerà numeri obsoleti o vuoti. Per quei percorsi, calcola sul server con Calculate, che valuta un'espressione arbitraria contro la cartella di lavoro caricata e restituisce il risultato:
var
Total: Variant;
LastDetail: Integer;
begin
LastDetail := DetailRow + Length(Items) - 1;
Total := Book.Calculate(Format('SUM(Invoice!D%d:D%d)',
[DetailRow, LastDetail]));
if (not VarIsNumeric(Total)) or
(Abs(Total - ExpectedTotal) > 0.005) then
raise Exception.Create('Invoice total does not match the order record');
if Book.SaveAs('invoice-2026-0611.xlsx') <> 1 then
raise Exception.Create('Save failed: check output path and permissions');
end;
Controllare il totale calcolato rispetto al record dell'ordine prima del salvataggio è un'assicurazione economica con un buon ritorno. Trasforma una fattura sbagliata in un job fallito. Un operatore può ritentare un job fallito in pochi secondi; una fattura sbagliata già nella casella di posta di un cliente costa a un account manager delle scuse e una correzione
Due famiglie di classi, un solo algoritmo
La stessa logica si porta tra i formati, ma non lo stesso codice. TXLSWorkbook per il vecchio .xls è basato su interfaccia e a conteggio di riferimenti, con indicizzazione dei fogli a base 1, e non lo liberi mai a mano. TXLSXWorkbook per .xlsx è un oggetto semplice che devi liberare in un try..finally, con indicizzazione dei fogli a base 0 e la convenzione per le formule mostrata sopra. FindText, ReplaceText, CopyRange e InsertRows vivono tutti su entrambi i lati, quindi lo schema ancora-clona-ricalcola si trasferisce senza intoppi. Il consiglio pratico è impegnarti su un formato per pipeline, oppure nascondere i due cicli di vita degli oggetti dietro un tuo sottile adattatore invece di disseminare la differenza in tutto il generatore
La dimensione raramente conta per il tipo di report che produce questo pattern. Clonare una riga stilizzata qualche migliaio di volte non è nulla per l'hardware attuale. Il percorso di salvataggio diventa il collo di bottiglia solo quando una banda di dettaglio raggiunge le sei cifre di righe, e a quel punto impostare StreamingWrite invia l'XML del foglio di lavoro direttamente nel pacchetto di output invece di bufferizzarlo; l'articolo sulla scrittura in streaming per job batch server tratta quando vale la pena fare questo compromesso. I grafici si comportano come il resto del layout: sul lato XLSX sia l'ancora del grafico sia i riferimenti alla sua serie si spostano quando InsertRows agisce sopra di essi, così un grafico sotto la riga dei totali resta legato ai dati giusti, mentre sul lato XLS i grafici risiedono sui propri fogli grafico e, come le tabelle pivot, non si spostano mai. Questo è un motivo in più per tenere i fogli di presentazione lontani dal foglio che il generatore espande
Questo approccio ancora-clona-ricalcola lascia che un designer possieda come appare una cartella di lavoro mentre il tuo codice possiede cosa dice, il che di solito è ciò che rende l'output Excel generato degno di essere mantenuto. Le chiamate di ricerca, copia e inserimento mostrate qui, insieme al motore di formule usato per il controllo del totale prima della consegna, sono incluse in HotXLS Delphi Component per Delphi e C++Builder