【実務・中級編】【上級】Visio VBAにおけるクラスモジュールの活用:Shapeオブジェクトをラップした独自業務オブジェクトの構築 – Visio VBA解析バイブル

スポンサーリンク

【Visio VBA極限活用】Shapeをラップせよ!クラスモジュールで実現する堅牢な業務オブジェクト設計

Visio VBA開発において、こんな絶望を味わったことはないだろうか。

  • 「`ActivePage.Shapes.Item(i)` のインデックスが途中でズレて、意図しない図形を破壊した」
  • 「プロパティ(Shape Data / カスタムプロパティ)にアクセスする文字列キーのタイポで、実行時エラーが頻発する」
  • 「同じ図形操作のロジックがあちこちに散らばり、仕様変更のたびに全モジュール修正の地獄を見る」

素の `Shape` オブジェクトを直接操作する泥臭いコードは、中小規模のマクロであれば動く。しかし、数百・数千の図形を扱うエンタープライズな自動レイアウトツールや、データベース連動型の図面生成システムにおいて、このアプローチは「技術的負債の塊」を生み出す最短ルートだ。

今回は、Visio VBAのポテンシャルを極限まで引き出し、保守性と拡張性を担保するための「Shapeオブジェクトのクラス化(ラッパー設計)」の極意を伝授する。

—

1. なぜ「素のShapeオブジェクト」を直接触ってはならないのか?

Visioのオブジェクトモデルは強力だが、同時に非常に緩い(Loose)。
`Visio.Shape` は、それが「サーバーラック」を表しているのか、「ネットワークケーブル」を表しているのかを、自らは持たない。ただの「画面上のグラフィック要素」に過ぎない。

これをそのまま業務ロジックに露出させると、以下のような悪臭(Bad Smells)がコードに漂い始める。

1. マジックストリングの氾濫: `shp.Cells(“Prop.IPAddress”).FormulaU` のような文字列依存がコードの至る所に散在する。
2. 型安全性の欠如: 意図しないマスターシェイプがドロップされても、VBAのコンパイラは検知できない。
3. ビジネスロジックの散逸: 「機器のステータスを赤色に変える」という処理が、描画モジュールやイベントモジュールに重複して書かれる。

解決策:ドメインモデルとしての「ラップ」

オブジェクト指向の基本に立ち返ろう。Visioの `Shape` を「データ構造と振る舞いを持つ独自の業務オブジェクト(例: `ServerNode` クラス)」で包み込む(ラップする)のだ。

これにより、呼び出し側はVisioの内部構造(CellsやRowのインデックスなど)を一切意識せず、直感的なメソッドとプロパティだけで図形を操作できるようになる。

—

2. アーキテクチャ設計:ラッパークラスの全体像

今回構築するアーキテクチャは以下の通り。

  • `clsServerNode`(クラスモジュール): 1つのVisio Shapeをラップし、サーバー機器としての固有の振る舞い(IP設定、ステータス変更、データ読み書き)をカプセル化する。
  • `modMain`(標準モジュール): エントリーポイント。データベースや外部ファイルからデータを取得し、`clsServerNode` を介してVisio上に具現化する。

—

3. プロダクションコード実装

百聞は一見に如かず。実務でそのまま使える堅牢なコードを提示する。

ステップ1:クラスモジュールの作成 (`clsServerNode`)

VBEのメニューから [挿入] > [クラス モジュール] を選択し、プロパティ名を `clsServerNode` に変更する。

‘ ==============================================================================
‘ クラス名: clsServerNode
‘ 概要: VisioのShapeオブジェクトをラップし、サーバー機器としての振る舞いを提供
‘ ==============================================================================
Option Explicit

‘ 内部保持する生(Raw)のVisio Shapeオブジェクト
Private m_VisioShape As Visio.Shape

‘ 定数定義(マジックストリングの排除)
Private Const PROP_IP As String = “Prop.IPAddress”
Private Const PROP_STATUS As String = “Prop.Status”
Private Const PROP_OWNER As String = “Prop.Owner”

‘ — イニタイライザ / ターミネータ —
Private Sub Class_Initialize()
‘ 特になし
End Sub

