Excel の「テーブル」を C# で作る — 「テーブルとして書式設定」が実際に保存しているもの

· unvell team
Excel の「テーブル」を C# で作る — 「テーブルとして書式設定」が実際に保存しているもの

Excel で一覧を選んで Ctrl+T を押すと、表が青くなります。見出しが塗られ、1 行おきに色が付き、見出し行には▽が出ます。書式を付けたように見えますが、書式ではありません。 ブックを保存してファイルの中を見ると、塗りを持つセルは 1 つもないのです。Excel が書いたのはテーブルというオブジェクト(Excel のオブジェクトモデルでいう ListObject)で、中身は「A1:D9 はテーブル、1 行目が見出し、TableStyleMedium2 で描く」という宣言だけです。色は、シートを描くたびに「そのセルがテーブルのどこにあるか」から決まります。

プログラムで Excel ファイルを作るときは、この違いがすべてです。見出しと縞を自分で塗ると、見た目はテーブルでも中身はただの塗った範囲になります。並べ替えれば縞が行ごと動き、途中に行を挿せば模様が崩れ、Excel の[テーブル デザイン]タブは出てきません。

この記事では、xlsx の中でテーブルがどう表現されているか、スタイル名が何を指しているか、そして Excel がファイルを開けるかどうかを分ける 3 つの決まりを整理します。そのうえで ReoGrid V5 の C# コードでテーブルを作ります。

提供時期について: テーブルは次の V5 リリース ReoGrid V5.1 で入る機能で、現在開発中です。いま NuGet で公開している 5.0.0 には含まれていません。テーブルスタイルのドキュメントは 5.1 の動作を説明しています。


要点

自分で塗ったセルExcel のテーブル
ファイルに入っているもの全セルの塗り範囲・見出し行・スタイル名
途中に行を挿入模様が崩れる下の行が塗り直される
並べ替え縞が行と一緒に動く縞はその場に残る
フィルター色の付いた行が 2 行続くことがある見えている行だけで塗り直される
見た目を変える全セルを塗り直す名前を 1 つ変える
Excel から見るとただの範囲テーブル(テーブル デザイン、▽、集計行)

xlsx の中のテーブル

テーブルは xl/tables/table1.xml という独立したパーツで、ワークシートからは <tableParts> で参照されます。この記事の後半のコードが書き出すもの(A1:D9 の売上一覧、見出し行と集計行付き)は次のとおりです。

<table id="1" name="Sales" displayName="Sales" ref="A1:D9" totalsRowCount="1">
  <autoFilter ref="A1:D8"/>
  <tableColumns count="4">
    <tableColumn id="1" name="支店" totalsRowLabel="計"/>
    <tableColumn id="2" name="上期" totalsRowFunction="custom">
      <totalsRowFormula>SUBTOTAL(109,B2:B8)</totalsRowFormula>
    </tableColumn>
    <!-- 下期・合計も上期と同じ -->
  </tableColumns>
  <tableStyleInfo name="TableStyleMedium2" showFirstColumn="0" showLastColumn="0"
                  showRowStripes="1" showColumnStripes="0"/>
</table>

押さえどころは 4 つです。

  • name はラベルではなく識別子です。 構造化参照(Sales[上期])で綴る名前なので、名前付き範囲と同じ規則に従います。先頭は英字か _、以降は英数字・.・_。空白は使えず、セル番地に読める名前もだめです。Q1 や T1 はセルであって名前ではありません。
  • tableColumn の名前は見出し行から採られ、重複できません。見出しが 2 列とも「数量」なら、2 列目は 数量2 になります。
  • 見た目のすべては tableStyleInfo です。 スタイル名と 4 つのスイッチ(行の縞・列の縞・先頭列・末尾列)だけで、色はファイルのどこにも書かれていません。Excel に見えている塗りは、Excel がもともと知っている TableStyleMedium2 の定義から来ています。
  • フィルターはここ、テーブルの中にあります。 シート側ではありません。詳しくは後述します。

Excel が保存するのはスタイル名だけ

