VB.NET で DataGridView の内容を Excel(.xlsx)に出力する — Office Interop を使わずに

· unvell team
VB.NET で DataGridView の内容を Excel(.xlsx)に出力する — Office Interop を使わずに

業務アプリを VB.NET で作っていると、いつかは同じ要望が来ます。「この画面の内容を Excel に出してほしい」。定番の答えは Microsoft.Office.Interop.Excel を使ったループです。開発機では動きます。ところが、Office の入っていないお客様の PC で止まり、出力のあとに EXCEL.EXE が残り、郵便番号の先頭のゼロが消えます。

この記事では、そのボタンを ReoGrid で作ります。ReoGrid は .xlsx ファイルを自分で書き出すので、Office の有無にかかわらずどの PC でも出力できます。コードは貼り付けて使える 1 つのモジュールで、単純なループが見落とす点も処理しています。掲載したコードはすべて Option Strict On でビルドし、実際の DataGridView で動かして、出力したファイルを Excel で開いて確かめました。

WinForms の DataGridView。得意先・受注番号・金額・受注日・郵便番号の列と出荷済のチェックボックス。得意先の列が先頭に移動してあり、最終行は空の新規行
アプリの画面 — DataTable にバインドした DataGridView。社内用の ID 列は非表示で、得意先 の列はユーザーがドラッグして先頭に移しています。
出力したシートを Excel で開いた状態。同じ列が同じ順に並び、見出しは太字で色付き、金額は 128,000、日付は 2026/11/02、郵便番号は 0600001 のように先頭のゼロを保ち、出荷済は TRUE または FALSE。空の新規行はない
保存した .xlsx を Excel で開いたところ — 列も順序も画面のとおりです。金額と日付は本物の数値と日付で、緑の三角は Excel が郵便番号を文字列として扱っている印です。だから 0600001 がそのまま残ります。

プロジェクトに合う ReoGrid を選ぶ

プロジェクト使うものパッケージ
.NET 10ReoGrid V5unvell.ReoGrid.One
.NET Framework 4.8、または .NET 8(Windows)ReoGrid V4unvell.ReoGrid4

VB.NET の WinForms アプリは今も .NET Framework が多く、その場合は V4 を使います。V4 のモジュールはこちら。どちらも UNVELL のパッケージフィードからインストールします(V5 の手順、V4 の手順)。30 日間のトライアルキーで、どちらも試せます。


DataGridView の出力で押さえるべき点

Rows と Columns を回すだけなら 10 行で書けそうに見えます。実際には、次の点に気を配る必要があります。

落とし穴起きることこのモジュールの対応
一番下の「新規行」最終行で NullReferenceException、またはファイルに空行が入るIsNewRow の行を飛ばす
編集中のセルツールバーのボタンはフォーカスを奪わないので、最後の入力が抜ける最初に grid.EndEdit() を呼ぶ
非表示の列社内用の ID がお客様に送るファイルに載るVisible の列だけを使う
並べ替えた列ファイルの列順が画面と違うDisplayIndex の順に並べる
.ToString() で書いた数値緑の三角だらけになり、SUM が 0 を返すセルの Value をそのまま渡す
先頭がゼロのコードInterop で代入すると、文字列 0600001 が数値 600001 になる文字列は文字列のまま
DBNullDBNull という文字が入る、または例外空のセルにする
書式のない日付セルに 46335 と表示される列の ValueType から日付の書式を付ける
.NET の書式文字列N0 は Excel の書式コードではなく、Excel が受け付けない#,##0 に変換する

出力モジュール — ReoGrid V5

.vbproj のプロジェクトにパッケージを追加します。

dotnet add package unvell.ReoGrid.One

アプリケーションの起動時に、ライセンスキーを 1 回だけ適用します。キーが無いと、保存のときに ReoGridLicenseException が発生します。

Imports unvell.ReoGrid.Core.License

ReoGridLicense.SetLicense(myLicenseKey)

続いて、次のモジュールを追加します。

Imports System.Windows.Forms
Imports unvell.ReoGrid.Core
Imports unvell.ReoGrid.Core.Style
Imports unvell.ReoGrid.IO.Excel

