【テクニカル・上級編】【上級】AcadApplication.MenuBarにカスタムメニューを動的に追加し、VBAツールを標準機能化 – AutoCAD VBA解析バイブル

スポンサーリンク

AutoCAD VBAを掌握する極限の知見:MenuBar動的拡張によるUIブートストラップの極意

AutoCAD VBAの領域において、多くの開発者は「マクロの実行(`VBAIDE`からの手動実行)」や「ボタンへの簡易的なマクロ割り当て」という初歩的な段階で足踏みをする。しかし、数百人規模のユーザーに自作の自動化ソリューションを配布するシニアエンジニアにとって、CUI(Custom User Interface)ファイルの煩雑な手動書き換えや、ワークスペースごとのデプロイ地獄は悪夢でしかない。

真に洗練されたエンタープライズ・オートメーションとは、AutoCADの起動プロセス、あるいはアドインの初期化フックと同時に、プログラム自身がUIを動的に構築・修復するシステムでなければならない。

今回は、`AcadApplication.MenuBar`を直接叩き、CUIファイルを一切汚染せずにカスタムメニューをプログラムからねじ込む、極限の実装テクニックを公開する。

1. AutoCADオブジェクトモデルの暗部:`MenuBar`と`PopupMenu`のライフサイクル

AutoCADのUI構造をコードから制御する場合、COMインターフェースの階層構造を正確に理解する必要がある。

  • `AcadApplication.MenuBar`:現在表示されているメニューバー(最上位のコレクション)
  • `AcadPopupMenus`:メニューバーに格納されているポップアップメニューのコレクション
  • `AcadPopupMenu`:個々のドロップダウンメニュー(「ファイル」「編集」など)
  • `AcadPopupMenuItem`:メニュー内の個別項目(クリック時にマクロを叩くトリガー)

ここで重要なのは、`MenuBar`に追加されたメニューは、AutoCADのセッション間で永続化されないという仕様だ。CUIXファイルに書き込まれない限り、AutoCADを再起動すれば消滅する。
一見するとデメリットに思えるこの仕様こそが、配布ツールにおいては「最強の自己修復・クリーンアンインストール性」をもたらす。インストーラーやアドインのロード時にこの動的生成ルーチンを走らせるだけで、常に最新かつ環境依存のないメニューバーを保証できるのだ。

2. 実装コード:動的メニュー構築モジュール

以下のコードは、既存の同名メニューの重複生成を防ぐ「イデポテント(べき等性)」を担保しつつ、安全にカスタムメニューを挿入するプロダクションクオリティのVBAコードである。

Option Explicit

‘ メニュー項目のインデックス保持用
Private Const MENU_CAPTION As String = “業務自動化(&A)”
Private Const MACRO_PREFIX As String = “-VBARUN ”

‘ ==============================================================================
‘ @100万行の現場を生き抜いたチーフアーキテクトによるセーフティ実装
‘ ==============================================================================
Public Sub InitializeCustomMenu()
On Error GoTo ErrorHandler

Dim currApp As AcadApplication
Dim menuBarColl As AcadMenuBar
Dim targetMenu As AcadPopupMenu
Dim menuItem As AcadPopupMenuItem
Dim i As Long
Dim menuExists As Boolean

Set currApp = ThisDrawing.Application
Set menuBarColl = currApp.MenuBar

‘ 1. 既存の同名メニューが存在するか走査(二重登録の防止)
menuExists = False
For i = 0 To menuBarColl.Count – 1
If menuBarColl.Item(i).Caption = MENU_CAPTION Then
Set targetMenu = menuBarColl.Item(i)
menuExists = True
Exit For
End If
Next i

‘ 2. 存在しない場合は新規作成し、MenuBarの末尾(ヘルプの直前など)に挿入
If Not menuExists Then
‘ AcadApplication.MenuGroupsからアクティブなグループを取得してPopupMenuを作成
Dim groupName As String
groupName = currApp.MenuGroups.Item(0).Name

Set targetMenu = currApp.MenuGroups.Item(groupName).Menus.Add(MENU_CAPTION)

‘ MenuBarコレクションにアタッチ(インデックスを指定して挿入)
‘ ※MenuBar.Addは存在しないため、MenuGroups経由で作成した後にMenuBarへインデックス指定でドッキングするアプローチをとる
‘ (注:AutoCADのバージョンによりMenuGroupsの構造が異なるため、安全なInsert手法を用いる)
Dim insertIndex As Long
insertIndex = menuBarColl.Count ‘ デフォルトは末尾
targetMenu.InsertInMenuBar insertIndex
End If

‘ 3. メニュー項目のクリアと再構築(常に最新のマクロパスを維持するため一度全削除を推奨)
‘ ※本番環境ではバージョン管理に合わせてスマートに差分更新することを推奨
For i = targetMenu.Count To 1 Step -1
targetMenu.Item(i – 1).Delete
Next i

‘ 4. コマンド項目の動的追加
‘ 引数: 挿入位置(1始まり), 表示名, 実行するマクロ文字列(VBAランタイムコマンド)

‘ 項目1: 図面一括クリーンアップ
Dim macroStr1 As String
macroStr1 = CStr(Chr(3) & Chr(3) & “_-VBARUN BatchCleanDrawing” & vbCr)
targetMenu.AddMenuItem targetMenu.Count + 1, “図面一括クリーンアップ…”, macroStr1

‘ セパレーター(区切り線)の挿入
targetMenu.AddSeparator targetMenu.Count + 1

