目次
VB.NET|ClosedXMLでExcel(.xlsx)を安全に読み書きする方法(列はA1/数値どちらも対応)
どうも!リョクちゃです。
Excelをアプリケーションで操作したいとき、現場で一番事故りにくいのは ClosedXML です。
Interop(Excel COM)は「動くけど運用で死ぬ」ことが多いので、読み書き中心なら ClosedXML が現実的。
- ✅ Excel本体が無くても動く(サーバーでも動かしやすい)
- ✅ COM特有の残プロセス・環境依存を避けやすい
- ✅
.xlsx前提なら実装がラク
ただし、“動くコード” と “運用で詰まらないコード” は別物。
この記事では 現場の落とし穴(入力、シート無し、上書き禁止)まで含めて解説します。
この記事でできること(目的)
- 読込:「列」「行」を入力し、指定セルの値を表示
- 書込:「列」「行」「値」を入力し、指定セルへ書き込み
- 列入力は A1形式(A,B,AA…)と数値(1=A,2=B…)両対応
- Sheet1 が無い場合でも落ちない(存在チェック&メッセージ)
- SaveAs(上書き禁止運用)パターンも対応
公式と現場のズレ(ここが大事)
公式や一般記事だと「Cell(“B2”)で読み取れます」で終わりがちですが、現場はここで詰まります。
- ❌ 列に「2」と入れたら落ちる(A1形式前提しかない)
- ❌ Sheet名が違って落ちる(Sheet1固定、テンプレ差し替えで事故)
- ❌ Saveで上書きしてテンプレが壊れる(上書き禁止運用が多い)
- ❌ ブックを閉じずにロック(次回以降 “使用中です” 地獄)
つまり、運用の前提を入れないと、使い続けられません。
ここを最初から潰します。
前提:ClosedXMLの導入(NuGet)
NuGetで ClosedXML をインストールします。
コードでは以下を使います。
|
1 2 3 |
Imports ClosedXML.Excel Imports System.IO |
NuGetからの入手
作成したプロジェクトで
- ツールタブをクリック
- NuGetパッケージマネージャをクリック
- ソリューションのNuGetパッケージの管理を選択

クリックしたら、NuGetソリューションタブが新たに開きます。
デフォルトでは、インストール済みが選択されているので、参照を選択します。

検索ボックスにClosedXMLと入力します。

赤枠で囲まれたClosedXMLを選択し、インストールしたいプロジェクトのチェックボックスにチェックを入れます。


選択をしたら、インストールをクリックします。

OKボタンを押します。

インストールが完了すると、プロジェクトの参照欄にClosedXMLが追加されます。

これでClosedXMLの準備は完了です。
練習用フォームの作成
フォームを作成します。

