【実務・中級編】Application.ActivePageの安全な切り替え:複数図面オープン時のヌル参照を防ぐ堅牢な実装 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:ActivePage依存からの脱却。複数図面オープン時のヌル参照を防ぐ堅牢なオブジェクト設計

開発プロジェクトの現場において、Visio VBAのコードレビューを行うと、いまだに散見される悪習がある。それが `ActiveWindow.Page` や `ActivePage` といった、「現在画面にアクティブ表示されている状態」に依存したコーディングだ。

「マクロを実行するんだから、今開いているページが操作対象になるのは当たり前だ」――そう考えていないだろうか?
その甘い設計が、将来的にどれほどの致命傷をもたらすか。プロの自動化エンジニアであれば、そのリスクを痛感しているはずだ。

本記事では、複数図面(Document)が同時オープンされる実務環境において、バックグラウンド処理や非同期的な操作でも微動だにしない、真に堅牢なVisio VBAのオブジェクト設計と実装アプローチを徹底解説する。

なぜ `ActivePage` は「地雷」なのか?

Visioのオブジェクトモデルにおいて、`ActiveApplication.ActivePage` や `ActiveWindow` は、ユーザーのUI操作と密結合している。
実務で自動化ツールやアドイン、他システム連携バッチを開発する際、この「UI依存」が以下のような深刻なバグを引き起こす。

1. フォーカスの競合によるヌル参照(Run-time error ’91’: Object variable or With block variable not set)
ユーザーが処理実行の瞬間に別のVisioウィンドウをクリックしたり、タスクバーから別アプリにフォーカスを移したりしただけで、`ActivePage` は `Nothing` を返す。これにより、容赦なくマクロはクラッシュする。
2. 意図しない図面・ページへの書き込み(サイレント・データ破損)
複数図面を開きながら作業しているユーザーが、マクロ実行中に別の図面を手前に持ってきた場合、意図とは全く異なる図面・ページに対して図形生成やプロパティ書き換えが実行される。これはエラーさえないため、データ破損として発覚が遅れる最悪のバグとなる。
3. 描画イベント・画面更新のオーバーヘッド
アクティブウィンドウを切り替えながら処理を行うコードは、画面の再描画(Redraw)が発生するため、処理速度が劇的に低下する。

プロが採るべき原則:「コンテキストの完全な特定」

Visio VBAで業務自動化ツールを作るなら、原則はただ一つ。
「UIの状態(何がアクティブか)に一切頼らず、コード内で明示的にDocument、Page、Shapeの参照を保持し、制御する」ことだ。

堅牢なオブジェクトチェーンの構築

安全なコードを書くための基本方針は、`Application` から起算するのではなく、「対象のDocumentオブジェクト」を起点として下位階層(Page、Shape)を直接手繰ることである。

[Application]
└─ [Documents (Collection)]
└─ [Document (Target)]
└─ [Pages (Collection)]
└─ [Page (Target)]
└─ [Shapes (Collection)]

この階層をコード上で明示的に変数に格納し、`.ActivePage` のようなプロパティを一切経由させずに処理を完結させる。

【実践】プロダクションコード例

以下に、実務のファイル連携や自動レイアウト生成を想定した、極めて堅牢なプロシージャのテンプレートを提示する。
このコードは、仮に裏でユーザーが別の図面を操作していたとしても、指定したファイルを正確にターゲットにして処理を安全に完結させる。

Option Explicit

‘ =========================================================================
‘ プロジェクト名: VisioAutomationMaster
‘ モジュール名 : mExportSafetyControl
‘ 概要 : アクティブ状態に依存しない、安全なページ・図面操作の実装例
‘ =========================================================================
Public Sub ExecuteRobustShapeProcessing()

Dim targetFilePath As String
targetFilePath = “C:\Data\AutomationTarget.vsdm” ‘ 実務では引数や設定ファイルから取得

Dim targetDoc As Visio.Document
Dim targetPage As Visio.Page
Dim isDocOpenedByMe As Boolean

‘ 画面描画とアラートを一時停止(パフォーマンス向上と予期せぬUI干渉の防止)
Dim originalScreenUpdating As Boolean
originalScreenUpdating = Application.ScreenUpdating
Application.ScreenUpdating = False
Application.ShowAlerts = False

On Error GoTo ErrorHandler

‘ ———————————————————————
‘ 1. Documentの安全な取得(既に開いていれば流用し、なければサイレントで開く)
‘ ———————————————————————
Set targetDoc = GetOrOpenDocument(targetFilePath, isDocOpenedByMe)
If targetDoc Is Nothing Then
Err.Raise vbObjectError + 1000, “ExecuteRobustShapeProcessing”, “対象ドキュメントの取得に失敗しました: ” & targetFilePath
End If

‘ ———————————————————————
‘ 2. Pageの安全な取得(ActivePageを使わず、名前またはインデックスで直接特定)
‘ ———————————————————————
‘ ※ここでは例として “システム構成図” という名前のページを直接指定
Set targetPage = GetPageByName(targetDoc, “システム構成図”)
If targetPage Is Nothing Then
Err.Raise vbObjectError + 1001, “ExecuteRobustShapeProcessing”, “指定されたページが見つかりません: システム構成図”
End If

