WinFormsアプリ開発を学ぶための、社員管理アプリ開発シリーズ第3弾です。
今回は、選択したデータをCSVファイルに保存する機能と、Excelファイルとして出力する機能を実装します。これで、データの永続化と外部ツールでの活用が可能になりますね。
以下の機能について実装していきます。
- CSVファイルの読み込み(←実践入門編(5)で作った)
- データの一覧表示(←実践入門編(5))
- データ検索/フィルタリング(←実践入門編(6)で作った)
- CSVファイルへの保存(←今回作る)
- Excel出力(←今回作る)
以下について学びます。
- CSVファイルへの書き込み方法
- ExcelファイルをC#から出力する方法(ClosedXMLを使う)
- SaveFileDialogの使い方
次のような方に役立つ内容となっています。
- データの永続化(保存)機能を実装したい方
- ExcelファイルをC#で扱いたい方
Windowsアプリ開発でExcelファイルを扱いたくなる場面は多いため、基本を学べば、いろいろと応用できます!
途中で、コードの整理(リファクタリング)も随時行っていきます。
前回までで作ったアプリを拡張し、演習1でCSV出力、演習2でExcel出力をそれぞれ実装します。
社員管理アプリ開発シリーズとして、前回・前々回の以下もまず見ていただくと、よりわかりやすいかと思います。


演習のコード一式はGitHubに置いてあります。
YouTube動画も作成しています。
演習1:CSVファイル保存機能の実装
前回演習のコードをベースとして、次の機能を作ってみましょう。
- 画面に「保存」ボタンを追加し、現在表示中の社員データをCSVファイルとして保存できるようにする。
まずUIの見た目部分を作成し、その後に中身のコードを書いていきましょう。
手順1:コントロール設置とプロパティ設定
以下のように「CSV保存ボタン」を配置します。
- CSV保存ボタン(Button)
- Name:saveCsvButton
- Text:CSV保存
以下のような画面構成となります。(全体の配置も少し調整しています)

手順2:コード作成
以下を修正します。
- CsvService.cs
- Employee.cs
- MainForm.cs
CsvService.csは以下のように修正します。
CsvServiceクラスへのCSV出力機能追加と同時に、コード全体もリファクタリングし整理しています。
using System.Text;
using EmployeeManager.Models;
namespace EmployeeManager.Services
{
public class CsvService
{
public List<Employee> ReadCsv(string filePath)
{
var lines = File.ReadAllLines(filePath, Encoding.UTF8);
return lines.Skip(1) // ヘッダー行をスキップ
.Select(ParseCsvLine)
.ToList();
}
private Employee ParseCsvLine(string line)
{
var values = line.Split(',');
return new Employee(
values[ 0 ],
values[ 1 ],
values[ 2 ],
DateTime.Parse(values[ 3 ])
);
}
public void WriteCsv(string filePath, List<Employee> employees)
{
var lines = employees.Select(ToCsvLine);
File.WriteAllLines(filePath, lines, Encoding.UTF8);
}
private string ToCsvLine(Employee employee)
{
return $"{employee.Id},{employee.Name},{employee.Department},{employee.JoinDate:yyyy/MM/dd}";
}
}
}
Employee.csは以下のように修正します。
namespace EmployeeManager.Models
{
public class Employee
{
public string Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public DateTime JoinDate { get; set; }
public Employee(string id, string name, string department, DateTime joinDate)
{
Id = id;
Name = name;
Department = department;
JoinDate = joinDate;
}
}
}
要点は以下です。
- CSV出力機能をCsvServiceクラスへ追加
- CSV入出力に関する役割はCsvServiceクラスへ集約
(Employeeクラスが担っていたCSV入力機能の一部を切り離し、CsvServiceクラスへ実装) - LINQ・デリゲートを活用してコードを簡略化
それぞれのクラスへ1つの役割(1つの機能)をもたせることで、機能修正があったときの対応も容易になります。
LINQ・デリゲートについて、詳しくは以下の記事もぜひ参考にしてください。