Excel の組込テーブルスタイルは 60 種(淡色 1–21、中間 1–28、濃色 1–11)で、ファイルを読むアプリは名前から見た目を知っていることが前提です。その中身は公開されていて、xlsx の規格 ECMA-376 が presetTableStyles.xml というファイルで定義しています。各スタイルは「要素」の集まりです。テーブル全体・見出し行・集計行・先頭列・末尾列・1 本目と 2 本目の行の縞・1 本目と 2 本目の列の縞のそれぞれに、塗り・フォント・罫線がテーマ色で決められています。

テーブルを手で真似ると、この定義の 2 点を間違えがちです。

  • 縞は先頭のデータ行から付きます。 組込スタイルが色を付けるのは 1 本目の縞なので、見出しのすぐ下の行が色付きで、その次の行は無地です。自前の縞を 1 行遅れで始めると、本物のテーブルと並べたときに 1 行ずれて見えます。
  • スタイルの系統ごとに、色だけでなく形が違います。 既定の中間 2(Medium 2)は、見出しを塗って行と行の間に細い罫線を引きます。淡色 1–7 は見出しを塗らず、上下の罫線と、アクセント色を暗くした文字色で表します。淡色 8–14 は見出しを塗り、縞を色ではなく罫線で区切ります。淡色 15–21 は格子です。濃色はテーブル全体を塗ります。
小さなテーブルが 6 つ並ぶ。黒い罫線に灰色の縞の Light 1、オレンジの見出しと罫線の Light 10、緑の格子の Light 21、青い見出しに薄青の縞の Medium 2、緑に白い罫線の Medium 14、黒い見出しの下をオレンジで塗った Dark 3
実行結果 — 組込スタイルのうち 6 種を ReoGrid V5 で描いたもの。どれも同じ一覧を AddTable に渡し、Style の名前だけを変えています。

