Wygeneruj skoroszyt z Delphi, opisz komórkę klasycznym API komentarzy, a Excel 365 umieści twój tekst w sekcji Notes: staroświecki żółty dymek bez pola odpowiedzi, bez awatara autora, bez przycisku rozwiązania wątku. HotXLS zapisuje prawdziwe komentarze wątkowe Excel 365 przez AddThreadedComment i AddThreadedReply w silniku XLSX, emitując dla każdego arkusza dedykowaną część xl/threadedComments/threadedCommentN.xml i rejestrując każdego autora w części person na poziomie skoroszytu, więc rozmowa, którą zapisuje twój kod, jest tym samym obiektem, na który recenzent odpowiada w Excelu
To rozróżnienie ma znaczenie, bo obie funkcje tylko wyglądają podobnie. Notatka to pływające pole tekstowe; komentarz wątkowy to zapis rozmowy ze stabilnymi identyfikatorami, drzewem odpowiedzi, rejestrem autorów i flagą rozwiązania — czyli tym, na czym faktycznie stoi przepływ recenzji. Ten artykuł najpierw przechodzi przez strukturę OOXML, bo gdy raz zobaczysz oba modele składowania obok siebie, każda różnica zachowania w Excelu przestaje być tajemnicza
Dlaczego moje komentarze pokazują się w Excelu 365 jako notatki?
Krótka odpowiedź: dwie generacje komentarzy dzielą jedną siatkę, a mieszkają w zupełnie różnych częściach pakietu. Model starszy — ten, w który celuje AddComment — trzyma tekst w xl/comments1.xml (typ zawartości application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml) i pozycjonuje dymek przez towarzyszącą część rysunku VML, xl/drawings/vmlDrawing1.vml. Ta para jest starsza niż wstążka; Excel 365 wciąż czyta ją wiernie, ale renderuje wynik jako „notatkę” i nie oferuje na niej żadnego interfejsu wątków, bo w składowaniu nie ma czego wątkować. Autor jest ciągiem do wyświetlenia, pozycja jest kształtem VML, i to cały model
Komentarze wątkowe, wprowadzone razem z Excel 365, całkowicie ignorują warstwę VML. Każdy arkusz z rozmową niesie własną część xl/threadedComments/threadedCommentN.xml pod typem zawartości application/vnd.openxmlformats-officedocument.spreadsheetml.threadedComments+xml, a skoroszyt niesie pojedynczą część xl/person/person.xml (typ zawartości ...spreadsheetml.people+xml), wymieniającą każdego uczestnika raz. HotXLS udostępnia oba modele na tym samym arkuszu: AddComment nadal zapisuje starszą parę, AddThreadedComment zapisuje nowe części. Jeśli twoje wyjście pojawia się jako notatki, wywołałeś pierwsze API, a chciałeś drugiego. Starszy model wciąż bywa właściwym narzędziem — wcześniejszy artykuł o komentarzach w komórkach i hiperłączach w przepływach recenzji omawia go dogłębnie, razem ze stroną XLS — ale nic w nim nie awansuje do wątku
Co HotXLS zapisuje do pakietu
HotXLS modeluje każdą odpowiedź jako obiekt TXLSXThreadedComment niosący zakotwiczenie w komórce (Ref), identyfikator w stylu GUID (Id), opcjonalny ParentId, AuthorId rozwiązywany przez listę person, kopię DisplayName, treść tekstu, flagę Done oraz DateTime w formacie ISO-8601. Przy zapisie każdy wpis staje się elementem <threadedComment>, którego atrybut t trzyma identyfikator, atrybut dT trzyma znacznik czasu, a atrybut parentT — obecny wyłącznie w odpowiedziach potomnych — nazywa identyfikator rodzica. Excel odtwarza drzewo rozmowy, dopasowując parentT do t; w XML nie ma żadnego zagnieżdżenia, jest tylko płaska lista plus odwołania
Część person to element, o którym najczęściej zapominają implementacje pisane ręcznie. Każdy authorId w komentarzu wątkowym musi rozwiązywać się do wpisu <person> w xl/person/person.xml, wpiętego w relacje skoroszytu, z własnym nadpisaniem typu zawartości — inaczej Excel nie ma jakiej nazwy wyświetlić. HotXLS obsługuje rejestrację automatycznie: AddThreadedComment szuka autora na skoroszytowej liście Persons po nazwie wyświetlanej i tworzy wpis ze świeżym GUID-em, gdy żadnego nie ma, więc kolejne komentarze tego samego autora dzielą jedną tożsamość. Relacje arkusza dostają relację threadedComments, relacje skoroszytu dostają relację person, a manifest typów zawartości dostaje oba nadpisania, i nic z tego nie pojawia się w twoim kodzie
Budowanie łańcucha odpowiedzi przez AddThreadedComment i AddThreadedReply
AddThreadedComment tworzy korzeń rozmowy: jego ParentId zostaje pusty, i to właśnie oznacza go jako szczyt wątku. AddThreadedReply przyjmuje parametr ParentRef — odwołanie do komórki, w której mieszka korzeń — znajduje korzeń w tym zakotwiczeniu i stempluje ParentId nowej odpowiedzi wartością Id korzenia. Oba wywołania zwracają nowy TXLSXThreadedComment, więc możesz ustawić znacznik czasu albo stan rozwiązania na obiekcie, który dopiero co utworzyłeś:
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Root: TXLSXThreadedComment;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('forecast.xlsx');
Sheet := Book.Sheets[0];
// Korzeń rozmowy; ParentId zostaje pusty
Root := Sheet.AddThreadedComment(8, 3,
'Q3 figure looks low against the pipeline export', 'Maria Ortiz');
Root.DateTime := '2026-07-06T09:12:00Z';
// Odpowiedzi kotwiczą w tej samej komórce; ParentRef nazywa komórkę korzenia
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;
Dwa szczegóły w tym listingu wynagradzają uwagę. Po pierwsze, przekazywane jest Root.Ref, a nie ręcznie sklecony ciąg: odwołanie wygenerowane przez bibliotekę na pewno zgadza się z tym, czego poszuka AddThreadedReply, co usuwa całą klasę błędów zakotwiczenia o jeden. Po drugie, obaj autorzy zostali podani jako zwykłe nazwy wyświetlane — rejestr person, tożsamości GUID i wpięcie authorId wydarzyły się za kulisami tych wywołań. Otwórz zapisany plik w Excel 365, a zakotwiczenie w komórce, kolejność odpowiedzi i dwoje odrębnych uczestników są żywe: kliknij Reply, a Excel dopisze do tego samego wątku, który zaczął twój kod
Odczyt rozmowy z powrotem: zachowanie przy pełnym cyklu
HotXLS przenosi komentarze wątkowe przez pełny cykl od wersji 2.122.0: ponowne otwarcie zapisanego pakietu odbudowuje listę rozmów każdego arkusza i listę uczestników skoroszytu z części, więc identyfikatory, powiązania odpowiedzi i nazwy wyświetlane przeżywają pełny cykl odczyt→zapis. Czytnik parsuje person.xml przed jakąkolwiek treścią arkusza, i to właśnie pozwala każdemu authorId rozwiązać się do nazwy wyświetlanej w chwili wczytania; sparsowane identyfikatory osób są zachowywane bez zmian, a nie generowane od nowa, więc skoroszyt kursujący między twoim kodem a Excelem zachowuje stabilne tożsamości autorów przez dowolnie wiele obiegów
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;
Składowanie w postaci płaskiej listy z odwołaniami przebija tu celowo: ThreadedComments wylicza odpowiedzi w kolejności części, a pusty ParentId identyfikuje każdy korzeń. Jeśli potrzebujesz kształtu drzewa — powiedzmy po to, by wyeksportować dziennik recenzji — pogrupuj najpierw po Ref, a potem w obrębie grupy każdej komórki łącz ParentId z Id. To zachowanie „zachowaj to, co sparsowałeś” jest jednym przypadkiem szerszej polityki biblioteki; artykuł o bezstratnym przenoszeniu motywów, list rozszerzeń i łańcucha obliczeń omawia, jak ta sama zasada stosuje się do reszty pakietu
Stan rozwiązania, znaczniki czasu i tożsamość autora
Trzy atrybuty niosą semantykę recenzji i wszystkie trzy da się zapisać na zwracanych obiektach. Done mapuje się na atrybut done — Excel pokazuje rozwiązany wątek zwinięty, z odnośnikiem Reopen. DateTime to stempel utworzenia w UTC wg ISO-8601 (dT); HotXLS podstawia stałą wartość zastępczą, gdy zostawisz go pustym, żeby część zawsze przechodziła walidację, ale to prawdziwy znacznik czasu czyni historię wątku czytelną, więc go ustaw. Po stronie tożsamości każdy TXLSXPerson udostępnia ProviderId — zwykle None dla wpisów tworzonych lokalnie — oraz UserId, który możesz wypełnić UPN-em albo adresem e-mail, gdy twoja aplikacja go zna:
var
Root: TXLSXThreadedComment;
Person: TXLSXPerson;
begin
Root := Sheet.ThreadedComments.FindAt('D9');
if Root <> nil then
Root.Done := True; // zapisane jako done="1"; Excel pokazuje wątek rozwiązany
Person := Book.Persons.FindByDisplayName('Maria Ortiz');
if Person <> nil then
Person.UserId := '[email protected]';
end;
Jedna granica warta jasnego postawienia: HotXLS zapisuje element <mentions> pusty. Rekordy wzmianek z @ — maszyneria, która podświetla nazwisko współpracownika w treści komentarza i wyzwala powiadomienie w Microsoft 365 — nie są modelowane, więc tekst zawierający znak @ jest przechowywany jako zwykły tekst. Dla generowanego skoroszytu recenzji rzadko jest to strata, ale jeśli twój przepływ zależy od powiadomień o wzmiankach, zaplanuj, że doda je człowiek już w Excelu. Tożsamość autora w części person łączy się naturalnie z polami pochodzenia na poziomie skoroszytu, opisanymi w artykule o metadanych skoroszytu i właściwościach dokumentu, gdzie mieszka reszta ścieżki audytu generowanego pliku
Co starsze wersje Excela robią z komentarzami wątkowymi?
Excel 2016 i wcześniejsze są starsze niż model wątkowy i po prostu ignorują części, których nie rozumieją. Kiedy sam Excel 365 zapisuje rozmowę wątkową, dopisuje też cień starszego komentarza — notatkę zastępczą o treści „[Threaded comment]…” — właśnie po to, żeby starsze wersje pokazały cokolwiek w zakotwiczonej komórce. HotXLS emituje wyłącznie części wątkowe, bez starszego cienia, więc plik zapisany przez twój kod nie pokazuje w Excelu 2016 żadnego wskaźnika komentarza. Jeśli wśród twoich odbiorców są instalacje sprzed 365, a adnotacja musi być tam widoczna, starsze API AddComment pozostaje kanałem zgodnym; unikaj tylko piętrzenia obu modeli na tej samej komórce bez przetestowania każdej wersji Excela, do której wysyłasz pliki, bo to, jak czytnik pogodzi notatkę i wątek na jednej komórce, jest decyzją czytnika, a nie twoją
Drugą twardą granicą jest sam format pliku. Komentarze wątkowe to struktura OOXML bez odpowiednika w BIFF8, więc strona .xls w HotXLS nie ma API wątkowego — skoroszyt, który musi zostać w starszym formacie binarnym, jest ograniczony do klasycznych notatek. W praktyce tabela decyzyjna jest krótka: zapisujesz .xlsx dla Excel 365 albo dla czytników webowych i mobilnych, używasz AddThreadedComment i dostajesz prawdziwe rozmowy; celujesz w Excel 2016 albo .xls, używasz AddComment i godzisz się na model notatki; audytujesz otrzymany plik, sprawdzasz jednocześnie ThreadedComments i Comments, bo skoroszyt, który żył w obu światach, może zgodnie z prawem nieść oba. Pełna powierzchnia API obu generacji jest udokumentowana na stronie produktu HotXLS Delphi Excel Component