Artykuł techniczny

Wątkowane komentarze w plikach Excela za pomocą Delphi i HotXLS

Wygeneruj skoroszyt z poziomu Delphi, dodaj adnotację do komórki za pomocą klasycznego API komentarzy, a program Excel 365 zapisze Twój tekst jako notatkę (Note): staromodne żółte pole bez pola odpowiedzi, bez awatara autora i bez przycisku rozwiązania wątku. Biblioteka HotXLS zapisuje prawdziwe wątkowane komentarze (threaded comments) programu Excel 365 za pomocą metod AddThreadedComment oraz AddThreadedReply w silniku XLSX. Tworzy ona dedykowany plik xl/threadedComments/threadedCommentN.xml dla każdego arkusza i rejestruje każdego autora w globalnej strukturze uczestników (person) skoroszytu, dzięki czemu konwersacja utworzona przez Twój kod staje się tym samym obiektem, na który recenzent odpowiada w programie Excel

Różnica ta jest kluczowa, ponieważ obie te funkcje są podobne tylko z wyglądu. Notatka to po prostu ruchome pole tekstowe; wątkowany komentarz to natomiast zapis konwersacji z trwałymi identyfikatorami, drzewem odpowiedzi, rejestrem autorów i flagą rozwiązania wątku — czyli elementami, na których opiera się rzeczywisty proces weryfikacji. W tym artykule najpierw przyjrzymy się strukturze OOXML, ponieważ zestawienie obu tych modeli zapisu wyjaśnia wszelkie różnice w zachowaniu programu Excel

Dlaczego moje komentarze wyświetlają się jako notatki w programie Excel 365?

Model tradycyjny — na który wskazuje metoda AddComment — przechowuje dane tekstowe w pliku xl/comments1.xml (typ zawartości application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml), a dymek pozycjonuje przy użyciu pomocniczego pliku rysunku VML xl/drawings/vmlDrawing1.vml. To rozwiązanie powstało na długo przed wprowadzeniem paska wstążki. Program Excel 365 nadal poprawnie odczytuje te dane, jednak renderuje je jako „notatkę” (note) i nie wyświetla dla nich interfejsu odpowiedzi, ponieważ struktura pliku nie daje możliwości tworzenia wątków. Autor to zwykły ciąg tekstowy, pozycja to kształt VML — i to cały model

Wątkowane komentarze, wprowadzone w programie Excel 365, całkowicie pomijają warstwę VML. Każdy arkusz z konwersacją posiada własny plik xl/threadedComments/threadedCommentN.xml o typie zawartości application/vnd.openxmlformats-officedocument.spreadsheetml.threadedComments+xml, a skoroszyt zawiera pojedynczy plik xl/person/person.xml (typ zawartości ...spreadsheetml.people+xml) z listą wszystkich uczestników. HotXLS udostępnia oba te modele w ramach jednego arkusza: AddComment nadal zapisuje tradycyjną parę plików, natomiast AddThreadedComment zapisuje nową strukturę. Jeśli Twój wynik wyświetla się jako notatka, oznacza to, że wywołałeś pierwszą funkcję API zamiast drugiej. Model tradycyjny wciąż jest odpowiednim narzędziem do niektórych zadań — co szczegółowo omawia wcześniejszy artykuł o komentarzach komórek i hiperłączach w procesach recenzji (w tym po stronie formatu XLS) — lecz żadne z jego ustawień nie przekształci go w wątek

Co HotXLS zapisuje w strukturze archiwum

HotXLS modeluje każdą odpowiedź jako obiekt TXLSXThreadedComment zawierający zakotwiczenie komórki (Ref), identyfikator typu GUID (Id), opcjonalny identyfikator rodzica ParentId, identyfikator autora AuthorId powiązany z listą osób, nazwę wyświetlaną DisplayName, treść komentarza, flagę ukończenia Done oraz znacznik czasu ISO-8601 DateTime. Podczas zapisu każdy wpis staje się elementem <threadedComment>, którego atrybut t przechowuje identyfikator, atrybut dT przechowuje znacznik czasu, a atrybut parentT — obecny wyłącznie w odpowiedziach podrzędnych — wskazuje na identyfikator rodzica. Excel odtwarza drzewo konwersacji poprzez dopasowanie parentT to t; struktura XML nie zawiera zagnieżdżeń, jest to po prostu płaska lista z odnośnikami