‘ 項目2: 属性データ抽出ツール
Dim macroStr2 As String
macroStr2 = CStr(Chr(3) & Chr(3) & “_-VBARUN ExportAttributes” & vbCr)
targetMenu.AddMenuItem targetMenu.Count + 1, “属性データ抽出”, macroStr2

MsgBox “カスタムメニュー [” & MENU_CAPTION & “] の初期化が正常に完了しました。”, vbInformation, “自動化システム”

‘ オブジェクト参照の明示的解放(メモリリーク防止の鉄則)
GoTo CleanUp

ErrorHandler:
MsgBox “メニュー初期化中に致命的なエラーが発生しました: ” & Err.Description, vbCritical, “エラー”

CleanUp:
Set menuItem = Nothing
Set targetMenu = Nothing
Set menuBarColl = Nothing
Set currApp = Nothing
End Sub

‘ ==============================================================================
‘ アンインストール(メニューのクリーンアップ)
‘ ==============================================================================
Public Sub RemoveCustomMenu()
On Error GoTo ErrorHandler

Dim currApp As AcadApplication
Dim menuBarColl As AcadMenuBar
Dim targetMenu As AcadPopupMenu
Dim i As Long

Set currApp = ThisDrawing.Application
Set menuBarColl = currApp.MenuBar

For i = menuBarColl.Count – 1 To 0 Step -1
If menuBarColl.Item(i).Caption = MENU_CAPTION Then
Set targetMenu = menuBarColl.Item(i)
‘ MenuBarから外す(メニュー自体を消去するのではなくバーから外す、あるいはRemoveメソッド)
targetMenu.RemoveFromMenuBar
Exit For
End If
Next i

MsgBox “カスタムメニューを削除しました。”, vbInformation

ErrorHandler:
Set targetMenu = Nothing
Set menuBarColl = Nothing
Set currApp = Nothing
End Sub

3. チーフアーキテクトが解説する実装の急所

上記のコードには、レガシーかつシビアなAutoCADのCOMラッパー特有の「罠」を回避するための知見が凝縮されている。

① マクロ文字列の先頭にある `Chr(3) & Chr(3)` の正体

コード内で `Chr(3) & Chr(3)` をマクロの先頭に付与している。

  • `Chr(3)` はアスキーコードの `ETX`(Ctrl+Cに相当)である。
  • AutoCADのコマンドラインに現在何らかのコマンドがアクティブに巣食っている場合、そのままVBAマクロを叩くと「コマンド実行中です」というエラーで弾かれる。
  • 強制的にキャンセル(Ctrl+C×2回)を送ることで、どんな非同期状態からでも確実にコマンドラインをニュートラルに戻し、マクロの暴走を防ぐという、過酷な現場で鍛え上げられた鉄則である。

② COMオブジェクトの明示的解放とガベージコレクション

VBAは自動メモリ管理を行う言語だが、AutoCADのCOMラッパー(`AcadApplication` や `AcadDocument`)は内部でC++のCOMオブジェクトを強烈に掴んでいる。
これをローカル変数に入れっぱなしにしてプロシージャを抜けると、AutoCADの終了時にメモリリークを引き起こしたり、最悪の場合ドキュメントクローズ時のクラッシュ(致命的な例外)の温床となる。
プロシージャの最後で必ず `Set xxx = Nothing` を逆順で実行し、COM参照カウンタを確実にデクリメントさせることが、シニアエンジニアとしての最低限の責務である。

③ べき等性(Idempotency)の担保

AutoCADのセッションが複数開かれたり、アドインが二重にロードされたりした際、同じメニューが何個もメニューバーに並ぶ情けない挙動を見かけたことはないか?
上記コードでは、走査ループによって「既に同名のメニューが存在するか」を厳密にチェックし、存在する場合は新規作成をスキップして項目だけをリフレッシュする設計にしている。これにより、何回スクリプトが走ってもUIが美しく保たれる。

4. エンタープライズ展開への布石:自動ブートストラップ化

このVBAモジュールを単体で手動実行させるのではなく、AutoCAD起動時に自動ロードされる `acad.lsp` や `acaddoc.lsp`、あるいは外部の.NET Loader(Plugin)から叩くことで、完全無人型のUIデプロイメントが完成する。

例えば、`acad.lsp` に以下の一文を仕込んでおく。

(defun S::STARTUP ()
(command “_.VBALOAD” “C:/EnterpriseTools/AutoCAD_Master.dvb”)
(command “_-VBARUN” “InitializeCustomMenu”)
(princ “\n[Enterprise AutoLoader] Custom Menu Initialized.”)
(princ)
)

このアーキテクチャを採用すれば、開発者が新しいVBAツールや機能を追加・修正した際、サーバー上の `.dvb` ファイルを差し替えるだけで、全社クライアントのAutoCADメニューが次回起動時に自動的にアップデートされる。CUIXファイルを個別端末に配布・上書きする苦行から、あなたを完全に解放する。

総括

AutoCAD VBAはレガシーな技術として語られがちだが、そのCOMインターフェースの奥底を正しく理解し、OSやアプリケーションのライフサイクルに調停させるコードを書くならば、現行のどのモダンなアドイン開発手法をも凌駕する「圧倒的な機動力」を発揮する。

UIの構築すらもコードの支配下に置き、環境に依存しない堅牢な自動化エコシステムを構築してほしい。

タイトルとURLをコピーしました