【実務・中級編】Application.OnComponentEnterStateの検知によるモーダルダイアログ表示中のVBA誤作動・フリーズ防止策 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:モーダルダイアログの罠と `OnComponentEnterState` による鉄壁のフリーズ防衛術

開発プロジェクトの現場で、こんな怪奇現象に直面したことはないだろうか。
「ユーザーがVisioの印刷ダイアログやファイル設定画面を開いている最中に、タイマーや外部イベントトリガーでVBAが発火し、Visioが完全に沈黙(フリーズ)した」

――結論から言おう。これはVisioの仕様の限界を無視した設計ミスであり、防ぐべき人災だ。

Visioは、ユーザーがモーダルダイアログ(プロパティウィンドウ、印刷設定、ページ設定など)を開いている間、内部のUIスレッドがダイアログ側のメッセージループに制御を奪われる。この状態で背後からVBAがShapeの操作やドキュメントの保存を行おうとすると、VisioのCOMサーバーはリクエストを処理できず、デッドロックを引き起こすか、最悪の場合はアプリケーションごと強制終了する。

今回は、このプログラマを地獄に突き落とすVisioの「モーダル地獄」に対し、`Application` オブジェクトのイベントと状態監視を駆使して1ミリの隙も作らない堅牢な防衛設計を授けよう。

1. なぜ「普通のVBAコード」では防げないのか?

多くの初学者は、イベントプロシージャ内でエラーハンドリング(`On Error Resume Next`など)を仕掛ければ回避できると錯覚している。しかし、モーダルダイアログ表示中のフリーズは「エラー」として発生しない。Visio自体が応答不能な「ビジー状態」に陥るため、VBA側で例外をキャッチすることすらできずにプロセスが固まるのだ。

ここで必要になるのが、「Visioが今、モーダル状態に入った瞬間を検知し、自律的にVBAの実行をスキップまたは安全に待機させる仕組み」である。

2. 解決の鍵:`Application` オブジェクトの監視と状態フラグ

Visio VBAにおいて、アプリケーション全体の挙動をフックするには `Visio.Application` のイベントを `WithEvents` で捉える必要がある。

ここで登場するのが、今回の中核テーマである `OnComponentEnterState` および関連する状態遷移イベントだ。Visioが何らかのコンポーネント(ダイアログや編集モード)に入ったとき、このイベントが発火する。

これを利用し、「現在ユーザーがモーダル操作中である」というグローバルな状態フラグ(Semaphore)を常にマシーングリーンに保つ設計構築を行う。

3. 【プロダクションコード】コピペで実装する鉄壁のセーフティマネージャー

以下のコードは、実務の業務効率化アドインやマクロ集にそのまま組み込める、プロダクションクオリティのクラスモジュールおよび標準モジュールだ。

クラスモジュール: `clsVisioSentinel`

(※プロジェクトに `clsVisioSentinel` という名前で追加してください)

VERSION 1.0 CLASS
BEGIN
MultiUse = -1 ‘True
END
Attribute VB_Name = “clsVisioSentinel”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
‘ ==============================================================================
‘ 領域・カテゴリ: Visio VBA / アプリケーション監視・フリーズ防止設計
‘ クラス名: clsVisioSentinel
‘ 概要: Visioのモーダルダイアログ表示やビジー状態を検知し、VBAの暴走を完全に阻止する番人
‘ ==============================================================================

Option Explicit

‘ WithEventsを使用してVisioのアプリケーションイベントをキャッチ
Public WithEvents VisApp As Visio.Application
Attribute VisApp.VB_VarHelpID = -1

‘ モーダル状態管理フラグ(Trueの間はVBA側の主処理を実行してはならない)
Private m_IsModalStateOpen As Boolean

‘ プロパティ: 現在モーダル画面が開いているか
Public Property Get IsModalLocked() As Boolean
IsModalLocked = m_IsModalStateOpen
End Property

‘ クラス初期化時にApplicationをバインド
Public Sub Initialize(ByVal appTarget As Visio.Application)
Set VisApp = appTarget
m_IsModalStateOpen = False
End Sub

‘ ——————————————————————————
‘ イベント1: コンポーネント(ダイアログ等)に入った瞬間を検知
‘ ——————————————————————————
Private Sub VisApp_OnComponentEnterState(ByVal app As Visio.Application, ByVal stateSequence As Long)
‘ stateSequence や Visioの内部ステートに基づきモーダル突入を判定
‘ 例: 印刷プレビュー、ファイルダイアログ、モーダルプロパティなど
m_IsModalStateOpen = True

#If DEBUG Then
Debug.Print “[Sentinel] モーダル状態へ突入しました。VBA実行をロックします。 State: ” & stateSequence
#End If
End Sub

‘ ——————————————————————————
‘ イベント2: コンポーネント状態から抜けた瞬間を検知
‘ ——————————————————————————
Private Sub VisApp_OnComponentExitState(ByVal app As Visio.Application, ByVal stateSequence As Long)
m_IsModalStateOpen = False

#If DEBUG Then
Debug.Print “[Sentinel] モーダル状態から復帰しました。VBAロックを解除します。”
#End If
End Sub

