Excel VBA で jQuery のようにアクセスできるライブラリを作れるか? | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3217
なところの続き。と言うか、あのときは Excel VBA 内に閉じてないと使いづらいのでは?と思っていたのですが、Excel VBA から COM ライブラリを呼び出す、という方法でもいいことに気づきました。そこで、C# の相互運用(COMアクセス)を利用して、Excel を jQuery 風に使うライブラリ作成の続きおば。
■用法
こんなコードを書いて実行をすると、
Sub test() ' 初期化 Dim obj As New ExQuery.Query obj.SetApplication Excel.Application obj.Cell("A1").Text = "最初" obj.Cell("A2:B10").Text = "埋める" ' 背景色を赤に設定 obj.Cell("A2").CSS("background-color") = RGB(255, 0, 0) ' 文字色と背景色を変える Dim v As Long v = RGB(0, 0, 255) With obj.Cell("A3") '.CSS("color") = RGB(255, 255, 255) .CSS("color") = "#FF00000" .CSS("background-color") = RGB(0, 0, 255) End With End Sub
以下のように Excel のシートを操作できます。
先行きは表を簡単に作るとか、検索を楽にするとかを実装したいですね。
■C#のソースコード
面倒なので全文を晒しておきます。プロジェクトを作る時に、
- Visual Studio 2010 は管理者モードで起動する
- Microsoft Excel を参照設定する。
- プロジェクトのプロパティで「COM 相互運用機能の登録」をチェックする
を忘れずに。管理者モードのほうは、COM をレジストリに登録するの必要です。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Excel = Microsoft.Office.Interop.Excel; using System.Runtime.InteropServices; namespace Moonmile.ExQuery { [ClassInterfaceAttribute(ClassInterfaceType.AutoDual)] [ComVisible(true)] public class Query { private Excel.Application _app; private Excel.Workbook _book; private Excel.Worksheet _sheet; private Excel.Range _sel; /// /// Initalize Excel.Application object /// public Excel.Application Application { get { return _app; } set { _app = value; _book = _app.ActiveWorkbook; _sheet = _app.ActiveSheet; _sel = _app.Selection; } } public void SetApplication(Excel.Application app) { this.Application = app; } public Excel.Workbook Book { get { return _book; } set { _book = value; _sheet = _app.ActiveSheet; _sel = _app.Selection; } } public Excel.Worksheet Sheet { get { return _sheet; } set { _sheet = value; _sel = _app.Selection; } } public Excel.Range Selection { get { return _sel; } set { _sel = value; } } /// /// default constructor /// public Query() { } public ExRange Cell( object row1, object col1 = null, object row2 = null, object col2 = null ) { if (row1 == null) return new ExRange(); var s = row1 as string; if (s != null) return Cell(row1.ToString()); if (row2 == null) return Cell(int.Parse(row1.ToString()), int.Parse(col1.ToString())); return Cell( int.Parse(row1.ToString()), int.Parse(col1.ToString()), int.Parse(row2.ToString()), int.Parse(col2.ToString())); } /// /// pattern: /// Cell("A1") /// Cell("A1:B10") /// Cell("#id") /// /// /// protected ExRange Cell(string s) { Excel.Range rg; if (s.StartsWith("#")) rg = _sheet.get_Range(s.Substring(1)); else if (s.StartsWith(".")) rg = _sheet.get_Range(s.Substring(1)); else rg = _sheet.get_Range(s); return new ExRange(rg); } /// /// pattern: /// Cell(1,2) /// /// /// /// protected ExRange Cell(int row, int col) { if (_sheet == null) return new ExRange(); var rg = _sheet.Cells[row, col]; return new ExRange(rg); } /// /// pattern: /// Cell(1,2,3,4) /// /// /// /// /// /// protected ExRange Cell(int row1, int col1, int row2, int col2) { if (_sheet == null) return new ExRange(); var rg = _sheet.Range[ _sheet.Cells[row1, col1], _sheet.Cells[row2, col2]]; return new ExRange(rg); } } [ClassInterfaceAttribute(ClassInterfaceType.AutoDual)] [ComVisible(true)] public class ExRange { private Excel.Range _range ; private CSS _css; protected internal ExRange() { } public ExRange(Excel.Range rg) { _range = rg; _css = new CSS(_range); } public string Text { get { return _range.Value; } set { _range.Value = value; } } public CSS css { get { return _css; } } } [ClassInterfaceAttribute(ClassInterfaceType.AutoDual)] [ComVisible(true)] public class CSS { private Excel.Range _range; protected internal CSS() { } protected internal CSS(Excel.Range rg) { _range = rg; } public string this[string key] { get { switch (key.ToLower()) { case "color": return _range.Font.Color; case "background-color": return _range.Interior.Color; } return "0"; } set { if (value.StartsWith("#")) { int r = Convert.ToInt32(value.Substring(1, 2), 16); int g = Convert.ToInt32(value.Substring(1, 4), 16); int b = Convert.ToInt32(value.Substring(1, 6), 16); value = (r + g * 0x100 + b * 0x10000).ToString(); } switch (key.ToLower()) { case "color": _range.Font.Color = value; break; case "background-color": _range.Interior.Color = value; break; } } } } }
■Excel VBA から使う COM 作成のコツ
いくつか引っ掛かるところを書き下しておきます。
Excel VBA の「integer」は、C# の Int64/short Int16/short にあたります。16ビットの数値なんですね。なので、COM のメソッドの引数は、short にしておく必要があります。そうしないと、Excel VBA から COM に値を引き渡すときにエラーになります。
Cell メソッドの引数で object を使っていますが、VBA の Variant は、object でしか取れないようです。
最初は、Cell メソッドを多重定義して公開していたのですが、VBA 側で、Cell/Cell_2/Cell_3 と名前を変えられてしまうので、デフォルト値を付けて同じ名前で実行できるように変えました。
Excel VBA のインテリセンスを利用するために [ClassInterfaceAttribute(ClassInterfaceType.AutoDual)] という属性をつけます。実は AutoDual を使うと、COM 側のインターフェースが変わる(メソッドの順番が変わるなど)たびに、VB 側のビルドが必要になるのですが、今回は Excel VBA を対象にするのでこのまま使っています。Excel VBA の場合はインタープリタ的に COM を読み込むので、タイプライブラリを起動時に読み込んでくれるためです。このあたりは、以下のサイトを参考にしてください。
.NETコンポーネントをVB6から使用するための方法
http://www.sev.or.jp/ijupiter/world/dc_interrop/dotnet_com_interrop.html
Visual Basic 6.0 から Visual Basic .NET または Visual Basic 2005 アセンブリを呼び出す方法
http://support.microsoft.com/default.aspx?scid=kb;ja;817248
ClassInterfaceType 列挙体 (System.Runtime.InteropServices)
http://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.classinterfacetype(v=vs.110).aspx
■今後の予定?
css メソッドをちまちまと実装して、Excel VBA の複雑な UI を楽にアクセスできるようになると良いかも。
あと、C# で書いたので内部的に LINQ が使えますよね。Where/Select メソッド等を適当に公開してやれば、Excel VBA で LINQ を使っている感じに、なるかもしれない。とかとか。
ピンバック: 9月13日の注目記事 | Javable.Jp
1年以上前の記事にコメントもなんですが。。。
「■Excel VBA から使う COM 作成のコツ」の1行目、「Excel VBA の「integer」は、C# の Int64/short にあたります。16ビットの数値なんですね。」
Int64/shortではなく、Int16/shortですね^^
さいです。「Int16/short」ですね。
Int64だと「なんて先進的なVBAッ!!!」ってことに。