ReoGrid V5 はこの 60 種の定義をそのまま持っていて、テーマ色は Office と同じ tint の計算式で解きます。Excel 自身のスウォッチとの差は RGB で 1〜2 程度です(中間 2 の縞は、Excel の #D9E1F2 に対して #DAE3F3)。

ファイルに残るのは名前だけなので、ユーザー定義スタイルは、その定義が一緒に運ばれる範囲でしか通用しません。 Excel はユーザー定義スタイルをブックの styles.xml に保存します。定義の見つからない名前のテーブルは、書式の無いテーブルとして開きます。Excel で開くファイルには、組込 60 種のどれかを使ってください。


Excel がファイルを開けるかどうかを分ける 3 つの決まり

テーブルのパーツは小さいのですが、Excel はここに厳格です。次のどれかが間違っていると、デスクトップ版の Excel はそのファイルを開けません。「問題が見つかりました。修復しますか?」の確認が出るのではなく、修復も含めて開けないのです。私たちは XML を 1 点ずつ手で直したファイルを Excel で開いて、それぞれを確かめました。

  1. フィルターはテーブルのものです。 テーブルの見出しに出る▽は、テーブルのパーツの中の <autoFilter> です。テーブルと重なる <autoFilter> がシート側にあると、ファイルは開けません。
  2. フィルターは集計行の手前までです。 テーブルが A1:D9 なら、フィルターは A1:D8 です。何で絞り込んでも集計行が消えないのはこのためです。
  3. 集計行に入っているものは、すべて列に宣言します。 文字は totalsRowLabel、数式は totalsRowFunction(sum や average など。任意の式なら custom と <totalsRowFormula>)です。宣言されていない値や数式が集計行に 1 つあるだけで、ファイルは読めなくなります。

Excel が自分で集計行を作るときは、セルに SUBTOTAL(109,Sales[合計]) と書きます。9 ではなく 109 なのは、フィルターで隠れた行も手で隠した行も合計から外すためです。番号の意味は SUBTOTAL と AGGREGATE の違いで説明しています。


C# でテーブルを作る — ReoGrid V5.1

コードはヘッドレス(コントロールも UI も無し)で動き、WinForms・WPF・Avalonia で共通です。

using unvell.ReoGrid.Core;
using unvell.ReoGrid.Core.Tables;
using unvell.ReoGrid.IO.Excel;

var workbook = new Workbook();
var ws = workbook.AddWorksheet("売上");

string[] header = ["支店", "上期", "下期", "合計"];
for (int c = 0; c < header.Length; c++) ws.SetText(0, c, header[c]);

(string Branch, double H1, double H2)[] rows =
[
    ("東京", 820, 910), ("大阪", 640, 580), ("名古屋", 410, 470), ("福岡", 355, 320),
    ("札幌", 210, 245), ("仙台", 180, 205), ("広島", 265, 230),
];
for (int r = 0; r < rows.Length; r++)
{
    ws.SetText(r + 1, 0, rows[r].Branch);
    ws.SetNumber(r + 1, 1, rows[r].H1);
    ws.SetNumber(r + 1, 2, rows[r].H2);
    ws.SetFormula(r + 1, 3, $"B{r + 2}+C{r + 2}");
}

// 集計行。色は V5 が付けるが、数式はアプリ側で入れる
ws.SetText(8, 0, "計");
ws.SetFormula(8, 1, "SUBTOTAL(109,B2:B8)");
ws.SetFormula(8, 2, "SUBTOTAL(109,C2:C8)");
ws.SetFormula(8, 3, "SUBTOTAL(109,D2:D8)");
ws.SetNumberFormat(RangePosition.Parse("B2:D9"), "#,##0");

// A1:D9 をテーブルにする。1 行目が見出し、9 行目が集計行
var table = ws.AddTable("A1:D9", new AddTableOptions
{
    Name = "Sales",            // 識別子なので空白不可。セル参照に読める名前も不可
    TotalsRowCount = 1,
    ShowFilterButton = true,   // 見出し行にフィルタ▽
});

XlsxWriter.Write(workbook, "sales.xlsx");
A1:D9 の売上テーブル。青い見出しにフィルタ▽、2・4・6・8 行目に薄青の縞、行間に細い青の罫線、二重線の下に太字の集計行 2,880・2,960・5,840
実行結果 — 上のコードを ReoGrid V5 で描いたもの。色と罫線はすべてスタイル名から来ています。sales.xlsx を Excel で開くと Sales という名前のテーブルになっていて、自分の塗りを持つセルは 1 つもありません。
  • AddTable の範囲は A1 形式のアドレス、名前付き範囲、RangePosition のどれでも指定できます。先頭行が見出しになり、列名はその行から採られます。
  • セルには何も書きません。 テーブルの色は各セル自身の書式の下にあり、描画のたびに解かれるので、10 万行のテーブルでもオブジェクトは 1 個です。XlsxWriter はテーブルのパーツ(前節のフィルターと集計行の宣言込み)を書き、セルの書式には手を付けません。
  • 集計行は色を付けるだけで、計算はしません。 V5 は集計行に色を付け、中身(ラベルと、各数式をユーザー定義の集計式として)をファイルに宣言しますが、集計関数を自動で入れることはしません。Excel なら入れるのが SUBTOTAL(109, …) です。V5 は Sales[上期] のような構造化参照にまだ対応していないので、数式は普通の範囲で書いています。
  • ShowFilterButton の既定が false なのは、ワークシートが持てる auto-filter が 1 つだけだからです。既定で true にすると、同じシートに 2 つ目のテーブルを作った瞬間に 1 つ目から▽が消えてしまいます。
  • SetFormula には先頭の = を付けません。

絞り込む

▽はシートの auto-filter で、テーブルの見出し行とデータ行に張られます。集計行は含みません。

// テーブル自身のフィルタ(ShowFilterButton が見出し行に張ったもの)
ws.AutoFilter!.SetColumnFilter(0, ["東京", "大阪", "福岡", "仙台"]);
ws.AutoFilter.Apply();
東京・大阪・福岡・仙台に絞り込んだテーブル。東京と福岡に縞、大阪と仙台は無地、集計行は残って 1,995・2,015・4,010
実行結果 — 4 支店に絞ったところ。縞は見えている行だけで付け直され、集計行は残り、SUBTOTAL(109, …) は見えている行だけを足した 1,995 / 2,015 / 4,010 になります。

縞は Excel と同じく見えている行で数えるので、絞り込んでも色付きの行が 2 行続くことはありません。集計行はフィルターの外にあるので、どう絞り込んでも消えません。


テーブルはセルの下にある

テーブルの色は、セルが持つ層の中でいちばん弱いものです。強い順に並べると次のとおりです。

  1. 条件付き書式
  2. セル自身の書式(セル → 行 → 列)
  3. テーブルスタイル
  4. シート全体の縞(テーブルの外だけ)

ですから 1 行だけ黄色く塗ればそこだけ縞を破れますし、条件付き書式(たとえば「300 未満を強調」)は縞の上に出ます。罫線も同じで、辺ごとに手書きの罫線が勝ちます。

保存するときにテーブルの色をセルへ書いてはいけない理由も、ここにあります。Excel はセル自身の書式をテーブルスタイルより優先するので、書き込んだ縞は行が動いてもその場に残ってしまいます。

テーブルは自分が表すデータに追従します。中に行を挿せば伸びて塗り直され、上に挿せば下へ動き、列を消せばその列は列リストからも消えます。

画面から操作する

WinForms・WPF・Avalonia の ReoGridControl には、選択範囲に対して同じ操作があります。

grid.FormatSelectionAsTable();                                     // Excel の Ctrl+T(▽付き)
grid.SetActiveTableStyle("TableStyleMedium7", firstColumn: true);  // カーソル位置のテーブルのスタイルを変える
grid.RemoveTableAtActiveCell();                                    // 範囲に変換

1 セルだけ選んだ状態では、シートの使用範囲がテーブルになります。ReoGrid Studio では Format ▸ Format as Table から、60 種のスタイルとテーブルスタイルのオプションを選べます。

スタイルの変更と、自前の配色

// スタイルやオプションは後から変えられる。Style は record struct なので with で書き換える
table.Style = table.Style with { Name = "TableStyleLight9", ShowFirstColumn = true };

// 自前の配色を登録することもできる
TableStyles.Register(new TableStyleDefinition
{
    Name = "BrandTable",
    HeaderFill = 0xFF1F3864,
    HeaderColor = 0xFFFFFFFF,
    HeaderBold = true,
    RowStripeFill = 0xFFEAF0F8,
    WholeBorderColor = 0xFF1F3864,
});

登録した配色は画面表示と PDF 出力で使えます。xlsx には名前しか残らず、Excel は知らない名前のスタイルのテーブルを書式無しで描きます。Excel で開くファイルには組込スタイルを使ってください。


まだできないこと

  • 集計行の集計関数。 行の色付け・宣言・保存まではしますが、数式はアプリ側で書きます。
  • 構造化参照(=Sales[上期]、=SUBTOTAL(109,Sales[合計]))。これを使った Excel のブックも、Excel が計算した値のまま開けます(V5 は読み込み時に再計算しません)。ただし V5 自身では計算できません。
  • 自動拡張。 テーブルのすぐ下の行に入力しても、テーブルは伸びません。
  • ユーザー定義スタイルの定義の xlsx 保存。 組込スタイルは名前ごと見た目が往復しますが、ユーザー定義スタイルは名前しか残りません。

V4 の場合

ReoGrid V4 にはテーブルのモデルがありません。xlsx の読み込みでテーブルのパーツを読まないので、Excel でテーブルにした一覧は V4 ではただのセルとして開きます。見出しの色も縞も、もともとセルには入っていないので表示されません。ユーザーがテーブルにしたブックを V4 のアプリで読み込んでいるなら、V5 への移行を検討する理由になります。


まとめ

  • 「テーブルとして書式設定」が保存するのはオブジェクト(範囲・見出し行・スタイル名)です。テーブルの色を持つセルはファイルに 1 つもありません
  • 組込 60 種は公開された定義です。系統ごとに形が違い、縞は先頭のデータ行から付きます
  • シート側のフィルターがテーブルと重なる、テーブルのフィルターが集計行を含む、集計行に宣言の無い値がある。このどれか 1 つでも Excel はファイルを開けません
  • ReoGrid V5.1 なら C# でテーブルを作り(AddTable にスタイル・▽・集計行)、WinForms / WPF / Avalonia アプリの中で描き、Excel が開ける本物のテーブルとして保存できます。ヘッドレスでも動きます

V5 の新機能を見る / 30 日間の評価版を試す


関連記事

ご自身のプロジェクトで ReoGrid を試す

.NET WinForms / WPF 向けの Excel 互換スプレッドシートコンポーネント。30 日間の無償トライアルをご利用いただけます。

ニュースレター

最新リリースをメールでお届け

ReoGrid の新バージョン・新機能・技術記事のお知らせをお送りします。配信停止はいつでも可能です。

関連する記事