1枚のワークシートを完璧に作り上げたとします。ヘッダー帯が結合され、列幅はデータに適合し、上部の2行が固定され、印刷範囲とマージン(余白)はきれいなA4出力向けに設定され、財務部門が見つけやすいようにタブに色が付けられています。ここで、地域ごとに1枚ずつ、同じレイアウトから始まる12枚のシートが必要になったとします。コード上でそのシートを12回再構築すると、微妙なズレが生じる原因になります。例えば、地域7の列幅が1ポイント狭くなったり、地域11の固定設定が抜け落ちたりしても、PDFがマネージャーのデスクに届くまで誰も気づきません。実際にやりたいことは、Excelの右クリックメニューにある「移動またはコピー」から「コピーを作成する」の処理をプログラムで実行すること、すなわち、完成したシートを元に独立した複製(デュプリケート)を量産することです
Excelのオートメーション操作なしでファイルを読み書きできる、DelphiおよびC++Builder向けのネイティブライブラリであるHotXLSのXLSXエンジンは、以前からシートの移動、削除、およびシート間でのセル範囲のコピーに対応していました。しかし、v2.91.0までは、1回の呼び出しでワークシート全体を複製することはできませんでした。そのリリースで2つのエントリポイントが追加されました。1つは、あるワークシートから別のワークシートへシートレベルの状態をコピーするTXLSXWorksheet.CopyFromであり、もう1つは、新しいシートを追加してCopyFromを代わりに実行するTXLSXSheets.Duplicateです。興味深いのは、単にデータをコピーする点ではなく、ディープコピー(参照先の複製)されるデータとそうでない(シャローコピーにとどまる)データとの間に意図的な境界線が引かれている点、およびその理由です
完成したシートを複製する単一の呼び出し
高レベルの操作を実行するのはDuplicateです。コピー元シートの1から始まるインデックスを渡すと、元のレイアウトとデータをミラーリングした新しいワークシートが返されます。インデックスの仕様はXLSX側のItems[]と一致しているため、最初のシートは0ではなく1になります。範囲外のインデックスを渡した場合は、例外が発生するのではなく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;
上記のコードスニペットには、詳しく見ておくべき2つのポイントがあります。第1に、FreezePanesはCells[Row, Col]のインデックス指定と整合するように、行を最初に取るFreezePanes(ARow, ACol)という引数形式を採用しています。複製されたシートは元の固定状態をそのまま継承します。第2に、このメソッドはより一般的なCopyではなくDuplicateと命名されていますが、これは単なる好みの問題ではありません。CopyはSystemユニットの標準ルーチンであり、文字列や動的配列の処理で常に使用されます。クラス内にCopyというメソッドを定義すると、メソッド内での名前解決時に競合(シャドウイング)が発生し、将来的に不具合の原因となります。Duplicateという名称を採用することでこの競合を回避し、呼び出しコードの可読性も維持しています
デフォルトの名前付けはExcelの規則に従う
引数を1つだけ取るオーバーロードを呼び出すか、名前として空文字列を渡すと、新しいシートはコピー元シートの名前に接尾辞(2)を付加した形式で命名され、名前が重複しないように数値が順次インクリメントされます。Templateシートを1回複製するとTemplate (2)になり、もう一度複製するとTemplate (2)がすでに使用されているためTemplate (3)になります。これはExcel自身の「コピーを作成する」コマンドが生成する名前と同じ動作であるため、生成されたワークブックはユーザーにとって極めて自然に見えます。重複チェックは現在アクティブなシートコレクション全体に対して実行されるため、以前の複製処理だけでなく、コードから手動で作成したシート名との重複も自動的に回避されます
地域ごとや月ごとにシートを生成する場合は、名前を明示的に指定するオーバーロードを使用することをお勧めします。(2)や(3)といった接尾辞の並びよりも、予測可能なRegion-NorthやRegion-Southといった名前付けの方が、後続 of コードで指定しやすく、名前付き定義やシートを跨ぐ数式の可読性も高く保つことができます
CopyFromがディープコピーする項目
内部的には、Duplicateはまず新しいシートを追加した後にCopyFrom(ASource)を呼び出します。すでに作成済みのシートへデータをクローンしたい場合は、CopyFromを直接呼び出すこともできます。CopyFromは、コピー元がnilである場合や、シート自身を自身にコピーしようとする不正なケースを事前にガードします(これらの場合は何もせず即座に復帰します)。それ以外のケースでは、広範囲なデータのコピー処理が実行されます
まずセルのデータがコピーされます。CopyFromはコピー元のUsedRange(データが存在するセルや結合領域を囲む最小のバウンディングボックス)を取得し、既存のCopyRangeToの仕組みを再利用して、すべての値、数式、およびセルごとのスタイルインデックスを複製先シートのA1を起点として転写します。セルのデータに加えて、テンプレートシートの完成度を決定づける以下のシートレベルの状態もすべて複製されます
- 結合された範囲:座標データに基づいて再作成され、バナーなどを元のレイアウト通りの矩形範囲で維持します
- 列幅と行高:非表示状態、折りたたみ状態、およびアウトラインレベルのリストを含めてそのままコピーされ、デフォルト以外の設定を持つ行や列が正確に整列します
- ウィンドウ枠の固定と表示状態:ズームレベル、枠線やゼロ値の表示、右から左への表示方向(RTL)、および表示タイプが継承されます
- 保護状態:アクションごとの許可オプションを含めて継承されるため、ロックされたテンプレートは複製先でも同様にロックされます
- ページ設定データ全体:余白、印刷の向き、用紙サイズ、拡大縮小、印刷範囲、印刷タイトル、ヘッダー・フッター、および枠線印刷・見出し印刷の各フラグがコピーされます
- オートフィルター範囲、タブの色、およびシートの表示・非表示状態が複製されます
これにより、コピー元と全く同じように印刷、フィルタリング、および表示が行われるシートが作成されます。また、セル、結合情報、および寸法データは、参照を共有(エイリアス)するのではなく、新しいシート上に物理的に再作成されるため、複製されたシートは完全に独立しています。複製先シートのセルに999を書き込んでも、コピー元シートの元の値は影響を受けません。この独立性は、複数の地域レポートを並行して作成するような用途において最も重要な特性であり、製品に同梱されているデモプログラムSheetCopyでも明確に示されています
シャローコピーにとどまる項目とその理由
ここからは制限事項についての説明です。グラフ、埋め込み画像、XLSXテーブル、データの入力規則、および条件付き書式ルールはコピーされません。これは設計上の見落としではなく、ドキュメントに明記されている意図的な制約です。不意のトラブルを避けるために、この仕様の理由を理解し、設計に組み込んでおくことが重要です
これらのオブジェクトは、単純なフィールドコピーでは適切に引き継げない固有の識別情報や参照関係を持っています。例えば、グラフは元データのデータ範囲を指し、OOXMLパッケージ内の描画リレーションシップ(描画用の関係定義)を保持しています。リレーションシップや系列の参照先を再マッピングせずにオブジェクトだけをクローンすると、不正なデータを参照するグラフが生成されたり、Excelで「ファイルの修復」が必要とされる壊れたパッケージが生成されたりします。テーブル名もワークブック内で一意である必要があり、ヘッダー行は特定の列に紐付けられ、自動生成される独自のリレーションシップを持っています。条件付き書式や入力規則も座標の範囲と結びついており、入力規則の場合は数式で他の範囲を参照することもあります。これらをすべて正確にディープコピーするには、参照関係の書き換えや識別子の新規発行が必要となり、多くの例外処理が発生します。オブジェクトだけをコピーして参照の修正を行わないような不完全なアプローチをとると、ファイルを開いたときに修復エラーが発生してコンテンツが失われてしまいます。そのため、エンジンは安全にコピーできる要素のみを処理し、参照関係を持つオブジェクトの処理は、最終的にデータが何を指すべきかを知っている開発者(呼び出し側)に委ねています
実際の実装手順としては、まずシートを複製してセル、レイアウト、および印刷設定を生成し、その後、最初に作成した際と同じAPIを使用して、複製先シート上でグラフ、テーブル、入力規則、あるいは条件付き書式を再構築します。複製先シートのデータ範囲に対してこれらを再作成するため、参照関係は自動的に正しく設定されます。例えば、A1:C10のデータを読み取るグラフの場合、複製先シートのA1:C10を指す新しいグラフを追加します。オートフィルターについても、フィルターを適用する範囲データ自体はコピーされるため、列の抽出基準のみを再適用します。条件付き書式や入力規則については、複製されたシートが継承する結合セルや範囲モデルを扱う方法について説明している結合セルとレポートテンプレートのレイアウトに記載されているAPI呼び出しを使用して再適用します
レポート出力ラインにおける複製処理の組み込み
ワークシートの複製は、プレースホルダー駆動型のデータ生成処理と非常に親和性があります。Delphiでのテンプレート駆動型レポート作成ガイドで説明しているトークンアンカー方式は、他者が編集可能なレイアウトへのデータ書き込みの課題を解決します。これに対し、複製処理は、同じレイアウトが1つのワークブック内で何度も必要になるという課題を解決します。これらを組み合わせることで、極めてスマートな実装パターンが完成します。すなわち、トークン、セル結合、および印刷設定を含むオリジナルのTemplateシートを1枚用意しておき、地域や期間ごとにDuplicateを呼び出して複製を作成し、その複製されたシートのトークンにデータを埋め込んでいきます。オリジナルのテンプレートは変更されないため、次のクローンの作成元として常にクリーンな状態に維持され、すべての出力シートが完全に同じレイアウトで作成されます
開発者を混乱させがちな処理順序について注意点があります。シートの複製は、データを流し込む前に実行してください。テンプレートは最後のデータではなく構造とスタイル情報のみを保持するべきであり、データを含まないスタイル付きシートをクローンすることで、各複製をクリーンな状態から開始できます。すでにデータが含まれているシートを複製すると、CopyFromがそのデータ範囲もそのままコピーしてしまいます。特殊な用途でそれを望む場合もありますが、一般的な帳票出力処理では避けるべきです
簡易的な動作確認の習慣
ディープコピーとシャローコピーの境界は、実際に確認するまで分かりにくいため、コピーが正常に行われたことを信用するだけでなく、テストコード等で簡単な整合性チェックを実行する習慣をつけると便利です。複製後に、コピー元シートから継承されたはずの設定情報を読み取って比較確認します
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.
結合セルの数、列幅、ウィンドウ枠の固定位置、およびタブの色の設定値などを確認することで、意図された項目が正常にコピーされているかを検証できます。一方で、グラフ、テーブル、入力規則、条件付き書式が含まれるシートについては、これらは複製先で再構築すべきリストとして管理してください。コピーされないのは仕様であり、バグ報告を行うのではなく、複製後にAPIを通じて再追加を行うのが正しいアプローチです。安全に処理できるデータはディープコピーし、参照が壊れる可能性のあるデータは複製側で再構築するというメンタルモデルを意識することが、この機能を最大限に活用するための鍵となります
ここで説明したワークシートの複製機能とCopyFromによる状態のコピー処理は、複製とデータ書き換えのライフサイクルを実証するサンプルプログラムSheetCopyとともに、ネイティブなHotXLS Delphi spreadsheet componentのv2.91.0に実装されています