Article technique

Commentaires de cellules et hyperliens Excel en Delphi avec HotXLS

Renommez une feuille de « Summary » en « Overview » dans un classeur généré, et chaque hyperlien interne qui pointait vers Summary!A1 ne mène plus nulle part. Aucune exception à l'enregistrement, aucune à l'ouverture. Le lien s'affiche toujours, semble toujours cliquable, et se résout silencieusement vers rien. Le même type de dysfonctionnement apparaît après une conversion par enregistrement sous ou un aller-retour .xls/.xlsx, quand un commentaire se retrouve décalé d'une colonne ou qu'un lien relatif perd sa cible. Les deux fonctionnalités portent un état de revue sur lequel de vraies personnes agissent, donc quand elles cassent, l'échec est invisible jusqu'à ce qu'un réviseur clique et que rien ne se passe.

C'est la raison pratique pour laquelle les commentaires et les hyperliens méritent plus d'attention que leur apparence cosmétique ne le suggère. HotXLS donne au code Delphi et C++Builder un accès direct en écriture aux deux, en XLS et XLSX, sans automatisation Excel dans la boucle. La contrepartie de ce contrôle est la responsabilité : la bibliothèque écrit exactement les cibles que vous lui fournissez et n'en valide aucune, donc maintenir un workflow de revue intact est la responsabilité de votre code, pas d'Excel.

Commentaires de cellules comme traces de revue générées par machine

Dans le modèle de classes XLSX, un commentaire est un objet au niveau de la feuille : il connaît sa ligne, sa colonne, un auteur et un corps de texte. Le champ auteur mérite sa place. Lorsqu'un classeur généré par votre code circule dans une chaîne de revue, la première question qu'un auditeur pose est de savoir qui a écrit une note donnée, et une note laissée sans auteur répond à cette question par un blanc. Marquez les commentaires générés avec une identité de service afin que la provenance ne soit jamais ambiguë.

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Authored note on the adjusted figure
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Update an existing note instead of stacking a second one
    Note := Sheet.Comments.FindAt(14, 4);
    if Note <> nil then
      Note.Text := Note.Text + ' [verified 2026-06-11]';

    Book.SaveAs('reconciliation-reviewed.xlsx');
  finally
    Book.Free;
  end;
end;

La sonde FindAt a plus de poids qu'il n'y paraît. Un traitement batch qui relance après un échec transitoire appellera volontiers AddComment une deuxième fois sur une cellule déjà annotée, et la cellule se retrouve avec deux notes empilées que personne n'a demandées. Sondez d'abord avec FindAt et mettez à jour l'objet renvoyé. La collection Comments expose aussi DeleteAt et DeleteInRange. Cette variante par plage est celle à utiliser quand on assainit un classeur avant qu'il quitte l'organisation : effacer les annotations QA internes d'une zone entière tient en un seul appel plutôt qu'en une boucle manuelle sur les cellules.

Les URL externes et les sauts internes utilisent des API différentes

OOXML conserve les deux types de liens dans des endroits différents. Une URL externe devient une entrée de relation dans la partie .rels de la feuille, la cellule pointant vers la relation par son identifiant. Un saut interne ne touche jamais la couche de relations ; c'est une simple chaîne de localisation telle que Summary!A1 stockée directement sur le lien. HotXLS conserve cette distinction visible dans l'API plutôt que de surcharger une seule méthode, ce qui signifie que vous choisissez le bon appel en sachant où se trouve la cible :

Sheet.Cells[2, 1].Value := 'Source record';
Sheet.AddHyperlink(2, 1, 'https://intranet.example.com/records/2214',
  'Open record 2214', 'ERP source entry');

Sheet.Cells[3, 1].Value := 'Totals';
Sheet.AddHyperlinkToCell(3, 1, 'Overview!B12', 'Jump to totals');

