Article Technique

Threaded Comments in Excel Files with Delphi and HotXLS

Générez un classeur depuis Delphi, annotez une cellule avec l'API classique de commentaire, et Excel 365 classera votre texte sous forme de Notes : un encadré jaune à l'ancienne sans champ Répondre, sans avatar d'auteur ni bouton de résolution. HotXLS écrit de véritables commentaires fils de discussion Excel 365 (threaded comments) via les méthodes AddThreadedComment et AddThreadedReply du moteur XLSX, émettant le composant dédié xl/threadedComments/threadedCommentN.xml par feuille et enregistrant chaque auteur dans le composant Person au niveau du classeur. Ainsi, la conversation écrite par votre code constitue le même objet auquel un réviseur répondra sous Excel

Cette distinction importe car les deux fonctionnalités ne se ressemblent qu'en apparence. Une note est une zone de texte flottante ; un commentaire fil de discussion est un enregistrement de conversation avec des identifiants stables, une arborescence de réponses, un registre d'auteurs et un indicateur de résolution — autant d'éléments sur lesquels s'appuie réellement un processus de révision. Cet article détaille d'abord la structure OOXML car une fois les deux modèles de stockage comparés, chaque différence de comportement dans Excel cesse d'être mystérieuse

Pourquoi mes commentaires s'affichent-ils comme des notes dans Excel 365 ?

La réponse courte : deux générations de commentaires partagent la même grille de calcul, et elles résident dans des parties du package totalement distinctes. Le modèle hérité — ciblé par AddComment — stocke le texte dans xl/comments1.xml (type de contenu application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml) et positionne l'infobulle via un composant de dessin VML associé, xl/drawings/vmlDrawing1.vml. Ce duo est antérieur au ruban d'Excel ; Excel 365 le lit toujours fidèlement, mais il présente le résultat sous forme de « note » et n'offre aucune interface de fil de discussion car le stockage n'en contient pas la structure. L'auteur est une simple chaîne d'affichage, la position est une forme VML, et le modèle s'arrête là

Les commentaires fils de discussion, introduits avec Excel 365, ignorent complètement la couche VML. Chaque feuille contenant une conversation possède son propre composant xl/threadedComments/threadedCommentN.xml sous le type de contenu application/vnd.openxmlformats-officedocument.spreadsheetml.threadedComments+xml, et le classeur contient un composant unique xl/person/person.xml (type de contenu ...spreadsheetml.people+xml) listant chaque participant. HotXLS expose les deux modèles sur la même feuille : AddComment écrit toujours la paire héritée, AddThreadedComment écrit les nouveaux composants. Si votre sortie s'affiche sous forme de notes, c'est que vous avez appelé la première API alors que vous vouliez la seconde. Le modèle hérité reste le bon outil pour certaines tâches — l'article précédent sur les commentaires de cellules et les hyperliens dans les flux de révision le détaille, y compris pour le format XLS — mais aucun élément de ce modèle ne peut être converti en fil de discussion

Ce que HotXLS écrit dans le package

HotXLS modélise chaque réponse sous la forme d'un objet TXLSXThreadedComment portant un ancrage de cellule (Ref), un identifiant de type GUID (Id), un ParentId facultatif, un AuthorId résolu par la liste de personnes, un miroir DisplayName, le corps du texte, un indicateur Done (résolu) et une date ISO-8601 DateTime. Lors de l'enregistrement, chaque entrée devient un élément <threadedComment> dont l'attribut t contient l'identifiant, l'attribut dT contient l'horodatage et l'attribut parentT — présent uniquement sur les réponses enfants — indique l'identifiant parent. Excel reconstruit l'arborescence de la conversation en associant parentT à t ; il n'y a pas d'imbrication dans le XML, juste une liste plate avec des références

Le composant Person est l'élément que la plupart des implémentations manuelles oublient. Chaque authorId dans un commentaire fil de discussion doit correspondre à une entrée <person> dans xl/person/person.xml, reliée aux relations du classeur avec son propre type de contenu substitué — sinon, Excel n'a aucun nom à afficher. HotXLS gère cette inscription automatiquement : AddThreadedComment recherche l'auteur dans la liste Persons du classeur par son nom d'affichage et crée une entrée avec un nouveau GUID s'il n'existe pas, de sorte que les commentaires consécutifs d'un même auteur partagent la même identité. Les relations de la feuille reçoivent la relation threadedComments, les relations du classeur reçoivent la relation Person, et le manifeste des types de contenu reçoit les deux substitutions, sans que rien n'apparaisse dans votre code

