Visio VBAを掌握する極限の知見:Shapeをラップして「業務オブジェクト」を創出する極意
開発プロジェクトの現場で、こんな絶望的なコードを見たことはないだろうか。
‘ メンテナンス不能な「魔改造」スパゲッティコードの典型
If vsoShape.Cells(“Prop.Cost”).ResultIU > 10000 Then
vsoShape.Cells(“Char.Color”).FormulaU = “RGB(255,0,0)”
vsoShape.DataRecordsetID = 5 ‘ 謎のハードコーディング
End If
形状(Shape)のプロパティ名を文字列で直撃し、あちこちのモジュールで `Cells(“Prop.XXXX”)` を乱発する。要件定義が変わってセルの名前が「Cost」から「Price」に変更された瞬間、プロジェクト全体が崩壊する。
私たちはドキュメント描画ツールを作っているのであって、文字列のパズルをしているのではない。
プロフェッショナルな業務自動化エンジニアなら、Visioのプリミティブな `Shape` オブジェクトをそのまま叩く愚行を直ちにやめ、「業務ドメインを表現するクラスモジュール」でラップすべきだ。
今回は、Visio VBAにおけるクラスモジュール構築の極限の知見を授けよう。型番、価格、バリデーション状態を内包し、データベースや外部APIとシームレスに連携する堅牢なオブジェクト指向アーキテクチャを解説する。
—
1. なぜVisio VBAでクラスモジュールを使うのか?
Visioのオブジェクトモデルは独特だ。`Application` から `Document`、`Page` を経由して `Shape` に至る階層構造は強力だが、標準の `Shape` オブジェクトには「価格」「型番」「在庫ステータス」といった業務固有の概念(セマンティクス)が存在しない。カスタムプロパティ(Shape Data)で持たせることはできるが、それは単なる「キーバリューの泥沼」だ。
クラスモジュールで `Shape` をラップする(=アダプターパターンを適用する)ことで、以下の恩恵を受ける。
1. カプセル化による変更耐性: セル名の変更や内部構造の改修は、クラス内部の隠蔽されたプライベートメソッドを修正するだけで完結する。呼び出し側のコードは1行も変える必要がない。
2. IntelliSenseの全開活用: `vsoShape.CellsU(…)` のスペルミスに怯える日々は終わる。`myDevice.Price = 5000` のように、型安全なプロパティアクセスを手に入れる。
3. ビジネスロジックの分離: 「図形を描画する処理」と「価格を計算する処理」「DBと同期する処理」が綺麗に分離され、単体テスト(概念的)が容易になる。
—
2. アーキテクチャ設計:堅牢なラッパークラスの全体像
今回構築するクラスは `ClsDeviceShape` とする。
このクラスは、1つの `Visio.Shape` への参照を保持し、以下の責任を持つ。
- 初期化とライフサイクル管理: 不正なShapeがバインドされた場合の堅牢なガード。
- プロパティのゲッター/セッター: `Shape Data (Prop.)` への安全なアクセス。
- 状態検証(Validation): 業務ルールに違反している図形を視覚的に赤変させるなどのセルフメンテナンス機能。
—
3. プロダクションコード実装
以下のコードは、そのままあなたのVBAプロジェクトに投入できる実用レベルの堅牢性を持たせている。エラーハンドリングと、Visio特有の重いオブジェクトアクセスの最適化(遅延バインディングの排除と適切な参照解放)を施してある。
実装ステップ 1: クラスモジュール `ClsDeviceShape` の作成
VBEを開き、[挿入] > [クラス モジュール] を選択。プロパティ名を `ClsDeviceShape` に変更し、以下のコードを貼り付ける。
Option Explicit
‘ — プライベートフィールド —
Private m_vsoShape As Visio.Shape
Private Const PROP_PREFIX As String = “Prop.”
‘ — イベント / エラー定義 —
Public Enum ValidationStatus
StatusValid = 0
StatusWarning = 1
StatusError = 2
End Enum
‘ — イニシャライザ(オブジェクトのバインド) —
Public Sub Init(ByVal targetShape As Visio.Shape)
If targetShape Is Nothing Then
Err.Raise 91, “ClsDeviceShape”, “初期化エラー: 渡されたShapeがNothingです。”
End If
Set m_vsoShape = targetShape
End Sub
‘ — ラップしている生シェイプへのアクセサ(必要に応じて公開) —
Public Property Get InnerShape() As Visio.Shape
Set InnerShape = m_vsoShape
End Property
‘ — プロパティ: 型番 (ModelNumber) —
Public Property Get ModelNumber() As String
On Error Resume Next
ModelNumber = m_vsoShape.Cells(PROP_PREFIX & “ModelNumber”).ResultStr(“”)
If Err.Number <> 0 Then ModelNumber = “UNKNOWN”
On Error GoTo 0
End Property
Public Property Let ModelNumber(ByVal value As String)
On Error Resume Next
m_vsoShape.Cells(PROP_PREFIX & “ModelNumber”).FormulaU = “””” & value & “”””
If Err.Number <> 0 Then
‘ Shape Dataが存在しない場合の動的追加処理などをここにフォールバック
Err.Raise Err.Number, “ClsDeviceShape”, “型番の設定に失敗しました: ” & Err.Description
End If
On Error GoTo 0
End Property
‘ — プロパティ: 価格 (Price) —
Public Property Get Price() As Currency
On Error Resume Next
Price = m_vsoShape.Cells(PROP_PREFIX & “Price”).ResultIU
If Err.Number <> 0 Then Price = 0
On Error GoTo 0
End Property
Public Property Let Price(ByVal value As Currency)
On Error Resume Next
‘ Visio内部単位(IU)を考慮した設定
m_vsoShape.Cells(PROP_PREFIX & “Price”).FormulaU = value
On Error GoTo 0
End Property
‘ — 業務ロジック: 状態検証とビジュアルフィードバック —
Public Function ValidateAndHighlight() As ValidationStatus
Dim currentPrice As Currency
currentPrice = Me.Price
Dim model As String
model = Me.ModelNumber
‘ 業務ルール: 型番が未設定、または価格が0以下の場合はエラー
If model = “UNKNOWN” Or model = “” Or currentPrice <= 0 Then
Call SetVisualState(StatusError)
ValidateAndHighlight = StatusError
ElseIf currentPrice > 1000000 Then
‘ 高額商品は警告
Call SetVisualState(StatusWarning)
ValidateAndHighlight = StatusWarning
Else
‘ 正常
Call SetVisualState(StatusValid)
ValidateAndHighlight = StatusValid
End If
End Function
‘ — プライベートヘルパー: 図形の色を変えて視覚化 —
Private Sub SetVisualState(ByVal status As ValidationStatus)
Select Case status
Case StatusError
‘ 枠線を赤、塗りを薄い赤に
m_vsoShape.Cells(“LineColor”).FormulaU = “RGB(255,0,0)”
m_vsoShape.Cells(“FillForegnd”).FormulaU = “RGB(255,200,200)”
Case StatusWarning
‘ 枠線をオレンジ
m_vsoShape.Cells(“LineColor”).FormulaU = “RGB(255,165,0)”
m_vsoShape.Cells(“FillForegnd”).FormulaU = “RGB(255,240,200)”
Case StatusValid
‘ デフォルト(黒・白)に復元
m_vsoShape.Cells(“LineColor”).FormulaU = “RGB(0,0,0)”
m_vsoShape.Cells(“FillForegnd”).FormulaU = “RGB(255,255,255)”
End Select
End Sub
実装ステップ 2: 標準モジュールでの利用(コントローラー層)
標準モジュールを1つ作成し、ページ上の全デバイスシェイプを一括してオブジェクトとして操作するプロシージャを記述する。
Option Explicit
Public Sub ProcessAllDevices()
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage
Dim vsoShape As Visio.Shape
Dim deviceObj As ClsDeviceShape
Dim processedCount As Long
processedCount = 0
‘ 画面描画を停止してパフォーマンスを極限まで引き上げる
Application.ScreenUpdating = False
On Error GoTo ErrorHandler
For Each vsoShape in vsoPage.Shapes
‘ 特定のマスターシェイプやレイヤー判定(ここでは名前に “Device” が含まれると仮定)
If InStr(1, vsoShape.Name, “Device”, vbTextCompare) > 0 Then
‘ クラスのインスタンス化とバインド
Set deviceObj = New ClsDeviceShape
deviceObj.Init vsoShape
‘ — 業務処理の実行 —
‘ 例: 外部データ連携を模した価格の更新
If deviceObj.ModelNumber = “DEV-X01” Then
deviceObj.Price = 1250000 ‘ 高額商品テスト
End If
‘ 検証とビジュアルの更新
Dim status As ValidationStatus
status = deviceObj.ValidateAndHighlight()
processedCount = processedCount + 1
End If
Next vsoShape
MsgBox “処理が完了しました。総処理シェイプ数: ” & processedCount, vbInformation
CleanUp:
Application.ScreenUpdating = True
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
—
4. プロジェクト成功のための「極限の知見」と注意点
実務でこのアーキテクチャを導入する際、シニアエンジニアとして知っておくべきハードルと回避策がある。
1. パフォーマンスの罠:大量シェイプ走査時のオーバーヘッド
クラスモジュールをループ内で `New` し続けると、VBAのランタイムにわずかながら負荷がかかる。数万個規模のシェイプを扱う図面では、インスタンスの生成・破棄コストが無視できなくなる場合がある。
- 対策: バッチ処理を行う際は、画面描画の停止(`Application.ScreenUpdating = False`)を必ず行い、イベントの無効化(`Application.EventEnabled = False`)も検討せよ。また、不要になったオブジェクト参照はループの都度 `Set deviceObj = Nothing` で明示的に解放する習慣をつけよ。
2. 外部データベース・API連携時の設計思想
「価格」や「型番」をVisioのプロパティ(Shape Data)だけに保持させてはならない。Visioはあくまで「ビュー(UI)」である。
- 知見: クラスモジュール内に `SyncWithDatabase()` メソッドを持たせる場合、ADO(ActiveX Data Objects)を用いたSQL ServerやSQLiteへのアクセス、あるいは `MSXML2.DOMDocument` や `WinHttp.WinHttpRequest` を用いたREST APIコールをカプセル化する。
- クラスの役割は「Visioの図形表現」と「外部データの真実(Source of Truth)」を同期させるブリッジに昇華させるのだ。
3. エラーハンドリングの境界線
Visioのセルアクセスは、該当するセルやカスタムプロパティが存在しない場合に実行時エラー(エラー番号:-2147352567 など)を吐く。
ラッパークラス内では `On Error Resume Next` を局所的に使用し、値が取得できなかった場合のフォールバック(デフォルト値の返却)を必ず実装すること。呼び出し側に余計なエラー処理の負担を強いてはならない。
—
終わりに
スパゲッティコードで構築されたVisioツールは、作成者以外誰もメンテナンスできず、数ヶ月後には「誰も触れないレガシー遺産」と化す。
しかし、今回解説したShapeラッパークラスの概念を取り入れることで、Visio VBAは「単なるマクロの集まり」から「堅牢な業務アプリケーション」へと生まれ変わる。
コードの美しさは、保守性と信頼性に直結する。次の開発では、ぜひこのオブジェクト指向アプローチを導入し、周囲を圧倒する質の高いシステムを構築してほしい。