Sur l'objet TXLSXHyperlink résultant, Url et Location sont mutuellement exclusifs, et IsInternal vous indique lequel des deux est renseigné. C'est ce flag que vous vérifiez quand vous inventoriez les liens d'un classeur ouvert et que vous devez traiter « quitte le fichier » et « reste dans le fichier » selon des règles différentes : un hôte externe peut faire l'objet d'une liste autorisée tandis qu'une cible interne doit seulement nommer une feuille existante. Les liens internes ne portent aucune partie de relation derrière eux, ce qui les rend aussi moins coûteux à réécrire en masse.

La panne décrite en introduction se situe entièrement côté interne, et elle découle d'un fait : une chaîne de localisation n'est pas une référence analysée. HotXLS écrit exactement le texte que vous lui fournissez, et rien ne repointe ce texte quand une feuille est renommée ensuite. Deux défenses tiennent en pratique. La première est la discipline sur l'ordre : renommez chaque feuille avant de générer le moindre lien, puis traitez les noms de feuilles comme des identifiants figés. La deuxième est plus robuste et résiste aux renommages effectués après coup. Pointez le lien vers un nom défini au niveau du classeur plutôt que vers une adresse brute Feuille!Cellule, car Excel réécrit la définition d'un nom quand la feuille sous-jacente change, de sorte que le lien suit automatiquement. Cette deuxième approche se combine naturellement avec les techniques décrites dans les noms définis et les formules inter-feuilles dans HotXLS.

Côté XLS : mêmes concepts, tuyauterie plus ancienne

La façade BIFF8 attache les commentaires aux plages plutôt qu'à une collection au niveau de la feuille. Vous appelez AddComment sur un IXLSRange et obtenez un TXLSComment ; la propriété Comment de la plage relit une note existante, et ClearComments les efface. L'arête vive ici est positionnelle. Un TXLSComment n'expose pas publiquement sa propre ligne et colonne, donc la boucle naturelle « parcourir tous les commentaires et signaler où ils se trouvent » fonctionne à rebours de l'API. Vous devez partir des cellules. Soit vous pilotez l'audit à partir de la liste des adresses que vous avez annotées, soit vous tenez votre propre journal de positions pendant l'écriture, car l'objet commentaire ne vous dira pas ensuite où il se trouve.

var
  Book: IXLSWorkbook;
  Sheet: IXLSWorksheet;
  Remark: TXLSComment;
begin
  Book := TXLSWorkbook.Create;
  Sheet := Book.Sheets.Add;
  Sheet.Name := 'Review';
  Sheet.Cells.Item[5, 2].Value := 4821.50;

  Remark := Sheet.Cells.Item[5, 2].AddComment('Awaiting sign-off from controller');
  Remark.Visible := True;   // pop the note open on first view

  Sheet.AddHyperlink(7, 2, 'https://intranet.example.com/signoff/4821',
    'Sign-off form', 'Opens the controller queue');
  Book.SaveAs('review.xls');
end;

Définir Visible à True est la manière héritée de rendre une note impossible à ignorer : la boîte jaune reste ouverte sur la feuille au lieu d'attendre un survol. TXLSComment va plus loin que son équivalent XLSX en exposant TextRuns, de sorte qu'une seule note peut porter un avertissement en gras à côté d'une explication ordinaire, une mise en forme que l'API de commentaires XLSX n'expose pas de la même manière. Les hyperliens de ce côté arrivent via trois surcharges progressives (adresse seule, puis texte d'affichage, puis info-bulle) et se lisent via la collection HyperLinks de la feuille, où chaque lien expose Address, SubAddress, DisplayText et ScreenTip.

Une feuille d'index de revue vaut mieux que des notes éparpillées

Au-delà d'une douzaine d'annotations environ, la lecture au survol cesse silencieusement de passer à l'échelle. Les notes s'accumulent sur des feuilles qu'un réviseur n'ouvre jamais, et celles qui comptent le plus sont exactement les plus faciles à manquer. La structure qui a le mieux tenu est une feuille d'index générée : une ligne par emplacement annoté, listant son nom de feuille, son adresse de cellule, son auteur et un court extrait de la note. La dernière colonne porte un hyperlien interne construit avec AddHyperlinkToCell qui saute directement vers la cellule annotée. Le réviseur parcourt une liste au lieu de chasser à travers une grille, et le nombre de lignes de cet index sert aussi d'inventaire de commentaires pour la passe d'audit ci-dessous.