Public Module DataGridViewExport

    Public Sub SaveAsXlsx(grid As DataGridView, path As String,
                          Optional selectedOnly As Boolean = False)

        ' 入力中のセルを確定する
        grid.EndEdit()

        ' ユーザーに見えている列を、見えている順に
        Dim columns = grid.Columns.Cast(Of DataGridViewColumn)().
            Where(Function(col) col.Visible).
            OrderBy(Function(col) col.DisplayIndex).
            ToList()

        ' 画面の順の行。一番下の空の「新規行」は除く
        Dim source = If(selectedOnly,
                        grid.SelectedRows.Cast(Of DataGridViewRow)(),
                        grid.Rows.Cast(Of DataGridViewRow)())
        Dim rows = source.
            Where(Function(row) row.Visible AndAlso Not row.IsNewRow).
            OrderBy(Function(row) row.Index).
            ToList()

        Dim book As New Workbook()
        Dim ws = book.AddWorksheet("Sheet1")

        ' 見出し行。列幅は画面の幅を使う
        For c = 0 To columns.Count - 1
            ws.SetText(0, c, columns(c).HeaderText)
            ws.Columns.SetSize(c, columns(c).Width)
        Next
        ws.SetRowStyle(0, New StyleRecord With {.Bold = True, .BackgroundColor = &HFFDDEBF7UI})

        ' データ。セルの値をそのまま渡すので、数値は数値のまま
        For r = 0 To rows.Count - 1
            For c = 0 To columns.Count - 1
                Dim value = rows(r).Cells(columns(c).Index).Value
                ws.SetObjectValue(r + 1, c, If(value Is DBNull.Value, Nothing, value))
            Next
        Next

        ' 数値と日付の書式
        If rows.Count > 0 Then
            For c = 0 To columns.Count - 1
                Dim code = ExcelFormatOf(columns(c))
                If code IsNot Nothing Then
                    ws.SetNumberFormat(New RangePosition(1, c, rows.Count, 1), code)
                End If
            Next
        End If

        XlsxWriter.Write(book, path)
    End Sub

    ' DataGridView の書式文字列は .NET のものなので、Excel の書式コードに変換する
    Private Function ExcelFormatOf(col As DataGridViewColumn) As String
        Select Case col.DefaultCellStyle.Format
            Case "N0" : Return "#,##0"
            Case "N2" : Return "#,##0.00"
            Case "C0" : Return "¥#,##0"
            Case "P1" : Return "0.0%"
            Case "yyyy/MM/dd HH:mm" : Return "yyyy/mm/dd hh:mm"
        End Select
        If col.ValueType Is GetType(Date) Then Return "yyyy/mm/dd"
        Return Nothing
    End Function

End Module

並べ替えや絞り込みをしたグリッドも、そのまま出力できます。列見出しのクリックや DataView の RowFilter は Rows そのものを変えるので、ファイルは画面のとおりになります。row.Visible = False で隠した行も出力から外れます。

ボタンから呼び出す

Imports System.IO

Private Sub ExportButton_Click(sender As Object, e As EventArgs) Handles ExportButton.Click
    Using dlg As New SaveFileDialog With {.Filter = "Excel ブック|*.xlsx", .FileName = "受注一覧.xlsx"}
        If dlg.ShowDialog(Me) <> DialogResult.OK Then Return
        Try
            SaveAsXlsx(DataGridView1, dlg.FileName)
        Catch ex As IOException
            MessageBox.Show("ファイルが別のプログラムで開かれています。閉じてからやり直してください。")
        End Try
    End Using
End Sub

この Catch は見た目以上に大切です。出力の失敗で一番多いのは、先週のファイルを Excel で開いたままにしているケースです。保存すると IOException が発生し、既存のファイルは元のまま残ります。


書式文字列を変換する

DefaultCellStyle.Format に入っているのは .NET の書式文字列で、Excel には別の書式コードがあります。見た目は似ていても意味が違うので、モジュールの中で対応させています。

DefaultCellStyle.FormatExcel の書式コード1234.5 の表示
N0#,##01,235
N2#,##0.001,234.50
C0¥#,##0¥1,235
P1(0.1234 の場合)0.0%12.3%
yyyy/MM/dd HH:mm(日付の場合)yyyy/mm/dd hh:mm2026/11/09 14:05

アプリで使っている書式の数だけ行を足してください。知っておきたい点が 2 つあります。

  • 画面の C0 は PC の地域設定に従いますが、Excel の書式コードは固定です。ファイルはどの PC でも同じ記号で表示されます。
  • 日付の書式には列の型が必要です。バインドした列にはデータから ValueType が入りますが、バインドしないグリッドでは col.ValueType = GetType(Date) を自分で指定してください。指定しないと日付のシリアル値が表示されます。数値も同じで、ValueType の無い列ではユーザーの入力が文字列のまま残ります。col.ValueType = GetType(Decimal) を指定すると数値になります。

その他の書式コードは Excel の表示形式コードを読み解く をご覧ください。


選択した行だけを出力する

SaveAsXlsx(DataGridView1, dlg.FileName, selectedOnly:=True)

SelectedRows は、最後に選択した行から順に並びます。行 0、1、3 の順に選択すると、列挙の順序は 3、1、0 です。モジュールの OrderBy(Function(row) row.Index) が画面の順に戻すので、ファイルは上から順に並びます。SelectedRows に入るのは行全体を選択した行だけなので、SelectionMode は FullRowSelect が向いています。