Plik rejestru osób (person part) to element, o którym zapomina większość samodzielnie pisanych bibliotek. Każdy identyfikator authorId w wątkowanym komentarzu musi odpowiadać pozycji <person> w pliku xl/person/person.xml, powiązanej z relacjami skoroszytu i posiadającej własny typ zawartości — w przeciwnym razie Excel nie wyświetli nazwy autora. HotXLS obsługuje tę rejestrację automatycznie: metoda AddThreadedComment wyszukuje autora na liście Persons skoroszytu po nazwie wyświetlanej i w razie jego braku tworzy nową pozycję z nowym kluczem GUID. Dzięki temu kolejne komentarze tego samego autora współdzielą jedną tożsamość. Relacje arkusza otrzymują powiązanie z threadedComments, relacje skoroszytu powiązanie z rejestrem osób, a plik typów zawartości (content-types) obie nowe definicje — wszystko to bez ingerencji w Twój kod

Tworzenie łańcucha odpowiedzi za pomocą AddThreadedComment i AddThreadedReply

Metoda AddThreadedComment tworzy korzeń konwersacji: jej identyfikator ParentId pozostaje pusty, co oznacza szczyt wątku. Z kolei AddThreadedReply przyjmuje parametr ParentRef — referencję komórki, w której znajduje się korzeń wątku — odnajduje go pod tym adresem i wpisuje identyfikator Id korzenia do pola ParentId nowej odpowiedzi. Obie funkcje zwracają nowy obiekt TXLSXThreadedComment, co pozwala od razu ustawić dla niego znacznik czasu lub status rozwiązania wątku:

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

    // Korzeń konwersacji; ParentId pozostaje pusty
    Root := Sheet.AddThreadedComment(8, 3,
      'Q3 figure looks low against the pipeline export', 'Maria Ortiz');
    Root.DateTime := '2026-07-06T09:12:00Z';

    // Odpowiedzi są zakotworzone w tej samej komórce; ParentRef wskazuje 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 przykładzie zasługują na uwagę. Po pierwsze, przekazujemy Root.Ref zamiast ręcznie budowanego ciągu tekstowego: wygenerowana przez bibliotekę referencja gwarantuje zgodność z tym, czego będzie szukać metoda AddThreadedReply, co eliminuje błędy przesunięcia adresów zakotwiczenia. Po drugie, obaj autorzy zostali podani jako zwykłe nazwy wyświetlane — rejestracja w bazie osób, przypisanie identyfikatorów GUID oraz powiązania authorId odbyły się automatycznie pod maską wywołań. Otwórz zapisany plik w Excelu 365, a zakotwiczenie komórek, kolejność odpowiedzi i dwaj różni uczestnicy będą w pełni aktywni: kliknij Odpowiedz, a Excel doda wpis do tego samego wątku, który zapoczątkował Twój kod

Odczytywanie konwersacji: zachowanie podczas zapisu i odczytu

Biblioteka HotXLS obsługuje pełny zapis i odczyt wątkowanych komentarzy od wersji v2.122.0: ponowne otwarcie zapisanego pakietu odtwarza listę konwersacji każdego arkusza oraz listę uczestników skoroszytu, dzięki czemu identyfikatory, powiązania odpowiedzi i nazwy wyświetlane przechodzą nienaruszone przez cały proces odczytu i zapisu. Czytnik analizuje plik person.xml przed wczytaniem zawartości arkuszy, co pozwala na dopasowanie każdego authorId do nazwy wyświetlanej już w trakcie ładowania. Wczytane identyfikatory osób są zachowywane bez ich ponownego generowania, więc skoroszyt przekazywany na przemian między Twoim kodem a programem Excel zachowuje spójne tożsamości autorów podczas dowolnej liczby cykli odczytu i zapisu

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('Korzeń pod adresem ', Cmt.Ref, ' by ', Cmt.DisplayName, ': ', Cmt.Text)
    else
      Writeln('  odpowiedź autora ', Cmt.DisplayName, ': ', Cmt.Text);
  end;
  Writeln('Participants: ', Book.Persons.Count);
end;

Płaska struktura zapisu z odnośnikami jest tu widoczna celowo: kolekcja ThreadedComments listuje odpowiedzi w kolejności zapisu w pliku XML, a pusty parametr ParentId wskazuje na korzenie wątków. Jeśli potrzebujesz struktury drzewiastej (na przykład do eksportu rejestru weryfikacji), pogrupuj wpisy najpierw po polu Ref, a następnie połącz ParentId z Id w ramach danej komórki. To zachowanie opierające się na zasadzie „zapisz to, co wczytałeś” stanowi przykład szerszej strategii biblioteki; artykuł o bezstratnym zapisie motywów, list rozszerzeń i łańcucha obliczeń (calc chain) opisuje, jak ta sama zasada dotyczy pozostałych części pliku

