Itera le celle di un template di report appena aperto e un titolo unito si comporta come una perdita. Leggi A1 e ottieni "Quarterly Statement"; leggi da B1 a F1, che visivamente stanno sotto lo stesso banner, e non ottieni nulla. Scrivi un valore in C1 per correggere l'intestazione e non compare mai a schermo. La griglia non ha perso i tuoi dati. Sta facendo esattamente ciò che significa un merge: sia in XLS che in XLSX, un rettangolo unito renderizza il contenuto di una sola cella, l'ancora in alto a sinistra, e tratta il resto come spazio coperto che contiene valori ma non li mostra mai. Gli utenti di Excel assorbono questo tramite tentativi ed errori. Un generatore di report deve codificarlo come una regola, perché nel codice generato il sintomo è una regione vuota senza alcuna eccezione a cui risalire. HotXLS, una libreria Object Pascal nativa che legge e scrive entrambi i formati Excel da Delphi e C++Builder, espone la tabella dei merge in modo abbastanza esplicito da poter programmare contro la regola invece di riscoprirla in un ticket di supporto
Un valore, un'ancora
Un merge è un'istruzione di visualizzazione sovrapposta a una griglia che non cambia forma. Ogni cella coperta esiste ancora nel file come proprio slot; il record del merge dice solo a un consumatore di dipingere il contenuto dell'ancora sull'intero rettangolo. Questa distinzione guida tre comportamenti che vale la pena interiorizzare prima di scrivere qualsiasi codice di layout. Leggere una cella coperta restituisce il proprio valore memorizzato, che per un banner che hai costruito è di solito vuoto, quindi qualsiasi codice che ispeziona un titolo unito deve risolvere e leggere l'ancora. Scrivere in una cella coperta ha successo a livello di file e non compare da nessuna parte, che è la trappola dell'intestazione invisibile dell'apertura. E separare una regione espone qualunque cosa fosse rimasta sotto per tutto il tempo, quindi un valore vagante scritto nello spazio coperto diventa un difetto visibile il giorno in cui qualcuno dissolve il merge
Sul lato XLSX quella tabella è un oggetto di prima classe. Sheet.MergedCells offre Add('A1:C1'), FindAt(Row, Col), DeleteAt e Items, e la chiamata a cui ricorri più spesso è FindAt: passale una qualsiasi coordinata e restituisce la regione unita che copre quella cella, oppure nil quando la cella è isolata. Questa singola ricerca è il fondamento di entrambe le metà della gestione corretta dei merge, la lettura sicura e la protezione in scrittura, ed entrambe compaiono più avanti
Due facciate, due idiomi di merge
HotXLS mantiene il motore BIFF8 classico .xls e il motore OOXML .xlsx come modelli a oggetti separati, e i due esprimono il merge in modo diverso perché discendono da convenzioni diverse. La facciata XLS segue l'idioma COM di Excel: prendi un intervallo da una proprietà indicizzata a due argomenti e chiami Merge con un OleVariant il cui valore decide la geometria finale
var
Book: IXLSWorkbook; // conteggio a interfaccia: nessun Free manuale
Sh: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
Sh := Book.Sheets[1]; // la collezione di fogli XLS è a base 1
Sh.Range['A1', 'F1'].Merge(False); // False = un singolo blocco unito
Sh.Cells.Item[1, 1].Value := 'Quarterly Statement';
Sh.Range['A3', 'F4'].Merge(True); // True = merge across: un merge per riga
Book.SaveAs('layout.xls');
end;
L'argomento di Merge è la parte che la gente sbaglia. Su un intervallo di due righe, Merge(True) produce due merge indipendenti a riga singola, che è il "Merge Across" di Excel ed esattamente ciò che vuoi per una banda di intestazione impilata le cui righe devono restare separabili. Merge(False) fonde l'intero rettangolo in un singolo blocco. L'intervallo riporta anche MergeCells come flag di stato, restituisce la regione che lo contiene tramite MergeArea, e si dissolve da solo con Unmerge. La facciata XLSX espone le stesse operazioni con nomi diversi: Sheet.MergeCells(Row1, Col1, Row2, Col2) accetta limiti interi, TXLSXRange.Merge accetta la variante equivalente Across, e la collezione MergedCells contiene il risultato
Un template che cresce con i suoi dati
Un template di report reale non è una griglia fissa. L'intestazione e i totali sono fissi, ma la sezione di dettaglio tra di essi si allunga in base a ciò che restituisce la query. Il pattern che regge mantiene una riga di dettaglio completamente stilizzata nel template, la clona una volta per record, e poi apre uno spazio davanti al blocco dei totali così che tutto ciò che è ancorato sotto scivola verso il basso senza perdere la formattazione
Sheet.Range['A1:F1'].Merge;
Sheet.Cells[1, 1].Value := 'INVOICE #2026-0611'; // il valore va nell'ancora, A1
Sheet.RowHeight[1] := 28;
TitleFont := Book.Fonts.Add('Calibri', 16, True, False);
Sheet.Cells[1, 1].FontIndex := TitleFont + 1; // indice del pool a base 0, lato cella a base 1
// la riga 5 è la riga di template di dettaglio stilizzata
for I := 0 to ItemCount - 1 do
Sheet.CopyRange(5, 1, 5, 6, 6 + I, 1); // stili e formule viaggiano con essa
// apre uno spazio sopra il blocco dei totali; il contenuto sotto si sposta in basso
Sheet.InsertRows(6 + ItemCount, 1);
Sheet.Range['A1:F1'].SetBorders(xlsxEdgeOutline, xlsxBorderMedium);
Due righe meritano un secondo sguardo. L'assegnazione del font porta con sé un off-by-one che morde silenziosamente: Fonts.Add restituisce una posizione nel pool a base 0, mentre una cella memorizza un riferimento al font a base 1 dove 0 significa il font predefinito, quindi omettere il + 1 non solleva nulla, semplicemente stila il tuo titolo nel carattere sbagliato. L'altra riga è CopyRange, che sposta formattazione e formule insieme ai valori. Questo è l'intero motivo per clonare una riga di template costruita a mano invece di ricostruirne l'aspetto nel codice. Un designer possiede l'aspetto una sola volta, nel template; il generatore si limita sempre a versare dati in copie di esso
Questa separazione scala ulteriormente quando il layout riutilizzabile vive nella propria cartella di lavoro, per esempio un foglio di bande di intestazione e piè di pagina condivise tra i report. CopyRangeTo esegue lo stesso clone attraverso i confini dei fogli di lavoro, accettando un foglio di destinazione più le coordinate di destinazione, così un generatore può mantenere un foglio template incontaminato e stampare le sue regioni in tanti fogli di output quanti ne servono a un job. L'alternativa, modificare il template sul posto e provare a ripristinarlo in seguito, è il genere di cosa che funziona finché un giorno un'esecuzione non si interrompe a metà
Cosa sposta InsertRows, e cosa non sposta
Il pattern di crescita del template funziona solo perché InsertRows di XLSX è una modifica strutturale e non un semplice spostamento di celle. Quando apre uno spazio rilocalizza le regioni unite, le altezze di riga, i collegamenti ipertestuali, i commenti, i riquadri bloccati, gli intervalli di autofiltro, le formattazioni condizionali, le validazioni dei dati, le tabelle, i nomi definiti, le ancore delle immagini e le ancore dei grafici che si trovano sotto il punto di inserimento, non solo i valori delle celle. È questo che permette al blocco dei totali di arrivare alla sua nuova riga con i merge e i formati numerici intatti invece di arrivare spogliato
I suoi due limiti documentati sono quelli attorno a cui progettare. La regolazione delle formule è limitata al foglio in fase di modifica: i riferimenti interni a quel foglio vengono riscritti, e anche una formula su un altro foglio che punta nell'area spostata viene riscritta, ma la regolazione segue solo i riferimenti che puntano al foglio modificato, quindi qualsiasi schema di riferimento tra cartelle di lavoro merita un audit proprio invece di una fiducia cieca. Il secondo limite è più netto, ed è sul lato XLS. Le tabelle pivot sopravvivono ai cicli di apertura e salvataggio come record grezzi preservati, non come oggetti modellati che HotXLS può spostare, quindi inserire righe non rilocalizza l'ingombro di una pivot. Qualsiasi template che costruisci per il formato .xls dovrebbe tenere le sue regioni pivot ben lontane da qualsiasi banda che cresce
Rifiutare di scrivere dati nello spazio di layout
Il fallimento delle celle unite che arriva davvero in produzione non è quello estetico. È strutturale: una riga di dettaglio finisce dentro una banda di layout unita, i suoi valori atterrano in celle coperte e diventano invisibili, e i totali di colonna silenziosamente smettono di corrispondere a ciò che chiunque legga il foglio può vedere. Poiché FindAt risponde alla domanda sulla regione di copertura per qualsiasi coordinata, il generatore può rifiutare quella scrittura nel momento in cui avverrebbe invece di spedire un report che sottoconta silenziosamente
// rifiuta di scrivere dati di dettaglio in una regione di layout unita
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;
Lo stesso controllo di confine vale ovunque un utente in seguito ordinerà o filtrerà l'output. Un intervallo con merge al suo interno non può essere ordinato in modo pulito, perché l'ordinamento sposta le righe indipendentemente e un merge che attraversa più righe non ha un'unica riga con cui viaggiare; Excel risponde con un errore o un layout scombinato. La disciplina che mantiene corretti i report è geografica. Confina i merge a bande di titolo, separatori di sezione e blocchi firma, e mantieni piatta la parte tabellare centrale del foglio. L'articolo sulla generazione di report da template sviluppa questa separazione tra layout e dati in un flusso di lavoro completo guidato da segnaposto, e l'articolo sulla formattazione condizionale e il rich text tratta la stilizzazione di quella banda dati piatta
Come i merge degradano in uscita
Un merge è un concetto di cartella di lavoro, e ogni formato di esportazione orientato al testo lo onora in misura diversa. Conoscere in anticipo i tre comportamenti risparmia un ciclo di QA. L'esportazione HTML riproduce i merge fedelmente, emettendo colspan e rowspan su un'unica tabella, così un report destinato al browser mantiene il suo aspetto a bande. L'esportazione RTF non estende affatto le colonne: il testo dell'ancora finisce nella propria cella e la larghezza rimanente del merge esce come celle vuote, il che lascia un titolo largo visivamente spinto a sinistra in un elaboratore di testi. Il CSV non ha alcun concetto di merge, quindi il valore dell'ancora occupa un campo e ogni cella coperta viene emessa come campo vuoto. La lezione per una cartella di lavoro che alimenta anche esportazioni delimitate è tenere tutto ciò che è strutturalmente rilevante fuori dalla geometria dei merge; l'articolo su esportazione CSV, TSV e HTML illustra ogni formato in dettaglio
Una rassicurazione per chiunque stia valutando questo aspetto rispetto alla dimensione del file: i merge costano quasi nulla alla scala di un report. La tabella dei merge è minuscola rispetto ai dati delle celle, e leggere una cella coperta passa comunque attraverso FindAt invece di una scansione. La pressione sulle prestazioni nelle cartelle di lavoro di grandi dimensioni viene da altrove, principalmente dalla crescita del pool di stili e dalla memoria trattenuta dal percorso di salvataggio, argomento che l'articolo sulle prestazioni delle cartelle di lavoro di grandi dimensioni affronta direttamente. Entrambe le API dei merge, le operazioni di modifica strutturale e le demo dei template sono incluse in HotXLS Delphi Component