Construire une chaîne de réponses avec AddThreadedComment and AddThreadedReply

AddThreadedComment crée la racine de la conversation : son ParentId reste vide, ce qui l'identifie comme le début du fil de discussion. AddThreadedReply prend un paramètre ParentRef — la référence de la cellule où réside la racine — trouve la racine à cet ancrage et affecte au ParentId de la nouvelle réponse l'identifiant Id de la racine. Les deux méthodes renvoient le nouvel objet TXLSXThreadedComment, ce qui vous permet de définir l'horodatage ou l'état de résolution sur l'objet fraîchement créé

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Root: TXLSXThreadedComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('forecast.xlsx');
    Sheet := Book.Sheets[0];

    // Conversation root; ParentId stays empty
    Root := Sheet.AddThreadedComment(8, 3,
      'Q3 figure looks low against the pipeline export', 'Maria Ortiz');
    Root.DateTime := '2026-07-06T09:12:00Z';

    // Replies anchor at the same cell; ParentRef names the root cell
    Sheet.AddThreadedReply(8, 3, Root.Ref,
      'Pipeline export missed the EMEA renewals, re-running', 'Jan Kowalski');
    Sheet.AddThreadedReply(8, 3, Root.Ref,
      'Confirmed, refreshed figure lands tomorrow', 'Maria Ortiz');

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

Deux détails de cette liste méritent attention. Premièrement, Root.Ref est transmis plutôt qu'une chaîne construite manuellement : la référence générée par la bibliothèque correspond à ce que AddThreadedReply recherchera, ce qui évite toute une classe de bugs d'ancrage décalés d'un élément. Deuxièmement, les deux auteurs ont été transmis sous forme de noms d'affichage simples — le registre des personnes, les identités GUID et le câblage de l'identifiant d'auteur authorId ont tous été gérés de manière transparente. Ouvrez le fichier enregistré sous Excel 365 et l'ancrage de la cellule, l'ordre des réponses et les deux participants distincts sont tous actifs : cliquez sur Répondre et Excel complétera le fil de discussion que votre code a initialisé

Lire une conversation en retour : comportement lors de l'aller-retour

HotXLS prend en charge les allers-retours de commentaires fils de discussion à partir de la version 2.122.0 : rouvrir un package enregistré reconstitue la liste des conversations de chaque feuille et la liste des participants du classeur depuis les différents composants. Ainsi, les identifiants, les liens de réponses et les noms d'affichage survivent à un cycle complet de lecture/écriture. Le lecteur analyse person.xml avant tout contenu de feuille, ce qui permet à chaque authorId de correspondre à un nom d'affichage lors du chargement. Les identifiants de personnes analysés sont préservés tels quels plutôt que régénérés, de sorte qu'un classeur transitant entre votre code et Excel conserve des identités d'auteurs stables au fil des allers-retours

var
  i: Integer;
  Cmt: TXLSXThreadedComment;
begin
  Book.Open('forecast-reviewed.xlsx');
  Sheet := Book.Sheets[0];
  for i := 0 to Sheet.ThreadedComments.Count - 1 do
  begin
    Cmt := Sheet.ThreadedComments[i];
    if Cmt.ParentId = '' then
      Writeln('Root at ', Cmt.Ref, ' by ', Cmt.DisplayName, ': ', Cmt.Text)
    else
      Writeln('  reply by ', Cmt.DisplayName, ': ', Cmt.Text);
  end;
  Writeln('Participants: ', Book.Persons.Count);
end;

Le stockage sous forme de liste plate avec références transparaît ici de manière délibérée : ThreadedComments énumère les réponses dans l'ordre d'apparition, et un ParentId vide identifie chaque racine. Si vous avez besoin de la structure arborescente — par exemple pour exporter un journal de révision — regroupez d'abord par Ref, puis liez ParentId à Id au sein du groupe de chaque cellule. Ce comportement de conservation de ce qui a été analysé est l'expression d'une règle plus générale de la bibliothèque ; l'article sur les allers-retours sans perte des thèmes, des listes d'extensions et de la chaîne de calcul décrit comment ce même principe s'applique au reste du package