仕上げ

XlsxWriter.Write の直前に次の行を足すと、そのまま渡せるシートになります。

' スクロールしても見出しが残るようにして、フィルターボタンを付ける
ws.View.FrozenRows = 1
ws.CreateAutoFilter(New RangePosition(0, 0, rows.Count + 1, columns.Count))

' C 列の合計。データの下に 1 行あけて置く
Dim total = rows.Count + 2
ws.SetText(total, 0, "合計")
ws.SetFormula(total, 2, $"SUM(C2:C{rows.Count + 1})")
ws.SetNumberFormat(total, 2, "#,##0")
ws.SetRowStyle(total, New StyleRecord With {.Bold = True})

合計の上の空行は残してください。Excel はフィルター範囲のすぐ下の行を範囲の一部として扱うので、そこに合計を置くとデータと一緒に絞り込まれ、並べ替えられてしまいます。

同じブックは PDF にもそのまま出力できます。ファイルの先頭に Imports unvell.ReoGrid.IO.Pdf を加えてください。

PdfExporter.Export(book, "受注一覧.pdf")

大きなグリッドもそのまま出力できます。バインドした DataGridView の 10 万行 × 6 列を、デスクトップ PC で V5 は 2〜3 秒、V4 は 3〜4 秒で出力でき、Excel でもすべての行を確認できました。


.NET Framework 4.8 では V4 で同じことをする

V4 のパッケージは unvell.ReoGrid4 で、.NET Framework 4.8 と .NET 8(Windows)で動きます。ライセンスの呼び出しは同じで、Imports unvell.ReoGrid.License を使います。行と列の選び方は V5 とまったく同じで、書き出しの部分だけが変わります。

Imports System.Windows.Forms
Imports unvell.ReoGrid
Imports unvell.ReoGrid.DataFormat
Imports unvell.ReoGrid.Graphics
Imports unvell.ReoGrid.IO

Public Module DataGridViewExport

    Public Sub SaveAsXlsx(grid As DataGridView, path As String,
                          Optional selectedOnly As Boolean = False)

        grid.EndEdit()

        Dim columns = grid.Columns.Cast(Of DataGridViewColumn)().
            Where(Function(col) col.Visible).
            OrderBy(Function(col) col.DisplayIndex).
            ToList()

        Dim source = If(selectedOnly,
                        grid.SelectedRows.Cast(Of DataGridViewRow)(),
                        grid.Rows.Cast(Of DataGridViewRow)())
        Dim rows = source.
            Where(Function(row) row.Visible AndAlso Not row.IsNewRow).
            OrderBy(Function(row) row.Index).
            ToList()

        Dim book = ReoGridControl.CreateMemoryWorkbook()
        Dim ws = book.Worksheets(0)
        ws.Resize(rows.Count + 1, columns.Count)

        For c = 0 To columns.Count - 1
            ws(0, c) = columns(c).HeaderText
            ws.SetColumnsWidth(c, 1, CUShort(columns(c).Width))
        Next
        ws.SetRangeStyles(0, 0, 1, columns.Count, New WorksheetRangeStyle With {
            .Flag = PlainStyleFlag.FontStyleBold Or PlainStyleFlag.BackColor,
            .Bold = True,
            .BackColor = SolidColor.FromArgb(255, 221, 235, 247)
        })

        ' 書式を先に設定する。文字列の列の先頭のゼロを保つため
        If rows.Count > 0 Then
            For c = 0 To columns.Count - 1
                ApplyFormat(ws, New RangePosition(1, c, rows.Count, 1), columns(c))
            Next
        End If

        For r = 0 To rows.Count - 1
            For c = 0 To columns.Count - 1
                Dim value = rows(r).Cells(columns(c).Index).Value
                ws(r + 1, c) = If(value Is DBNull.Value, Nothing, value)
            Next
        Next

        book.Save(path, FileFormat.Excel2007)
    End Sub

    Private Sub ApplyFormat(ws As Worksheet, range As RangePosition, col As DataGridViewColumn)
        Select Case col.DefaultCellStyle.Format
            Case "N0"
                ws.SetRangeDataFormat(range, CellDataFormatFlag.Number,
                    New NumberDataFormatter.NumberFormatArgs With {.DecimalPlaces = 0, .UseSeparator = True})
                Return
            Case "N2"
                ws.SetRangeDataFormat(range, CellDataFormatFlag.Number,
                    New NumberDataFormatter.NumberFormatArgs With {.DecimalPlaces = 2, .UseSeparator = True})
                Return
            Case "C0"
                ws.SetRangeDataFormat(range, CellDataFormatFlag.Currency,
                    New CurrencyDataFormatter.CurrencyFormatArgs With {.PrefixSymbol = "¥", .DecimalPlaces = 0, .UseSeparator = True})
                Return
        End Select

        If col.ValueType Is GetType(Date) Then
            ws.SetRangeDataFormat(range, CellDataFormatFlag.DateTime,
                New DateTimeDataFormatter.DateTimeFormatArgs With {.Format = "yyyy/MM/dd"})
        ElseIf col.ValueType Is GetType(String) Then
            ws.SetRangeDataFormat(range, CellDataFormatFlag.Text)
        End If
    End Sub

