Visio VBAを掌握する極限の知見:`Application.IsUndoingOrRedoing` による無限ループの完全鎮圧
Visioのイベント駆動型アーキテクチャは、巨大な図面自動化システムや動的インフラストラクチャ管理ツールを構築する上で強力な武器となる。しかし、`DocumentEvent`や`Visio.EOT_ShapeAdded`、`Visio.EOT_ShapeDeleted`などのイベントハンドラを実装した瞬間、開発者はVisioの内部トランザクション構造という名の深淵に足を踏み入れることになる。
特に、ユーザーが「元に戻す(Undo)」あるいは「やり直し(Redo)」を実行した瞬間、イベントの連鎖は暴走を始め、最悪の場合はスタックオーバーフローによる強制終了、あるいはドキュメントの致命的な破損を引き起こす。
本稿では、レガシーなVBA環境であっても、この非同期トランザクションの奔流を完全に制御下におくための極限の知見を授ける。
—
1. 悪夢のメカニズム:なぜUndo/Redoでイベントが暴走するのか
VisioのUndo/Redoスタックは、単なる「操作の巻き戻し」ではない。それは「オブジェクトの状態変化の逆再生」である。
VBA側で `Shape` のプロパティを変更したり、図形を生成・削除したりする際、これらはすべてVisioのUndoトランザクションに記録される。しかし、ユーザーがUI上で「Ctrl + Z(Undo)」を押したとき、何が起きるか?
1. ユーザーがUndoを実行する。
2. Visioのエンジンが過去の状態へシェイプを巻き戻す。
3. この「巻き戻しによるプロパティ変更」が、新たなシェイプ変更イベント(`ShapeChanged`など)としてVBAに発火する。
4. イベントハンドラ側がこれを「ユーザーの新規操作」と誤認し、追加の図形操作や外部APIへのリクエストを発行する。
5. その操作自体が新たなUndoキューに積まれ、イベントが無限に再帰呼び出しされる(あるいはデッドロックに陥る)。
この構造的欠陥を放置したシステムは、実運用において必ず破綻する。ここで必要となるのが、Visioアプリケーションの現在のトランザクション状態を正確に検知し、判定する防壁である。
—
2. 救世主:`Application.IsUndoingOrRedoing` の真価
Visio 2010以降(およびそれ以降のモダンなバージョン)、アプリケーションオブジェクトには、この暴走を防ぐための隠し砦とも言うべきプロパティが備わっている。それが `Application.IsUndoingOrRedoing` だ。
このプロパティは、現在VisioがUndoまたはRedoのトランザクションを処理中である場合に `True` を返す。イベントハンドラの最上流でこのフラグを監視し、該当する場合は即座に処理をスキップ(Guard Clause)させること。これが、プロフェッショナルなVisioアーキテクトの絶対的な鉄則である。
実装パターン:堅牢なイベントハンドラのアーキテクチャ
以下に、メモリリークの危険性を排除し、かつUndo/Redo時の暴走を完全に遮断するイベントシンクの実装コードを示す。
‘ ==============================================================================
‘ クラスモジュール名: clsVisioEventListener
‘ 用途: Undo/Redoの暴走を完全に防ぐ、安全なイベント監視クラス
‘ ==============================================================================
Option Explicit
Private WithEvents vsoApp As Visio.Application
Private m_IsInitialized As Boolean
‘ イベント監視の初期化
Public Sub Initialize(ByVal targetApp As Visio.Application)
Set vsoApp = targetApp
m_IsInitialized = True
End Sub
‘ 終了時のクリーンアップ(COMイベントの循環参照を防ぐために必須)
Public Sub Terminate()
Set vsoApp = Nothing
m_IsInitialized = False
End Sub
‘ Shape変更イベントのハンドラ
Private Sub vsoApp_ShapeChanged(ByVal Shape As IVShape)
On Error GoTo ErrorHandler
‘ 1. アプリケーションの生存確認と基本ガード
If Not m_IsInitialized Then Exit Sub
‘ 2. 【極限重要】Undo / Redo 実行中であれば、一切の処理を行わず即座に抜ける
If vsoApp.IsUndoingOrRedoing Then
Debug.Print “[Guard] Undo/Redo検知: 処理をスキップしました (Shape ID: ” & Shape.ID & “)”
Exit Sub
End Sub
‘ 3. ユーザーの通常操作による変更に対するビジネスロジックの実行
‘ (例: データベースとの同期、カスタムプロパティの自動更新など)
Call ExecuteBusinessLogic(Shape)
Exit Sub
ErrorHandler:
‘ ログ出力またはエラーハンドリング基盤への転送
Debug.Print “Error in vsoApp_ShapeChanged: ” & Err.Description
End Sub
Private Sub ExecuteBusinessLogic(ByVal shp As Visio.Shape)
‘ ※ここに実際の重い処理やAPI連携を記述
‘ 例: shp.Cells(“Prop.LastModified”).FormulaU = “=””” & Now & “”””
‘ 注意: ここで行った変更もVisioのUndoスタックに積まれるため、
‘ 必要に応じて UndoScope を明示的に制御すること。
End Sub
—
3. チーフアーキテクトが教える:さらなる極限の最適化と罠
上記のガード句を入れるだけで大半の暴走は防げるが、エンタープライズレベルの安定性を求めるならば、以下の「現場の知見」を理解しておかなければならない。
A. COMイベントの循環参照とメモリリークの根絶
`WithEvents` を用いたイベントハンドリングにおいて最も頻発するバグは、「イミディエイトウィンドウへの出力や変数の保持によるオブジェクトの解放漏れ」である。
`Visio.Application` を保持するクラスモジュールは、明示的に `Terminate` メソッド等を呼び出して `Set vsoApp = Nothing` を実行しない限り、ドキュメントを閉じた後もメモリ上に残存し続ける(ゾンビオブジェクト化)。
マルチドキュメント環境やアドイン開発においては、これが原因でメモリリークを引き起こし、最終的にVisioプロセス全体がクラッシュする。
B. 独自のアンドゥスコープ(`BeginUndoScope`)との組み合わせ
もしVBA側からプログラムで複数の図形を一括操作し、それを1つの「元に戻す(Undo)」単位としてまとめたい場合は、`Application.BeginUndoScope` を使用する。
Dim lScopeID As Long
lScopeID = Visio.Application.BeginUndoScope(“カスタム図形一括更新”)
On Error GoTo RollbackLabel
‘ — ここで複数の図形操作を行う —
‘ (この操作によるShapeChangedイベントが発生するが、
‘ プログラムからの操作かユーザーのUndoかを見極める必要がある場合がある)
Visio.Application.EndUndoScope lScopeID, True
Exit Sub
RollbackLabel:
Visio.Application.EndUndoScope lScopeID, False
プログラム自身が書き込みを行っている最中であることを示す独自フラグ(プライベートな Boolean 変数 `m_IsProcessingByVBA` など)を併用し、「ユーザーの直接操作」「プログラムによる自動処理」「Undo/Redoによる巻き戻し」の3つを厳密に分離設計すること。これが大規模Visioシステムの破綻を防ぐ唯一の道である。
—
4. 結言
Visio VBAはレガシーな技術と見なされがちだが、その背後にあるCOMアーキテクチャとイベントモデルは非常に奥深い。
`Application.IsUndoingOrRedoing` という小さなプロパティの存在意義を深く理解し、イベントのライフサイクルを完全に掌握すること。それこそが、アマチュアの「動くだけのマクロ」と、プロフェッショナルが構築する「堅牢なミッションクリティカル・アドイン」を分かつ境界線である。
妥協のないコード設計で、あなたのVisioソリューションを極限の安定性へと導いてほしい。