Status rozwiązania, znaczniki czasu i tożsamość autorów

Trzy atrybuty niosą ze sobą semantykę weryfikacji i wszystkie są zapisywalne w zwracanych obiektach. Właściwość Done odpowiada atrybutowi done w pliku XML — Excel wyświetla rozwiązany wątek jako zwinięty z odnośnikiem do jego ponownego otwarcia. DateTime to znacznik czasu utworzenia według standardu ISO-8601 UTC (atrybut dT); HotXLS wstawia stały znacznik zastępczy, jeśli pole pozostanie puste, dzięki czemu plik przechodzi walidację, lecz rzeczywisty znacznik czasu ułatwia analizę historii wątku, stąd warto go ustawiać. Po stronie tożsamości każdy obiekt TXLSXPerson udostępnia właściwość ProviderId (zazwyczaj o wartości None dla wpisów tworzonych lokalnie) oraz UserId, którą można uzupełnić adresem email lub nazwą UPN, jeśli aplikacja nimi dysponuje:

var
  Root: TXLSXThreadedComment;
  Person: TXLSXPerson;
begin
  Root := Sheet.ThreadedComments.FindAt('D9');
  if Root <> nil then
    Root.Done := True;   // zapisywane jako done="1"; Excel wyświetli wątek jako rozwiązany

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

Jedno ograniczenie, o którym należy wspomnieć wprost: HotXLS zapisuje element <mentions> jako pusty. Oznaczenia typu @-mention — mechanizm podświetlający imię współpracownika w treści komentarza i wyzwalający powiadomienie w usłudze Microsoft 365 — nie są modelowane, stąd tekst zawierający znak @ jest zapisywany jako zwykły tekst. W przypadku generowanych skoroszytów weryfikacyjnych rzadko stanowi to problem, lecz jeśli proces wymaga powiadomień o wzmiankach, należy założyć, że doda je użytkownik w programie Excel. Tożsamość autorów w rejestrze osób naturalnie uzupełnia się z polami autorskimi na poziomie skoroszytu, opisanymi w artykule o metadanych skoroszytów i właściwościach dokumentów, gdzie zapisywana jest pozostała ścieżka audytu generowanego pliku

Jak starsze wersje programu Excel traktują wątkowane komentarze?

Program Excel 2016 i starsze wersje powstały przed opracowaniem modelu wątkowanego i po prostu ignorują elementy, których nie rozumieją. Gdy sam program Excel 365 zapisuje wątkowaną konwersację, tworzy dodatkowo cień w postaci tradycyjnego komentarza — notatkę zastępczą z tekstem „[Threaded comment]…” — właśnie po to, by starsze wersje wyświetliły cokolwiek w zakotwiczonej komórce. HotXLS generuje wyłącznie elementy wątkowane bez tego tradycyjnego cienia, więc plik zapisany przez Twój kod po otwarciu w Excelu 2016 nie pokaże żadnego znacznika komentarza. Jeśli docelowi użytkownicy korzystają ze starszych wersji Excela (przed wersją 365) i komentarz musi być dla nich widoczny, zgodnym kanałem komunikacji pozostaje tradycyjne API AddComment. Należy jednak unikać nakładania obu tych modeli na tę samą komórkę bez przetestowania zachowania we wszystkich docelowych wersjach Excela, ponieważ sposób pogodzenia notatki i wątku w jednej komórce zależy wyłącznie od danej przeglądarki

Innym sztywnym ograniczeniem jest sam format pliku. Wątkowane komentarze to struktura standardu OOXML, która nie posiada swojego odpowiednika w formacie BIFF8, stąd obsługa formatu .xls w HotXLS nie posiada wątkowanego API — skoroszyt zapisywany w starym formacie binarnym jest ograniczony do klasycznych notatek. W praktyce kryteria wyboru są jasne: jeśli zapisujesz plik .xlsx dla Excela 365 bądź wersji przeglądarkowych/mobilnych, użyj metody AddThreadedComment, aby uzyskać prawdziwe wątki konwersacji; jeśli celujesz w Excela 2016 lub format .xls, użyj AddComment i pozostań przy modelu notatek; przy audycie otrzymanego pliku sprawdzaj zarówno listę ThreadedComments, jak i Comments, ponieważ skoroszyt, który był edytowany w obu tych środowiskach, może zawierać oba rodzaje wpisów. Pełny opis API dla obu technologii znajduje się na stronie produktu HotXLS Delphi Excel Component