Teknisk artikel

Trådede kommentarer i Excel-filer med Delphi og HotXLS

Generér en arbejdsbog fra Delphi, annotér en celle med det klassiske kommentar-API, og Excel 365 arkiverer din tekst under Noter: en gammeldags gul boks uden svarfelt, uden forfatteravatar, uden knap til at markere som løst. HotXLS skriver ægte trådede Excel 365-kommentarer gennem AddThreadedComment og AddThreadedReply på XLSX-motoren, udsender den dedikerede del xl/threadedComments/threadedCommentN.xml pr. ark og registrerer hver forfatter i person-delen på arbejdsbogsniveau, så den samtale, din kode skriver, er det samme objekt, som en korrekturlæser svarer på i Excel

Skelnen betyder noget, fordi de to funktioner kun ligner hinanden. En note er en flydende tekstboks; en trådet kommentar er en samtalepost med stabile identifikatorer, et svartræ, et forfatterregister og et løst-flag — netop det, en gennemgangsproces rent faktisk kører på. Denne artikel gennemgår OOXML-strukturen først, for når du ser de to lagringsmodeller side om side, holder hver eneste adfærdsforskel i Excel op med at være et mysterium

Hvorfor vises mine kommentarer som noter i Excel 365?

Det korte svar: to generationer af kommentarer deler ét gitter, og de bor i helt forskellige dele af pakken. Den gamle model — den, AddComment rammer — gemmer tekst i xl/comments1.xml (indholdstype application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml) og placerer boblen gennem en tilhørende VML-tegningsdel, xl/drawings/vmlDrawing1.vml. Det par er ældre end båndet; Excel 365 læser det stadig trofast, men gengiver resultatet som en note og tilbyder ingen tråd-brugerflade på det, fordi der ikke er noget i lagringen at tråde. Forfatter er en visningsstreng, position er en VML-form, og det er hele modellen

Trådede kommentarer, der kom med Excel 365, ignorerer VML-laget fuldstændigt. Hvert ark med en samtale bærer sin egen del xl/threadedComments/threadedCommentN.xml under indholdstypen application/vnd.openxmlformats-officedocument.spreadsheetml.threadedComments+xml, og arbejdsbogen bærer én enkelt del xl/person/person.xml (indholdstype ...spreadsheetml.people+xml), der lister hver deltager én gang. HotXLS eksponerer begge modeller på det samme regneark: AddComment bliver ved med at skrive det gamle par, AddThreadedComment skriver de nye dele. Hvis dit output dukker op som noter, kaldte du det første API og ville have det andet. Den gamle model er stadig det rigtige værktøj til visse opgaver — den tidligere artikel om cellekommentarer og hyperlinks i gennemgangsprocesser dækker den i dybden, inklusive XLS-siden — men intet i den opgraderer til en tråd

Diagram, der stiller den gamle Excel-note gemt med en VML-tegning over for de trådede Excel 365-kommentardele, HotXLS skriver fra Delphi
Den klassiske note og den trådede Excel 365-kommentar gemmer samtaler i forskellige OOXML-dele, så Excel tilbyder forskellige funktioner for hver af dem

Hvad HotXLS skriver ind i pakken

HotXLS modellerer hvert svar som et TXLSXThreadedComment-objekt, der bærer et celleanker (Ref), en GUID-agtig identifikator (Id), et valgfrit ParentId, et AuthorId, der slås op i personlisten, et DisplayName-spejl, selve teksten, et Done-flag og en ISO-8601-DateTime. Ved gemning bliver hver post til et <threadedComment>-element, hvis t-attribut rummer identifikatoren, hvis dT-attribut rummer tidsstemplet, og hvis parentT-attribut — kun til stede på svar-børn — navngiver forælderens identifikator. Excel rekonstruerer samtaletræet ved at matche parentT mod t; der er ingen indlejring i XML-koden, bare en flad liste plus referencer

Person-delen er den brik, de fleste hjemmestrikkede implementeringer glemmer. Hvert authorId i en trådet kommentar skal kunne slås op i en <person>-post i xl/person/person.xml, koblet ind i arbejdsbogsrelationerne, med sin egen tilsidesættelse af indholdstypen — ellers har Excel intet visningsnavn at vise. HotXLS klarer registreringen automatisk: AddThreadedComment slår forfatteren op på arbejdsbogens Persons-liste efter visningsnavn og opretter en post med en frisk GUID, når ingen findes, så på hinanden følgende kommentarer fra samme forfatter deler én identitet. Regnearksrelationerne får threadedComments-relationen, arbejdsbogsrelationerne får person-relationen, og content-types-manifestet får begge tilsidesættelser, uden at noget af det dukker op i din kode

Sådan bygger du en svarkæde med AddThreadedComment og AddThreadedReply

AddThreadedComment opretter samtalens rod: dens ParentId forbliver tom, og det er netop det, der markerer den som toppen af tråden. AddThreadedReply tager en ParentRef-parameter — cellereferencen, hvor roden bor — finder roden ved det anker og stempler det nye svars ParentId med rodens Id. Begge returnerer det nye TXLSXThreadedComment, så du kan sætte tidsstempel eller løst-tilstand på det objekt, du netop har oprettet:

Diagram over en HotXLS-svarkæde fra Delphi, hvor AddThreadedComment opretter roden, og svar kobles sammen gennem parentT-referencer
AddThreadedComment bygger roden, og AddThreadedReply kæder sig til den, hvilket giver en flad liste i delen, som Excel samler igen gennem parentT-referencer
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Root: TXLSXThreadedComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('forecast.xlsx');
    Sheet := Book.Sheets[0];

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

    // Svar forankres i den samme celle; ParentRef navngiver rodcellen
    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;

