Сгенерируйте рабочую книгу из Delphi, добавьте к ячейке аннотацию с помощью классического API комментариев, и Excel 365 поместит ваш текст в "Заметки" (Notes) — желтое окно старого стиля без поля ответа, аватара автора и кнопки решения. HotXLS записывает полноценные цепочки комментариев (threaded comments) Excel 365 с помощью методов AddThreadedComment и AddThreadedReply в движке XLSX. При этом для каждого листа создается отдельная часть xl/threadedComments/threadedCommentN.xml, а каждый автор регистрируется в глобальном списке пользователей книги person. В результате созданное программно обсуждение представляет собой тот же объект, на который рецензент отвечает в Excel
Разница имеет значение, поскольку эти две функции похожи только внешне. Заметка — это плавающее текстовое окно, а цепочка комментариев — это запись обсуждения со стабильными идентификаторами, деревом ответов, реестром авторов и флагом решения (то, на чем строится процесс рецензирования). В этой статье мы сначала разберем структуру OOXML, поскольку, как только вы сопоставите обе модели хранения данных, любые различия в поведении Excel перестанут казаться загадочными
Почему мои комментарии отображаются как заметки в Excel 365?
Краткий ответ: два поколения комментариев сосуществуют в одной таблице, но хранятся в совершенно разных частях пакета. Устаревшая модель (на которую нацелен метод AddComment) сохраняет текст в xl/comments1.xml (тип контента application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml) и позиционирует всплывающее окно через сопутствующую графическую часть VML xl/drawings/vmlDrawing1.vml. Эта пара была создана еще до появления ленточного интерфейса. Excel 365 по-прежнему считывает ее корректно, но отображает результат как "заметку" и не предлагает для нее интерфейса цепочки ответов, так как в структуре хранения просто нечего связывать в цепочку. Имя автора — это отображаемая строка, положение — фигура VML, и этим ограничивается вся модель
Цепочки комментариев, появившиеся в Excel 365, полностью игнорируют уровень VML. Каждый лист с обсуждением имеет собственную часть xl/threadedComments/threadedCommentN.xml с типом контента application/vnd.openxmlformats-officedocument.spreadsheetml.threadedComments+xml, а книга содержит один файл xl/person/person.xml (тип контента ...spreadsheetml.people+xml), где каждый участник перечислен один раз. HotXLS предоставляет доступ к обеим моделям на одном листе: метод AddComment продолжает записывать устаревшую пару файлов, а метод AddThreadedComment создает новые разделы. Если результаты вашей работы отображаются в виде заметок, значит, вы вызвали первый API, хотя вам требовался второй. Устаревшая модель по-прежнему подходит для ряда задач — предыдущая статья о комментариях ячеек и гиперссылках в процессах рецензирования подробно описывает ее работу, включая формат XLS, но никакие ее элементы нельзя преобразовать в цепочку ответов
Что HotXLS записывает в пакет
HotXLS представляет каждый ответ в виде объекта TXLSXThreadedComment, содержащего привязку к ячейке (Ref), идентификатор в формате GUID (Id), необязательный ParentId, AuthorId для сопоставления со списком пользователей, зеркальное имя DisplayName, текст комментария, флаг завершения Done и отметку времени ISO-8601 DateTime. При сохранении каждая запись превращается в элемент <threadedComment>, атрибут t которого содержит идентификатор, атрибут dT — временную метку, а атрибут parentT (присутствует только в ответах) указывает на идентификатор родительского комментария. Excel восстанавливает дерево обсуждения, сопоставляя parentT с t. В XML-коде нет вложенности — это плоский список с перекрестными ссылками
Раздел person — это часть, которую упускает большинство самописных реализаций. Каждый authorId в цепочке комментариев должен указывать на запись <person> в xl/person/person.xml, связанную со связями книги и имеющую переопределение типа контента — иначе Excel не сможет отобразить имя автора. HotXLS выполняет эту регистрацию автоматически: метод AddThreadedComment ищет автора в списке книги Persons по отображаемому имени и создает новую запись с новым GUID, если автора нет в списке. Таким образом, последовательные комментарии одного автора используют единый идентификатор. Связи листа получают связь threadedComments, связи книги — связь с person, а манифест типов контента регистрирует оба переопределения, и все это происходит без написания дополнительного кода на вашей стороне
Создание цепочки ответов с помощью AddThreadedComment и AddThreadedReply
Метод AddThreadedComment создает корень обсуждения: его свойство ParentId остается пустым, что и обозначает начало цепочки. AddThreadedReply принимает параметр ParentRef (ссылку на ячейку, в которой находится корень), находит корень по этой привязке и записывает идентификатор корня Id в свойство ParentId нового ответа. Оба метода возвращают созданный объект TXLSXThreadedComment, поэтому вы можете задать временную метку или статус решения для созданного комментария:
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;
В приведенном листинге заслуживают внимания две детали. Во-первых, передается Root.Ref, а не сформированная вручную строка: ссылка, сгенерированная библиотекой, гарантированно совпадает с тем, что будет искать AddThreadedReply, что исключает ошибки со смещением привязок. Во-вторых, оба автора были переданы в виде простых отображаемых имен — реестр пользователей, идентификаторы GUID и связи authorId настроились автоматически. Откройте сохраненный файл в Excel 365: привязка к ячейкам, порядок ответов и разделение участников будут работать. При нажатии кнопки "Ответить" Excel добавит новый комментарий в ту же цепочку, которую создал ваш код
Чтение обсуждения: особенности импорта-экспорта
HotXLS поддерживает полный цикл импорта-экспорта цепочек комментариев, начиная с версии v2.122.0: открытие сохраненного пакета восстанавливает список обсуждений каждого листа и список участников книги из соответствующих файлов. Благодаря этому идентификаторы, ссылки ответов и отображаемые имена полностью сохраняются при чтении и записи. Читатель парсит person.xml перед разбором содержимого листов, что позволяет сопоставить каждый authorId с именем пользователя при загрузке. Прочитанные идентификаторы пользователей сохраняются без изменений вместо повторной генерации, поэтому книга, проходящая обработку в вашем коде и в Excel, сохраняет стабильные профили авторов
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;
Структура хранения в виде плоского списка с перекрестными ссылками находит здесь прямое отражение: коллекция ThreadedComments перечисляет ответы в порядке их расположения в файле, а пустой ParentId указывает на корень обсуждения. Если вам нужна древовидная структура (например, для экспорта журнала рецензирования), сгруппируйте комментарии по свойству Ref, а затем свяжите ParentId с Id внутри группы каждой ячейки. Такое поведение с сохранением прочитанной структуры является отражением общего правила библиотеки. О том, как это применимо к остальным частям пакета, рассказано в статье о бесшовном переносе тем, списков расширений и цепочки вычислений
Статус решения, временные метки и профили авторов
Эти три атрибута несут семантику рецензирования, и все они доступны для записи в возвращаемых объектах. Свойство Done сопоставляется с атрибутом done — Excel показывает решенные обсуждения свернутыми с возможностью повторного открытия. DateTime — это метка создания в формате ISO-8601 UTC (dT). HotXLS подставляет фиксированный заполнитель при отсутствии значения для обеспечения валидности XML, но реальная временная метка делает историю обсуждения читаемой, поэтому ее стоит задавать. Что касается профилей пользователей, каждый объект TXLSXPerson предоставляет свойство ProviderId (обычно None для записей, созданных локально) и UserId, куда можно записать логин или email, если они известны вашему приложению:
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;
Честная граница возможностей: HotXLS оставляет элемент <mentions> пустым. Записи упоминаний через @ (механизм, который выделяет имя коллеги в тексте комментария и отправляет уведомление в Microsoft 365) не моделируются, поэтому текст со знаком @ сохраняется в виде обычной строки. Для генерируемого отчета это обычно некритично, но если ваш рабочий процесс требует отправки уведомлений с упоминаниями, запланируйте их ручное добавление в Excel. Профили авторов в разделе person хорошо сочетаются со свойствами книги, описанными в статье о метаданных и свойствах документов книги, где фиксируется остальной след аудита сгенерированного файла
Как старые версии Excel работают с цепочками комментариев?
Версии Excel 2016 и более ранние были созданы до появления цепочек комментариев, поэтому они просто игнорируют новые разделы, структуру которых не понимают. Когда сам Excel 365 сохраняет цепочку обсуждения, он дополнительно записывает теневой устаревший комментарий — заметку-заполнитель с текстом "[Threaded comment]...", чтобы старые версии отображали хоть что-то в целевой ячейке. HotXLS выводит только цепочки комментариев без создания устаревших теневых заметок, поэтому в файле, записанном вашим кодом, при открытии в Excel 2016 индикатор комментариев отображаться не будет. Если вам нужно обеспечить совместимость с версиями до Excel 365 и комментарии должны быть там видны, используйте устаревший API AddComment. Избегайте добавления обеих моделей к одной ячейке без тестирования во всех целевых версиях Excel, так как то, как именно программа чтения сопоставит заметку и цепочку комментариев на одной ячейке — это логика самой программы, а не вашей библиотеки
Другое строгое ограничение касается самого формата файлов. Цепочки комментариев являются структурой OOXML и не имеют аналогов в BIFF8, поэтому двоичная часть .xls в HotXLS не содержит этого API — книга в устаревшем бинарном формате ограничена только классическими заметками. На практике логика выбора проста: если вы записываете .xlsx для Excel 365, веб-версии или мобильных клиентов, используйте AddThreadedComment для получения полноценных обсуждений. Если вы ориентируетесь на Excel 2016 или формат .xls, вызывайте AddComment для работы с моделью заметок. При чтении полученного файла проверяйте коллекции ThreadedComments и Comments, так как книга, прошедшая через разные версии редакторов, может содержать элементы обоих типов. Полное описание API для обоих поколений приведено на странице HotXLS Delphi Excel Component