Private Sub Class_Terminate()
‘ 参照の解放(循環参照・メモリリークの防止)
Set m_VisioShape = Nothing
End Sub

‘ — プロパティ (Let / Get) —

‘ ラップしている実体シェイプへの参照を取得(読み取り専用)
Public Property Get Shape() As Visio.Shape
Set Shape = m_VisioShape
End Property

‘ シェイプのバインド(初期化時に一度だけ、あるいは生成時に設定)
Public Sub Bind(ByVal TargetShape As Visio.Shape)
If TargetShape Nothing Then
Set m_VisioShape = TargetShape
Else
Err.Raise 9999, “clsServerNode”, “無効なShapeオブジェクトが渡されました。”
End If
End Sub

‘ IPアドレスプロパティ
Public Property Get IPAddress() As String
On Error Resume Next
IPAddress = m_VisioShape.CellsU(PROP_IP).ResultStr(visNoUnit)
If Err.Number <> 0 Then IPAddress = “”
On Error GoTo 0
End Property

Public Property Let IPAddress(ByVal Value As String)
If m_VisioShape.CellExistsU(PROP_IP, visExistsAnywhere) Then
m_VisioShape.CellsU(PROP_IP).FormulaU = Chr(34) & Value & Chr(34)
End If
End Property

‘ ステータスプロパティ
Public Property Get Status() As String
On Error Resume Next
Status = m_VisioShape.CellsU(PROP_STATUS).ResultStr(visNoUnit)
If Err.Number <> 0 Then Status = “Unknown”
On Error GoTo 0
End Property

Public Property Let Status(ByVal Value As String)
If m_VisioShape.CellExistsU(PROP_STATUS, visExistsAnywhere) Then
m_VisioShape.CellsU(PROP_STATUS).FormulaU = Chr(34) & Value & Chr(34)
‘ ステータス変更に伴うビジュアルの動的変更もクラス内に隠蔽する
Call UpdateVisuals(Value)
End If
End Property

‘ — ビジネスロジック(メソッド) —

‘ サーバー名(テキスト)の設定
Public Property Get ServerName() As String
ServerName = m_VisioShape.Text
End Property

Public Property Let ServerName(ByVal Value As String)
m_VisioShape.Text = Value
End Property

‘ 座標の設定(位置調整の抽象化)
Public Sub MoveTo(ByVal PinX As Double, ByVal PinY As Double)
m_VisioShape.CellsU(“PinX”).FormulaU = PinX
m_VisioShape.CellsU(“PinY”).FormulaU = PinY
End Sub

‘ ステータスに応じた色変更ロジック(カプセル化の真骨頂)
Private Sub UpdateVisuals(ByVal CurrentStatus As String)
Select Case UCase(CurrentStatus)
Case “ACTIVE”, “RUNNING”
‘ 緑色系へ変更 (FillForegnd)
m_VisioShape.CellsU(“FillForegnd”).FormulaU = “RGB(144, 238, 144)”
Case “WARNING”
‘ 黄色系へ変更
m_VisioShape.CellsU(“FillForegnd”).FormulaU = “RGB(255, 255, 0)”
Case “ERROR”, “DOWN”
‘ 赤色系へ変更
m_VisioShape.CellsU(“FillForegnd”).FormulaU = “RGB(255, 182, 193)”
Case Else
‘ デフォルト(グレー)
m_VisioShape.CellsU(“FillForegnd”).FormulaU = “RGB(220, 220, 220)”
End Select
End Sub

ステップ2:標準モジュールからの呼び出し (`modMain`)

標準モジュール側は、Visioのセル操作や座標計算という「汚い詳細」から完全に解放され、極めてクリーンな記述になる。

‘ ==============================================================================
‘ 標準モジュール: modMain
‘ 概要: クラスを活用した業務自動化の実行フロー
‘ ==============================================================================
Option Explicit

Sub CreateOrUpdateServerInfrastructure()
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage

‘ 擬似的な外部データ(本来はDBやCSV、APIから取得する構造体や配列)
‘ ここでは簡略化のためハードコーディング
Dim serverName As String: serverName = “AppServer-01”
Dim ipAddress As String: ipAddress = “192.168.1.50”
Dim status As String: status = “Warning”

