C#からExcelを操作するライブラリ

はじめに。

C#からPIAでExcelCOM使うとリークしてしまうのが難点。

ExcelCOMに1枚クラスを被せることで
使いにくいExcelCOMの存在をできるだけ隠蔽することを試みます。

  • クラスを被せることのメリット
    • COMの解放(Marshal.ReleaseComObject)のことを考えなくてよくなる。
      • わずらわしいリークに悩まされることが激減する。
    • COMの戻り値を適切な型にキャストしたりしなくてよい。
      • キャストミスによるトラブルに悩まされることが激減する。
      • インテリセンス(入力支援)が効くので作業効率アップ。
  • クラスを被せることのデメリット
    • ExcelのCOMオブジェクト、メソッド、プロパティを1つずつラッピングする必要がある。
      • 膨大なので全てをラッピングするのは不可能なので、よく使いそうなものだけ対応。

ライブラリ仕様

  • 特徴
    • Marshal.ReleaseComObject はライブラリ側で自動で行う。
    • Excel2000、ExcelXP(2002) PIA 対応。(#defineで指定)
  • その他
    • Excel2003、2007 PIA でも使えるかもしれません。
    • PIAは.NETとCOMのI/Fなので、開発をExcel2000PIAで行っていれば、(COMレベルの互換性によりますが)ExcelXP以上でも動くはず。

Visual C# Express 2008 で作ったもの一式はこちら。
http://goungoun.dip.jp/app/upload/libSimpleExcel/libSimpleExcel20090210.zip

対応してるExcelオブジェクト、メソッド、プロパティ一覧

対応しているのは次のクラス(Excelオブジェクト)、メソッド、プロパティ
のみです。足りないものは既存のものを参考に追加してください。

  • class Application
    • bool Visible
    • bool DisplayAlerts
    • Workbooks Workbooks
    • void Quit()
  • class Workbooks
    • Workbook Open(string filename_,bool read_only_,Excel.XlFileFormat format_,string delimiter_)
    • Workbook Open(string filename_, bool read_only_)
    • Workbook Add()
    • int Count
    • Workbook this[int idx_]
    • Workbook this[string name_]
  • class Workbook
    • Sheets Sheets
  • class Sheets
    • int Count
    • Worksheet this[int idx_]
    • Worksheet this[string name_]
  • class Worksheet
    • string Name
    • void Delete()
    • void CopyAfter(Worksheet aft_)
    • void CopyBefore(Worksheet bef_)
    • Range Cells
    • Range Range
    • Range Rows(int row_)
    • Range Columns(int col_)
  • class Range
    • Range this[int row_, int col_]
    • Range this[Range cell1_, Range cell2_]
    • Range Resize(int row_, int col_)
    • bool Delete()
    • bool Delete(Excel.XlDeleteShiftDirection direction_)
    • object Value
    • Font Font
    • Interior Interior
  • class Font
    • int ColorIndex
    • bool Bold
  • class Interior
    • int ColorIndex
    • Excel.XlPattern Pattern

PIAの入手方法

Office2000なら、Interop.Excel.dll
OfficeXPなら、Microsoft.Office.Interop.Excel.dll

入手先は、

Office2000なら。
プロジェクト→参照の追加→COMタブ→
Microsoft Excel 9.0 Object Livraly 1.3
X:\Program Files\Microsoft Office\Office\EXCEL9.OLB

Office XP PIAなら。
Microsoft Office XP 用の PIA (Primary Interop Assemblies) のダウンロード
http://support.microsoft.com/kb/328912/ja


ソース

lib.SimpleExcel.cs(ライブラリ側)

使用するPIAの種類に応じて先頭のdefineを切り替える。

//#define PIA_OFFICE_2000
#define PIA_OFFICE_XP

using System;
using System.Collections;
using System.Runtime.InteropServices;
#if PIA_OFFICE_XP
using Excel = Microsoft.Office.Interop.Excel;
#endif

namespace lib.SimpleExcel
{
    /// <summary>
    /// Excel関連のオブジェクトを分類する為の独自クラス
    /// </summary>
    public class Object : Base
    {
        private string _type_name = "";

        public Object(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
            if (comobj_ == null)
            {
                this._type_name = "null";
            }
            else if (Marshal.IsComObject(comobj_))
            {
                if ((comobj_ as Excel.Range) != null)
                {
                    this._type_name = "Excel.Range";
                }
                else if ((comobj_ as Excel.Range) != null)
                {
                    this._type_name = "Excel.Range";
                }
            }
            if (this._type_name == "")
            {
                Type t = comobj_.GetType();
                this._type_name = t.FullName;
            }
        }

        public string TypeName
        {
            get { return this._type_name; }
        }
    }

    /// <summary>
    /// Base
    /// </summary>
    public abstract class Base : IDisposable
    {
        private static int _com_object_counter = 0;
        private Base _parent = null;
        private ArrayList _childs = null;
        protected object _comobj = null;

        /// <summary>
        /// 現在使用中の Excel COM の数
        /// 0 でない場合、Excelプロセス が開放されていないことになるので、
        /// 本ライブラリでExcel操作後は、必ず0になっている*はず*
        /// </summary>
        public static int ComObjectCounter
        {
            get { return Base._com_object_counter; }
        }

        /// <summary>
        /// Excelのプロセスを取得
        /// IDとウィンドウタイトルから素性を判断できる。
        /// </summary>
        /// <param name="msg_"></param>
        /// <returns></returns>
        public static System.Diagnostics.Process[] GetExcelProcess(ref string msg_)
        {
            System.Diagnostics.Process[] ps =
                System.Diagnostics.Process.GetProcessesByName("Excel");

            msg_ = "";
            foreach (System.Diagnostics.Process p in ps)
            {
                msg_ += string.Format("Id:{0} Title:{1} \r\n", p.Id, p.MainWindowTitle);
            }
            return ps;
        }

        public Base(Base parent_, object comobj_)
        {
            //          if (parent_ == null)
            //          {
            //              System.Windows.Forms.MessageBox.Show("null");
            //          }

            this._parent = parent_;
            if (parent_ != null)
            {
                parent_.AddChild(this);
            }

            this._childs = new ArrayList();

            this._comobj = comobj_;
            if (Marshal.IsComObject(this._comobj))
            {
                Base._com_object_counter++;
            }
        }

        public object ComObject
        {
            get { return this._comobj; }
        }

        protected void AddChild(Base obj_)
        {
            this._childs.Add(obj_);
        }

        public void Dispose()
        {
            // 子を破棄
            if (this._childs != null)
            {
                for (int idx = 0; idx < this._childs.Count; idx++)
                {
                    Base obj = (Base)this._childs[idx];
                    obj.Dispose();
                }
                this._childs = null;
            }

            // 自分を破棄
            if (this._comobj != null && Marshal.IsComObject(this._comobj))
            {
                Marshal.ReleaseComObject(this._comobj);
                Base._com_object_counter--;
            }
            this._comobj = null;

            // 親情報を削除
            this._parent = null;

            GC.Collect();   // 一応と思っていたら、これがないとExcelで一般保護違反がでることがあった。
        }
    }

    /// <summary>
    /// Excel.Application
    /// </summary>
    public class Application : Base
    {
        public Application()
            : base(null, new Excel.Application())
        {
        }

        private Excel.Application _application
        {
            get { return (Excel.Application)this._comobj; }
        }

        public bool Visible
        {
            get { return this._application.Visible; }
            set { this._application.Visible = value; }
        }

        public bool DisplayAlerts
        {
            get { return this._application.DisplayAlerts; }
            set { this._application.DisplayAlerts = value; }
        }

        public Workbooks Workbooks
        {
            get { return new Workbooks(this, this._application.Workbooks); }
        }

        public void Quit()
        {
            this._application.Quit();
        }
    }

    /// <summary>
    /// Excel.Workbooks
    /// </summary>
    public class Workbooks : Base
    {
        public Workbooks(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
        }

        private Excel.Workbooks _workbooks
        {
            get { return (Excel.Workbooks)this._comobj; }
        }

        private object _workbooks_open(string Filename, object UpdateLinks, object ReadOnly, object Format, object Password, object WriteResPassword, object IgnoreReadOnlyRecommended, object Origin, object Delimiter, object Editable, object Notify, object Converter, object AddToMru, object Local, object CorruptLoad)
        {
#if PIA_OFFICE_2000
            object ret = this._workbooks.Open(Filename, UpdateLinks, ReadOnly, Format, Password, WriteResPassword, IgnoreReadOnlyRecommended, Origin, Delimiter, Editable, Notify, Converter, AddToMru);
#endif
#if PIA_OFFICE_XP
            object ret = this._workbooks.Open(Filename, UpdateLinks, ReadOnly, Format, Password, WriteResPassword, IgnoreReadOnlyRecommended, Origin, Delimiter, Editable, Notify, Converter, AddToMru, Local, CorruptLoad);
#endif
            return ret;
        }

        public Workbook Open(
            string filename_,
            bool read_only_,
            Excel.XlFileFormat format_,
            string delimiter_
            )
        {
            return new Workbook(this, this._workbooks_open(
                filename_,
                Type.Missing,
                read_only_,
                format_,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                delimiter_,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing));
        }

        public Workbook Open(string filename_, bool read_only_)
        {
            return new Workbook(this, this._workbooks_open(
                filename_,
                Type.Missing,
                read_only_,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing,
                Type.Missing));
        }

        public Workbook Add()
        {
            return new Workbook(this, this._workbooks.Add(Type.Missing));
        }

        public int Count
        {
            get { return this._workbooks.Count; }
        }

        public Workbook this[int idx_]
        {
            get { return new Workbook(this, this._workbooks.get_Item(idx_)); }
        }

        public Workbook this[string name_]
        {
            get { return new Workbook(this, this._workbooks.get_Item(name_)); }
        }
    }

    /// <summary>
    /// Excel.Workbook
    /// </summary>
    public class Workbook : Base
    {
        public Workbook(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
        }

        private Excel.Workbook _workbook
        {
            get { return (Excel.Workbook)this._comobj; }
        }

        public Sheets Sheets
        {
            get { return new Sheets(this, this._workbook.Sheets); }
        }
    }

    /// <summary>
    /// Excel.Sheets
    /// </summary>
    public class Sheets : Base
    {
        public Sheets(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
        }

        private Excel.Sheets _sheets
        {
            get { return (Excel.Sheets)this._comobj; }
        }

        public int Count
        {
            get { return this._sheets.Count; }
        }

        public Worksheet this[int idx_]
        {
            get { return new Worksheet(this, this._sheets.get_Item(idx_)); }
        }

        public Worksheet this[string name_]
        {
            get { return new Worksheet(this, this._sheets.get_Item(name_)); }
        }
    }

    /// <summary>
    /// Excel.Worksheet
    /// </summary>
    public class Worksheet : Base
    {
        public Worksheet(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
        }

        private Excel.Worksheet _worksheet
        {
            get { return (Excel.Worksheet)this._comobj; }
        }

        public string Name
        {
            get { return this._worksheet.Name; }
            set { this._worksheet.Name = value; }
        }

        public void Delete()
        {
            this._worksheet.Delete();
        }

        public void CopyAfter(Worksheet aft_)
        {
            // 指定したワークシートの右側へコピー
            // this._worksheet を aft_ の 右側へコピー
            this._worksheet.Copy(Type.Missing, aft_._comobj);
        }

        public void CopyBefore(Worksheet bef_)
        {
            // 指定したワークシートの左側へコピー
            // this._worksheet を bef_ の 左側へコピー
            this._worksheet.Copy(bef_._comobj, Type.Missing);
        }

        public Range Cells
        {
            get { return new Range(this, this._worksheet.Cells); }
        }

        public Range Range
        {
            get { return new Range(this, this._worksheet.Cells); }
        }

        public Range Rows(int row_)
        {
            return new Range(this, this._worksheet.Rows[row_, Type.Missing]);
        }

        public Range Columns(int col_)
        {
            return new Range(this, this._worksheet.Columns[Type.Missing, col_]);
        }
    }

    /// <summary>
    /// Excel.Range
    /// </summary>
    public class Range : Base
    {
        public Range(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
        }

        private Excel.Range _range
        {
            get { return (Excel.Range)this._comobj; }
        }

        public Range this[int row_, int col_]
        {
            get { return new Range(this, this._range.get_Item(row_, col_)); }
        }

        public Range this[Range cell1_, Range cell2_]
        {
            get { return new Range(this, this._range.get_Range(cell1_._comobj, cell2_._comobj)); }
        }

        public Range Resize(int row_, int col_)
        {
            return new Range(this, this._range.get_Resize(row_, col_));
        }

        public bool Delete()
        {
            return (bool)this._range.Delete(Type.Missing);
        }

        public bool Delete(Excel.XlDeleteShiftDirection direction_)
        {
            return (bool)this._range.Delete(direction_);
        }

        public object Value
        {
#if PIA_OFFICE_2000
            get { return this._range.Value; }
            set { this._range.Value = value; }
#endif
#if PIA_OFFICE_XP
            get { return this._range.get_Value(Excel.XlRangeValueDataType.xlRangeValueDefault); }
            set { this._range.set_Value(Excel.XlRangeValueDataType.xlRangeValueDefault, value); }
#endif
        }

        public Font Font
        {
            get { return new Font(this, this._range.Font); }
        }

        public Interior Interior
        {
            get { return new Interior(this, this._range.Interior); }
        }
    }

    /// <summary>
    /// Excel.Font
    /// </summary>
    public class Font : Base
    {
        public Font(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
        }

        private Excel.Font _font
        {
            get { return (Excel.Font)this._comobj; }
        }

        public int ColorIndex
        {
            get { return (int)this._font.ColorIndex; }
            set { this._font.ColorIndex = value; }
        }

        public bool Bold
        {
            get { return (bool)this._font.Bold; }
            set { this._font.Bold = value; }
        }
    }

    /// <summary>
    /// Excel.Interior
    /// </summary>
    public class Interior : Base
    {
        public Interior(Base parent_, object comobj_)
            : base(parent_, comobj_)
        {
        }

        private Excel.Interior _interior
        {
            get { return (Excel.Interior)this._comobj; }
        }

        public int ColorIndex
        {
            get { return (int)this._interior.ColorIndex; }
            set { this._interior.ColorIndex = value; }
        }

        public Excel.XlPattern Pattern
        {
            get { return (Excel.XlPattern)this._interior.Pattern; }
            set { this._interior.Pattern = value; }
        }
    }

}
Form1.cs(使用例)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            lib.SimpleExcel.Application obj_app = new lib.SimpleExcel.Application();

            // 確実にオブジェクトを開放する為、usingは必須
            using (obj_app)
            {
                obj_app.DisplayAlerts = false;
                obj_app.Visible = true;

                // 新規Workbookを開く
                lib.SimpleExcel.Workbook obj_workbook = obj_app.Workbooks.Add();

                // 不要なSheetを削除
                //      MessageBox.Show(obj_workbook.Sheets.Count.ToString());
                while (obj_workbook.Sheets.Count != 1)
                {
                    obj_workbook.Sheets[2].Delete();
                }
                //      MessageBox.Show(obj_workbook.Sheets.Count.ToString());

                // Sheet名付け
                lib.SimpleExcel.Worksheet obj_sheet = obj_workbook.Sheets[1];
                obj_sheet.Name = "data";

                // 配列データをシートへ複写
                object[,] data = {
                    { "データ0-0", "データ0-1" },
                    { "データ1-0", "データ1-1" },
                };
                obj_sheet.Cells[1, 1].Value = data; // これでは1セルしかコピーされない。
                obj_sheet.Range[obj_sheet.Cells[3, 1], obj_sheet.Cells[4, 2]].Value = data;         // 明示的に範囲指定
                obj_sheet.Cells[5, 1].Resize(data.GetLength(1), data.GetLength(0)).Value = data;    // 配列データをGetLengthして範囲指定

                // セル値を読む
                object val_obj = obj_sheet.Cells[4, 2].Value;
                if ((val_obj as string) != null)
                {
                    MessageBox.Show((string)val_obj);
                }

                // 取得した型を読む
                lib.SimpleExcel.Object obj1 = new lib.SimpleExcel.Object(null, val_obj);
                //      MessageBox.Show(obj1.TypeName);

                // 範囲でセル値を読む
                val_obj = obj_sheet.Range[obj_sheet.Cells[3, 1], obj_sheet.Cells[3, 2]].Value;
                string str = "";
                if ((val_obj as object[,]) != null)
                {
                    object[,] obj2 = (object[,])val_obj;
                    for (int row = 1; row <= obj2.GetLength(0); row++)
                    {
                        for (int col = 1; col <= obj2.GetLength(1); col++)
                        {
                            if ((obj2[row, col] as string) != null)
                            {
                                str += (string)obj2[row, col] + ",";
                            }
                        }
                        str += "\n";
                    }
                }
                //      MessageBox.Show(str);

                // 行削除
                obj_sheet.Rows(1).Delete();
                obj_sheet.Rows(1).Delete();
                obj_sheet.Rows(1).Delete();
                obj_sheet.Rows(1).Delete();

                obj_app.Quit();

                //      MessageBox.Show("ComObjectCounter=" + lib.SimpleExcel.Base.ComObjectCounter.ToString());
            }
            //  MessageBox.Show("ComObjectCounter=" + lib.SimpleExcel.Base.ComObjectCounter.ToString());
            obj_app = null;

        }
    }
}