‘ ———————————————————————
‘ 3. ページに対する安全なビジネスロジックの実行
‘ ———————————————————————
Call ProcessShapesOnPage(targetPage)

‘ 変更を保存(必要に応じて)
targetDoc.Save

MsgBox “処理が正常に完了しました。”, vbInformation, “自動化ツール”

CleanUp:
‘ ———————————————————————
‘ 4. 厳格な後処理(自分が開いた場合のみ閉じる、画面描画の復元)
‘ ———————————————————————
If isDocOpenedByMe And Not (targetDoc Is Nothing) Then
targetDoc.Close
End If

Application.ScreenUpdating = originalScreenUpdating
Application.ShowAlerts = True
Exit Sub

ErrorHandler:
‘ 異常系ハンドリング
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “致命的なエラー”
‘ 必要に応じたログ出力やロールバック処理をここに記述
Resume CleanUp
End Sub

‘ =========================================================================
‘ ヘルパー関数: ドキュメントのオープン状態を管理し確実に取得する
‘ =========================================================================
Private Function GetOrOpenDocument(ByVal filePath As String, ByRef outOpenedByMe As Boolean) As Visio.Document
Dim doc As Visio.Document
Dim fileNameOnly As String

fileNameOnly = Mid(filePath, InStrRev(filePath, “\”) + 1)
outOpenedByMe = False

‘ 既にアプリケーション内で開かれているかチェック
On Error Resume Next
Set doc = Application.Documents(fileNameOnly)
On Error GoTo 0

If doc Is Nothing Then
‘ 開いていない場合は、不可視(あるいは通常のバックグラウンド)で開く
‘ 実務では Workspaces や ReadOnly オプションも適宜調整
Set doc = Application.Documents.Open(filePath)
outOpenedByMe = True
End If

Set GetOrOpenDocument = doc
End Function

‘ =========================================================================
‘ ヘルパー関数: ActivePageに頼らず名前からPageオブジェクトを直接解決する
‘ =========================================================================
Private Function GetPageByName(ByVal targetDoc As Visio.Document, ByVal pageName As String) As Visio.Page
Dim pg As Visio.Page

For Each pg In targetDoc.Pages
‘ Visioのページ名比較(必要に応じて UCase 等で大文字小文字を吸収)
If pg.Name = pageName Or pg.NameU = pageName Then
Set GetPageByName = pg
Exit Function
End If
Next pg

‘ 見つからなかった場合
Set GetPageByName = Nothing
End Function

‘ =========================================================================
‘ ビジネスロジック本体: Pageオブジェクトを完全にカプセル化して処理
‘ =========================================================================
Private Sub ProcessShapesOnPage(ByVal pg As Visio.Page)
Dim shp As Visio.Shape

‘ 例: ページ上の全シェイプに対して何らかのプロパティ操作を行う
For Each shp in pg.Shapes
‘ ここに実務ロジックを記述(例:特定のレイヤーに属する図形の改行処理など)
‘ Debug.Print shp.Name
Next shp
End Sub

コードのアーキテクチャ的解説

1. `Application.ScreenUpdating = False` の徹底
バックグラウンド処理において、画面描画の抑制は必須である。これを行わないと、複数図面が開いている環境ではWindowsのウィンドウマネージャーが描画競合を起こし、予期せぬフリーズや描画崩壊を誘発する。
2. 自己責任のライフサイクル管理 (`isDocOpenedByMe`)
ツールが勝手に図面を開いたのであれば、終了時に閉じるべきだ。しかし、ユーザーがすでに開いていた図面を勝手に閉じると大クレームにつながる。そのため、「このマクロがオープンに関与したか」をフラグで厳格に管理し、クリーンアップ処理を分岐させる設計にしている。
3. `GetPageByName` による名前解決
`ActivePage` の代わりに、`Document.Pages` コレクションをイテレートして目的のページを確実につかむ。Visioではユーザーがページ名を変更するリスクがあるため、必要に応じてローカル名(`.Name`)とUI独立名(`.NameU`)の両方を考慮できるように拡張しておくと、多言語環境やテンプレート展開時にも耐えうる。

チーフアーキテクトからの提言

「動けばいい」という妥協の産物は、開発者自身が現場を離れた後、必ず負債となって組織に跳ね返る。特にVisioは、図面やステンシル、マルチウィンドウが複雑に絡み合う特殊なアプリケーションだ。

`ActivePage` や `ActiveWindow` といった甘い誘惑に逃げ込むのは今日で終わりにしよう。
「ドキュメントを特定し、ページを特定し、状態に依存しないコードを書く」。この鉄則を遵守するだけで、あなたの組んだ自動化ツールは、プロフェッショナルが認める「ビクともしない堅牢なインフラストラクチャ」へと生まれ変わる。

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