Articolo tecnico

HotXLS: protection, page setup, and printing in Delphi

Tre gruppi di impostazioni del foglio di lavoro non hanno nulla a che fare con i valori delle celle e tutto a che fare con come si comporta il file una volta uscito dal tuo codice. La protezione del foglio decide quali celle un utente può modificare dopo che hai consegnato la cartella di lavoro. L'impostazione di pagina fissa orientamento, formato carta e margini. Le impostazioni di stampa (righe di titolo ripetute, scalatura e interruzioni di pagina manuali) controllano come una griglia di lunghezza arbitraria finisce sulla carta. Nessuna delle tre si nota quando osservi i dati a occhio in un visualizzatore, e tutte e tre si rompono silenziosamente sul campo quando sono sbagliate. HotXLS, una libreria nativa per fogli di calcolo per Delphi e C++Builder, espone la superficie completa per .xls e .xlsx, il che significa che riproduce anche ogni regola controintuitiva di Excel incorporata in quella superficie

La prima di queste regole inganna quasi tutti la prima volta che proteggono un foglio generato. Chiama Protect e all'improvviso nessuno può digitare in nessuna cella, incluse le colonne di input attorno a cui hai costruito la cartella di lavoro. Nulla nel tuo codice ha toccato quelle colonne, ed è esattamente per questo che succede

Ogni cella nasce bloccata

ECMA-376 definisce locked come parte del record di formattazione di una cella, non come proprietà della protezione in sé, e per default vale true. La protezione del foglio è semplicemente l'interruttore che rende il flag applicabile. Quindi l'intera griglia porta un flag di blocco fin dal momento in cui esiste, dormiente, e la chiamata a Protect li attiva tutti in una volta. La soluzione è impostare l'ordine deliberatamente: costruisci il layout, sblocca esplicitamente gli intervalli che gli utenti devono modificare, e proteggi per ultimo

Diagramma dell'ordine di protezione HotXLS in Delphi dove ogni cella nasce con locked true, gli intervalli di input vengono prima sbloccati con SetLocked, e Sheet.Protect chiamato per ultimo mantiene modificabili le celle sbloccate
Le celle arrivano bloccate per default, quindi sbloccare prima gli intervalli di input e chiamare Protect per ultimo per tenerli modificabili
Book := TXLSXWorkbook.Create;
try
  Sheet := Book.Sheets.Add('Timesheet');
  // ... riga di intestazione, colonna nomi e formule di tariffa scritte qui ...
  Sheet.Range['B2:B50'].SetLocked(False);         // il personale digita qui le ore
  Sheet.Range['F2:F50'].SetFormulaHidden(True);   // mantieni privata la matematica delle tariffe
  Sheet.Protect('review-2026');                   // ora i flag di blocco entrano in azione
  Book.SaveAs('timesheet.xlsx');
finally
  Book.Free;
end;

SetFormulaHidden fa qualcosa di separato e facile da trascurare: mentre la protezione è attiva, la cella mostra ancora il suo valore calcolato, ma la barra della formula non mostra nulla. Questo conta quando una formula incorpora tariffe di fatturazione, margini o pesi di punteggio che preferiresti non consegnare a ogni destinatario che clicca su un totale. Sulla facciata XLS la stessa intenzione si esprime per intervallo tramite IXLSRange.Locked e FormulaHidden. Il foglio di lavoro lì porta anche quindici flag Allow* (AllowSort, AllowAutoFilter, AllowFormatCells e gli altri), così un foglio protetto può comunque essere ordinato e filtrato invece di restare congelato come un reperto sigillato

Cosa protegge davvero la password di protezione

Entrambi i formati memorizzano la password di protezione del foglio e della cartella di lavoro come un hash legacy a 4 cifre esadecimali. Sedici bit significano che innumerevoli stringhe collidono con una data password, e gli strumenti di rimozione sono a una ricerca di distanza. Considera la protezione come una cintura di sicurezza contro modifiche accidentali, non come controllo degli accessi. È lo strumento giusto per impedire ai revisori di scrivere sopra la colonna delle formule ed è lo strumento sbagliato per qualsiasi cosa coinvolga la parola riservato