Dim targetShape As Visio.Shape
Dim serverNode As clsServerNode
Set serverNode = New clsServerNode

‘ 1. 既存の図形を検索するか、新規作成するか
Set targetShape = FindShapeByText(vsoPage, serverName)

If targetShape Is Nothing Then
‘ 新規作成ロジック(マスターシェイプをドロップ)
‘ ※あらかじめドキュメントステンシルに “Server” マスターが存在する前提
Dim vsoMaster As Visio.Master
On Error Resume Next
Set vsoMaster = Visio.Documents.Item(“Basic Network Shapes.vssx”).Masters.Item(“Server”)
On Error GoTo 0

If vsoMaster Is Nothing Then
MsgBox “必要なマスターシェイプが見つかりません。”, vbCritical
Exit Sub
End If

Set targetShape = vsoPage.Drop(vsoMaster, 4.0, 5.0)
End If

‘ 2. ラッパークラスにシェイプをバインド
serverNode.Bind targetShape

‘ 3. オブジェクトのプロパティを通じて直感的に操作(型安全&インテリセンスが効く)
serverNode.ServerName = serverName
serverNode.IPAddress = ipAddress
serverNode.Status = status ‘ 自動的に色も変わる!
serverNode.MoveTo 5.0, 6.0

‘ 4. クリーンアップ
Set serverNode = Nothing
Set targetShape = Nothing

MsgBox “サーバーノードの同期が完了しました。”, vbInformation
End Sub

‘ 補助関数:テキストで図形を検索
Private Function FindShapeByText(ByVal vsoPage As Visio.Page, ByVal TargetText As String) As Visio.Shape
Dim shp As Visio.Shape
For Each shp In vsoPage.Shapes
If Trim(shp.Text) = TargetText Then
Set FindShapeByText = shp
Exit Function
End If
Next shp
Set FindShapeByText = Nothing
End Function

—

4. プロフェッショナルが教える運用上の注意点・ベストプラクティス

この設計を実際の業務システムや大規模マクロに導入する際、知っておくべき「プロの知見」を共有する。

1. コレクションクラスの併用(高度なパターン)

もし図形が数十〜数百個ある場合、`clsServerNode` を管理するためのコレクションクラス(例: `clsServerCollection`)を自作することを強く推奨する。
これにより、`coll.FindByIP(“192.168.1.50”)` のような高度なクエリメソッドをVBA上で実現でき、SQLデータベースやExcelマスタとの同期処理が劇的にエレガントになる。

2. パフォーマンスの罠(画面描画の抑制)

複数のラップされたオブジェクトを一括処理する際、ループ内で毎回 Visio のプロパティ書き込みを行うと、画面の再描画が発生して劇的に遅くなる。
処理の冒頭と末尾で以下の最適化を入れること。

‘ 処理速度を劇的に向上させる呪文
Application.ScreenUpdating = False
‘ — ここでループ処理やオブジェクト操作 —
Application.ScreenUpdating = True
Application.ForceRecalc ‘ 必要に応じた再計算

3. メモリリークと循環参照の回避

VBAのガベージコレクションは参照カウント方式だ。
クラス内でVisioのオブジェクトを保持しているため、インスタンス破棄時には必ず `Set m_VisioShape = Nothing` を明示し、不要になったコレクションやインスタンスは `Set obj = Nothing` で即座にメモリからパージする習慣をつけよ。

—

終わりに:VBAを「おもちゃ」から「エンタープライズコード」へ

「たかがマクロ、されどマクロ」。
多くの開発者がVisio VBAを単なる「お絵かきスクリプト」と侮り、スパゲッティコードを量産して自ら首を絞めている。

今回紹介したクラスモジュールによるラップ手法を取り入れれば、Visio VBAはもはや場当たり的な自動化ツールではなく、堅牢で拡張性の高いドメイン駆動型アプリケーションへと生まれ変わる。

プロフェッショナルなエンジニアたる者、動くだけのコードではなく、「保守しやすく、変更に強いコード」をデザインしよう。あなたの背中を見ているチームメンバーや、将来の保守担当者が、必ず感謝する日が来る。

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