Article technique

Une grille de tableur personnalisée en Delphi avec HotXLS

HotXLS fournit TXLSWorkbookViewer, un contrôle VCL natif qui rend les classeurs XLS, XLSX, XLSM, et ODS comme une grille de tableur interactive à l'intérieur d'un formulaire Delphi ou C++Builder, sans installer Excel ni le piloter via l'automatisation OLE. Bien construire ce type de contrôle signifie résoudre trois problèmes spécifiques : faire correspondre un clic de souris qui atterrit à l'intérieur d'une cellule fusionnée à la cellule logique correcte, garder la position de défilement, les bandes d'en-tête, et la sélection de cellule cohérentes tandis qu'un utilisateur parcourt une feuille bien plus grande que la fenêtre visible, et décider ce qu'un clic sur un marqueur de commentaire ou une cellule à lien hypertexte devrait réellement faire

La plupart des ateliers Delphi recourent à une visionneuse de tableur pour des raisons qui n'ont rien à voir avec l'édition : un poste d'audit qui prévisualise les classeurs téléversés avant leur entrée dans un pipeline, un kiosque ou une visionneuse de rapport où Microsoft Office ne fait pas partie de l'image de déploiement, ou un outil d'assurance qualité qui doit montrer le contenu d'un classeur sans l'imprévisibilité de l'automatisation d'un véritable processus Excel via COM. Une simple grille de chaînes vous donne rapidement du texte dans des cellules, mais un fichier tableur n'est pas une simple grille : les cellules fusionnent en blocs qui n'existent qu'une fois dans le modèle sous-jacent, les feuilles portent des bandes d'en-tête fixes et des positions de défilement horizontal et vertical indépendantes, et les cellules individuelles portent des commentaires et des liens hypertexte qui nécessitent leur propre modèle d'interaction. TXLSWorkbookViewer est la réponse de HotXLS à cet écart, et sa conception interne est un plan raisonnable pour quiconque construit un contrôle similaire à partir de zéro

Comment une visionneuse de classeur évite-t-elle de dépendre d'Excel ?

TXLSWorkbookViewer évite entièrement Excel en lisant via le propre modèle d'objets analysé de HotXLS plutôt qu'en ouvrant un document via Excel et en le pilotant comme une marionnette. La propriété Workbook lie un TXLSWorkbook existant pour les fichiers XLS classiques, et XlsxWorkbook lie un TXLSXWorkbook pour les variantes XLSX, XLSM, et modèle ; l'un ou l'autre peut déjà être ouvert ailleurs dans l'application, et la visionneuse ne fait que le lire. Lorsque le contrôle doit posséder le fichier lui-même, LoadFromFile inspecte l'extension, achemine XLSX, XLSM, XLTX, XLTM, et ODS via le moteur moderne et tout le reste via le moteur classique, et libère quel que soit le classeur qu'il a créé une fois le contrôle vidé ou détruit

var
  Viewer: TXLSWorkbookViewer;
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  if Book.Open('quarterly-report.xlsx') <> 1 then
    raise Exception.Create('Could not open workbook');

  Viewer := TXLSWorkbookViewer.Create(Self);
  Viewer.Parent := Self;
  Viewer.Align := alClient;
  Viewer.XlsxWorkbook := Book;        // the viewer does not take ownership
  Viewer.GoToCell(1, 1);

  Caption := Viewer.WorksheetName + ': ' + Viewer.SelectedCellText;
end;

Localiser la bonne cellule à l'intérieur d'une plage fusionnée

Résoudre un clic vers la bonne cellule dans TXLSWorkbookViewer est une recherche en deux étapes, et cette séparation compte parce que la géométrie de pixels et la sémantique de tableur sont réellement des problèmes différents. La première étape est de la géométrie pure : une méthode privée CellAtPoint parcourt les largeurs de colonnes et les hauteurs de lignes depuis la position de défilement actuelle jusqu'à trouver la bande qui contient les coordonnées X et Y cliquées, sans aucune conscience des cellules fusionnées. La seconde étape est sémantique : chaque chemin qui change la sélection, un clic de souris, une touche fléchée, Tab, ou un appel direct à GoToCell, s'achemine via une unique routine interne ChangeSelection, qui normalise la ligne et la colonne brutes par rapport à toute fusion et les fait s'aligner sur la cellule d'ancrage de la fusion avant que la sélection ne change réellement

