2017年6月15日木曜日

[C#] Excelファイルを簡単に読み書きする方法

 今回は久しぶりの元ネタなし、プログラムそのものに関する話題です。今回解こうという課題は、C#のプログラムでExcelファイルの内容を読んだり書いたりしてみようというものです。とは言っても、Excelファイル上のグラフや図などを相手にするのではなく、単にセルに入力された文字列を取得したり、セルに書き出したりという基本動作だけのシンプルなプログラムを作ってみます。

 手元にはちょっと古いVisualStudio2010しかなかったので、これでやってみますが、もちろんもっと新しいバージョンのものを使っても一向に構いません。Excelファイルをプログラムから読み書きするために、Microsoft.Office.Interop.Excelというライブラリーを使用します。ソリューションエクスプローラー上で [参照設定] を右クリックし、こんな画面(↓)からMicrosoft.Office.Interop.Excelを追加してください。

 それではプログラムを書いてみましょう。直接Microsoft.Office.Interop.Excelを扱うのは面倒なので、ExcelAccessorという名前のラッパークラスを準備してみます(↓)。コンストラクタでExcelファイルのパスを指定し、セルの値にはインデクサを使用してアクセスします。Microsoft.Office.Interop.Excelの中ではセルはRangeというクラスのオブジェクトですが、扱いが面倒なのでここでは文字列で扱うことにしましょう。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Excel;
using System.Reflection;
using System.Threading;
namespace Yamachan
{
    public class ExcelAccessor : IDisposable
    {
        private Application excelApp;
        public Workbook Workbook
        {
            get;
            private set;
        }
        public Worksheet SelectedSheet
        {
            get;
            private set;
        }
        /// <summary>
        /// セルの値。インデックスは1始まりであることに注意。
        /// </summary>
        /// <param name="row">行番号(1始まり)</param>
        /// <param name="column">列番号(1始まり)</param>
        /// <returns>セルの値</returns>
        public string this[int row, int column]
        {
            get
            {
                if (SelectedSheet == null) return string.Empty;
                Range range = SelectedSheet.Cells[row, column] as Range;
                return (range==null || range.Value == null) ?
                                string.Empty : range.Value.ToString();
            }
            set
            {
                if (SelectedSheet != null)
                    SelectedSheet.Cells[row, column] = value;
            }
        }
        /// <summary>
        /// コンストラクタ。
        /// ファイル名を指定してExcelファイルをオープンします。
        /// </summary>
        /// <param name="path">オープンするExcelファイル</param>
        public ExcelAccessor(string path)
        {
            // エクセルファイルをオープン
            excelApp = new Application();
            excelApp.Visible = false;
            Workbook = excelApp.Workbooks._Open(path, Type.Missing,
                Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                Type.Missing, Type.Missing, Type.Missing);
            // 最初のシートを選択状態にする
            if (Workbook != null && Workbook.Sheets != null
                    && Workbook.Sheets.Count > 0)
                SelectedSheet = Workbook.Sheets[1] as Worksheet;
        }
        /// <summary>
        /// シートを選択します。
        /// </summary>
        /// <param name="sheetName">選択するシート名</param>
        /// <returns>選択されたシート</returns>
        public Worksheet SelectSheet(string sheetName)
        {
            if (Workbook == null || Workbook.Sheets == null) return null;
            foreach (Worksheet sheet in Workbook.Sheets)
            {
                if (sheet.Name == sheetName)
                {
                    sheet.Select(Type.Missing);
                    this.SelectedSheet = sheet;
                    return sheet;
                }
            }
            return null;
        }
        /// <summary>
        /// Excelファイルをクローズします。
        /// </summary>
        /// <param name="save">上書き保存する場合true</param>
        public void Close(bool save)
        {
            SelectedSheet = null;
            if (Workbook != null)
            {
                Workbook.Close(save, Type.Missing, Type.Missing);
                Workbook = null;
            }
            if (excelApp != null)
            {
                excelApp.Workbooks.Close();
                excelApp.Quit();
                excelApp = null;
            }
        }
        #region IDisposable メンバ
        /// <summary>
        /// このアクセッサを破棄します。
        /// </summary>
        public void Dispose()
        {
            Close(false);
        }
        #endregion
    }
}

テスト用に準備したExcelファイルはこんな感じ(↓)。A〜C欄にそれぞれ英語・日本語・数字で適当な値を入れてみました。Test.xlsxという名前をつけてexeファイルと同じディレクトリに入れます。

 それでは、Excelファイルのセルの文字列を取得するテストプログラムを書いてみましょう。ExcelAccessorはDisposableインタフェースを実装したので、using節を使用してクローズし忘れを防止します。「テストシート」という名前のシートを選択してから、インデクサを使用して2次元配列であるかのごとくセルに入力された文字列を取得します。注意しなければならないのは、プログラマーはインデックス0から始まることに慣れていますが、本家のMicrosoft.Office.Interop.Excelでインデックスが1から始まるのに合わせて、ExcelAccessorのインデクサでも1始まりを使用していることです。1始まりが気持ち悪いという人は、ExcelAccessor側で0始まりになるよう調節するといいかもしれません。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace Yamachan
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var excel 
                = new ExcelAccessor(Directory.GetCurrentDirectory()+"\\Test.xlsx"))
            {
                excel.SelectSheet("テストシート");
                for (int row = 1; row <= 2; row++)
                {
                    for (int column = 1; column <= 3; column++)
                    {
                        Console.WriteLine("["+row+", "+column+"] "+excel[row, column]);
                    }
                }
            }
            Console.ReadLine();
        }
    }
}

 さて、このプログラムの実行結果はこんな感じです(↓)。プログラム最後のConsole.ReadLine()は、テストプログラムが一瞬で終わってしまうのでコマンドプロンプトが閉じないようにしているだけです。この状態から何か入力して(入力しなくても)エンターキーを押せば、ウィンドウが閉じます。どうですか、簡単に各セルに入力された値が文字列として得られました。


 さて次は、このファイルのセルにプログラムから何か文字列を書き出して見ましょう(↓)。今度はちょっと横着して、Mainメソッドのみ。注意すべきは、ExcelAccessorはDispose()メソッドはファイルを上書きしないように閉じる実装にしたので、上書き保存したい場合は明示的にClose()メソッドにtrueを渡してファイルを閉じなければならないことくらいでしょうか。これも、そういう実装が嫌な方は、Dispose()でも上書きする実装にしてもいいかもしれません。

        static void Main(string[] args)
        {
            var excel = new ExcelAccessor(
                Directory.GetCurrentDirectory() + "\\Test.xlsx");
            excel.SelectSheet("テストシート");
            excel[3,4] = "書込み";
            excel.Close(true);
        }

 このテストプログラムを実行すると、Test.xlsxファイルはこんな風に(↓)プログラムから書き出された文字列がセルに入っています。

 自分が扱っているシステムでは、いまだに帳票をExcelファイルで出力したり、データ一括入力をExcelファイルから行なったりしていますので、簡単にExcelファイルを扱えるExcelAccessorクラスは使いまわして重宝しています。ExcelAccessorはセルの値を文字列で読んだり書いたりするというだけのシンプルな機能ですが、例えば、あらかじめフォーマットを作り込んだマスター用Excelファイルをコピーしてそこに帳票データを書き込むとか、アンケートにユーザー答えを入力してもらったExcelファイルをプログラムが読み取って集計したりなど、以外にもこのシンプルな機能だけで十分なケースが多いように思います。

0 件のコメント:

コメントを投稿