État de résolution, horodatages et identité de l'auteur

Trois attributs portent la sémantique de la révision, et tous trois sont accessibles en écriture sur les objets retournés. Done correspond à l'attribut done — Excel affiche alors un fil résolu sous forme repliée avec un lien Rouvrir. DateTime est l'horodatage de création UTC au format ISO-8601 (dT) ; HotXLS substitue une valeur fictive lorsque vous le laissez vide afin que le composant reste valide, mais un véritable horodatage rend l'historique du fil lisible, il convient donc de le définir. Côté identité, chaque TXLSXPerson expose un identifiant de fournisseur ProviderId — généralement None pour les entrées créées localement — et un identifiant d'utilisateur UserId que vous pouvez renseigner avec un UPN ou une adresse e-mail lorsque votre application les connaît

var
  Root: TXLSXThreadedComment;
  Person: TXLSXPerson;
begin
  Root := Sheet.ThreadedComments.FindAt('D9');
  if Root <> nil then
    Root.Done := True;   // saved as done="1"; Excel shows the thread resolved

  Person := Book.Persons.FindByDisplayName('Maria Ortiz');
  if Person <> nil then
    Person.UserId := 'maria.ortiz@example.com';
end;

Une limite à formuler clairement : HotXLS écrit l'élément <mentions> vide. Les enregistrements de mentions @ — le mécanisme qui met en valeur le nom d'un collègue dans le texte du commentaire et déclenche une notification dans Microsoft 365 — ne sont pas modélisés ; le texte contenant un symbole @ est stocké sous forme de texte brut. Pour un classeur de révision généré, c'est rarement un problème, mais si votre flux dépend de ces notifications, prévoyez qu'un opérateur humain ajoute ces mentions sous Excel. L'identité de l'auteur dans le composant Person s'associe naturellement avec les champs de provenance au niveau du classeur traités dans métadonnées de classeur et propriétés de document, où réside le reste de la piste d'audit d'un fichier généré

Que font les anciennes versions d'Excel avec les commentaires fils de discussion ?

Excel 2016 et les versions antérieures sont antérieurs au modèle de fil de discussion, et ils ignorent simplement les composants qu'ils ne comprennent pas. Lorsqu'Excel 365 enregistre une conversation de fil de discussion, il écrit également un commentaire hérité sous forme d'ombre — une note fictive affichant « [Commentaire fil de discussion]… » — précisément pour que les anciennes versions affichent un contenu au niveau de la cellule ancrée. HotXLS émet les seuls composants de fil de discussion, sans ombre héritée, de sorte qu'un fichier écrit par votre code n'affichera aucun indicateur de commentaire sous Excel 2016. Si votre public utilise des installations antérieures à Office 365 et que l'annotation doit y être visible, l'API héritée AddComment demeure la solution compatible. Évitez simplement de superposer les deux modèles sur une même cellule sans tester chaque version d'Excel cible, car la façon dont un lecteur concilie une note et un fil sur une même cellule relève de son propre arbitrage

L'autre limite incontournable est le format de fichier lui-même. Les commentaires fils de discussion sont une structure OOXML sans équivalent BIFF8, de sorte que la partie .xls de HotXLS ne dispose d'aucune API de fil de discussion — un classeur devant rester au format binaire hérité est limité aux notes classiques. En pratique, le choix est simple : si vous écrivez des fichiers .xlsx pour Excel 365 ou des lecteurs Excel web/mobiles, utilisez AddThreadedComment pour obtenir de véritables conversations ; si vous ciblez Excel 2016 ou le format .xls, utilisez AddComment et acceptez le modèle de note ; si vous inspectez un fichier reçu, vérifiez à la fois ThreadedComments et Comments car un classeur ayant vécu dans les deux mondes peut légitimement contenir les deux modèles. L'ensemble de l'API pour les deux générations est documenté sur la page du produit HotXLS Delphi Excel Component