Un livello più su, ProtectWorkbook sulla facciata XLSX blocca la struttura della cartella di lavoro, il che impedisce di aggiungere, rinominare, eliminare o riordinare i fogli. Impostalo ogni volta che l'elenco dei fogli è esso stesso un contratto con un parser a valle che indicizza i fogli per nome o posizione. Un foglio rinominato rompe l'importazione dall'altra parte esattamente come farebbe una colonna eliminata. La facciata XLS rispecchia questa stratificazione con TXLSWorkbook.Protect a livello di cartella di lavoro e chiamate Protect per singolo foglio, più una proprietà isProtected per il codice che deve ispezionare un file ereditato prima di modificarlo

Quando il requisito è una riservatezza reale, il meccanismo cambia del tutto. SaveAsEncrypted produce un pacchetto cifrato AES secondo lo schema ECMA-376 Standard Encryption, trattato in dettaglio nella guida all'output XLSX protetto con AES, e la facciata XLS legacy scrive e legge file .xls cifrati con RC4 tramite EncryptionPassword e l'overload con password di Open. La differenza non è accademica. Un foglio protetto viaggia in chiaro, quindi qualsiasi strumento zip può leggerne i valori delle celle, mentre un pacchetto cifrato è illeggibile senza la password. Una riga di audit che dice "il file delle buste paga deve essere protetto" quasi sempre significa cifratura, qualunque vocabolario capiti di usare

Diagramma che contrappone la protezione del foglio HotXLS, che memorizza un hash legacy a 16 bit e lascia i valori delle celle in chiaro leggibili da qualsiasi strumento zip, con l'output AES SaveAsEncrypted che resta illeggibile senza la password
La protezione del foglio è una cintura di sicurezza contro modifiche accidentali mentre i valori in chiaro restano leggibili, e solo la crittografia AES nasconde i contenuti

L'impostazione di pagina fa parte del contratto del documento

Il comportamento di stampa è invisibile a schermo, ed è per questo che viene rilasciato rotto così spesso. Nel momento in cui un cliente stampa la cartella di lavoro, o la esporta in PDF per un revisore contabile, margini, scalatura e titoli ripetuti diventano requisiti funzionali che nessuno ha testato. Sulla facciata XLSX queste impostazioni pendono direttamente dal foglio di lavoro:

Sheet.PageLandscape := True;
Sheet.PaperSize := xlsxPaperA4;
Sheet.SetPageMargins(0.5, 0.5, 0.75, 0.75, 0.3, 0.3);
Sheet.CenterHeader := 'Monthly Timesheet';
Sheet.RightFooter := 'Page &P of &N';
Sheet.PrintArea := '$A$1:$F$60';     // riferimento nudo: qui nessun nome di foglio
Sheet.PrintTitleRows := '$1:$1';     // la riga di intestazione si ripete su ogni pagina
Sheet.FitToWidth := 1;
Sheet.FitToHeight := 0;              // cresce verso il basso man mano che i dati crescono
Sheet.PrintGridlines := False;

Due di queste righe nascondono trappole. Le stringhe di intestazione e piè di pagina usano i codici di formattazione di Excel: &P per la pagina corrente, &N per il conteggio totale, con &L, &C e &R per indirizzare esplicitamente le tre sezioni. L'altra trappola è PrintArea, che accetta di proposito un riferimento di cella nudo. HotXLS lo memorizza non qualificato e antepone il nome del foglio quando scrive il file, quindi passare tu stesso 'Timesheet!$A$1:$F$60' produce un riferimento doppiamente qualificato e malformato. La stessa cautela si applica un livello più sotto: le aree di stampa e i titoli di stampa vengono persistiti come i nomi definiti integrati _xlnm.Print_Area e _xlnm.Print_Titles, quindi non aggiungere mai a mano voci _xlnm.* tramite DefinedNames, altrimenti i due meccanismi si contenderanno lo stesso slot