①~⑭のコントロールについては以下の表のとおりです。
| No | コントロール | 名前 |
|---|---|---|
| 1 | Label | Label1 |
| 2 | Label | Label2 |
| 3 | Label | Label3 |
| 4 | Label | Label4 |
| 5 | TextBox | tbxReadColumn |
| 6 | TextBox | tbxReadRow |
| 7 | TextBox | tbxWriteColumn |
| 8 | TextBox | tbxWriteRow |
| 9 | Button | btnRead |
| 10 | Label | Label5 |
| 11 | TextBox | tbxWriteText |
| 12 | Label | Label6 |
| 13 | Label | lbReadResult |
| 14 | Button | btnWrite |
各コントロールのフォントは、メイリオの18ptを設定しています。
実行環境
筆者の実行環境
- VisualStudio2019
- Windows 10 64bit
- .Net Framework 4.5.1
※ Windows7 8 8.1でも動作はできますが、画面デザインの表示が変わるかもしれません。
実務で使う判断基準(ClosedXMLでOKか?)
- ✅ .xlsx でOK → ClosedXMLが最有力
- ❌ .xls 必須 → ClosedXMLは不可(別手段が必要)
- △ .xlsm(マクロ) → 読み込みはできても運用要件次第(別途検討)
この記事は .xlsx前提 です。
実装:安全な読み書き(列:A1/数値対応、シート存在チェック、SaveAs対応)
まずは「列入力」を統一する(A, AA… / 1, 2… → どちらもOK)
ユーザー入力が以下どちらでも通るようにします。
- A1形式:A, B, C, AA, AB …
- 数値:1, 2, 3 …(1=A, 2=B)
読取りボタン押下時の処理
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
''' <summary> ''' 読取りボタンを押下 ''' </summary> Private Sub btnRead_Click(sender As Object, e As EventArgs) Handles btnRead.Click Try ' 入力チェック If String.IsNullOrWhiteSpace(Me.tbxReadRow.Text) OrElse String.IsNullOrWhiteSpace(Me.tbxReadColumn.Text) Then MessageBox.Show("読み取る列・行を入力してください。(例:列=B または 2、行=2)") Return End If If Not File.Exists(ExcelPath) Then MessageBox.Show($"Excelファイルが見つかりません:{ExcelPath}") Return End If Dim row As Integer If Not Integer.TryParse(Me.tbxReadRow.Text.Trim(), row) OrElse row <= 0 Then MessageBox.Show("行は1以上の数値で入力してください。(例:2)") Return End If Dim colName As String = NormalizeColumnText(Me.tbxReadColumn.Text) Dim cellAddress As String = colName & row.ToString() Using book As New XLWorkbook(ExcelPath) Dim sheet As IXLWorksheet = GetWorksheetOrThrow(book, SheetName) Dim result As String = sheet.Cell(cellAddress).GetValue(Of String)() Me.lbReadResult.Text = result End Using Catch ex As Exception MessageBox.Show($"読み取りに失敗しました。{Environment.NewLine}{ex.Message}") End Try End Sub |
書込むボタン押下
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
''' <summary> ''' 書込むボタンを押下(SaveAs:上書き禁止運用) ''' </summary> Private Sub btnWrite_Click(sender As Object, e As EventArgs) Handles btnWrite.Click Try ' 入力チェック If String.IsNullOrWhiteSpace(Me.tbxWriteRow.Text) OrElse String.IsNullOrWhiteSpace(Me.tbxWriteColumn.Text) Then MessageBox.Show("書き込む列・行を入力してください。(例:列=C または 3、行=2)") Return End If If String.IsNullOrWhiteSpace(Me.tbxWriteText.Text) Then MessageBox.Show("書き込む値を入力してください。") Return End If If Not File.Exists(ExcelPath) Then MessageBox.Show($"Excelファイルが見つかりません:{ExcelPath}") Return End If Dim row As Integer If Not Integer.TryParse(Me.tbxWriteRow.Text.Trim(), row) OrElse row <= 0 Then MessageBox.Show("行は1以上の数値で入力してください。(例:2)") Return End If Dim colName As String = NormalizeColumnText(Me.tbxWriteColumn.Text) Dim cellAddress As String = colName & row.ToString() Using book As New XLWorkbook(ExcelPath) Dim sheet As IXLWorksheet = GetWorksheetOrThrow(book, SheetName) ' 書き込み sheet.Cell(cellAddress).Value = Me.tbxWriteText.Text ' 上書き禁止:SaveAsで別ファイル出力 Dim outPath As String = BuildOutputPath(ExcelPath) book.SaveAs(outPath) MessageBox.Show($"書き込みが完了しました。{Environment.NewLine}出力先:{outPath}") End Using Catch ex As Exception MessageBox.Show($"書き込みに失敗しました。{Environment.NewLine}{ex.Message}") End Try End Sub |
列変換関数(数値→A1列名)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
''' <summary> ''' 数値列(1=A,2=B,...)をExcel列名(A, B, ..., AA, AB, ...)へ変換 ''' </summary> Private Function ColumnNumberToName(colNum As Integer) As String If colNum <= 0 Then Throw New ArgumentOutOfRangeException(NameOf(colNum), "列番号は1以上を指定してください。") Dim result As String = "" Dim n As Integer = colNum While n > 0 n -= 1 Dim remainder As Integer = n Mod 26 result = Chr(Asc("A"c) + remainder) & result n \= 26 End While Return result End Function |
入力列を「最終的にA1列名へ統一」する関数
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
''' <summary> ''' 列入力をA1形式へ正規化する ''' - "B" や "AA" はそのまま ''' - "2" のような数値は 2=B に変換 ''' </summary> Private Function NormalizeColumnText(input As String) As String Dim s As String = If(input, "").Trim() If s = "" Then Throw New ArgumentException("列が未入力です。") ' 数値なら列名に変換 Dim colNum As Integer If Integer.TryParse(s, colNum) Then Return ColumnNumberToName(colNum) End If ' 文字列列名として扱う(A~Zのみ) s = s.ToUpper() For Each ch As Char In s If ch < "A"c OrElse ch > "Z"c Then Throw New ArgumentException("列は A~Z または数値(1=A,2=B...)で入力してください。") End If Next Return s End Function |
「Sheet1が無い」場合のハンドリング
現場ではテンプレ差し替えでシート名が変わることがよくあります。
Worksheet(“Sheet1”) を直接呼ぶと例外なので、存在確認します。
|
1 2 3 4 5 6 7 8 9 10 |
''' <summary> ''' シートが存在するか確認して取得(無ければ例外) ''' </summary> Private Function GetWorksheetOrThrow(book As XLWorkbook, sheetName As String) As IXLWorksheet If Not book.Worksheets.Any(Function(ws) ws.Name = sheetName) Then Throw New ArgumentException($"指定したシートが見つかりません:{sheetName}") End If Return book.Worksheet(sheetName) End Function |
SaveAs(上書き禁止運用)パターン
テンプレを直接編集して Save() すると、原本が壊れて戻せないことがあります。
現場では以下どちらかが多いです。
- ✅ 常に別名保存(SaveAs)
- ✅ 出力用ファイルをコピーしてから編集 → Save
ここでは SaveAs方式 を入れます。
- 出力先:
C:\output\test_yyyyMMdd_HHmmss.xlsxのように日時付与 - 同名衝突を回避
|
1 2 3 4 5 6 7 8 9 10 11 |
Private Function BuildOutputPath(originalPath As String) As String Dim dir As String = Path.Combine(Path.GetDirectoryName(originalPath), "output") Directory.CreateDirectory(dir) Dim baseName As String = Path.GetFileNameWithoutExtension(originalPath) Dim ext As String = Path.GetExtension(originalPath) Dim stamp As String = DateTime.Now.ToString("yyyyMMdd_HHmmss") Return Path.Combine(dir, $"{baseName}_{stamp}{ext}") End Function |
✅完成コード(読込+書込+SaveAs+シート存在チェック+列数値対応)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
Imports ClosedXML.Excel Imports System.IO Imports System.Linq Public Class testExcel Private Const ExcelPath As String = "C:\test.xlsx" Private Const SheetName As String = "Sheet1" ''' <summary> ''' 読取りボタンを押下 ''' </summary> Private Sub btnRead_Click(sender As Object, e As EventArgs) Handles btnRead.Click Try ' 入力チェック If String.IsNullOrWhiteSpace(Me.tbxReadRow.Text) OrElse String.IsNullOrWhiteSpace(Me.tbxReadColumn.Text) Then MessageBox.Show("読み取る列・行を入力してください。(例:列=B または 2、行=2)") Return End If If Not File.Exists(ExcelPath) Then MessageBox.Show($"Excelファイルが見つかりません:{ExcelPath}") Return End If Dim row As Integer If Not Integer.TryParse(Me.tbxReadRow.Text.Trim(), row) OrElse row <= 0 Then MessageBox.Show("行は1以上の数値で入力してください。(例:2)") Return End If Dim colName As String = NormalizeColumnText(Me.tbxReadColumn.Text) Dim cellAddress As String = colName & row.ToString() Using book As New XLWorkbook(ExcelPath) Dim sheet As IXLWorksheet = GetWorksheetOrThrow(book, SheetName) Dim result As String = sheet.Cell(cellAddress).GetValue(Of String)() Me.lbReadResult.Text = result End Using Catch ex As Exception MessageBox.Show($"読み取りに失敗しました。{Environment.NewLine}{ex.Message}") End Try End Sub ''' <summary> ''' 書込むボタンを押下(SaveAs:上書き禁止運用) ''' </summary> Private Sub btnWrite_Click(sender As Object, e As EventArgs) Handles btnWrite.Click Try ' 入力チェック If String.IsNullOrWhiteSpace(Me.tbxWriteRow.Text) OrElse String.IsNullOrWhiteSpace(Me.tbxWriteColumn.Text) Then MessageBox.Show("書き込む列・行を入力してください。(例:列=C または 3、行=2)") Return End If If String.IsNullOrWhiteSpace(Me.tbxWriteText.Text) Then MessageBox.Show("書き込む値を入力してください。") Return End If If Not File.Exists(ExcelPath) Then MessageBox.Show($"Excelファイルが見つかりません:{ExcelPath}") Return End If Dim row As Integer If Not Integer.TryParse(Me.tbxWriteRow.Text.Trim(), row) OrElse row <= 0 Then MessageBox.Show("行は1以上の数値で入力してください。(例:2)") Return End If Dim colName As String = NormalizeColumnText(Me.tbxWriteColumn.Text) Dim cellAddress As String = colName & row.ToString() Using book As New XLWorkbook(ExcelPath) Dim sheet As IXLWorksheet = GetWorksheetOrThrow(book, SheetName) ' 書き込み sheet.Cell(cellAddress).Value = Me.tbxWriteText.Text ' 上書き禁止:SaveAsで別ファイル出力 Dim outPath As String = BuildOutputPath(ExcelPath) book.SaveAs(outPath) MessageBox.Show($"書き込みが完了しました。{Environment.NewLine}出力先:{outPath}") End Using Catch ex As Exception MessageBox.Show($"書き込みに失敗しました。{Environment.NewLine}{ex.Message}") End Try End Sub '======================== ' 補助関数群 '======================== ''' <summary> ''' 列入力をA1形式へ正規化("B" / "AA" / "2" などを許容) ''' </summary> Private Function NormalizeColumnText(input As String) As String Dim s As String = If(input, "").Trim() If s = "" Then Throw New ArgumentException("列が未入力です。") ' 数値なら列名へ変換 Dim colNum As Integer If Integer.TryParse(s, colNum) Then Return ColumnNumberToName(colNum) End If ' 文字列列名として扱う(A~Zのみ) s = s.ToUpper() For Each ch As Char In s If ch < "A"c OrElse ch > "Z"c Then Throw New ArgumentException("列は A~Z または数値(1=A,2=B...)で入力してください。") End If Next Return s End Function ''' <summary> ''' 数値列(1=A,2=B,...)をExcel列名(A, B, ..., AA, AB, ...)へ変換 ''' </summary> Private Function ColumnNumberToName(colNum As Integer) As String If colNum <= 0 Then Throw New ArgumentOutOfRangeException(NameOf(colNum), "列番号は1以上を指定してください。") Dim result As String = "" Dim n As Integer = colNum While n > 0 n -= 1 Dim remainder As Integer = n Mod 26 result = Chr(Asc("A"c) + remainder) & result n \= 26 End While Return result End Function ''' <summary> ''' シートが存在するか確認して取得(無ければ例外) ''' </summary> Private Function GetWorksheetOrThrow(book As XLWorkbook, sheetName As String) As IXLWorksheet If Not book.Worksheets.Any(Function(ws) ws.Name = sheetName) Then Throw New ArgumentException($"指定したシートが見つかりません:{sheetName}") End If Return book.Worksheet(sheetName) End Function ''' <summary> ''' 上書き禁止運用:出力用ファイルパスを生成(outputフォルダ+日時付き) ''' </summary> Private Function BuildOutputPath(originalPath As String) As String Dim dir As String = Path.Combine(Path.GetDirectoryName(originalPath), "output") Directory.CreateDirectory(dir) Dim baseName As String = Path.GetFileNameWithoutExtension(originalPath) Dim ext As String = Path.GetExtension(originalPath) Dim stamp As String = DateTime.Now.ToString("yyyyMMdd_HHmmss") Return Path.Combine(dir, $"{baseName}_{stamp}{ext}") End Function End Class |
実行編