次に、MainForm.csでCSV保存ボタンへ以下のイベントを設定します。
- saveCsvButton
- Click:saveCsvButton_Click
こちらについてもCSV出力のイベントコード追加にあわせて、コード全体をリファクタリングしました。
using EmployeeManager.Models;
using EmployeeManager.Services;
namespace EmployeeManager.Forms
{
public partial class MainForm : Form
{
private readonly CsvService _csvService;
private List<Employee> _employees = new List<Employee>();
public MainForm()
{
InitializeComponent();
_csvService = new CsvService();
}
private void searchCondition_Changed(object sender, EventArgs e)
{
ApplyFilter();
}
private void loadCsvButton_Click(object sender, EventArgs e)
{
LoadCsvFile();
}
private void saveCsvButton_Click(object sender, EventArgs e)
{
SaveCsvFile();
}
private void ApplyFilter()
{
string searchText = searchBox.Text.ToLower().Trim();
DateTime fromDate = fromDatePicker.Value.Date;
DateTime toDate = toDatePicker.Value.Date;
var filteredList = _employees.Where(emp =>
(emp.Name.ToLower().Contains(searchText) ||
emp.Department.ToLower().Contains(searchText))
&&
(emp.JoinDate.Date >= fromDate &&
emp.JoinDate.Date <= toDate)
).ToList();
employeeDataGridView.DataSource = filteredList;
}
private void LoadCsvFile()
{
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "CSVファイル|*.csv";
if (dialog.ShowDialog() == DialogResult.OK)
{
try
{
_employees = _csvService.ReadCsv(dialog.FileName);
employeeDataGridView.DataSource = _employees;
ShowSuccessMessage("CSVファイルを読み込みました。");
}
catch (Exception ex)
{
ShowErrorMessage("エラーが発生しました", ex);
}
}
}
}
private void SaveCsvFile()
{
if (!ValidateDataExists()) return;
var filteredList = employeeDataGridView.DataSource as List<Employee>;
if (filteredList == null) return;
using (SaveFileDialog dialog = new SaveFileDialog())
{
dialog.Filter = "CSVファイル|*.csv";
dialog.Title = "CSVファイルを保存";
if (dialog.ShowDialog() == DialogResult.OK)
{
try
{
_csvService.WriteCsv(dialog.FileName, filteredList);
ShowSuccessMessage("CSVファイルを保存しました。");
}
catch (Exception ex)
{
ShowErrorMessage("保存中にエラーが発生しました", ex);
}
}
}
}
private bool ValidateDataExists()
{
if (_employees.Count == 0)
{
MessageBox.Show("保存するデータがありません。", "警告",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
private void ShowSuccessMessage(string message)
{
MessageBox.Show(message, "成功",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void ShowErrorMessage(string message, Exception ex)
{
MessageBox.Show($"{message}:{ex.Message}", "エラー",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
全体的に整理していますが、要点は以下です。
- CSV出力用のイベントコードを追加
(saveCsvButton_Click、SaveCsvFile) - 保存時のデータチェック用のコードを追加
(ValidateDataExists) - 成功・失敗時のメッセージ表示コードを共通化
(ShowSuccessMessage、ShowErrorMessage)
CSVを保存するときには「SaveFileDialog」を使っています。使い方は読み込みのときに使った「OpenFileDialog」とほぼ同じです。
開発を続ける中で、適宜コード整理(リファクタリング)も行うとよいでしょう。
前よりもコードが全体としてスッキリしたかも!
アプリを実行
アプリを実行してみましょう。CSVファイル保存ボタンで、現在選択している社員一覧をCSV形式で保存できます。

演習2:Excelファイル出力機能の実装
続いて、Excel出力機能を実装していきます。この機能により、社員データを見やすく整形されたExcelファイルとして出力できるようになります。
最初にExcel出力用のパッケージを導入し、それを用いて実装を進めます。
手順1:Excel出力用のパッケージ選定と導入
今回、Excel操作用のライブラリとして「ClosedXML」を使用します。このライブラリは、ExcelファイルをC#から簡単に操作できる機能を提供しています。
ソリューションエクスプローラでプロジェクトの右クリックメニューで「NuGetパッケージの管理」をクリックしてパッケージ画面を表示し、ClosedXMLをインストールしましょう。

パッケージの選び方や導入方法について、詳しくは以下の記事も参考にしてください。

特にこだわりがなければ、なるべくメジャーなものを選んでおくのがよいでしょう。
ChatGPT(Web検索連携)で最新のおすすめライブラリを調べてもらう方法も、最初のとっかかりとして有効かと思います。
以下はChatGPT(無料版)にC# Excelライブラリについて調べてもらっている様子です。

手順2:コントロール配置とプロパティ設定
MainFormで、「Excelエクスポートボタン」を配置します。
- Excelエクスポートボタン(Button)
- Name:exportExcelButton
- Text:Excelエクスポート
以下のような画面構成となります。

手順3:コード作成
以下を追加・修正します。
- ExcelService.cs【追加】
- MainForm.cs【修正】
ExcelService.csをServicesフォルダ配下で新しく作成します。
using ClosedXML.Excel;
using EmployeeManager.Models;
namespace EmployeeManager.Services
{
public class ExcelService
{
public void ExportToExcel(string filePath, List<Employee> employees)
{
using var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("社員リスト");
// ヘッダーの設定
SetupHeaders(worksheet);
// データの書き込み
WriteEmployeeData(worksheet, employees);
// ワークシートの書式設定
FormatWorksheet(worksheet);
// ファイルの保存
workbook.SaveAs(filePath);
}
private static void SetupHeaders(IXLWorksheet worksheet)
{
// 定数の定義
const int HEADER_ROW = 1;
var headers = new[] { "社員番号", "氏名", "部署", "入社日" };
for (int headerIndex = 0; headerIndex < headers.Length; headerIndex++)
{
int columnIndex = headerIndex + 1;
var headerCell = worksheet.Cell(HEADER_ROW, columnIndex);
headerCell.Value = headers[headerIndex];
headerCell.Style.Font.Bold = true;
headerCell.Style.Fill.BackgroundColor = XLColor.LightGray;
headerCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
}
}
private static void WriteEmployeeData(IXLWorksheet worksheet, List<Employee> employees)
{
// 定数の定義
const int HEADER_ROW = 1;
const int DATA_START_ROW = HEADER_ROW + 1;
const int ID_COLUMN = 1;
const int NAME_COLUMN = 2;
const int DEPARTMENT_COLUMN = 3;
const int JOIN_DATE_COLUMN = 4;
for (int employeeIndex = 0; employeeIndex < employees.Count; employeeIndex++)
{
int currentRow = DATA_START_ROW + employeeIndex;
var employee = employees[employeeIndex];
// 各列にデータを設定
worksheet.Cell(currentRow, ID_COLUMN).Value = employee.Id;
worksheet.Cell(currentRow, NAME_COLUMN).Value = employee.Name;
worksheet.Cell(currentRow, DEPARTMENT_COLUMN).Value = employee.Department;
// 日付の書式設定
var joinDateCell = worksheet.Cell(currentRow, JOIN_DATE_COLUMN);
joinDateCell.Value = employee.JoinDate;
joinDateCell.Style.DateFormat.Format = "yyyy/MM/dd";
}
}
private static void FormatWorksheet(IXLWorksheet worksheet)
{
// データ範囲の罫線を設定
var usedDataRange = worksheet.RangeUsed();
if (usedDataRange != null)
{
usedDataRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
usedDataRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
}
}
}
}
ClosedXMLライブラリの活用し複雑なExcel操作をシンプルに実現しています。以下が要点です。
- ExportToExcel メソッド(全体フロー)
- ワークブックを作成し、ヘッダー設定、データ書き込み、書式調整の各工程を順に実行した後、指定パスにファイルを保存。
- SetupHeaders メソッド(ヘッダ行設定)
- 各列の見出しを設定し、太字化、背景色、中央揃えの書式を適用。
- WriteEmployeeData メソッド(社員データ書き込み)
- 社員リストを順にループし、各社員情報を2行目以降に配置しながら、特に日付データには専用の表示形式を適用。
- FormatWorksheet メソッド(ワークシート全体の見た目を整理)
- 読みやすく整ったシートに仕上げるため、使用範囲に罫線を設定。
次に、MainForm.csででExcelエクスポートボタンへ以下のイベントを設定します。
- exportExcelButton
- Click:exportExcelButton_Click
コードは以下のように修正・追加します。Excelエクスポートボタンが押されたら、ExcelServiceクラスを使ってExcelファイルを出力します。
…
public partial class MainForm : Form
{
private readonly CsvService _csvService;
private readonly ExcelService _excelService; //★追加
private List<Employee> _employees = new List<Employee>();
public MainForm()
{
InitializeComponent();
_csvService = new CsvService();
_excelService = new ExcelService(); //★追加
}
…
…
…
//★Excelエクスポートボタンのクリック時のイベント
private void exportExcelButton_Click(object sender, EventArgs e)
{
ExportToExcelFile();
}
…
//★Excelファイルを出力
private void ExportToExcelFile()
{
if (!ValidateDataExists()) return;
var filteredList = employeeDataGridView.DataSource as List<Employee>;
if (filteredList == null) return;
using (SaveFileDialog dialog = new SaveFileDialog())
{
dialog.Filter = "Excelファイル|*.xlsx";
dialog.Title = "Excelファイルを保存";
if (dialog.ShowDialog() == DialogResult.OK)
{
try
{
_excelService.ExportToExcel(dialog.FileName, filteredList);
ShowSuccessMessage("Excelファイルを出力しました。");
}
catch (Exception ex)
{
ShowErrorMessage("出力中にエラーが発生しました", ex);
}
}
}
}
…
Excelファイルの出力は、CsvServiceでCSVファイルを出力するときとほとんど同じ感じだね!
アプリを実行
アプリを実行してみましょう。選択している社員データをExcel形式で保存できます。

保存したExcelファイルを開いてみましょう。

ついにExcelファイル出力までできたね!
これで、CSV、Excel、DataGridViewなどを使った表形式データ操作・表示の基本はばっちりですね!
他にもClosedXMLでExcel操作を色々試してみたい方はClosedXMLのGitHub内のWikiページを参考にするよいでしょう。
まとめ
本記事では、WinFormsアプリでのCSVファイル保存とExcel出力機能の実装について学びました。
SaveFileDialogを活用して、ユーザーが指定した場所に表示中の社員データをCSV形式で出力する機能を実装しました。
ClosedXMLライブラリを導入し、より見やすく整形されたExcelファイルとして社員データを出力できるようにしました。
機能追加と同時に既存コードを整理し、成功・エラーメッセージの表示処理やデータ検証といった共通処理をメソッド化することで、コードの重複を減らし可読性を向上させました。
次回から、また別のトピックをテーマに実践的なWinFormsアプリ開発を一緒に進めていければと思います。
引き続き、一緒にC# WinFormsアプリ開発を学んでいきましょう!