End Module

2 つのループの順序には意味があります。V4 は数値に見える文字列を数値として読むので、文字列の列にはデータを入れる前に書式を設定します。これで 0600001 が 0600001 のまま残ります。

見出しの固定は ws.FreezeToCell(1, 0) です。合計を入れるときは ws.Resize(rows.Count + 2, columns.Count) でシートを 1 行広げ、数式を文字列で代入します(ws(rows.Count + 1, 2) = "=SUM(C2:C5)")。

V4 での現時点の制限

  • Boolean の列は、True または False という文字列で保存します。
  • V4.6 では、同じシートの左側に小数桁の同じ数値列があると、通貨の列の通貨記号が失われます。小数桁を変えるか、通貨の列を先に置いてください。
  • yyyy/MM/dd のパターンは Excel 標準の短い日付形式で保存され、PC の地域設定に従います。日本語の環境では 2026/11/2 と表示されます。yyyy-MM-dd などの他のパターンは指定どおりに保存します。

V5 では、いずれの場合も本物の TRUE / FALSE を書き出し、通貨記号も日付の書式コードも指定どおりに保存します。


Interop から移行する理由

Interop は本物の Excel を操作する仕組みなので、Excel にまつわる条件をすべて引き受けることになります。

Office InteropReoGrid
ユーザーの PC の Office必須Office なしで動作
0600001 のような文字列セルを先に文字列書式にしない限り、数値 600001 に変換される入力どおりに保存
出力のあとCOM オブジェクトの解放漏れで EXCEL.EXE が残ることがあるアプリの中で完結する
サービスやタスクスケジューラ上Microsoft のサポート対象外画面なしで動作

アプリの Excel 機能がこのボタンだけなら、上のモジュールで Office への依存をなくせます。保存の前にユーザーがシートを編集したい場合は、ReoGridControl を DataGridView の代わりに置けます。数式・フィルター・印刷まで最初から備えています。


まとめ

  • 列は Visible と DisplayIndex で選び、IsNewRow の行は飛ばす。最初に grid.EndEdit() を呼ばないと最後の編集が抜ける
  • .ToString() ではなく cell.Value を渡すと、数値も日付もそのままの型で届く
  • DefaultCellStyle.Format は .NET の文字列なので、N0 などは Excel の書式コードに変換する
  • 選択行の出力は SelectedRows を Index で並べ直すと画面の順になる
  • Excel で開いたままのファイルに備えて IOException を捕まえる
  • .NET 10 なら V5、.NET Framework 4.8 なら V4。モジュールはほぼ同じ形で書ける

V5 の新機能を見る / 30 日間のトライアルを始める


関連情報

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

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

ニュースレター

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

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

関連する記事

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

Excel の「テーブルとして書式設定」は、見出しに色が付き 1 行おきに縞が入るので書式に見えます。ところが保存したファイルを開いてみると、塗りを持つセルは 1 つもありません。Excel が保存しているのは「範囲・見出し行・スタイル名」を持つオブジェクトです。xlsx の中のテーブルの正体、組込 60 種のスタイルの中身、そして Excel がファイルを開けるかどうかを分ける 3 つの決まりを整理し、ReoGrid V5.1 の C# コードでフィルター▽と集計行付きのテーブルを作ります。

VB.NET で Excel ファイルを読み込み・書き出し・表示する

VB.NET で `.xlsx` ファイルを扱うための実践ガイド — データの読み込み、レポートの生成、WinForms / WPF デスクトップアプリへのライブスプレッドシートビューアの埋め込み。いずれも Office をインストールせずに実現できます。

Excel の表示形式コードを読み解く —

#,##0;[Red]-#,##0;"-" は記号の羅列に見えますが、4 つのセクション・桁の記号・色・条件・文字列でできた小さな言語です。読めるようになれば、必要なコードを自分で書けます。この記事では Excel のユーザー定義の表示形式を、千円単位・百万円単位、値を隠す ;;;、単位の付け方、勤務時間の [h]:mm、「33 が 3 つで合計 100」になる理由まで整理し、ReoGrid V5 の C# コードで同じコードを使います。Excel からコピーしたコードがそのまま動き、XLSX に保存しても保たれます。