Una scalatura che regge i volumi di dati di produzione

La combinazione FitToWidth := 1 con FitToHeight := 0 si legge come "adatta sempre le colonne a una pagina, poi usa tutte le pagine in verticale che servono ai dati", ed è il default corretto per qualsiasi report il cui numero di righe varia. La trappola è tarare una percentuale fissa o una coppia fit-to-page su un file di test da trenta righe: dai in pasto le stesse impostazioni a seicento righe di produzione e l'output o esplode in dozzine di pagine tagliate o si restringe sotto la soglia di leggibilità. Scala la larghezza, lascia crescere la lunghezza, e ripeti la riga di intestazione tramite PrintTitleRows così che pagina diciassette resti leggibile da sola

Diagramma dello scaling di stampa HotXLS in Delphi con FitToWidth impostato a 1 così ogni pagina resta larga un foglio, FitToHeight impostato a 0 così le pagine crescono verso il basso, PrintTitleRows che ripete la fascia di intestazione, e interruzioni di pagina rigenerate dopo ClearAllPageBreaks
FitToWidth 1 con FitToHeight 0 tiene ogni pagina larga un foglio mentre righe titolo ripetute e interruzioni rigenerate preservano la leggibilità

Le interruzioni manuali seguono la stessa disciplina di rigenerazione di tutto il resto in una cartella di lavoro generata. AddRowBreak(BeforeRow) inizia una nuova pagina prima di un confine di sezione, ma quando il generatore viene rieseguito e le righe si spostano, un'interruzione obsoleta finisce in mezzo alla tabella. Chiama prima ClearAllPageBreaks, poi riaggiungi le interruzioni calcolate dai contatori di riga propri del generatore invece di correggere le vecchie posizioni. Sulla facciata XLS i controlli equivalenti risiedono su Sheet.PageSetup (orientamento, formato carta, margini, stringhe di intestazione e piè di pagina, fit-to-pages), con RepeatRows e RepeatColumns che coprono i titoli di stampa

Verificare il risultato prima che lo faccia un cliente

I bug di protezione e di stampa condividono una proprietà: sono banali da verificare a mano e quasi mai verificati. Apri il file generato in Excel e dedicagli novanta secondi. Digita in una cella di input e conferma che accetti il tasto premuto; digita in una cella bloccata e conferma che appaia il prompt di protezione; controlla che una formula nascosta lasci vuota la barra della formula. Poi esegui Anteprima di stampa su un dataset delle dimensioni di produzione, non un campione di trenta righe, e leggi il numero di pagine, la riga di titolo ripetuta e la numerazione del piè di pagina. L'anteprima è il passaggio che si ripaga da solo, perché la geometria di stampa dipende da impostazioni senza alcun rendering a schermo, e a parte una stampante fisica è l'unico posto in cui un errore di scalatura diventa mai visibile

Un'ultima impostazione completa la revisione. FreezePane(ACol, ARow) mantiene visibile il blocco di intestazione mentre un revisore scorre. Questo è comportamento a schermo piuttosto che comportamento di stampa, ma un revisore giudica l'intero deliverable in una volta sola. E una cartella di lavoro che inizia la sua vita come layout mantenuto da un designer ottiene gran parte di questo gratuitamente: il flusso di generazione di report da template mantiene l'impostazione di pagina nel template, dove un essere umano l'ha tarata su una stampante reale, e lascia al codice il compito di riempire i dati e riapplicare la protezione una volta stabilizzato il layout

HotXLS è una libreria Object Pascal nativa per fogli di calcolo per Delphi e C++Builder; il riferimento API completo per protezione e impostazione di pagina si trova sulla pagina prodotto di HotXLS Delphi Component