‘ ——————————————————————————
‘ 補助イベント: ユーザー操作によるドキュメント切替時などの安全確保
‘ ——————————————————————————
Private Sub VisApp_AppModalBegin(ByVal app As Visio.Application)
m_IsModalStateOpen = True
End Sub

Private Sub VisApp_AppModalEnd(ByVal app As Visio.Application)
m_IsModalStateOpen = False
End Sub

標準モジュール: `modMainController`

(※実際の業務処理を実行するエントリーポイント)

Option Explicit

‘ 番人クラスのインスタンスをグローバル保持(スコープ切れによるイベント消滅を防ぐ)
Public G_Sentinel As clsVisioSentinel

‘ ——————————————————————————
‘ 1. 監視システムの起動(Visio起動時やアドインロード時に実行)
‘ ——————————————————————————
Public Sub StartVisioSentinel()
If G_Sentinel Is Nothing Then
Set G_Sentinel = New clsVisioSentinel
‘ Applicationオブジェクトを渡して監視開始
G_Sentinel.Initialize Visio.Application
MsgBox “Visioフリーズ防衛システム(Sentinel)が稼働を開始しました。”, vbInformation, “初期化完了”
Else
MsgBox “既にセンチネルは稼働中です。”, vbInformation, “通知”
End If
End Sub

‘ ——————————————————————————
‘ 2. 実務の業務処理(安全設計が組み込まれたメインプロシージャの雛形)
‘ ——————————————————————————
Public Sub ExecuteHeavyBatchProcess()
‘ 【鉄則】まずはセンチネルが初期化されているか、モーダルロック中ではないかを確認する
If G_Sentinel Is Nothing Then
‘ 初期化されていなければ安全のため即座に処理中断
MsgBox “エラー: 監視システムが有効化されていません。[StartVisioSentinel]を実行してください。”, vbCritical, “中断”
Exit Sub
End If

If G_Sentinel.IsModalLocked Then
‘ ユーザーがダイアログ操作中のため、今回の実行スキップ(あるいはキューイング)
MsgBox “現在、Visioで設定画面またはダイアログが開いています。” & vbCrLf & _
“処理の競合によるフリーズを防ぐため、処理を中断しました。画面を閉じてから再度実行してください。”, _
vbExclamation, “セーフティガード作動”
Exit Sub
End If

‘ —- ここから下が安全に実行できるメインロジック —-
Dim shp As Visio.Shape
Dim pg As Visio.Page

On Error GoTo ErrorHandler

‘ 例としてアクティブページの全シェイプを走査する重い処理
Set pg = ActivePage

‘ 処理中の画面描画を停止してパフォーマンスを極限まで引き上げる
Visio.Application.ScreenUpdating = False
Visio.Application.ShowChanges = False

For Each shp in pg.Shapes
‘ 業務ロジックの記述(例: 特定シェイプのプロパティ書き換えなど)
‘ shp.Text = “Processed”
Next shp

MsgBox “一括処理が正常に完了しました。”, vbInformation, “完了”

CleanUp:
‘ 画面描画の復元(絶対に忘れてはならない)
Visio.Application.ScreenUpdating = True
Visio.Application.ShowChanges = True
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
Resume CleanUp
End Sub

4. ファイル・データベース連携時におけるさらなる「実務の知見」

上記のようなセーフティガードを入れた上で、さらに外部ファイル(ExcelやAccess、あるいはSQL Serverなどのデータベース)と連携するツールを構築する際、プロフェッショナルとして守るべき設計指針を提示する。

1. 非同期・イベント駆動処理の過信禁物
Visio VBAはシングルスレッドで動作する。外部DBへの書き込みや大量のExcelインポートを行う際、処理が数秒以上ブロックされると、その間にユーザーがダイアログを開こうとしてタイミング悪く競合が起きる。DB連携処理の前にも必ず `G_Sentinel.IsModalLocked` のチェックを挟め。
2. トランザクションの確実なロールバック
ファイル・DB連携中に万が一Visio側が不安定になった場合を想定し、データベース側は必ずトランザクション(`BeginTrans` / `CommitTrans` / `Rollback`)で囲むこと。中途半端なデータをVisioの図面と外部DBに残さないのが、プロエンジニアの最低限のプライドだ。

5. チーフアーキテクトからの総括

「動けばいいや」で作られたVBAマクロは、開発者の手元では動くが、現場のユーザーが使った瞬間に謎のフリーズを引き起こしてゴミクズと化す。その原因の多くは、VisioのUIスレッドとVBAの実行スレッドの衝突、すなわちアプリケーションの状態管理の欠如にある。

今回紹介した `OnComponentEnterState` を活用した番人クラスの設計を取り入れれば、ユーザーがどんなに気まぐれにダイアログを開き閉めしようとも、Visioがフリーズの恐怖に怯えることはなくなる。

あなたの書くコードを、単なる「動くスクリプト」から、現場で絶対的な信頼を得る「堅牢なプロダクト」へと引き上げろ。実装は今すぐ、目の前のエディタから始まる。

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