L'index est peu coûteux à construire car votre générateur connaît déjà chaque position qu'il a touchée. Ajoutez un tuple (feuille, ligne, colonne, auteur, résumé) à une liste au fur et à mesure que vous écrivez chaque commentaire, puis émettez la feuille d'index en dernier afin que son nombre de lignes soit définitif avant l'enregistrement. Deux améliorations sont payantes : triez l'index par gravité ou par feuille plutôt que par ordre d'insertion, et placez un lien de retour dans l'en-tête de l'index pour qu'un réviseur puisse remonter en haut après chaque élément. Parce que les liens internes sont de simples chaînes de localisation sans rien dans la couche de relations derrière eux, même un index de mille lignes n'ajoute presque rien à la taille du fichier ni au temps d'enregistrement.

Cette même feuille porte ses fruits au retour. Quand le classeur révisé revient, votre code lit les valeurs de statut saisies dans les cellules à côté des lignes d'index plutôt que de rescanner chaque feuille pour les commentaires qui ont pu changer. Une colonne de cellules de statut structurées se parse proprement ; un éparpillement de notes en texte libre, non.

Une passe d'audit avant livraison qui détecte vraiment les pannes

Aucune de ces API ne valide une cible. Un lien vers une feuille que vous avez supprimée, un hôte intranet mal orthographié, un partage de fichiers désaffecté le trimestre dernier : tous s'enregistrent sans un murmure. ECMA-376 spécifie comment un lien est stocké, pas qu'il se résout vers quoi que ce soit. Un classeur portant des métadonnées de revue mérite donc une courte phase d'audit, exécutée juste avant SaveAs :

  • Collectez chaque localisation interne écrite pendant la génération et confirmez que le nom de feuille avant le point d'exclamation existe encore dans la collection de feuilles du classeur.
  • Vérifiez les URL externes contre une liste autorisée de schémas et d'hôtes. Les chemins file:// nus et les chemins UNC divulguent des détails d'environnement et cassent dès que le fichier quitte votre réseau.
  • Comptez les commentaires par feuille et comparez avec ce que votre générateur avait l'intention d'écrire. Une relance qui a doublé les notes apparaît ici plutôt que dans la boîte de réception du réviseur.
  • Supprimez les annotations internes avec DeleteInRange chaque fois que le destinataire se trouve en dehors de l'organisation.

Les équipes qui construisent leurs classeurs depuis une couche de données peuvent intégrer cette phase dans la même étape de pipeline qui valide déjà les données, de sorte que la vérification des métadonnées suit gratuitement. Les mécanismes sont ceux décrits dans l'export de résultats de requêtes de base de données vers des rapports Excel, orientés vers les liens et commentaires plutôt que vers les lignes.

Un détail de quotation trébuche les gens quand ils construisent des chaînes de localisation à la main. Une feuille dont le nom contient un espace doit être entre guillemets dans la localisation, exactement comme la barre de formule le fait : 'Quarterly Totals'!A1, pas Quarterly Totals!A1. HotXLS applique les mêmes règles que le moteur de formules pour les références inter-feuilles, donc si un lien fonctionne dans une formule de feuille sa quotation fonctionnera ici aussi. Passez-lui un nom non quoté avec un espace et vous obtenez le même lien mort silencieux annoncé en introduction.

Les commentaires et les hyperliens sont les parties d'un classeur généré sur lesquelles les réviseurs agissent sans y réfléchir à deux fois, ce qui explique précisément pourquoi une cible qui pointe vers rien fait de vrais dégâts avant que quiconque ne le remarque. Construisez la passe de validation une fois, exécutez-la sur chaque classeur avant expédition, et le workflow de revue reste intact à travers les renommages et conversions. La surface API complète des deux façades XLS et XLSX est documentée sur la page produit HotXLS Component.