プログラム起動時

読み取るセルの番地を入力

読取ったセルの情報を表示

C2にあるセルに”赤い”と書き込む場合

書込まれた

正しく書き込まれていることがわかります。
実務メモ(短く補足)
- 列入力は「B」でも「2」でもOK(2=B)
- Sheet1が無い場合は、メッセージ表示して落ちない
- 書き込みは SaveAs で必ず別ファイル出力(テンプレ破壊を防ぐ)
.xlsは ClosedXML 対象外(必要なら別手段)
書籍紹介(実務でExcel操作を“事故らせない”ために)
このパートが刺さる人
- 業務で Excelテンプレが頻繁に変わる
- 「とりあえず動く」Excel操作コードが増殖している
- 自分以外の人が触るVB.NETアプリを保守している
逆に、
「趣味でちょっとExcelを触りたいだけ」という人には 少し重い かもしれません。
① Excel入出力が増える現場向け
向いてる人
- Excel操作がアプリの“付属機能”ではなく“中核処理”になっている
- Save / SaveAs / テンプレ管理で過去に痛い目を見た
向いていない人
- 1セル読むだけ、書くだけで終わる小ツール用途
👉 この記事の「SaveAs(上書き禁止運用)」の考え方を、
より体系的に整理したい人向けです。
② VB.NETの保守性を上げたい人向け
向いてる人
- フォームの
Clickイベント に処理を書きがち - Excel操作・DB操作・UI処理が混ざってきている
向いていない人
- 使い捨てツールしか作らない人
👉 「動くけど後から地獄」にならないための
VB.NET設計の基礎体力を補ってくれます。
③ データを扱うならセットで
向いてる人
- Excel → DB → 帳票 という流れが見えている
- いずれExcelを“卒業”したいと思っている
向いていない人
- Excelだけで完結する業務が確定している人
👉 Excel操作を「ゴール」にしない人ほど、
早めに読んでおくと後がラクです。
💡 補足(読み飛ばしてOK)
この記事のコードは「今すぐ使える」ことを重視していますが、
長期運用では“設計”が効いてきます。
上の書籍は、その“判断基準”を補うためのものです。