L'ancre est la cellule supérieure gauche de la plage fusionnée, et c'est la seule cellule de ce bloc qui détient réellement une valeur, un format, un commentaire, ou un lien hypertexte dans le modèle de classeur sous-jacent ; chaque autre cellule que la fusion couvre visuellement est vide dans les données elles-mêmes. Pour les classeurs XLS classiques, l'ancre provient de Cell.MergeArea, un IXLSRange dont Row et Column pointent vers la cellule propriétaire ; pour les classeurs XLSX et ODS, MergedCells.FindAt renvoie un TXLSXMergedRange exposant la même ancre sous forme de Row1 et Col1. Le rendu résout un problème équivalent indépendamment, en étendant le rectangle d'une cellule fusionnée à toute son étendue de ligne et de colonne et en sautant les cellules à l'intérieur de cette étendue, si bien que le contour de sélection enveloppe tout le bloc fusionné plutôt que seulement son coin d'ancrage, et écrire des mises en page fusionnées plutôt que simplement les relire est un problème apparenté mais distinct couvert dans l'article compagnon sur la mise en page des cellules fusionnées pour les modèles de rapport

var
  Sheet: TXLSXWorksheet;
begin
  Sheet := Book.Sheets.Add('Summary');
  Sheet.MergeCells(2, 2, 3, 4);       // B2:D3
  Sheet.Cells[2, 2].Value := 'Region totals';

  Viewer.XlsxWorkbook := Book;
  Viewer.GoToCell(3, 4);              // targets the bottom-right corner of the merge
  // SelectedRow is now 2 and SelectedCol is now 2: normalized to the anchor cell
end;

Qu'est-ce qui garde le défilement, les en-têtes, et la sélection synchronisés ?

TXLSWorkbookViewer garde cohérents trois éléments d'état séparés : la position de défilement logique détenue dans TopRow et LeftCol, les barres de défilement Windows natives que le contrôle demande via WS_HSCROLL et WS_VSCROLL dans CreateParams, et la sélection actuelle dans SelectedRow et SelectedCol. Faire glisser une barre de défilement ou faire tourner la molette de la souris déclenche WM_HSCROLL, WM_VSCROLL, ou WM_MOUSEWHEEL, qui met à jour TopRow ou LeftCol et redessine ; la sélection ne bouge pas, ce qui correspond à la façon dont Excel lui-même sépare le panoramique de la sélection. Après chacune de ces mises à jour, UpdateScrollBars repousse la nouvelle position dans la barre de défilement native via SetScrollInfo, si bien que le curseur ne dérive jamais en désaccord avec ce que la grille montre réellement

La navigation au clavier exécute la même synchronisation dans la direction opposée : déplacer la sélection au-delà du bord de la grille visible appelle EnsureSelectionVisible, qui pousse TopRow ou LeftCol en accumulant les largeurs de colonnes et hauteurs de lignes réelles plutôt qu'en incrémentant simplement d'une unité, puisque les lignes et les colonnes peuvent porter des tailles personnalisées, puis appelle UpdateScrollBars afin que le curseur reflète l'endroit où le clavier vient d'emmener la vue. Les bandes d'en-tête de numéro de ligne et de lettre de colonne, dimensionnées via RowHeaderWidth et ColumnHeaderHeight, sont la partie de ce contrôle qui reste fixe à l'écran pendant que TopRow et LeftCol font défiler les données en dessous, et c'est toute l'étendue du gel que ce contrôle effectue par lui-même : ce n'est pas la fonctionnalité Volets figés d'Excel, et il n'existe aucun moyen intégré d'épingler une ligne ou une colonne de données arbitraire pendant que le reste de la feuille défile devant elle. Une limite qui mérite d'être testée avant de livrer une visionneuse sur des fichiers que vous ne contrôlez pas entièrement est que TopRow et LeftCol ne sont pas bornés par rapport à la plage réellement utilisée de la feuille de calcul, si bien qu'un curseur glissé jusqu'à sa limite structurelle peut atterrir sur la ligne 1 048 576 ou la colonne 16 384 et montrer une grille vide au lieu de la dernière ligne ou colonne qui détient réellement des données ; les classeurs assez volumineux pour rendre cela perceptible sont généralement aussi assez volumineux pour nécessiter l'attention côté chargement couverte dans l'article sur la performance des grands classeurs

Câbler les commentaires et les liens hypertexte aux événements de souris et de sélection

