Один лист у вас уже доведен до идеала. Полоса заголовка объединена, ширины столбцов соответствуют данным, верхние две строки закреплены, область печати и поля настроены под аккуратный экспорт на A4, а вкладка окрашена так, чтобы отдел финансов быстро ее находил. Теперь отчету нужно двенадцать таких листов, по одному на регион, и каждый должен начинаться с одинакового макета. Пересобирать этот лист в коде двенадцать раз означает приглашать медленное расхождение: для региона 7 один столбец станет на point уже, регион 11 потеряет закрепление, и никто не заметит этого, пока PDF не окажется на столе у менеджера. На деле вам нужна программная версия правого клика Excel, Move or Copy, Create a copy: взять готовый лист и штамповать из него независимые дубликаты
Движок XLSX в HotXLS, нативной библиотеке для Delphi и C++Builder, которая читает и пишет файлы Excel без автоматизации самого Excel, уже умел перемещать листы, удалять листы и копировать диапазоны ячеек между листами. Чего он не умел до v2.91.0, так это клонировать целый worksheet одним вызовом. Этот релиз добавляет две точки входа: TXLSXWorksheet.CopyFrom , которая копирует состояние уровня листа с одного worksheet на другой, и TXLSXSheets.Duplicate , которая добавляет новый лист и вызывает CopyFrom за вас. Интересно не то, что оно что-то копирует. Интересна осознанная граница между тем, что копируется глубоко, и тем, что не копируется, а также причина, по которой линия проведена именно там
Один вызов для клонирования готового листа
Операция верхнего уровня это Duplicate . Передайте ей 1-based индекс исходного листа, и она вернет совершенно новый worksheet, повторяющий исходный layout и данные. Соглашение об индексах совпадает с Items[] на стороне XLSX, поэтому первый лист имеет индекс 1 , а не 0 . Если передать индекс вне диапазона, вы получите nil , а не исключение, тот же контракт отказа, который используется остальной коллекцией листов XLSX
var
Book: TXLSXWorkbook;
Template, Copy: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Template := Book.Sheets.Add('Template');
Template.Cells[1, 1].Value := 'Quarterly Statement';
Template.Range['A1:C1'].Merge;
Template.ColWidth[1] := 18;
Template.FreezePanes(2, 1); // freeze top row + first column
Template.TabColorIsAuto := False;
Template.TabColor := $FF1F4E79;
// Clone with an explicit name...
Copy := Book.Sheets.Duplicate(1, 'Region-North');
// ...or let it pick the Excel-style default name.
Copy := Book.Sheets.Duplicate(1); // -> "Template (2)"
Book.SaveAs('regions.xlsx');
finally
Book.Free;
end;
end;
В этом фрагменте стоит замедлиться на двух вещах. Во-первых, FreezePanes принимает аргументы в порядке row-first, FreezePanes(ARow, ACol) , поэтому он выровнен с индексацией Cells[Row, Col] . Дубликат наследует точную границу закрепления. Во-вторых, метод называется Duplicate , а не более очевидно Copy , и это не stylistic preference. Copy является стандартной процедурой в модуле System и постоянно используется для строк и dynamic array. Метод с именем Copy в классе затенял бы его внутри тел методов и создавал бы именно ту неоднозначность разрешения имен, которая бьет по вам через полгода. Duplicate обходит эту проблему целиком и при этом читается правильно в точке вызова
Имя по умолчанию следует правилу самого Excel
Когда вы вызываете overload с одним аргументом или передаете пустую строку имени, новый лист получает имя исходного с суффиксом (2) , и суффикс повышается до тех пор, пока имя не станет уникальным. Продублируйте лист Template один раз и получите Template (2) . Продублируйте его еще раз и получите Template (3) , потому что Template (2) уже занято. Это повторяет имена, которые Excel генерирует в своей команде Create a copy, поэтому workbook, созданный вашим кодом, выглядит так, как пользователь ожидает от ручного дублирования. Проверка уникальности идет по живой коллекции листов, а значит, она обходит и имена, которые вы создавали вручную, а не только результаты предыдущих дублирований
Если вы генерируете один лист на регион или месяц, лучше опирайтесь на overload с явным именем. Предсказуемая схема Region-North , Region-South удобнее для последующего обращения, чем цепочка суффиксов (2) , (3) , и она делает defined names и cross-sheet formulas более читаемыми
Что CopyFrom копирует глубоко
Под капотом Duplicate добавляет лист, а затем вызывает CopyFrom(ASource) , который вы можете вызывать и напрямую, когда хотите клонировать содержимое на лист, уже созданный заранее. CopyFrom сразу закрывает два вырожденных случая: копирование из nil или копирование листа в самого себя, в обоих случаях метод просто сразу выходит и ничего не делает. Все, что идет дальше, это собственно копирование, и оно намеренно широкое
Сначала идут данные ячеек. CopyFrom запрашивает у источника его UsedRange , компактную ограничивающую рамку заполненных ячеек и merged regions, а затем повторно использует имеющуюся механику CopyRangeTo , чтобы перенести в target каждое значение, формулу и индекс стиля ячейки, начиная с A1 . Поверх ячеек он воспроизводит полный слой состояния уровня листа, который и делает шаблон завершенным:
- Merged ranges, пересоздаваемые по координатам, чтобы заголовочная полоса перекрывала тот же прямоугольник
- Ширины столбцов и высоты строк, а также списки hidden, collapsed и outline-level, копируются дословно, чтобы нестандартные строки и столбцы выравнивались точно
- Freeze panes и view state: zoom level, отображение gridline и zero-value, направление right-to-left и тип view
- Состояние protection с его опциями разрешений для отдельных действий, чтобы заблокированный шаблон оставался заблокированным тем же способом
- Весь блок page setup: margins, orientation, paper size, scaling и fit-to-page, print area, print titles, headers и footers, а также флаги print-gridlines и print-headings
- Диапазон AutoFilter, цвет вкладки и видимость листа
Результат состоит в том, что лист печатается, фильтруется и выглядит идентично источнику. А поскольку ячейки, merged ranges и списки размеров физически пересоздаются на новом листе, а не получают общие ссылки, дубликат полностью независим. Запишите 999 в ячейку на копии, и источник сохранит исходное значение. Эта независимость является самым важным свойством клона, предназначенного для параллельных региональных отчетов, и поставляемый demo SheetCopy проверяет это явно
Что остается поверхностным и почему
А теперь честная часть. Charts, embedded images, XLSX tables, data validations и правила conditional formatting не копируются. Это документированная и намеренная граница, а не недосмотр, и ее полезно понимать заранее, чтобы вы могли спланировать обходной путь, а не удивляться результату
Каждая из этих коллекций несет идентичность и ссылки, которые не переживают наивного копирования полей. Chart указывает на диапазон исходных данных и владеет drawing relationship в пакете OOXML. Если клонировать объект без переназначения relationship и ссылок серий, получится chart, который рендерится по неправильным данным, или пакет, который Excel пометит как требующий repair. Table имеет имя, которое должно быть уникальным в пределах workbook, строку заголовка, привязанную к конкретным столбцам, и собственный автоматически создаваемый relationship. Conditional formats и data validations привязаны к координатным диапазонам, а validation вдобавок может ссылаться на другие диапазоны формулой. Корректное deep-copy для любого из этих объектов означает переписывание ссылок и выпуск новых identity, а это уже реальная работа с реальными режимами отказа. Делать это наполовину, скопировав объект, но не его ссылки, хуже, чем не копировать вовсе: файл откроется с prompt на repair и молча потеряет часть содержимого. Поэтому движок копирует то, что может перенести чисто, а коллекции, несущие ссылки, оставляет вызывающей стороне, которая лучше знает, на что именно должен указывать target
На практике это означает такой workflow для более богатого шаблона: продублировать лист, чтобы получить ячейки, layout и print setup, а затем заново построить chart, table, validations или conditional formats на копии тем же API, который вы использовали при первом создании. Поскольку вы пересоздаете их уже относительно собственных диапазонов дубликата, ссылки по построению получаются корректными. Для chart, который читает A1:C10 , добавьте на копии новый chart, указывающий на A1:C10 этой копии. Для AutoFilter, который должен остаться рабочим, помните, что диапазон фильтра переносится, поэтому вам нужно лишь заново применить критерии по столбцам. Правила conditional formatting и data validation вы добавляете теми же вызовами, что описаны в статье об объединенных ячейках и layout шаблонов отчетов , где разбираются merge table и range model, которые копия унаследует
Где дублирование вписывается в reporting pipeline
Дублирование worksheet является естественным дополнением к генерации, основанной на placeholder. Подход с токенами в руководстве по template-driven генерации отчетов в Delphi решает задачу записи данных в layout, который редактируют другие люди. Дублирование решает задачу, когда такой layout нужен много раз в одном workbook. Вместе шаблон получается чистым: храните один безупречный лист Template с его токенами, merged ranges и print setup, затем для каждого региона или периода вызывайте Duplicate , заполняйте токены клона соответствующим срезом данных и переходите к следующему. Исходный template никогда не мутируется, поэтому он остается надежным источником для следующего клона, а каждый выходной лист начинается с побайтно идентичного layout
Одна заметка о порядке операций избавляет от целого класса недоразумений. Дублируйте лист до того, как начнете заливать в него данные, а не после. Template должен хранить структуру и оформление, а не цифры за прошлый квартал, и клонирование пустого, но оформленного листа означает, что каждый дубликат начинается чистым. Если продублировать лист, который уже содержит данные, эти данные перейдут вместе с ним, потому что CopyFrom копирует used range честно и полностью. Иногда именно это вам и нужно, но для fan-out отчета обычно нет
Быстрая привычка проверки
Поскольку разделение на deep-copy и shallow-copy незаметно, пока вы специально его не проверите, лучше встроить в задание пятирядную проверку, чем просто верить, что все перенеслось как надо. После дублирования прочитайте назад структурные сигналы, которые клон обязан унаследовать, и зафиксируйте, что они совпадают с источником
Copy := Book.Sheets.Duplicate(1, 'Region-North');
WriteLn(Format('merged=%d colA=%.1f freezeRow=%d tabAuto=%d',
[Copy.MergedCells.Count, Copy.ColWidth[1],
Copy.FreezeRow, Integer(Copy.TabColorIsAuto)]));
// Prove independence: mutate the copy, confirm the source is untouched.
Copy.Cells[2, 2].Value := 999;
// Template.Cells[2, 2].Value is still whatever it was.
Количество merge, ширина одного столбца, номер freeze row и флаг tab colour показывают, что слой, который действительно копируется, действительно дошел. Отдельно, на любом листе, где были chart, table, validations или conditional formats, считайте их списком на восстановление в копии: их отсутствие сделано намеренно, и исправляется это несколькими вызовами, а не bug report. Именно эта ментальная модель, глубокое там, где это безопасно, и поверхностное там, где ссылки сломались бы, полностью описывает, как пользоваться этой возможностью без ошибок
Дублирование worksheet и описанное здесь копирование состояния через CopyFrom поставляются в v2.91.0 нативного HotXLS Delphi spreadsheet component , вместе с выполняемым sample SheetCopy , который прогоняет цикл clone-and-mutate от начала до конца