AutoCAD から、Excel のデータを読む
AutoCAD のカスタマイズ用言語?の AutoLISP は、AutoCAD で作図をする際の便利ツールを組むのには手軽で良いのですが、VLA オブジェクトを使用する事で、ActiveX 経由で Excel を操作できたりします。
当然ですが Excel がインストールされている必要があります。
VLA の基本的な使い方
こちらでも記載しましたが、LISP から AutoCAD VBA のコマンドを扱うのに使用できます。
先に示したリンク先の例では、LISP のジオメトリック関数 inters
では、直線同士の交点しか算出できませんが、VBA の IntersectWith
なら、円や円弧、スプライン等の曲線との交点も算出できますが、そのためにわざわざ VBEditor で dvb ファイルを作って vl-vbaload
でロードして (command "vbarun" 〜
とか面倒な事はしたくないので、VLA オブジェクトを使って、VBA の IntersectWith
を利用しているわけです。
この様に、LISP から手軽?に AutoCAD の VBA のメソッドやプロパティにアクセスしたり、他のアプリケーションを ActiveX 経由で操作するのに、使用できます。
実際の使い方
では、実際に使い方を確認してみましょう。前提として
- Microsoft Office の VBA がある程度わかる
- AutoLISP の基礎が分かる
事を前提に記載しています。
アプリケーションオブジェクトの作成
Access や Word の VBA から Excel を起ち上げる、あるいは VBA を実行している Excel アプリとは別のプロセスの Excel を起動したい場合は、下記の様に記載すると思います。
Set objApp = CreateObject("Excel.Application")
また、起動済みの Excel を取得するという場合は、下記のようにすると思います。
Set objApp = GetObject(, "Excel.Application")
これらを VLA & ActiveX で行うと、下記の様になります。
(setq objapp (vlax-create-object "Excel.Application"))
(setq objapp (vlax-get-object "Excel.Application"))
既に起動している場合は、その起動済みアプリケーションオブジェクトを返し、起動していない場合は新たに起動してそのアプリケーションオブジェクトを返す、下記の様な関数もあります。
(setq objapp (vlax-get-or-create-object "Excel.Application"))
アプリケーションやその他VLAオブジェクトは、使用が終わったら開放(後述)が必要です。
プロパティの取得
Excel の VBA では、各オブジェクトにはオブジェクトモデルを使用してアクセスすると思います。
例えば、アクティブブックの1つ目のシートの「A1」セルの内容を取得する場合、下記のように記載すると思います。
varVal = ActiveWorkbook.Worksheets(1).Range("A1")
いきなり Range オブジェクトの指定までで終わっていますが、正しくは下記のように書きます
varVal = ActiveWorkbook.Worksheets(1).Range("A1").Value
.Value
を省略した場合、代入先の変数の型や Set
によるオブジェクト代入かどうかによって、Range オブジェクトを返すか、省略時のデフォルトである Value プロパティを返すかが決まります。
その時の書き方で塩梅良く対応してくれる、VBA の良い所でもあり、悪い所でもあります。
これを AutoLISP の VLA で記載したらどうなるか?
(setq
obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
obj_rng (vlax-get-property obj_sheet 'Range "A1") ;A1セルのRangeオブジェクトを取得
var_val (vlax-get-property obj_rng 'Value) ;A1セルの値を取得
);setq
(vlax-release-object obj_book) ;使わなくなった Book オブジェクトのの開放
(vlax-release-object obj_sheet) ;使わなくなった Sheet オブジェクトのの開放
(vlax-release-object obj_rng) ;使わなくなった Range オブジェクトのの開放
(vlax-variant-value var_val) ;値がバリアント型なので、変換する
大変長くなってしまいました。既にExcelアプリケーションオブジェクトは取得済みで、objapp
に格納されている前提です。
Excel の VBA では、アクティブワークブックを取得するには Set obj_Book = objapp.ActiveWorkbook
と記載すればよいですし
最初のワークシートを Set obj_sheet = objapp.ActiveWorkbook.Worksheets(1)
で取得できます。
この様に VBA では、オブジェクトモデルに従って、「.」で繋げて指定できますが、LISP(VLA)で同じ様に記載しようとすると (setq obj_sheet (vlax-get-property (vlax-get-property (vlax-get-property objapp 'ActiveWorkbook) 'Worksheets) 'Item 1))
となります。な、長い・・・
この様に
Exce の VBA で 変数 = オブジェクト.プロパティ(引数)
で取得するもの
↓
LISP(VLA)で (setq 変数 (vlax-get-property オブジェクト 'プロパティ 引数))
で取得できる
という事なのです。
Item について
先ほどの例では、最初のシートを選択する際、Excel VBA では Set obj_sheet = obj_book.Worksheets(1)
の様に記載しましたが、LISP(VLA) では (setq obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1))
の様に記載しました。
これは、VBA でも
Set obj_sheet = obj_book.Worksheets(1)
を正確に記載すると
Set obj_sheet = obj_book.Worksheets.Item(1)
となるためです。
Rangeオブジェクト に Value を記載しなくても Value が取得出来たりと、表記で色々省略できる VBA ですが、VLA から利用する際は省略できないものが多いので注意が必要です。
この辺の話は、後述の Cells でも絡みます。
バリアント型について
VBA には、「バリアント型」という何でも入れれる便利な(そしてトラブルにもなりがちな)データ型があります。
型を指定せずに宣言した変数は、バリアント型になります。
LISP(VLA) では、バリアント型の変数内に入っているデータを、LISP で扱えるデータ型にして取得する vlax-variant-value
という関数があります。
格納されている値のデータ型を取得する vlax-variant-type
という関数もあります。
先の例でも (setq var_val (vlax-get-property obj_rng 'Value))
で取得したセルの内容を、(vlax-variant-value var_val)
で型固定の値に変換しています。
LISP 自体が、変数に型を指定しないため、どうしてこのような構造なのかよく分かりませんが・・・
同様に、VBA の配列 ⇔ LISP の LIST を変換する関数もあります(ここでは触れません)。
Cells プロパティでセルの値を取得
今迄のプロパティ取得の方法から、行番号、列番号を指定して、Cells プロパティで値を取る手法も、想像がついたかと思います。
(setq
obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
obj_rng (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1) ;A1セルのRangeオブジェクトを取得
var_val (vlax-get-property obj_rng 'Value) ;A1セルの値を取得
);setq
(vlax-release-object obj_book) ;使わなくなった Book オブジェクトのの開放
(vlax-release-object obj_sheet) ;使わなくなった Sheet オブジェクトのの開放
(vlax-release-object obj_rng) ;使わなくなった Range オブジェクトのの開放
(vlax-variant-value var_val) ;値がバリアント型なので、変換する
実は、上記方法では、上手く取得できません!
(vlax-get-property obj_rng 'Value)
の箇所でエラーになります。
これは、(vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)
が返すのが、オブジェクトを値として持つバリアント型の戻り値だからです。
そのため、下記のように記載する必要があります。
(setq
obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
obj_rng (vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)) ;A1セルのRangeオブジェクトを取得
var_val (vlax-get-property obj_rng 'Value) ;A1セルの値を取得
);setq
(vlax-release-object obj_book) ;使わなくなった Book オブジェクトのの開放
(vlax-release-object obj_sheet) ;使わなくなった Sheet オブジェクトのの開放
(vlax-release-object obj_rng) ;使わなくなった Range オブジェクトのの開放
(vlax-variant-value var_val) ;値がバリアント型なので、変換する
取得した (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)
はセル範囲を示す Range オブジェクトを格納したバリアント型の戻り値です。
これを (vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1))
とする事で、Range オブジェクトを取得できます。
プロパティの値を指定(セルに値を書き込む)
プロパティに値を設定するには、VBAだと下記のように記載すると思います。
ActiveWorkbook.Worksheets(1).Cells(1, 1).Value = varVal
これを LISP(VLA) で実現するには、プロパティの値を取得するのに vlax-get-property
を使用したように、プロパティ値を設定するには vlax-put-property
を使用します。
実際に、下記の様に記載してみると・・・
(setq
obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
obj_rng (vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)) ;A1セルのRangeオブジェクトを取得
);setq
(vlax-put-property obj_rng 'Value (vlax-make-variant varVal))
;オブジェクト開放は省略
これでは動きません。
セルに値を書き込むには、何故か Value
プロパティは使用できず、Value2
を使用しなければなりません。
そのため、下記のように記載する必要があります。
(setq
obj_book (vlax-get-property objapp 'ActiveWorkbook) ;アクティブワークブックを取得
obj_sheet (vlax-get-property (vlax-get-property obj_book 'Worksheets) 'Item 1) ;最初のシートを取得
obj_rng (vlax-variant-value (vlax-get-property (vlax-get-property obj_sheet 'Cells) 'Item 1 1)) ;A1セルのRangeオブジェクトを取得
);setq
(vlax-put-property obj_rng 'Value2 (vlax-make-variant varVal))
;オブジェクト開放は省略
さて、ココには他にも新しく vlax-make-variant
が使われています。
(vlax-get-property obj_rng 'Value)
でセルの値を取得した際に、vlax-variant-value
を使って取得したバリアント型の値を LISP で扱える値に変換したように、セルに値を書き込む際には、その値をバリアント型にする必要があります。
その為に、(vlax-make-variant varVal)
でバリアント型の値に変換しています。
メソッドの実行
VBA に限らず、オブジェクトに対する操作として、プロパティの取得・設定のほかに、メソッドの実行があります。
obj_rng.ClearContents
上記の様な、obj_rng の Range 範囲をクリアするメソッドは、LISP(VLA) では下記の様になります。
(vlax-invoke-method obj_rng 'ClearContents)
VLA オブジェクトの開放
変数に格納するなどして得たVLAオブジェクトは、開放しないとメモリを占有し続けます。
例えば、ワークブックオブジェクトは (vlax-invoke-method obj_book 'Close :vlax-false)
で閉じる事が出来ますし、
例えば、アプリケーションオブジェクトは (vlax-invoke-method objapp 'Quit)
で終了する事が出来ます。
しかし、それだけでは obj_book 変数や objapp 変数が使用していたメモリは、解放されません。
これらを解放するためには
(vlax-release-object obj_book)
(vlax-release-object objapp)
等として、メモリを解放する必要があります。
ワークシートの最終行を得る (副題:プロパティか?メソッドか?)
obj_sheet 変数に格納されたワークシートの最終行を取得する場合、色々なやり方がありますが
lngEndRow = obj_sheet.Cells.SpecialCells(xlCellTypeLastCell).Row
私は上記の様に SpecialCells を使った方法を用います。
さて、これを LISP(VLA) で記載すると・・・
(setq
obj_cells (vlax-get-property obj_sheet 'Cells)
obj_scells (vlax-get-property obj_cells 'SpecialCells 11)
lngEndRow (vlax-get-property obj_scells 'Row)
);setq
;オブジェクト開放は省略
こうなると思ったんですよ。でも実際は動きません。
何故なら、SpecialCells
は、プロパティじゃなくてメソッドだから。
だから、下記のようにする必要があります。
(setq
obj_cells (vlax-get-property obj_sheet 'Cells)
obj_scells (vlax-invoke-method obj_cells 'SpecialCells 11)
lngEndRow (vlax-get-property obj_scells 'Row)
);setq
;オブジェクト開放は省略
これを見た時に、私の中で「プロパティとメソッドの区別」が良く分からなくなりました・・・