Microsoft Excelとの接続

セル

Excel のワークシートのセルは、セルを範囲で管理する Renge オブジェクトを利用してアクセスします。

Excel ユーザーの現在の選択範囲を知るには、Excel アプリケーションオブジェクトの Selection プロパティを利用します。次はユーザーが画面上で選択されているセル範囲を表す Renge オブジェクト取得します。

(setq cells (vlax-get-property excelApplication 'Selection))

複数のセルの範囲を選択状態にしていると判りやすいですが、選択範囲で一か所、通常は左上がアクティブなセルとして表示されます。次はアクティブなセルを表す Renge オブジェクトを得ます。

(setq cells (vlax-get-property excelApplication 'ActiveCell))

プログラムの側から範囲を指定してワークシートから Renge オブジェクトを得る場合は次のようにします。これは、画面上での選択されているセルとは無関係です。

(setq cells (vlax-get-property worksheet 'Range "A1:D3"))

次は Renge オブジェクトがどの範囲を指しているか文字列で得ます。例えば、"A1:D3"などです。

(vlax-get-property cells 'Address :vlax-True :vlax-True 1 ;| A1スタイル |;)

セル範囲を選択状態とするためには、Select メソッドを使用します。

(vlax-invoke-method cells 'Select)

アクティブなセルを変更するには一つのセルを表す Renge オブジェクトを作成して、Activate メソッドを呼びます。次は“B2”をアクティブセルにします。

(setq activeCell (vlax-get-property worksheet 'Range "B2"))
(vlax-invoke-method activeCell 'Activate)

アクティブセルが画面上の選択範囲から外れていた場合はセルのアクティベートが優先され、既存の選択は解除されます。

Renge オブジェクトが複数ではなく一つのセルを表している場合、セルの値を読み書きするには Value2 プロパティを使用します。対象セルが通貨や日付形式の場合でフォーマットされて表示されているテキストをそのまま値として得る場合は Text プロパティが利用できます。いずれの場合も、バリアント型の値をやり取りします。

一つのセルではなくて、セル範囲の値を読み書きする場合にも Value2 プロパティを使用できます。複数のセルの場合はセーフ配列のバリアント型でやり取りしますが、セーフ配列にセットする値もバリアント型です。セーフ配列でなかった場合は、すべてのセルに同じ値がセットされます。

次は、セル範囲を表す Renge オブジェクトから値を得て、LISP のリストや他のデータタイプに合うように変換する関数です。

(defun Excel:readValue:sub (item /)
  (cond ((listp item) (mapcar 'Excel:readValue:sub item))
        (T (vlax-variant-value item))
  )
)

(defun Excel:readValue (rangeObj / vtype value)
  (setq value (vlax-get-property rangeObj 'Value2))
  (if (<= vlax-vbArray (vlax-variant-type value))
    (setq value (vlax-safearray->list (vlax-variant-value value)))
  )
  (Excel:readValue:sub value)
)

次は、LISP のデータを与えると、Renge オブジェクトの範囲に値をセットする関数です。複数のセルが対象で、アトムのデータを渡すとすべてのセルにその値がセットされ、リストのデータを渡した場合は対応するリストの値をセットし、データが多い場合は無視され、足りない場合は「N/A」となります。

(defun Excel:writeValue:sub (item /)
  (cond ((listp item) (mapcar 'Excel:writeValue:sub item))
        (T (vlax-make-variant item))
  )
)

(defun Excel:writeValue (rangeObj value /)
  (if (listp value)
    (setq value (vlax-safearray-fill
                  (if (listp (car value))
                    (vlax-make-safearray
                      vlax-vbVariant
                      (cons 1 (length value))
                      (cons 1 (length (car value)))
                    )
                    (vlax-make-safearray vlax-vbVariant (cons 0 (length value)))
                  )
                  (mapcar 'Excel:writeValue:sub value)
                )
    )
  )
  (vlax-put-property rangeObj 'Value2 (vlax-make-variant value))
)

Renge オブジェクトの Item プロパティ

Renge オブジェクトは複数のセルに対応できましたが、そのままでは文字列による行・列のインデックスでプログラムからは扱いにくい面があります。しかし Renge オブジェクトの Item プロパティを利用すると整数のインデックスでセルにアクセスできます。Item プロパティのインデックスは行・列とも 1 から始まります。ワークシートの絶対位置からセルにアクセスしたいときは、WorkSheet オブジェクトの Cells プロパティからワークシートの全てのセルを表す Renge オブジェクトが得られます。

次は、ワークシートの「A2」の値を得ます。

(vlax-get-property (vlax-get-property worksheet 'Cells) 'Item 2 1)

一般的なセル範囲を表す Renge オブジェクトでも Item プロパティは同様に扱えます。次は、「B3:E5」の範囲から、範囲の左上を 1,1 として、相対的な行 3 列 2 のアドレス、すなわち「C5」の値を得ます。インデックスは、Renge のセル範囲を超えた値でも有効です。

(setq cells (vlax-get-property worksheet 'Range "B3:E5"))
(vlax-get-property cells 'Item 3 2)

プログラム例

Excel との簡単な連携プログラムを作成してみます。ユーザーの指示に従って、AutoCAD のテキストの内容をExcel のセルにセットしたり、Excel のセルの内容を AutoCAD の既存のテキストにセットしたりします。なお、プログラムを短くするためにエラー処理は最低限とします。

Excel アプリケーションの起動と終了、ブックの保存はユーザーに任せることとし、AutoLISP プログラムは既に開かれているブックの、アクティブなシート、アクティブなセルにアクセスします。

Excel に接続する部分を担う excel:communicate 関数は次の通りです。接続した後の具体的な処理は、apply 関数と同じ要領で指示することとします。処理が済んだら、Excel のオブジェクトを指す VLA オブジェクトは直ちに解放します。

この関数に渡す具体的な内容を表す関数内では次のシンボルが既に使えるようにしています。

シンボル 説明
$excelApplication Excel アプリケーションオブジェクト
$workbook アクティブな Workbook オブジェクト
$worksheet アクティブな Worksheet オブジェクト
$selection アプリケーションの現在の選択、多くの場合でセルの選択範囲
$activecell アクティブセルを表す Renge オブジェクト
(defun excel:communicate
       ($_func $_args / $_result $excelApplication $workbook $worksheet $selection $activecell)
  (if (and (setq $excelApplication (vlax-get-object "Excel.Application"))
           (setq $workbook (vlax-get-property $excelApplication 'ActiveWorkbook))
      )
    (progn (setq $worksheet  (vlax-get-property $excelApplication 'ActiveSheet)
                 $selection  (vlax-get-property $excelApplication 'Selection)
                 $activecell (vlax-get-property $excelApplication 'ActiveCell)
                 $_result      (apply $_func $_args)
           )
           (vlax-release-object $activecell)
           (vlax-release-object $selection)
           (vlax-release-object $worksheet)
           (vlax-release-object $workbook)
           (vlax-release-object $excelApplication)
    )
    (progn (if (and $excelApplication (null (vlax-object-released-p $excelApplication)))
             (vlax-release-object $excelApplication)
           )
           (prompt "\nPlease start Excel application and open a book!")
    )
  )
  $_result
)

上の関数を使って、Excel からアクティブセルの内容を受け取る関数と、アクティブセルにテキストを送る関数は次のように作成できます。シンボル $activecell には、excel:communicate によってアクティブセルの Renge オブジェクトが代入されていますので、具体的な処理を行う関数のバリエーションが増えても、同じように Application オブジェクトを取得して、それからアクティブな〜という手順は省略することができます。

(defun excel:receiveText ()
  (vlax-variant-value
    (excel:communicate (function (lambda () (vlax-get-property $activecell 'Text))) nil)
  )
)

(defun excel:sendText (string)
  (excel:communicate
    (function
      (lambda (value) (vlax-put-property $activecell 'Value2 (vlax-make-variant value)))
    )
    (list string)
  )
)

AutoCAD のテキストのみを選択可能な専用の関数を用意します。

(defun selectTextEntity (string / ename flag)
  (while (null flag)
    (setq ename (car (entsel string)))
    (if (and (= (type ename) 'ENAME)
             (= (vla-get-ObjectName (vlax-ename->VLA-object ename)) "AcDbText")
        )
      (setq flag T)
      (setq ename nil)
    )
  )
  ename
)

次は、ユーザーからの一回分の指示を処理する関数です。

(defun comExcel:commandLoop (/ com ename text)
  (initget 0 "Receive Send eXit")
  (setq com (getkword "\nコマンドを選択 [Receive/Send/eXit] : "))
  (cond
    ((= com "Receive")
     (if (and (setq text (excel:receiveText)) ; Excel -> AutoCAD
              (setq
                ename (selectTextEntity (strcat "\n" text " に置き換えるテキストを選択 : "))
              )
         )
       (vla-put-TextString (vlax-ename->VLA-object ename) text)
     )
    )
    ((= com "Send")                     ; AutoCAD -> Excel
     (if (setq ename (selectTextEntity "\nExcel に送るテキストを選択 : "))
       (excel:sendText (vla-get-TextString (vlax-ename->VLA-object ename)))
     )
    )
  )
  com
)

次が、コマンドのメイン関数で、"eXit"が選ばれるまで処理を繰り返します。

(defun c:comExcel ()
  (prompt "\nExcel と通信します -  Excel のブックを開いてください")
  (while (/= (comExcel:commandLoop) "eXit"))
  (princ)
)

これら一連の関数をロードすることによって AutoCAD ユーザーは「COMEXCEL」とコマンドをタイプすることによって、プログラムを実行できるようになります。