TXLSWorkbookViewer traite les commentaires et les liens hypertexte comme des attributs de quelle que soit la cellule actuellement sélectionnée plutôt que comme des cibles de survol, si bien que SelectedCellCommentText, SelectedCellCommentAuthor, et SelectedCellHyperlink se mettent à jour chaque fois que OnSelectionChange se déclenche, que la sélection ait bougé par clic de souris, touche fléchée, ou appel à GoToCell. Une cellule commentée reçoit un petit triangle rouge peint dans son coin supérieur droit comme indice visuel, similaire au propre drapeau de commentaire d'Excel, mais ce marqueur est purement visuel ; il n'y a aucune infobulle déclenchée au survol intégrée au contrôle, si bien qu'une application qui veut une fenêtre contextuelle au survol plutôt qu'à la sélection doit construire cette couche elle-même. L'activation des liens hypertexte fonctionne selon la même logique de sélection d'abord : double-cliquer sur une cellule appelle ActivateSelectedCell, qui lit SelectedCellHyperlink et, si elle n'est pas vide, lève OnHyperlinkClick avec l'adresse cible et un paramètre var Handled: Boolean pour que le gestionnaire le définisse

Ce que OnHyperlinkClick ne fait pas est tout aussi important : TXLSWorkbookViewer n'appelle jamais ShellExecute ni n'ouvre de navigateur de lui-même, que le gestionnaire règle Handled sur vrai ou le laisse sur faux. La navigation, et toute décision quant à ce qui compte comme une cible sûre, relève entièrement de la responsabilité de l'application hôte, ce qui est le bon comportement par défaut pour un composant qui n'a aucune idée s'il est intégré dans un outil interne de confiance ou une visionneuse pour des fichiers qu'un client vient de téléverser

procedure TMainForm.ViewerSelectionChange(Sender: TObject; Row, Col: Integer);
begin
  if Viewer.SelectedCellCommentText <> '' then
    StatusBar.SimpleText := Viewer.SelectedCellCommentAuthor + ': ' +
      Viewer.SelectedCellCommentText
  else
    StatusBar.SimpleText := Viewer.SelectedCellHyperlink;
end;

procedure TMainForm.ViewerHyperlinkClick(Sender: TObject;
  const Target: WideString; var Handled: Boolean);
begin
  ShellExecute(0, 'open', PWideChar(Target), nil, nil, SW_SHOWNORMAL);
  Handled := True;
end;

Portée de la sélection et limites de la navigation au clavier

La sélection dans TXLSWorkbookViewer est toujours une unique cellule logique, suivie sous forme de SelectedRow et SelectedCol ; il n'y a aucune sélection de plage rectangulaire multi-cellule dans le contrôle de base, si bien que toute fonctionnalité ayant besoin d'agir sur un bloc de cellules doit être construite au-dessus plutôt que lue depuis un objet de sélection. La couverture du clavier est délibérément basique : les touches fléchées déplacent une cellule à la fois, Origine retourne au début de la ligne ou, avec Ctrl, à la cellule A1, Page précédente et Page suivante sautent de dix lignes, et Tab et Maj+Tab avancent à travers les colonnes ; il n'y a aucun saut Ctrl+Flèche jusqu'au bord d'une région de données et aucune sélection de plage étendue par Maj, si bien que les utilisateurs venant directement d'Excel remarqueront l'écart sur une feuille dense

Les limites de colonnes sont appliquées au même point d'étranglement ChangeSelection qui gère la normalisation des fusions, et elles diffèrent selon le moteur à dessein : une visionneuse liée à un TXLSWorkbook classique se borne à la colonne 256, le plafond structurel du format BIFF8, tandis qu'une liée à TXLSXWorkbook respecte la limite moderne de 16 384 colonnes qu'XLSX a héritée d'Excel 2007 et versions ultérieures. Les lignes sont plafonnées à 1 048 576 dans les deux cas, si bien que la différence pratique entre ouvrir un fichier XLS hérité et un fichier XLSX dans la même visionneuse porte entièrement sur jusqu'où vers la droite la grille est disposée à vous laisser aller

Rien de tout cela n'est exotique une fois décomposé en recherche de pixel, normalisation d'ancrage, et une poignée de gestionnaires de messages, mais faire s'accorder les trois sous de véritables fichiers, avec de véritables fusions, commentaires, et liens hypertexte, constitue l'essentiel du travail dans un composant comme celui-ci. TXLSWorkbookViewer fait partie du composant Excel HotXLS standard pour Delphi et C++Builder, aux côtés des modèles d'objets classique et XLSX à partir desquels il effectue son rendu