To detaljer i den listning fortjener opmærksomhed. For det første sendes Root.Ref i stedet for en håndbygget streng: den reference, biblioteket genererede, matcher med garanti det, AddThreadedReply vil slå op, hvilket fjerner en hel klasse af off-by-one-ankerfejl. For det andet blev begge forfattere sendt som almindelige visningsnavne — personregistret, GUID-identiteterne og authorId-koblingen skete alt sammen bag kaldene. Åbn den gemte fil i Excel 365, og celleforankringen, svarrækkefølgen og de to forskellige deltagere er alle levende: klik på Svar, og Excel føjer til den samme tråd, som din kode startede

At læse en samtale tilbage: adfærd ved rundtur

HotXLS rundturer trådede kommentarer fra og med version 2.122.0: at genåbne en gemt pakke genopbygger hvert arks samtaleliste og arbejdsbogens deltagerliste ud fra delene, så identifikatorer, svarlinks og visningsnavne overlever en fuld læs→skriv-cyklus. Læseren parser person.xml før alt arkindhold, og det er netop det, der lader hvert authorId slå op i et visningsnavn ved indlæsning; parsede personidentifikatorer bevares, som de er, i stedet for at blive genereret på ny, så en arbejdsbog, der pendler mellem din kode og Excel, holder stabile forfatteridentiteter gennem vilkårligt mange rundture

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;

Lagringen som flad liste plus referencer skinner bevidst igennem her: ThreadedComments opregner svar i delens rækkefølge, og et tomt ParentId identificerer hver rod. Har du brug for træformen — for eksempel til at eksportere en gennemgangslog — så gruppér først efter Ref og kæd derefter ParentId til Id inden for hver celles gruppe. Denne bevar-det-du-parsede-adfærd er ét eksempel på en bredere politik i biblioteket; artiklen om tabsfri rundtur af temaer, udvidelseslister og beregningskæden dækker, hvordan det samme princip gælder for resten af pakken

Løst-tilstand, tidsstempler og forfatteridentitet

Tre attributter bærer gennemgangssemantikken, og alle tre kan skrives på de returnerede objekter. Done mapper til done-attributten — Excel viser en løst tråd klappet sammen med et Genåbn-link. DateTime er ISO-8601-UTC-oprettelsesstemplet (dT); HotXLS indsætter en fast pladsholder, når du lader den stå tom, så delen altid validerer, men et rigtigt tidsstempel er det, der gør trådens historik læsbar, så sæt det. På identitetssiden eksponerer hvert TXLSXPerson et ProviderId — typisk None for lokalt oprettede poster — og et UserId, du kan udfylde med et UPN eller en e-mailadresse, når din applikation kender den:

var
  Root: TXLSXThreadedComment;
  Person: TXLSXPerson;
begin
  Root := Sheet.ThreadedComments.FindAt('D9');
  if Root <> nil then
    Root.Done := True;   // gemmes som done="1"; Excel viser tråden som løst

  Person := Book.Persons.FindByDisplayName('Maria Ortiz');
  if Person <> nil then
    Person.UserId := '[email protected]';
end;

Én grænse skal siges ligeud: HotXLS skriver <mentions>-elementet tomt. Poster for @-omtaler — maskineriet, der lyser en kollegas navn op inde i kommentarteksten og udløser en notifikation i Microsoft 365 — er ikke modelleret, så tekst med et @-tegn gemmes som ren tekst. For en genereret gennemgangsarbejdsbog er det sjældent et tab, men hvis din arbejdsgang afhænger af notifikationer om omtaler, så regn med, at et menneske tilføjer dem inde i Excel. Forfatteridentitet i person-delen hænger naturligt sammen med proveniensfelterne på arbejdsbogsniveau, der er dækket i arbejdsbogsmetadata og dokumentegenskaber, hvor resten af en genereret fils revisionsspor bor

Hvad gør ældre Excel-versioner med trådede kommentarer?

Excel 2016 og tidligere er ældre end trådmodellen, og de ignorerer ganske enkelt dele, de ikke forstår. Når Excel 365 selv gemmer en trådet samtale, skriver den også en gammeldags kommentarskygge — en pladsholdernote, der lyder "[Threaded comment]…" — netop for at ældre versioner viser noget ved den forankrede celle. HotXLS udsender kun de trådede dele, uden gammel skygge, så en fil skrevet af din kode viser slet ingen kommentarindikator, når den åbnes i Excel 2016. Hvis dit publikum omfatter installationer fra før 365, og annotationen skal være synlig dér, er det gamle AddComment-API stadig den kompatible kanal; bare undgå at stable begge modeller på den samme celle uden at teste hver eneste Excel-version, du leverer til, for hvordan en læser forener en note og en tråd på én celle, er læserens beslutning, ikke din

Diagram over versionskompatibilitet for trådede HotXLS-kommentarer på tværs af Excel 365, Excel 2016 og gamle xls-arbejdsbøger
Den samme fil viser en samtale, ingenting eller en note afhængigt af Excel-versionen og filformatet

Den anden hårde grænse er selve filformatet. Trådede kommentarer er en OOXML-struktur uden modstykke i BIFF8, så .xls-siden af HotXLS har intet tråd-API — en arbejdsbog, der skal blive i det gamle binære format, er begrænset til klassiske noter. I praksis er beslutningstabellen kort: skriver du .xlsx til Excel 365 eller til Excel på web og mobil, så brug AddThreadedComment og få rigtige samtaler; sigter du efter Excel 2016 eller .xls, så brug AddComment og accepter notemodellen; auditerer du en fil, du har modtaget, så tjek både ThreadedComments og Comments, for en arbejdsbog, der har levet i begge verdener, kan med rette bære begge dele. Den fulde API-flade for begge generationer er dokumenteret på produktsiden HotXLS Delphi Excel Component