Le ticket support dit : « Le classeur de feuille de temps que vous générez est cassé — nos employés ne peuvent plus saisir les heures. » Une seule chose a changé dans la dernière version : le générateur a commencé à appeler Protect sur la feuille. Personne n'a touché la colonne des heures, et c'est précisément le bug. Dans le modèle de cellules OOXML, chaque cellule porte Locked = True par défaut ; l'indicateur reste dormant jusqu'à ce que la protection de feuille s'active, et à cet instant toute la grille se fige d'un coup. HotXLS, bibliothèque de tableur native Delphi et C++Builder, expose toute la surface de protection et de mise en page des formats .xls et .xlsx — ce qui signifie qu'il reproduit aussi fidèlement chaque sémantique Excel contre-intuitive de cette surface. Cet article parcourt celles qui génèrent des tickets.
Chaque cellule naît verrouillée
ECMA-376 définit locked comme partie de l'enregistrement de formatage d'une cellule, pas comme propriété de la protection elle-même, et sa valeur par défaut est true. La protection de feuille n'est que l'interrupteur qui rend l'indicateur applicable. Le bon ordre de génération est donc : construire la mise en page, déverrouiller explicitement les plages que les utilisateurs doivent modifier, puis seulement protéger :
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Timesheet');
// ... header row, name column, and rate formulas written here ...
Sheet.Range['B2:B50'].SetLocked(False); // staff type hours here
Sheet.Range['F2:F50'].SetFormulaHidden(True); // keep the rate math private
Sheet.Protect('review-2026'); // now the lock flags bite
Book.SaveAs('timesheet.xlsx');
finally
Book.Free;
end;
SetFormulaHidden mérite une note : tant que la protection est active, la cellule affiche toujours sa valeur calculée mais la barre de formule ne montre rien. C'est important lorsque les formules incorporent des taux de facturation, marges ou pondérations de score que vous préférez ne pas remettre à chaque destinataire qui clique un total. Sur la façade XLS, la même intention s'exprime par plage avec IXLSRange.Locked et FormulaHidden, et la feuille ajoute quinze indicateurs Allow* — AllowSort, AllowAutoFilter, AllowFormatCells et leurs voisins — afin qu'une feuille protégée reste utilisable pour trier et filtrer au lieu de devenir une vitrine scellée.
Ce qu'est le mot de passe de protection, et ce qu'il n'est pas
Les deux formats stockent le mot de passe de protection de feuille et de classeur comme un hash legacy sur 4 chiffres hexadécimaux. Seize bits signifient que d'innombrables chaînes entrent en collision avec un mot de passe donné, et les outils de retrait se trouvent en une recherche ; traitez donc la protection comme une ceinture de sécurité contre les éditions accidentelles — jamais comme un contrôle d'accès. C'est le bon outil pour « empêcher les relecteurs d'écraser la colonne de formule » et le mauvais outil pour tout ce qui contient le mot confidentiel.
Un niveau au-dessus, ProtectWorkbook sur la façade XLSX verrouille la structure du classeur : impossible d'ajouter, renommer, supprimer ou réordonner les feuilles. Cela vaut la peine dès que la liste des feuilles est elle-même un contrat avec un parseur aval qui indexe les feuilles par nom ou position — une feuille renommée casse l'import de l'autre côté aussi sûrement qu'une colonne supprimée. La façade XLS reflète cette couche avec TXLSWorkbook.Protect au niveau classeur et des appels Protect par feuille, avec isProtected disponible pour le code qui doit inspecter des fichiers hérités avant de les modifier.
Lorsque l'exigence est une vraie confidentialité, le mécanisme change entièrement : SaveAsEncrypted produit un package chiffré AES selon le schéma ECMA-376 Standard Encryption, couvert en détail dans le guide de sortie XLSX protégée par AES, et la façade XLS legacy écrit et lit des fichiers .xls chiffrés RC4 via EncryptionPassword et la surcharge avec mot de passe de Open. La distinction n'est pas académique — une feuille protégée circule en clair et n'importe quel outil zip peut lire ses valeurs de cellules, tandis qu'un package chiffré est illisible sans le mot de passe. Les exigences d'audit qui disent « le fichier de paie doit être protégé » veulent presque toujours le second mécanisme, quel que soit le vocabulaire employé.
La mise en page fait partie du contrat documentaire
Le comportement d'impression est invisible à l'écran, raison pour laquelle il est si souvent livré cassé. Dès qu'un client imprime le classeur — ou l'exporte en PDF pour un auditeur — marges, mise à l'échelle et titres répétés deviennent des exigences fonctionnelles. Sur la façade XLSX, les réglages pendent directement de la feuille :
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'; // bare reference: no sheet name here
Sheet.PrintTitleRows := '$1:$1'; // header row repeats on every page
Sheet.FitToWidth := 1;
Sheet.FitToHeight := 0; // grow downward as the data grows
Sheet.PrintGridlines := False;
Deux de ces lignes cachent des pièges. Les chaînes d'en-tête et de pied utilisent les codes de formatage d'Excel — &P pour la page courante, &N pour le nombre total, avec &L, &C et &R disponibles pour adresser explicitement les trois sections. Et PrintArea prend volontairement une référence de cellules nue : HotXLS la stocke non qualifiée et préfixe le nom de feuille lors de l'écriture du fichier ; passer vous-même 'Timesheet!$A$1:$F$60' produit donc une référence mal formée. Dans la même famille, zones d'impression et titres d'impression sont persistés comme les noms définis intégrés _xlnm.Print_Area et _xlnm.Print_Titles — n'ajoutez jamais d'entrées _xlnm.* manuellement via DefinedNames, sinon les deux mécanismes se disputeront le même emplacement.
Une mise à l'échelle qui survit aux volumes de production
La combinaison FitToWidth := 1 avec FitToHeight := 0 se lit comme « toujours ajuster les colonnes sur une page en largeur, puis prendre autant de pages en hauteur que nécessaire », et c'est la valeur par défaut correcte pour tout rapport dont le nombre de lignes varie. Le piège consiste à régler un pourcentage fixe ou une paire fit-to-page sur un fichier de test de trente lignes : donnez les mêmes réglages à six cents lignes de production et la sortie explose en dizaines de pages coupées ou se réduit sous le seuil de lisibilité. Ajustez la largeur, laissez la longueur croître, et répétez la ligne d'en-tête via PrintTitleRows pour que la page dix-sept reste lisible seule.
Les sauts manuels suivent la même discipline de régénération que tout le reste dans les classeurs générés. AddRowBreak(BeforeRow) démarre une nouvelle page avant une frontière de section, mais lorsque le générateur tourne à nouveau et que les lignes se décalent, les anciens sauts tombent au milieu du tableau ; appelez donc ClearAllPageBreaks d'abord et recréez les sauts depuis les propres compteurs de lignes du générateur plutôt que de patcher d'anciennes positions. Sur la façade XLS, les contrôles équivalents vivent sur Sheet.PageSetup (orientation, taille papier, marges, chaînes d'en-tête et de pied, ajustement aux pages), avec RepeatRows et RepeatColumns pour les titres d'impression.
Vérifier le résultat avant le client
Les bugs de protection et d'impression partagent une propriété : ils sont triviaux à vérifier manuellement et presque jamais vérifiés. Ouvrez le fichier généré dans Excel et prenez quatre-vingt-dix secondes — saisissez dans une cellule d'entrée (doit réussir), saisissez dans une cellule verrouillée (doit afficher l'invite de protection), vérifiez que les formules masquées montrent une barre de formule vide, et lancez l'aperçu avant impression sur un jeu de données de taille production pour confirmer le nombre de pages, la ligne de titre répétée et la numérotation du pied. L'étape d'aperçu est celle qui se rembourse : la géométrie d'impression dépend de réglages qui n'ont aucun rendu à l'écran, c'est donc le seul endroit, hors imprimante physique, où une erreur d'échelle devient visible.
Deux réglages adjacents complètent l'expérience de revue. FreezePane(ACol, ARow) garde le bloc d'en-tête visible pendant qu'un relecteur fait défiler — comportement écran, pas impression, mais les relecteurs jugent le livrable entier ensemble. Et les classeurs qui commencent comme mises en page maintenues par un concepteur obtiennent gratuitement la plupart de cet article : le flux de génération de rapports par modèle garde la mise en page dans le modèle, où un humain l'a réglée contre une vraie imprimante, laissant le code remplir les données et réappliquer la protection une fois la mise en page stabilisée.
Questions fréquentes
Pourquoi toute la feuille est-elle en lecture seule après l'appel à Protect ?
Parce que chaque cellule vaut Locked = True par défaut et que la protection active l'indicateur globalement. Appelez SetLocked(False) sur les plages de saisie avant Protect, pas après.
Le mot de passe de protection de feuille est-il sécurisé ?
Non. Il est stocké comme hash legacy 16 bits qui entre en collision et se retire trivialement. Utilisez-le pour prévenir les accidents ; utilisez le chiffrement de fichier AES lorsque le contenu lui-même doit être protégé.
Comment répéter la ligne d'en-tête sur chaque page imprimée ?
Définissez PrintTitleRows := '$1:$1' sur la façade XLSX, ou appelez RepeatRows(0, 0) sur la feuille de la façade XLS — les deux persistent comme nom défini _xlnm.Print_Titles lu par Excel.
HotXLS est une bibliothèque native Object Pascal de tableur pour Delphi et C++Builder ; la référence complète de l'API de protection et de mise en page se trouve sur la page produit HotXLS Component.