HotXLS stocke les sélections de feuille et les positions de défilement par volet à travers une seule API consciente des volets, à la fois sur TXLSWorksheet et TXLSXWorksheet : SelectAreas, GetSelectedAreas, ScrollWindow et TryGetWindowScroll. Pour les fichiers .xls classiques, HotXLS écrit des enregistrements Selection BIFF8 (0x001D) de 1369 zones au plus chacun, convertit les noms logiques de volets en octets de volet définis par le format, et garde chaque axe de défilement sur l'enregistrement Window2 ou Pane où Excel l'attend
Le problème apparaît d'ordinaire dans un outil de rapprochement ou d'audit. L'outil ouvre un export de grand livre, repère chaque cellule en désaccord avec le système source, et sauvegarde le classeur avec ces cellules déjà sélectionnées sous une ligne d'en-tête figée, si bien que le relecteur tombe sur les différences au lieu de les chercher en faisant défiler. Avec quarante différences, cela fonctionne bien. Le fichier de fin de mois en compte 3 000, et un unique enregistrement Selection contenant 3 000 zones ne peut pas exister : son corps demanderait 18 009 octets, plus du double de ce qu'un enregistrement BIFF8 peut transporter. La position de défilement a un piège similaire. Sur une feuille à volets figés, ce que l'utilisateur regardait se résume à quatre volets partageant deux positions de rangée et deux positions de colonne, pas à une seule coordonnée
Pourquoi une grande sélection demande-t-elle plus d'un enregistrement Selection ?
Une grande sélection demande plusieurs enregistrements parce qu'un corps d'enregistrement BIFF8 est plafonné à 8224 octets et que chaque zone sélectionnée coûte six octets fixes. [MS-XLS] §2.4.248 découpe l'enregistrement Selection en une partie fixe de 9 octets (l'octet de volet, rwAct et colAct pour la cellule active, irefAct pour la zone active et cref pour le nombre de zones) suivie de cref structures RefU, chacune portant deux rangées 16 bits et deux colonnes 8 bits. Le plus grand nombre qui tient est (8224 − 9) / 6 arrondi vers le bas, soit 1369, et cela produit un corps de 8223 octets, un octet sous la limite. TXLSWorksheet.StoreSelectionGroup utilise cette constante comme MaxAreasPerRecord et écrit un groupe plus grand sous forme d'enregistrements Selection consécutifs pour le même volet, 1369 zones à la fois
Le détail qui mord, c'est irefAct. Chaque fragment répète la même rangée active, la même colonne active et le même index de zone active, et irefAct indexe la séquence agrégée de tous les fragments, pas les zones à l'intérieur de l'enregistrement qui le transporte. Une sélection dépassant la limite d'une seule zone rend cela concret : 1370 zones dont la dernière est active deviennent deux enregistrements, le premier avec cref 1369 et le second avec cref 1, et tous deux portent irefAct 1369. Cette valeur est plus grande que le propre compte de zones du second enregistrement. Un lecteur qui vérifie irefAct contre cref dans chaque enregistrement rejette un fichier valide, et un lecteur qui remplace son état à chaque enregistrement perd les 1369 premières zones. Le lecteur HotXLS concatène les enregistrements consécutifs d'un même volet en un seul groupe, exige que chaque fragment s'accorde sur la cellule active et l'index, et ne lance le contrôle de plage qu'à l'enregistrement EOF de la feuille, une fois la séquence complète connue. La surcharge SelectAreas orientée volet n'a donc aucun plafond de 1369 zones. Elle valide chaque référence A1 et l'index actif avant de prendre le verrou d'écriture de la feuille, et renvoie False avec la sélection précédente inchangée si quoi que ce soit est mal formé
var
Book: TXLSWorkbook;
Sheet: TXLSWorksheet;
Diffs: TXLSSelectedAreas;
I: Integer;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add;
Sheet.FreezePanes(1, 1); // la ligne d'en-tête et la colonne A restent en place
SetLength(Diffs, 3000);
for I := 0 to High(Diffs) do
Diffs[I] := Format('C%d', [I + 2]);
// Figer réinitialise la sélection stockée, donc sélectionnez après le gel.
// 3000 zones sont sauvegardées en trois enregistrements Selection : 1369 + 1369 + 262
if not Sheet.SelectAreas(xlspBottomRight, Diffs, 0) then
raise Exception.Create('Selection rejected');
Book.SaveAs('reconciliation.xls');
finally
Book.Free;
end;
end;
Quel octet de volet un enregistrement Selection utilise-t-il ?
Un enregistrement Selection identifie son volet par le code numérique que le format définit : 0 pour bas-droite, 1 pour haut-droite, 2 pour bas-gauche et 3 pour haut-gauche. L'énumération publique TXLSPanePosition est déclarée en ordre de lecture, xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight, si bien que Ord(xlspTopLeft) vaut 0, ce qui est le volet bas-droite dans le fichier. Caster l'énumération directement dans l'octet de volet écrirait chaque sélection haut-gauche sur le volet bas-droite sans aucune erreur. Chaque point d'entrée HotXLS conscient des volets convertit l'énumération à travers une instruction case explicite, si bien que les appelants ne manipulent jamais les codes numériques. L'existence du volet est également vérifiée : le volet haut-droite n'existe qu'avec une division verticale, le bas-gauche qu'avec une division horizontale, et le bas-droite qu'avec les deux. Pour un volet que la géométrie courante de division ou de gel n'a pas, SelectAreas renvoie False, et GetSelectedAreas renvoie un tableau vide avec ActiveAreaIndex à -1, sans créer de volet, d'objet de sélection ni de cellule dans le classeur
Où réside la position de défilement de chaque volet ?
La position de défilement de chaque volet est répartie sur deux enregistrements, parce que quatre volets ne partagent que deux positions de rangée et deux positions de colonne. Dans un classeur classique, la première rangée visible des volets supérieurs et la première colonne visible des volets de gauche sont Window2.rwTop et Window2.colLeft, tandis que la rangée des volets inférieurs et la colonne des volets de droite sont Pane.rwTop et Pane.colLeft. ScrollWindow(xlspTopRight, R, C) écrit donc Window2.rwTop et Pane.colLeft, et fixer la colonne du volet haut-droite déplace aussi le volet bas-droite, exactement comme les deux partagent une barre de défilement horizontale dans Excel. Les méthodes publiques utilisent des numéros de rangée et de colonne en base 1. Un volet absent renvoie False et met les deux sorties de la requête à zéro, et une coordonnée hors plage est rejetée avant que l'un des axes ne change. Rien de tout cela ne dépend de la façon dont une visionneuse peint la grille. Un contrôle de rendu garde ses propres TopRow et LeftCol, comme le décrit l'article sur le rendu de classeurs dans une grille VCL personnalisée, et ce sont des états d'exécution, pas ce qui est sauvegardé
XLSX étale les mêmes données sur deux éléments : sheetView/@topLeftCell (ECMA-376 Partie 1, §18.3.1.87) pour la fenêtre dans son ensemble et l'enfant pane/@topLeftCell (§18.3.1.66) pour le côté inférieur droit d'une division. Les deux attributs peuvent être présents en même temps. HotXLS lit d'abord l'attribut extérieur dans les champs de niveau fenêtre, laisse l'enfant pane ne remplacer que les champs de niveau volet, et réécrit les deux séparément. Aplatir les deux couches en une seule est exactement ainsi qu'une position de défilement supérieure ou de gauche disparaît silencieusement au chargement. Les copies de feuilles transportent les deux couches dans les deux moteurs. Les points d'entrée plus anciens gardent leur comportement d'origine : les propriétés classiques ScrollRow et ScrollColumn, et le SetPaneScroll et GetPaneScroll XLSX en base 0. La géométrie de gel et de division elle-même se configure avec les réglages de niveau feuille couverts dans protection de feuille, mise en page et impression
var
Row, Col: Integer;
begin
Sheet.FreezePanes(1, 1);
// Bas-droite : axe de rangée inférieur (Pane.rwTop) et axe de colonne droit (Pane.colLeft)
Sheet.ScrollWindow(xlspBottomRight, 500, 3);
// Haut-droite partage l'axe de colonne droit, donc ceci déplace aussi le bas-droite vers la colonne 6
Sheet.ScrollWindow(xlspTopRight, 1, 6);
if Sheet.TryGetWindowScroll(xlspBottomRight, Row, Col) then
Memo1.Lines.Add(Format('Bottom-right starts at row %d, column %d', [Row, Col]));
// Bas-droite commence à la rangée 500, colonne 6
end;
Que se passe-t-il quand un enregistrement Selection est corrompu ?
Quand un enregistrement Selection est corrompu, HotXLS le conserve en octets opaques, signale le code de diagnostic 1304 (xlsDiagnosticSelectionRecordInvalid) et réécrit le corps d'origine octet pour octet à la sauvegarde. Avant qu'un enregistrement ne rejoigne le groupe de son volet, le lecteur le vérifie dans l'ordre. L'octet de volet doit être 3 ou moins. Les enregistrements d'un même volet doivent être contigus dans le flux. Les 9 octets fixes doivent être présents. cref doit être entre 1 et 1369, et le corps doit mesurer exactement 9 + cref × 6 octets. Chaque fragment d'un groupe doit s'accorder sur la cellule active et irefAct, irefAct ne doit pas avoir son bit de signe positionné, la colonne active doit être sur la grille, et aucune zone ne doit avoir de bornes inversées. Les problèmes propres à un enregistrement physique sont signalés une fois par enregistrement. Les contradictions qui n'apparaissent qu'après agrégation, comme un irefAct pointant au-delà du nombre total de zones ou une cellule active en dehors de la zone indexée, sont signalées une fois par groupe à l'EOF. Un groupe invalide reste invisible pour l'API typée : GetSelectedAreas renvoie un tableau vide avec l'index -1 pour ce volet, tandis que tous les autres volets continuent de fonctionner
var
I: Integer;
D: TXLSDiagnostic;
begin
if Book.Open('supplier-upload.xls') <> 1 then
Exit;
for I := 0 to Book.Diagnostics.Count - 1 do
begin
D := Book.Diagnostics[I];
if D.Code = xlsDiagnosticSelectionRecordInvalid then
Log.Add(Format('%s: record $%.4x kept opaque (%s)',
[D.SheetName, D.RecordId, D.Message]));
end;
end;
Comment les sélections survivent-elles aux insertions de rangées et de colonnes ?
Les sélections survivent aux modifications structurelles parce qu'insérer ou supprimer des rangées ou des colonnes entières remappe chaque groupe de volets représenté dans les moteurs classique et XLSX à travers un remappeur partagé unique. Les zones survivantes gardent leur ordre et la zone active garde son identité. Si la zone active est supprimée, la première zone survivante qui la suit devient active, puis la dernière zone survivante qui la précède si rien ne suit. Si toutes les zones sont supprimées, le groupe se réduit à une cellule à la frontière de suppression, et une cellule active ne tombant plus à l'intérieur de la zone choisie se déplace vers le coin supérieur gauche de cette zone, si bien que l'index et la coordonnée ne se contredisent jamais. Les limites sont délibérées. Les groupes classiques invalides sont sautés par le remappeur plutôt que réécrits en une sélection inventée, si bien que leurs octets d'origine continuent de faire l'aller-retour. Éditer un volet ne remplace que les enregistrements de ce volet et laisse les autres identiques à l'octet près. ODS ne reçoit aucun état de sélection par volet, parce qu'ODF n'a aucune structure de vue de feuille équivalente pour le porter
Si votre application écrit des fichiers .xls que les utilisateurs ouvrent et doivent parcourir, que ce soit pour relire les cellules signalées, reprendre là où ils s'étaient arrêtés ou partager un tableau de bord figé, l'API de sélection et de défilement consciente des volets fait partie du composant tableur HotXLS pour Delphi, et elle fonctionne de la même manière pour XLS et XLSX