WinForms

【C#/WinForms実践入門編(7)】社員管理アプリを作る ~Excel・CSV出力機能を実装~【ClosedXMLを使う】

WinFormsアプリ開発を学ぶための、社員管理アプリ開発シリーズ第3弾です。

今回は、選択したデータをCSVファイルに保存する機能と、Excelファイルとして出力する機能を実装します。これで、データの永続化と外部ツールでの活用が可能になりますね。

以下の機能について実装していきます。

  1. CSVファイルの読み込み(←実践入門編(5)で作った)
  2. データの一覧表示(←実践入門編(5))
  3. データ検索/フィルタリング(←実践入門編(6)で作った)
  4. CSVファイルへの保存(←今回作る)
  5. Excel出力(←今回作る)

以下について学びます。

  • CSVファイルへの書き込み方法
  • ExcelファイルをC#から出力する方法(ClosedXMLを使う)
  • SaveFileDialogの使い方

次のような方に役立つ内容となっています。

  • データの永続化(保存)機能を実装したい方
  • ExcelファイルをC#で扱いたい方
プロ太

Windowsアプリ開発でExcelファイルを扱いたくなる場面は多いため、基本を学べば、いろいろと応用できます!

途中で、コードの整理(リファクタリング)も随時行っていきます。

前回までで作ったアプリを拡張し、演習1でCSV出力、演習2でExcel出力をそれぞれ実装します。

社員管理アプリ開発シリーズとして、前回・前々回の以下もまず見ていただくと、よりわかりやすいかと思います。

【C#/WinForms実践入門編(5)】社員管理アプリを作る ~CSV読み込みとDataGridView表示~ 前回までで、WinFormsアプリ開発の基礎的な内容を学びました。今回からデータ操作の実践シリーズがスタートします。 次のような...
【C#/WinForms実践入門編(6)】社員管理アプリを作る ~ユーザ入力でリアルタイムキーワード検索~【LINQ応用】 前回はCSVファイルから社員データを読み込み、DataGridViewに一覧表示する基本機能を実装しました。 今回は、ユーザが目...

演習のコード一式は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・デリゲートについて、詳しくは以下の記事もぜひ参考にしてください。

C#入門編(15)デリゲートとラムダ式 ~メソッドの部品化と再利用!~ 今回は「デリゲートとラムダ式」について解説します。 デリゲートはメソッド(関数)を部品化して再利用可能にするC#の機能です。ラム...
C#入門編(16)LINQ ~データ操作を効率的に行う~ 今回は「LINQ (Language Integrated Query)」について解説します。LINQは、C#でデータの操作や検索を効...

次に、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をインストールしましょう。

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

C#入門編(18)NuGetパッケージの使い方 ~CSVファイルを読み込む~【Visual Studio+nuget】 実践的なアプリ開発では、他人が作った既存部品をいかにうまく使うかがキモになります。 だいたいのアプリ開発は以下のイメージです。 ...
プロ太

特にこだわりがなければ、なるべくメジャーなものを選んでおくのがよいでしょう。

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アプリ開発を学んでいきましょう!

ABOUT ME
プロ太
●仕事:現在は個人事業主(メンター・情報発信等)、大手IT企業で技術者・マネージャ(15年以上)、大学の外部講師、学生時代は学習塾で非常勤講師(約4年間) ●博士(工学)の学位取得 ●高校生の頃に独学で始め、プログラミング歴20年以上 ●言語:C# 、Java、C/C++、Python、JavaScript/TypeScript等 ●分野:Webアプリ、テスト自動化、生成AI、デバッガ、コード解析、ドメイン特化言語

ご依頼・ご相談について

プログラミング学習のご相談、お仕事のご依頼については、
こちらのお問い合わせページをご確認ください。