【上級】Visio VBAにおけるクラスモジュールの活用:Shapeオブジェクトをラップした独自業務オブジェクトの構築
Visio VBAによる大規模な図面自動生成や、設備管理・ネットワークトポロジ等のシステム連携基盤を構築する際、多くの開発者が挫折するポイントがある。それは、生(生の状態)の `Shape` オブジェクトを直接操作し続けることに起因する、コードの肥大化とスパゲッティ化だ。
`ActivePage.Shapes.Item(i)` や `Cells(“Prop.EquipmentID”)` のような魔法の文字列(Magic Strings)がコードの至る所に散らばり、プロパティ名の変更一つでシステム全体が崩壊する。レガシーなVBA開発現場において、これは悪夢の定番である。
本稿では、この悪夢を断ち切り、Visioのプリミティブな `Shape` をクラスモジュールで「ラップ」することで、強固なカプセル化と高い保守性を誇る独自業務オブジェクトを構築する極限の知見を授ける。
—
1. なぜShapeをそのまま扱ってはならないのか?
Visioのオブジェクトモデルは、COM(Component Object Model)の薄いラッパーに過ぎない。VBAから `Shape` オブジェクトのプロパティやシェイプシート(ShapeSheet)にアクセスするたびに、COMの境界を越えたMARSHALING(マーシャリング)が発生し、パフォーマンスが著しく低下する。
さらに、業務ロジックとVisioの描画プリミティブが密結合すると、以下のような致命的な問題が生じる。
- 型安全性の欠如: `Shape.CellsU(“Prop.Status”)` のような文字列アクセスは、コンパイル時に検証できず、実行時エラーの温床となる。
- ライフサイクル管理の欠如: どの処理が図形を生成し、どの処理がそれを破棄したのかが追跡困難になる。
- ビジネスロジックの散逸: 「機器が稼働中か否か」という判定ロジックが、図面描画スクリプトのあちこちに重複して記述される。
これらを解決唯一の解が、「Shapeオブジェクトのクラスによるラップ(Wrapper Pattern)」である。
—
2. アーキテクチャ設計:業務オブジェクトの構築
今回は例として、プラント設備やネットワーク機器を模した「設備ノード(DeviceNode)」という独自業務オブジェクトを想定する。
このクラスは、内部に本物の `Visio.Shape` を隠蔽し、外部に対しては `ID`、`Status`、`UpdateStatus()` といった強烈に型付けされたインターフェースを提供する。
実装手順 1: クラスモジュール `clsDeviceNode` の作成
VBE(Visual Basic Editor)でクラスモジュールを挿入し、名前を `clsDeviceNode` とする。以下のコードを実装せよ。
‘ =================================================================
‘ クラスモジュール: clsDeviceNode
‘ 概要: VisioのShapeオブジェクトをラップし、設備としての振る舞いを定義する
‘ =================================================================
Option Explicit
‘ 内部で保持するVisioのShapeオブジェクト
Private m_shp As Visio.Shape
‘ シェイプシートのセクション定義(マジックストリングの排除)
Private Const PROP_ID As String = “Prop.DeviceID”
Private Const PROP_STATUS As String = “Prop.Status”
Private Const PROP_UPDATED_AT As String = “Prop.UpdatedAt”
‘ — 初期化・解放 (Lifecycle Management) —
‘ 既存のシェイプを紐付ける場合
Public Sub Init(ByRef targetShape As Visio.Shape)
If targetShape Is Nothing Then
Err.Raise 5, “clsDeviceNode.Init”, “渡されたShapeオブジェクトがNothingです。”
End If
Set m_shp = targetShape
End Sub
‘ 新規にシェイプをマスターからドロップして生成する場合
Public Sub Create(ByRef targetPage As Visio.Page, ByRef masterObj As Visio.Master, ByVal x As Double, ByVal y As Double)
Set m_shp = targetPage.Drop(masterObj, x, y)
‘ 初期プロパティの書き込み
Me.DeviceID = “DEV-” & Format(Now, “hhmmss”) & “-” & Int(Rnd 1000)
Me.Status = “IDLE”
End Sub
‘ — プロパティ (Encapsulation) —
Public Property Get InnerShape() As Visio.Shape
‘ 危険な逃げ道だが、低レベルAPI操作のために公開する場合のgetter
Set InnerShape = m_shp
End Property
Public Property Get UniqueID() As String
‘ Visio内部の固有ID (GUID) を返す
UniqueID = m_shp.UniqueID(Visio.VisUniqueIDArgs.visGetUniqueIDGuid)
End Property
Public Property Get DeviceID() As String
On Error Resume Next
DeviceID = m_shp.CellsU(PROP_ID).ResultStr(“”)
On Error GoTo 0
End Property
Public Property Let DeviceID(ByVal value As String)
‘ セルが存在しない場合のエラーハンドリングを考慮しつつ書き込み
m_shp.CellsU(PROP_ID).FormulaU = “””” & value & “”””
End Property
Public Property Get Status() As String
On Error Resume Next
Status = m_shp.CellsU(PROP_STATUS).ResultStr(“”)
If Status = “” Then Status = “UNKNOWN”
On Error GoTo 0
End Property
Public Property Let Status(ByVal value As String)
m_shp.CellsU(PROP_STATUS).FormulaU = “””” & value & “”””
‘ ステータス変更に応じて図形の色を動的に変更する(ビジネスロジックのカプセル化)
Call ApplyVisualState(value)
End Property
‘ — メソッド (Business Logic) —
‘ 状態に応じた視覚表現の変更(シェイプシートのカラー変更)
Private Sub ApplyVisualState(ByVal statusValue As String)
Select Case UCase(statusValue)
Case “RUNNING”
‘ 緑色へ変更 (RGB: 0, 176, 80)
m_shp.CellsU(“FillForegnd”).FormulaU = “RGB(0,176,80)”
Case “ERROR”
‘ 赤色へ変更 (RGB: 255, 0, 0)
m_shp.CellsU(“FillForegnd”).FormulaU = “RGB(255,0,0)”
Case Else
‘ グレーへ変更 (RGB: 192, 192, 192)
m_shp.CellsU(“FillForegnd”).FormulaU = “RGB(192,192,192)”
End Select
‘ 更新タイムスタンプを記録
m_shp.CellsU(PROP_UPDATED_AT).FormulaU = “””” & Format(Now, “yyyy-mm-dd hh:nn:ss”) & “”””
End Sub
‘ オブジェクトの明示的破棄
Public Sub Delete()
If Not m_shp Is Nothing Then
m_shp.Delete
Set m_shp = Nothing
End If
End Sub
‘ クラス破棄時のクリーンアップ
Private Sub Class_Terminate()
‘ メモリリーク防止のため参照を確実に解放
Set m_shp = Nothing
End Sub
—
3. 標準モジュールからの利用:圧倒的なコードの洗練
上記のようにクラス化されたオブジェクトを標準モジュールからどのように呼び出すかを見てみよう。コードの可読性が劇的に向上し、メンテナンスコストが極小化することが一目でわかるはずだ。
‘ =================================================================
‘ 標準モジュール: modMain
‘ =================================================================
Option Explicit
Sub Main_ProcessEquipment()
‘ 画面描画の停止によるパフォーマンス爆発的向上
Application.ScreenUpdating = False
Application.Settings.ShowAlerts = False
Dim targetPage As Visio.Page
Set targetPage = ActivePage
On Error GoTo ErrorHandler
‘ 1. 独自クラスのインスタンス化と新規図形生成
Dim device As clsDeviceNode
Set device = New clsDeviceNode
‘ ドキュメントステンシルからマスターシェイプを取得してドロップ
Dim mst As Visio.Master
Set mst = Visio.Documents.Item(1).Masters.Item(“Process Equipment”) ‘ ※環境に合わせて変更
device.Create targetPage, mst, 4.0, 5.0
‘ 2. 独自のプロパティを強型付けで操作
device.DeviceID = “PUMP-01-A”
device.Status = “RUNNING”
Debug.Print “Created Device ID: ” & device.DeviceID & ” [GUID: ” & device.UniqueID & “]”
‘ 3. 一定の処理を行った後、ステータスを異常に変更(色も自動で赤に変わる)
device.Status = “ERROR”
‘ 4. 必要に応じた破棄
‘ device.Delete ‘ 必要に応じて呼び出し
MsgBox “処理が完了しました。”, vbInformation, “成功”
CleanUp:
‘ 描画の復元とメモリ解放
Application.ScreenUpdating = True
Application.Settings.ShowAlerts = True
Set device = Nothing
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
Resume CleanUp
End Sub
—
4. チーフアーキテクトが教える:極限のメモリ最適化と実運用への布石
大規模な図面(数千個のシェイプを持つP&IDやネットワーク図)を扱う場合、VBAのメモリ管理とオブジェクトのライフサイクルを誤ると、あっさりOut of Memory (エラー 7)でプロセスがクラッシュする。以下の知見を必ず心に刻んでほしい。
1. 循環参照の回避(Circular References)
クラス内で `Visio.Shape` を保持する際、もし `Shape` 側から VBA のクラスインスタンスを逆引きする必要がある場合(例:Visioのイベントハンドラからクラスを特定する場合など)、`Shape.AddNamedDataObject` やカスタムプロパティに識別子(GUID等)を埋め込み、コレクションクラス(`Collection` や `Dictionary`)で一元管理するアーキテクチャを採用せよ。クラスインスタンス同士の循環参照はVBAではメモリリークの温床となる。
2. 画面描画の完全抑制 (`ScreenUpdating`)
前述のサンプルコードでも入れているが、`Shape.CellsU` やプロパティ変更を行うたびにVisioが再描画を行っては、処理時間が何百倍にも膨れ上がる。
大量のシェイプをラップして一括処理する場合は、必ず以下を挟むこと。
Application.ScreenUpdating = False
Application.CommandBars.DisableCustomize = True ‘ 余計なイベントの発生を抑制
3. コレクションクラスによる一括管理(Repository Pattern)
単体の `clsDeviceNode` をバラバラに扱うのではなく、複数のノードをラップするコレクションクラス(例: `clsDeviceRepository`)を別途作成し、図面上のすべての対象シェイプを一括して読み込んでラップする構造を作れ。これにより、データベースや外部API(REST APIなど)とのJSON同期、あるいはExcelへのインポート・エクスポートといったシステム間連携が一撃で実装可能となる。
—
5. 結び
Visio VBAは「動けばいい」というレベルの低いスクリプト言語として扱われがちだ。しかし、オブジェクト指向の原則(カプセル化、単一責任の原則)をクラスモジュールによって持ち込むことで、C#やVB.NET等のモダン言語に匹敵する堅牢なビジネスロジック層を構築できる。
生きた `Shape` をそのまま触るコードは今日で終わりにしよう。クラスで包み込み、あなたのVisioソリューションを真のエンタープライズレベルへと昇華させよ。
