はい、Visio VBAの自動化でしばしば見落とされがちな、しかし極めて重要な設計思想について語りましょう。現場の自動化ツール開発者諸君、耳を傾けてください。
Visio VBAを掌握する極限の知見:Shape.IsSelectedを使わないスマートな非破壊ハイライト術
Visio VBAを用いた業務自動化の現場で、私たちは日々、効率と正確性の両立を追求しています。しかし、その過程で安易な実装に走りがちであり、結果としてユーザー体験を損ない、堅牢性を欠くツールを生み出してしまうケースが後を絶ちません。今回は、その典型的なアンチパターンの一つである「ユーザーの選択状態を破壊するハイライト処理」を徹底的に糾弾し、真にスマートな非破壊ハイライトの設計思想と実装を伝授します。
導入:なぜ `Shape.IsSelected` を使ったハイライトは「愚策」なのか
多くの開発者が、特定の条件に合致するシェイプをハイライト表示する際、まず思いつくのは `Shape.Select` メソッドや `Shape.IsSelected` プロパティでしょう。しかし、断言します。これは避けるべき愚策です。
なぜか?その理由は多岐にわたります。
1. ユーザーの選択状態の破壊: `Shape.Select` を実行した時点で、ユーザーがそれまで行っていた選択はすべて解除され、コードが選択したシェイプに置き換わります。これはユーザーの作業フローを寸断し、極めて不快な体験を提供します。
2. UIのフリッカーとパフォーマンス劣化: 大量のシェイプを選択・解除するたびに、VisioのUIが視覚的にちらつき、CPUリソースを消費します。特にネットワーク図やフローチャートなどの複雑な図面では、処理速度の著しい低下を招きます。
3. Undoスタックの肥大化と誤作動: `Shape.Select` はVisioのUndoスタックに操作を記録します。ハイライトのために何十、何百もの選択操作を記録することは、ユーザーが本当に元に戻したい操作をUndo履歴の深くに埋もれさせ、予期せぬ挙動を引き起こす可能性があります。
4. コードの複雑化とバグの温床: 処理後に元の選択状態を復元しようとすれば、元の選択状態を事前に保存し、処理後に復元するロジックが必要になります。これはコードを不必要に複雑にし、バグの温床となります。特に、エラー発生時の復元漏れは致命的です。
私たちは、Visioが提供する強力なオブジェクトモデルを最大限に活用し、ユーザーの作業を邪魔することなく、かつ堅牢に機能するツールを構築する責任があります。そのための第一歩が、この「非破壊ハイライト」の理解です。
非破壊ハイライトの設計思想:ユーザー体験と堅牢性の両立
真にスマートなハイライト処理は、以下の原則に基づいています。
- ユーザーの選択状態を絶対に汚染しない。
- UIのフリッカーを最小限に抑える。
- Undoスタックに余計な操作を記録しない。
- 処理終了後、元の状態に完全に復元されることを保証する。
- エラー発生時でも安全に復旧できる。
これを実現するためには、シェイプの「塗りつぶしの色」を直接操作し、その変更前後の状態を適切に管理する必要があります。
実装の鍵となるテクニック
1. 元の状態の保存と復元:
- ハイライト対象のシェイプの元の塗りつぶしの色 (`FillForegnd`) を一時的に保存します。
- 保存した情報は、`Dictionary` オブジェクトなどを活用し、`Shape.ID` をキーとして管理します。`Shape.ID` はドキュメント内で一意かつ不変なため、シェイプを特定するのに最適です。
- 処理完了後、またはエラー発生時に、この保存された情報に基づいてシェイプを元の色に戻します。
2. パフォーマンス最適化:
- `Application.ScreenUpdating = False`: Visioの描画更新を一時的に停止し、UIのフリッカーとパフォーマンス劣化を防ぎます。これは大量のシェイプを扱う際には必須です。
- `Application.BeginUndoScope` / `Application.EndUndoScope`: 一連の操作を一つのUndo単位としてまとめます。これにより、ユーザーがUndoする際にハイライト処理全体を一括で元に戻せるようになり、Undoスタックの肥大化も防げます。
- `Application.EventsEnabled = False` / `Application.DeferRecalc = True` (慎重に): 特定の状況下では、イベントの発火を停止したり、再計算を遅延させたりすることでパフォーマンスを向上させられます。ただし、副作用も大きいため、利用は慎重に検討し、必ず元の状態に戻すこと。今回はそこまで踏み込みませんが、知見として持っておくべきです。
3. 堅牢なエラーハンドリング:
- 処理中にエラーが発生した場合でも、ハイライトされたシェイプが元の色に戻ることを保証する必要があります。
- `On Error GoTo` を使用し、クリーンアップ処理(元の色に戻す、`ScreenUpdating` を有効に戻す、`EndUndoScope` を呼び出すなど)を必ず実行する設計とします。
コード例:非破壊ハイライトの実装
以下に、上記の原則に基づいた堅牢な非破壊ハイライト処理のVBAコード例を示します。これはモジュールに記述し、必要に応じて呼び出してください。
‘—————————————————————————————————
‘ モジュールレベル変数:ハイライト状態を管理するための辞書
‘ この辞書には、ハイライトされたシェイプのIDとその元の色(RGB文字列)が格納されます。
‘ グローバル変数ではなくモジュールレベル変数とすることで、他のモジュールからの意図しないアクセスを防ぎます。
Private g_dicHighlightedShapes As Object ‘ Dictionaryオブジェクトを格納
Private Const HIGHLIGHT_COLOR_RGB As String = “RGB(255,165,0)” ‘ ハイライト色 (オレンジ)
Private Const HIGHLIGHT_DURATION_SECONDS As Long = 1 ‘ ハイライト持続時間 (秒)
‘—————————————————————————————————
”’
”’ ユーザーの現在の選択状態を汚染せず、堅牢性とパフォーマンスを考慮した設計です。
”’
”’ 処理対象のVisioページオブジェクト
Public Sub HighlightShapesTemporarily(ByVal targetPage As Visio.Page)
Dim shp As Visio.Shape
Dim originalFillColor As String
Dim bScreenUpdating As Boolean
Dim bEventsEnabled As Boolean
Dim bDeferRecalc As Boolean
Dim undoScopeID As Long ‘ UndoScopeのID
‘ 既にハイライト処理が進行中の場合は、二重実行を防ぐために終了
If Not g_dicHighlightedShapes Is Nothing And g_dicHighlightedShapes.Count > 0 Then
MsgBox “既にハイライト処理が実行中です。完了を待ってから再度実行してください。”, vbExclamation
Exit Sub
End If
‘ Dictionaryオブジェクトを初期化
Set g_dicHighlightedShapes = CreateObject(“Scripting.Dictionary”)
‘ エラーハンドリングの開始
On Error GoTo ErrorHandler
‘ 既存のVisio設定を保存し、パフォーマンス最適化のために一時的に変更
With Visio.Application
bScreenUpdating = .ScreenUpdating
.ScreenUpdating = False ‘ 画面更新を停止
bEventsEnabled = .EventsEnabled
.EventsEnabled = False ‘ イベントの発火を停止 (パフォーマンス向上)
bDeferRecalc = .DeferRecalc
.DeferRecalc = True ‘ 再計算を遅延 (パフォーマンス向上)
‘ 一連の操作を単一のUndo単位として開始
undoScopeID = .BeginUndoScope(“ハイライト表示”)
End With
‘ ページ内のすべてのシェイプをループ
For Each shp In targetPage.Shapes
‘ ここにハイライトの条件を記述します。
‘ 例: シェイプのテキストに “重要” が含まれている場合
If InStr(1, shp.Text, “重要”, vbTextCompare) > 0 Then
‘ シェイプの元の塗りつぶし色を保存
originalFillColor = shp.Cells(“FillForegnd”).ResultStr(Visio.visNoCast)
If Not g_dicHighlightedShapes.Exists(shp.ID) Then ‘ 既に登録済みの場合はスキップ
g_dicHighlightedShapes.Add shp.ID, originalFillColor
End If
‘ シェイプの塗りつぶし色をハイライト色に変更
shp.Cells(“FillForegnd”).FormulaForce = HIGHLIGHT_COLOR_RGB
End If
Next shp
‘ ハイライト表示を確認するための待機
Visio.Application.Wait (Now + TimeValue(“00:00:” & HIGHLIGHT_DURATION_SECONDS))
‘ 元の状態に復元
Call RestoreHighlightedShapes
‘ 正常終了時のクリーンアップ
GoTo CleanUp
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
‘ エラー発生時も元の状態に戻す試み
If Not g_dicHighlightedShapes Is Nothing Then
Call RestoreHighlightedShapes
End If
CleanUp:
‘ Visio設定を元の状態に戻す
With Visio.Application
If undoScopeID <> 0 Then .EndUndoScope undoScopeID, True ‘ UndoScopeを終了
.DeferRecalc = bDeferRecalc
.EventsEnabled = bEventsEnabled
.ScreenUpdating = bScreenUpdating
End With
‘ Dictionaryオブジェクトを解放
Set g_dicHighlightedShapes = Nothing
On Error GoTo 0 ‘ エラーハンドリングを解除
End Sub
”’
”’ クリーンアップ処理として呼び出されます。
”’
Private Sub RestoreHighlightedShapes()
Dim shp As Visio.Shape
Dim shpID As Variant
Dim originalColor As String
Dim app As Visio.Application
Set app = Visio.Application
If Not g_dicHighlightedShapes Is Nothing And g_dicHighlightedShapes.Count > 0 Then
‘ 画面更新とイベントを一時停止(二重停止にならないよう注意)
Dim bScreenUpdatingBeforeRestore As Boolean
Dim bEventsEnabledBeforeRestore As Boolean
Dim bDeferRecalcBeforeRestore As Boolean
bScreenUpdatingBeforeRestore = app.ScreenUpdating
app.ScreenUpdating = False
bEventsEnabledBeforeRestore = app.EventsEnabled
app.EventsEnabled = False
bDeferRecalcBeforeRestore = app.DeferRecalc
app.DeferRecalc = True
‘ 保存された情報に基づいてシェイプの色を元に戻す
For Each shpID In g_dicHighlightedShapes.Keys
On Error Resume Next ‘ シェイプが削除されている可能性を考慮
Set shp = app.ActivePage.Shapes.ItemFromID(shpID) ‘ IDからシェイプを取得
If Not shp Is Nothing Then
originalColor = g_dicHighlightedShapes.Item(shpID)
shp.Cells(“FillForegnd”).FormulaForce = originalColor
End If
Set shp = Nothing
On Error GoTo 0
Next shp
‘ Dictionaryをクリア
g_dicHighlightedShapes.RemoveAll
‘ 画面更新とイベントを元の状態に戻す
app.DeferRecalc = bDeferRecalcBeforeRestore
app.EventsEnabled = bEventsEnabledBeforeRestore
app.ScreenUpdating = bScreenUpdatingBeforeRestore
End If
Set app = Nothing
End Sub
コードの解説とポイント
- `g_dicHighlightedShapes`: モジュールレベルで宣言された `Dictionary` オブジェクトです。これにより、`HighlightShapesTemporarily` プロシージャが終了しても、ハイライト状態の情報が保持され、`RestoreHighlightedShapes` で安全にアクセスできます。`Shape.ID` をキーにすることで、シェイプの参照が失われても、IDを使って再取得が可能です。
- 状態保存と復元: `Application.ScreenUpdating`, `Application.EventsEnabled`, `Application.DeferRecalc` の各設定を処理開始前に保存し、処理終了時(エラー時を含む)に必ず元の状態に戻しています。これは堅牢なVBAコードの基本です。
- `Application.BeginUndoScope` / `EndUndoScope`: ハイライト処理全体を単一のUndo操作としてまとめることで、ユーザーがワンクリックでハイライトの適用・解除を切り替えられるようになります。これにより、Undoスタックがクリーンに保たれます。
- `FillForegnd` の直接操作: `shp.Cells(“FillForegnd”).FormulaForce = HIGHLIGHT_COLOR_RGB` を使用して、シェイプの塗りつぶし色を直接変更します。これにより、選択操作を伴わずにビジュアルな変更が可能です。`ResultStr(Visio.visNoCast)` は、元の色を文字列(例: `RGB(255,0,0)`)として取得する際に便利です。
- エラーハンドリング: `On Error GoTo ErrorHandler` を導入し、エラー発生時でも `RestoreHighlightedShapes` を呼び出すことで、ハイライト状態が解除されることを保証しています。`On Error Resume Next` は、`RestoreHighlightedShapes` 内でシェイプが既に削除されている可能性に備えて使用しており、特定の箇所でのみ限定的に適用することが重要です。
- `Application.Wait`: 一時的なハイライト効果を視覚的に確認するためのものです。本番環境では、ユーザー操作によって解除する仕組みや、メッセージ表示後に自動解除するなどの工夫も考えられます。
- 条件定義: `If InStr(1, shp.Text, “重要”, vbTextCompare) > 0 Then` の部分を、あなたの業務要件に合わせた条件に書き換えてください。Shape Data、レイヤー、コネクタの接続状態など、Visioのオブジェクトモデルが提供するあらゆるプロパティを条件として利用可能です。
外部データ連携における注意点
本テーマはVisio内部での一時的な操作ですが、実務では外部データベースやファイルから取得したデータに基づいてシェイプをハイライトすることがほとんどでしょう。その際の注意点をいくつか挙げます。
1. データ取得の堅牢性:
- Excel、CSV、Access、SQL Serverなどの外部データソースへの接続は、必ずエラーハンドリングを組み込んでください。接続失敗、ファイル見つからず、SQLエラーなどは日常茶飯事です。
- データ取得プロシージャ自体を独立させ、エラー発生時にはVisioの操作に進まないように設計すべきです。
2. Visioシェイプと外部データの紐付け:
- 外部データとVisioシェイプを関連付けるユニークなキーが必要です。通常は、シェイプのカスタムプロパティ(Shape Data)に外部DBのIDなどを格納します。
- 大量のシェイプを処理する場合、ループ内でDBクエリを実行するのではなく、まず必要なデータをすべてメモリ(`Dictionary`や`Collection`)に読み込み、そのデータを使ってVisioシェイプを処理する「インメモリ処理」を徹底してください。これにより、DBアクセス回数を最小限に抑え、パフォーマンスを劇的に向上させます。
3. 非同期処理とユーザーフィードバック:
- 大量のデータ処理や複雑なハイライト条件の場合、処理に時間がかかることがあります。その際、ユーザーインターフェースがフリーズしないよう、可能であれば非同期処理を検討するか、少なくとも進行状況を示すプログレスバーやステータスメッセージを表示するべきです。Visio VBAでは非同期処理は難しいですが、`DoEvents` を適切に挟むことでUIの応答性を保つことができます。
4. トランザクション管理:
- 外部データベースを更新する処理を伴う場合、DB側でのトランザクション管理を適切に行い、Visioでの操作とDB更新の整合性を保つように設計してください。VisioのUndoScopeとは異なる概念ですが、システム全体の堅牢性には不可欠です。
まとめと今後の展望
今回解説した非破壊ハイライトのテクニックは、単なる色変更の範疇を超え、Visio VBAにおける「ユーザー体験を損なわずに自動化を実装する」という根本的な設計思想を体現しています。
- `Application.ScreenUpdating = False`
- `Application.BeginUndoScope` / `EndUndoScope`
- 元の状態の確実な保存と復元
- 堅牢なエラーハンドリング
これらは、Visio VBAに限らず、あらゆるOfficeアプリケーションの自動化において、プロフェッショナルな開発者が遵守すべき黄金律です。これらの原則を常に意識し、あなたの作成するツールが、ユーザーにとって真に価値あるものとなるよう、設計に魂を込めてください。
Visioのオブジェクトモデルは深く、その力を最大限に引き出すには、表面的なメソッドの利用に留まらず、内部動作やパフォーマンスへの影響を理解することが不可欠です。今回の知見を活かし、さらなる高みを目指してください。
