Visio VBAを掌握する極限の知見:`IsUndoingOrRedoing`で制するイベント駆動開発の要塞
こんにちは。業務自動化エンジニアとして数々の巨大なVisioソリューションを構築してきた私から、現場のエンジニアへ警鐘を鳴らしたい。
「Visioの図形変更イベントをフックして、DBや外部ファイルと自動同期するツールを作ったら、なぜかVisioがフリーズした、あるいは無限ループでデータが破壊された」
……この悪夢のような現象に直面したことはないだろうか?
素人が書いたコードは、ユーザーが「Ctrl + Z(元に戻す)」や「Ctrl + Y(やり直し)」を押した瞬間、いとも簡単に暴走する。なぜなら、VBAのイベントハンドラは「ユーザーが操作したのか」「VisioがUndo/Redoのためにプログラム上で図形を復元しているのか」をデフォルトでは区別してくれないからだ。
今回は、このVisio開発における最大の罠を完全無力化し、プロダクション環境に耐えうる堅牢なイベント処理システムを構築するための極限の知見を授けよう。
—
1. なぜイベント処理は暴走するのか?(非効率な設計の正体)
初心者がやりがちな設計はこうだ。
`ShapeAdded` や `ShapeChanged` イベントを拾い、その中で図形のテキストやプロパティを書き換える、あるいは外部データベースを更新する。
ここでユーザーが「元に戻す」を実行したとする。
1. ユーザーが `Ctrl + Z` を押す。
2. Visioが内部的に図形を前の状態に戻す。
3. 図形が変更されたため、`ShapeChanged` イベントが強制発火する。
4. イベントハンドラは「図形が変わった!」と勘違いし、外部DBへ誤ったデータを送信したり、さらに図形を書き換えるコードを実行する。
5. その書き換え自体が新たなイベントを生み、無限ループ・メモリリーク・強制終了へ突入する。
この地獄を防ぐために、フラグ変数をグローバルに持たせて制御しようとする者もいるが、マルチドキュメント環境や予期せぬエラー発生時のフラグ戻し忘れなどにより、コードベースを汚染する悪手でしかない。
ここで登場するのが、Visioが隠し持つ神プロパティ `Application.IsUndoingOrRedoing` である。
—
2. 解決の鍵:`Application.IsUndoingOrRedoing` とは何か
`Application.IsUndoingOrRedoing` は、現在Visioのエンジンが「Undo(元に戻す)」または「Redo(やり直し)」のトランザクションを実行中であるかどうかを、ブール値(`True` / `False`)で返す。
これさえ知っていれば、イベントハンドラの最上流でこう宣言するだけでいい。
If Visio.Application.IsUndoingOrRedoing Then Exit Sub
たったこれだけだ。しかし、この1行を書き忘れたプロトタイプは、現場に投入した瞬間に爆発する「不発弾」に等しい。プロとアマを分ける境界線は、このライフサイクル状態を正確にハンドリングできているかどうかに他ならない。
—
3. 【プロダクションコード】安全なイベントハンドリングの実装
それでは、実務でそのまま使える堅牢なクラスモジュールの設計コードを公開しよう。
ここでは、図形の変更を検知して外部ログ(またはDB連携を想定したイミディエイト窓口)へ安全に同期する仕組みを構築する。
① クラスモジュール:`clsEventSink`
(※イベントをフックするため、クラスモジュールとして作成してください)
Option Explicit
‘ 監視対象のVisio Applicationオブジェクト
Public WithEvents VisApp As Visio.Application
Private Sub Class_Initialize()
‘ 初期化時の処理
End Sub
Private Sub Class_Terminate()
Set VisApp = Nothing
End Sub
‘====================================================================
‘ Shape変更イベントのハンドラ
‘====================================================================
Private Sub VisApp_ShapeChanged(ByVal Shape As Visio.IVShape)
On Error GoTo ErrorHandler
‘ 【極限の知見】Undo/Redo実行中であれば、即座に処理をスルーする
If VisApp.IsUndoingOrRedoing Then
Exit Sub
End If
‘ 【重要】プログラム自身による図形変更の場合も暴走を防ぐためのガード
‘ (必要に応じて独自のセマフォフラグをここに挟む)
‘ — 実業務ロジックの開始 —
Debug.Print “[Sync] 図形が変更されました: ID = ” & Shape.ID & “, Text = ” & Left(Shape.Text, 20)
‘ 例:外部データベースやJSONファイルへの非同期・同期書き込み処理をここに記述
‘ Call SyncToDatabase(Shape)
Exit Sub
ErrorHandler:
MsgBox “ShapeChanged イベント内でエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
End Sub
② 標準モジュール:イベントの有効化・無効化制御
Option Explicit
‘ イベントカプセル化のためのグローバルインスタンス
Public EventSink As clsEventSink
Public Sub StartVisioEventListener()
‘ 多重起動を防ぐ
If Not EventSink Is Nothing Then
MsgBox “イベント監視は既に稼働しています。”, vbInformation
Exit Sub
End If
Set EventSink = New clsEventSink
Set EventSink.VisApp = Visio.Application
MsgBox “Visio イベント監視システムを起動しました。”, vbInformation
End Sub
Public Sub StopVisioEventListener()
Set EventSink = Nothing
MsgBox “Visio イベント監視システムを停止しました。”, vbInformation
End Sub
—
4. ファイル・データベース連携における実務上の注意点
この設計を用いて外部リソース(SQL Server、SQLite、JSON/Excelファイル等)と連携する際、以下のアーキテクチャ上の鉄則を守ってほしい。
1. トランザクションの分離
VisioのUndoツリーと外部DBのトランザクションは別物である。ユーザーがVisioで「元に戻す」を行った場合、Visio上の図形は戻るが、外部DB側のデータは自動的には巻き戻らない。
もし厳密な整合性を求めるなら、`IsUndoingOrRedoing = True` を検知したタイミングで、外部DB側に対しても「直前の変更をロールバック(または逆更新)する処理」を走らせる設計が必要になる。
2. パフォーマンスの最適化(重い処理の回避)
`ShapeChanged` は、ユーザーが図形をドラッグして移動させている最中にも細かく発火する。この中で重いDB通信を行うとVisioがカクつく。実務では、変更検知から実際の同期処理まで数ミリ秒のディレイ(TimerやApplication.QueueCommand等)を挟む、あるいはバッチ処理化するキューイング構造を検討せよ。
—
5. チーフアーキテクトからのメッセージ
VBAはレガシーな言語だと言われることがある。だが、それは使い手の技量が低い言い訳に過ぎない。Visioのオブジェクトモデルのライフサイクルを深く理解し、今回紹介した `IsUndoingOrRedoing` のような内部ステータスを完璧にコントロールできれば、VBAであっても鉄壁のエンタープライズ向け自動化ソリューションを構築できる。
コピペで動かして終わりにするのではなく、「なぜこの1行が必要なのか」の本質をコードから読み解き、あなたの現場の業務効率化ツールをワンランク上の品質へと引き上げてほしい健闘を祈る。
