【テクニカル・上級編】接続情報の詳細解析:Shape.ConnectsとConnectオブジェクトによる接続点・矢印方向の精密抽出 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:接続情報の詳細解析

Visio VBAによる業務自動化。この領域に深く踏み込む者であれば、単なる図形の配置やプロパティ操作に留まらず、図形間の「接続」という最も動的で情報量の多い関係性を正確に把握する重要性を痛感しているはずだ。我々、長年レガシーアーキテクチャの最前線に立ち、システムの血肉と化してきたエンジニアにとって、Visioの図面は単なる絵ではない。それは、業務プロセス、ネットワークトポロジ、システムアーキテクチャの「真実」を映し出す、生きたデータソースである。

本稿では、`Shape.Connects`コレクションと`Connect`オブジェクトを深く掘り下げ、単なる接続の有無だけでなく、「どの接続ポイント(X/Yセル)」に「どの方向」でコネクタが結合しているかまで、その真髄を解析するVBA実装手順を伝授する。これは、Visio図面から極めて精度の高いリレーションシップデータを抽出し、外部システムとの連携、あるいは高度な自動解析を実現するための、避けては通れない極致である。

1. 接続情報の奥底を覗く:なぜ`Connects`コレクションが重要なのか

Visio図面における「接続」は、表面上は一本の線に見えるかもしれない。しかし、その裏側には複雑なロジックと膨大な情報が隠されている。我々が自動化エンジニアとして求めるのは、その表面的な情報ではなく、コネクタがどの図形のどの「接続点」に、どのような「糊付け」のタイプで接続されているか、といった詳細なメタデータである。

従来のVBAによる接続解析では、コネクタの始点・終点座標と図形の境界ボックスを比較するような、あいまいな手法が散見された。しかし、これはVisioが持つ本来の接続ロジックを無視したものであり、精度に欠ける。`Shape.Connects`コレクションは、Visioが内部的に管理している接続情報をそのままVBAオブジェクトとして提供してくれる、まさに宝の山なのだ。

このコレクションは、コネクタであればその始点・終点が接続している図形と接続点の情報を、図形であればそれに接続している全てのコネクタの情報を網羅する。特にコネクタの場合、その`Connects`コレクションを解析することで、真に正確な接続関係を把握することが可能となる。

2. `Connect`オブジェクトの解剖:接続点と方向性の真実

`Shape.Connects`コレクションを構成する個々の`Connect`オブジェクトこそが、接続情報の核心を握っている。このオブジェクトが提供するプロパティ群を正しく解釈することで、我々はVisioの内部モデルと対話することができる。

主要なプロパティとその意味を深掘りする。

  • `Connect.FromSheet` / `Connect.ToSheet`:
  • コネクタの場合、`FromSheet`はコネクタの始点が接続している図形、`ToSheet`は終点が接続している図形を指す。
  • 図形の場合、`FromSheet`はコネクタ、`ToSheet`はそのコネクタのもう一方の端が接続している図形を指す。
  • これらのプロパティは、直接`Visio.Shape`オブジェクトを返すため、接続先の図形に対するあらゆる操作や情報取得が可能となる。これがシステム間連携におけるリレーショナルデータの基盤となる。
  • `Connect.FromCell` / `Connect.ToCell`:
  • これは非常に重要だ。コネクタの始点/終点が、接続先の図形のどのShapeSheetセルに結合しているかを示す「セル名」を返す。
  • 例えば、`”Connections.X1″`、`”Connections.Y1″`のような固定接続点や、`”PinX”`、`”PinY”`(図形の中心点)、`”BeginX”`、`”EndX”`(コネクタの始点・終点)などが該当する。
  • このセル名こそが、接続の「物理的」なポイントを特定する鍵となる。`Shape.Cells(Connect.FromCell).ResultIU`のようにアクセスすることで、その接続点の実際の座標値や関連する情報を取得できる。
  • `Connect.GlueType`:
  • 接続の「種類」を示す。`visGlueTypeStatic`(静的接続、固定接続点への結合)や`visGlueTypeDynamic`(動的接続、図形の境界線への結合)が代表的だ。
  • `Dynamic`な接続の場合、コネクタは図形が移動・リサイズされても常にその境界線に沿って移動する。`Static`な接続は、特定の接続点に固定される。
  • この情報は、図形変更時の挙動予測や、接続の堅牢性を評価する上で不可欠である。
  • `Connect.FromPart` / `Connect.ToPart`:
  • これは、接続元の図形(コネクタ)または接続先の図形(ターゲット)の、どの「部分」に接続しているかを示す値だ。Visio SDKの`VisPartType`定数で定義される。
  • 例えば、`visFromBeginX`(コネクタの始点)、`visFromEndX`(コネクタの終点)、`visFromTopEdge`(図形の上辺)、`visFromLeftEdge`(図形の左辺)、`visFromConnect`(特定の接続点)などがある。
  • 特に`visFromConnect`の場合、その後に続く数値がShapeSheetの`Connections`セクションの行インデックスを示す。これにより、固定接続点の詳細な位置を特定できる。
  • このプロパティを解析することで、単なる「接続」ではなく、「上辺に結合しているのか」「特定の接続点1に結合しているのか」といった、よりセマンティックな情報を抽出できる。

これらのプロパティを組み合わせることで、我々はVisio図面から極めて粒度の高い接続トポロジを再構築することが可能になる。

3. VBA実装:接続点・矢印方向の精密抽出

ここからが実践だ。アクティブなVisioドキュメント内の全てのコネクタに対し、その接続情報を詳細に解析し、出力するVBAプロシージャを示す。

Option Explicit

‘// Visio Applicationオブジェクトをグローバルで宣言 (適切なライフサイクル管理が重要)
Private vsoApp As Visio.Application

‘// —————————————————————————————————-
‘// プロシージャ名: ExtractDetailedVisioConnections
‘// 概要: アクティブなVisioドキュメント内のコネクタから、詳細な接続情報を抽出します。
‘// 接続先の図形、接続ポイント(セル)、接着タイプ、接続部分を解析し、
‘// イミディエイトウィンドウに出力します。
‘//
‘// 目的:
‘// – Visioの内部接続モデル (Connectsコレクション) の理解を深める。
‘// – 高度なデータ抽出とシステム連携の基盤を構築する。
‘// – オブジェクトのライフサイクルとパフォーマンス最適化のベストプラクティスを示す。
‘//
‘// ターゲット読者: シニアVBAエンジニア、システムアーキテクト
‘// —————————————————————————————————-
Public Sub ExtractDetailedVisioConnections()

‘// — 1. オブジェクト宣言と初期化 —
‘// 大規模なVisioファイルや複雑な接続構造を扱う際、明示的なオブジェクト管理は
‘// メモリリークやパフォーマンス低下を防ぐために不可欠です。
‘// 特に古いVBA環境では、ガベージコレクションの挙動が予測しにくいため、
‘// 慎重なオブジェクト解放が求められます。
Dim vsoDoc As Visio.Document
Dim vsoPage As Visio.Page
Dim vsoShape As Visio.Shape
Dim vsoConnect As Visio.Connect
Dim vsoFromShape As Visio.Shape ‘ 接続元 (コネクタ) の始点が接続している図形
Dim vsoToShape As Visio.Shape ‘ 接続元 (コネクタ) の終点が接続している図形
Dim strOutput As String ‘ 出力文字列バッファ

‘// ——————————————————————–
‘// 2. パフォーマンス最適化とエラーハンドリング
‘// – 画面更新の停止: 大量の図形操作やループ処理中にVisioの再描画を抑制し、処理速度を大幅に向上させます。
‘// – イベント抑制: Visioのイベントハンドラが無駄に発火するのを防ぎます。
‘// – エラーハンドリング: 想定外の状況(ドキュメントがない、図形が破損しているなど)に対応し、
‘// スクリプトの異常終了を防ぎ、安定した運用を保証します。
‘// ——————————————————————–
On Error GoTo ErrorHandler

Set vsoApp = Visio.Application
vsoApp.ScreenUpdating = False
vsoApp.EventsEnabled = False ‘ イベント抑制も重要な最適化ポイント

‘// アクティブなドキュメントが存在するか確認
If vsoApp.ActiveDocument Is Nothing Then
MsgBox “アクティブなVisioドキュメントが開かれていません。”, vbCritical
GoTo CleanUp
End If
Set vsoDoc = vsoApp.ActiveDocument

Debug.Print “— Visio接続情報 詳細解析レポート —”
Debug.Print “ドキュメント: ” & vsoDoc.Name
Debug.Print “処理開始時刻: ” & Now

‘// — 3. 全ページを走査 —
For Each vsoPage In vsoDoc.Pages
Debug.Print vbCrLf & “ページ: ” & vsoPage.Name
Debug.Print “————————————”

‘// — 4. ページ内の全図形を走査 —
For Each vsoShape In vsoPage.Shapes
‘// コネクタであるかどうかの判定 (Shape.OneD = True)
‘// 2次元図形 (OneD = False) はコネクタを持たないが、
‘// 自身に接続されているコネクタ情報を持つ場合がある。
‘// 今回はコネクタ (OneD = True) を起点に解析する。
If vsoShape.OneD Then ‘ コネクタ図形の場合
Debug.Print vbCrLf & “コネクタ図形 (ID:” & vsoShape.ID & “, NameU:'” & vsoShape.NameU & “‘)”

‘// — 5. コネクタのConnectsコレクションを走査 —
‘// コネクタは通常、2つのConnectオブジェクトを持つ (始点と終点)。
For Each vsoConnect In vsoShape.Connects
‘// Connectオブジェクトのプロパティを解析
Set vsoFromShape = Nothing
Set vsoToShape = Nothing

‘// FromSheetとToSheetは、Connectオブジェクトの性質によって意味が異なる
‘// コネクタ図形が持つConnectオブジェクトの場合:
‘// – Connect.FromSheet: コネクタの始点が接続している図形
‘// – Connect.ToSheet: コネクタの終点が接続している図形
‘// – ただし、vsoConnectはコネクタ自身のオブジェクトではないため、
‘// vsoConnect.FromSheetはコネクタの始点が接続している図形、
‘// vsoConnect.ToSheetはそのコネクタの始点が接続している図形
‘// つまり、Connectオブジェクトは常に「ある図形の特定の端」と
‘// 「別の図形の特定の接続点」の関係を表現する。
‘// コネクタの始点/終点接続は、このコレクション内で2つのConnectオブジェクトとして表現される。

‘// Connectオブジェクトが表す接続の方向を判定 (From/Toは相対的)
‘// ここでは、Connect.FromCellがBeginX/Yである場合を「始点側の接続」と見なす
‘// Connect.FromCellがEndX/Yである場合を「終点側の接続」と見なす
Dim isBeginConnection As Boolean
isBeginConnection = (InStr(1, vsoConnect.FromCell, “Begin”, vbTextCompare) > 0)

strOutput = ” ”
If isBeginConnection Then
strOutput = strOutput & “[始点側接続]: ”
Set vsoToShape = vsoConnect.ToSheet ‘ コネクタの始点が接続している図形
Else
strOutput = strOutput & “[終点側接続]: ”
Set vsoToShape = vsoConnect.ToSheet ‘ コネクタの終点が接続している図形
End If

‘// 接続先の図形情報を取得
If Not vsoToShape Is Nothing Then
strOutput = strOutput & “接続先図形 (ID:” & vsoToShape.ID & “, NameU:'” & vsoToShape.NameU & “‘)”
Else
strOutput = strOutput & “接続先図形: (なし)”
End If

‘// 接続元のセル情報 (コネクタ側の接続点)
strOutput = strOutput & vbCrLf & ” – コネクタ側接続点セル: ‘” & vsoConnect.FromCell & “‘”
‘// 接続先のセル情報 (ターゲット図形側の接続点)
strOutput = strOutput & vbCrLf & ” – ターゲット側接続点セル: ‘” & vsoConnect.ToCell & “‘”

‘// GlueTypeの解析
strOutput = strOutput & vbCrLf & ” – 接着タイプ (GlueType): ” & GetGlueTypeString(vsoConnect.GlueType)

‘// FromPart/ToPartの解析 (詳細な接続部分)
‘// FromPart: コネクタ側のFromCellが、ターゲット図形のどの部分に接続しているか
‘// ToPart: ターゲット図形側のToCellが、コネクタのどの部分に接続しているか (これはあまり使われない)
‘// 実際には、Connect.FromPart はコネクタ自身の接続点を、Connect.ToPart はターゲット図形の接続点を指す。
strOutput = strOutput & vbCrLf & ” – コネクタ側接続部分 (FromPart): ” & GetPartTypeString(vsoConnect.FromPart)
strOutput = strOutput & vbCrLf & ” – ターゲット側接続部分 (ToPart): ” & GetPartTypeString(vsoConnect.ToPart)

‘// 接続点の座標値も取得してみる (ToCellが示すターゲット図形上のポイント)
If Not vsoToShape Is Nothing And vsoToShape.SectionExists(visSectionConnections, 0) Then
If vsoToShape.CellsExists(vsoConnect.ToCell, visExistsAnywhere) Then
Dim vsoCellX As Visio.Cell
Dim vsoCellY As Visio.Cell
Dim strToCellX As String
Dim strToCellY As String

‘// ToCellが”Connections.Xn”形式の場合、対応するY座標を取得
If InStr(1, vsoConnect.ToCell, “Connections.X”, vbTextCompare) > 0 Then
strToCellX = vsoConnect.ToCell
strToCellY = Replace(vsoConnect.ToCell, “Connections.X”, “Connections.Y”)
If vsoToShape.CellsExists(strToCellY, visExistsAnywhere) Then
strOutput = strOutput & vbCrLf & ” – ターゲット側接続点座標 (ToCell): ” _
& vsoToShape.Cells(strToCellX).ResultIU & “, ” _
& vsoToShape.Cells(strToCellY).ResultIU & ” (ページ単位)”
Else
strOutput = strOutput & vbCrLf & ” – ターゲット側接続点座標 (ToCell): ” _
& vsoToShape.Cells(strToCellX).ResultIU & ” (Xのみ, ページ単位)”
End If
ElseIf InStr(1, vsoConnect.ToCell, “Connections.Y”, vbTextCompare) > 0 Then
‘ Connections.Ynのみの場合 (Xは別途取得または省略)
strOutput = strOutput & vbCrLf & ” – ターゲット側接続点座標 (ToCell): ” _
& vsoToShape.Cells(vsoConnect.ToCell).ResultIU & ” (Yのみ, ページ単位)”
Else ‘ その他のセル (PinX, PinYなど)
If vsoToShape.CellsExists(vsoConnect.ToCell, visExistsAnywhere) Then
strOutput = strOutput & vbCrLf & ” – ターゲット側接続点値 (ToCell): ” _
& vsoToShape.Cells(vsoConnect.ToCell).ResultIU & ” (ページ単位)”
Else
strOutput = strOutput & vbCrLf & ” – ターゲット側接続点値 (ToCell): (セル値取得不可)”
End If
End If
End If
End If

Debug.Print strOutput

‘// Connectオブジェクトの明示的な解放
‘// ループ内で大量に生成されるオブジェクトは、VBAのGCではすぐに解放されない場合がある。
‘// 明示的なNothing代入は、メモリフットプリントを抑え、安定稼働に寄与する。
Set vsoConnect = Nothing
Next vsoConnect
End If
Set vsoShape = Nothing ‘ Shapeオブジェクトもループ終端で解放
Next vsoShape
Set vsoPage = Nothing ‘ Pageオブジェクトも解放
Next vsoPage

Debug.Print vbCrLf & “処理終了時刻: ” & Now
Debug.Print “— レポート終了 —”

CleanUp:
‘// — 6. 最終的なオブジェクト解放と設定のリセット —
‘// プロシージャの終了時に必ず実行されるようにする。
If Not vsoDoc Is Nothing Then Set vsoDoc = Nothing
If Not vsoApp Is Nothing Then
vsoApp.ScreenUpdating = True ‘ 画面更新を元に戻す
vsoApp.EventsEnabled = True ‘ イベント抑制を解除
Set vsoApp = Nothing
End If
Exit Sub

ErrorHandler:
‘// エラー発生時の処理
Debug.Print “!!! エラー発生: ” & Err.Number & ” – ” & Err.Description
Resume CleanUp ‘ クリーンアップ処理へジャンプし、アプリケーションの状態を正常に戻す

End Sub

‘// —————————————————————————————————-
‘// 関数名: GetGlueTypeString
‘// 概要: Visioの接着タイプ (visGlueType) 定数を人間が読める文字列に変換します。
‘// —————————————————————————————————-
Private Function GetGlueTypeString(ByVal glueType As Visio.VisGlueTypes) As String
Select Case glueType
Case visGlueTypeNoGlue: GetGlueTypeString = “接着なし”
Case visGlueTypeStatic: GetGlueTypeString = “静的接着 (固定接続点)”
Case visGlueTypeDynamic: GetGlueTypeString = “動的接着 (図形境界)”
Case visGlueTypeWalking: GetGlueTypeString = “ウォーキング接着 (図形境界上の最も近い点)”
Case Else: GetGlueTypeString = “未知の接着タイプ (” & glueType & “)”
End Select
End Function

‘// —————————————————————————————————-
‘// 関数名: GetPartTypeString
‘// 概要: Visioの接続部分タイプ (VisPartType) 定数を人間が読める文字列に変換します。
‘// “Connections.Xn”形式の接続点も識別します。
‘// —————————————————————————————————-
Private Function GetPartTypeString(ByVal partType As Long) As String
Const visFromConnect As Long = &H80000000 ‘ Connectionsセクションの接続点を表すビットフラグ

If (partType And visFromConnect) <> 0 Then
‘ Connectionsセクションの接続点の場合
‘ 上位ビットを除去して、Connectionsセクションの行インデックスを取得
Dim connectionIndex As Long
connectionIndex = partType And Not visFromConnect
GetPartTypeString = “固定接続点 (Connections.X” & connectionIndex & “/Y” & connectionIndex & “)”
Exit Function
End If

Select Case partType
Case visFromNone: GetPartTypeString = “なし”
Case visFromBeginning: GetPartTypeString = “開始点 (BeginX/Y)”
Case visFromEnd: GetPartTypeString = “終了点 (EndX/Y)”
Case visFromTopEdge: GetPartTypeString = “上辺”
Case visFromBottomEdge: GetPartTypeString = “下辺”
Case visFromLeftEdge: GetPartTypeString = “左辺”
Case visFromRightEdge: GetPartTypeString = “右辺”
Case visFromCentroid: GetPartTypeString = “重心”
Case visFromMiddle: GetPartTypeString = “中央”
Case visFromFirstPin: GetPartTypeString = “最初のピン”
Case visFromLastPin: GetPartTypeString = “最後のピン”
Case Else: GetPartTypeString = “未知の接続部分 (” & partType & “)”
End Select
End Function

コード解説と極限の知見

1. オブジェクトのライフサイクル管理:

  • `vsoApp`, `vsoDoc`, `vsoPage`, `vsoShape`, `vsoConnect`といったVisioオブジェクトは、VBAのメモリ空間において参照カウントベースで管理される。特にループ内で大量に生成・参照される`vsoConnect`や`vsoShape`のようなオブジェクトは、処理が終了しても即座に解放されない場合がある。
  • `Set obj = Nothing`による明示的な解放は、メモリフットプリントを低減し、特に長時間稼働するバッチ処理や大規模なVisioファイル処理において、メモリ不足やパフォーマンス劣化を防ぐための必須プラクティスである。これはレガシーなCOMオブジェクトを扱う上での鉄則だ。
  • `Private vsoApp As Visio.Application`としてアプリケーションオブジェクトをグローバルに宣言することで、プロシージャスコープを超えたアプリケーションレベルでの管理を示唆している。実運用では、シングルトンパターンを模倣したクラスモジュールで`Visio.Application`インスタンスを管理することも有効だ。

2. パフォーマンス最適化:

  • `Application.ScreenUpdating = False`と`Application.EventsEnabled = False`は、Visio VBAにおいて最も基本的なパフォーマンスチューニングだ。図面再描画やイベント処理のオーバーヘッドは、大量の図形を扱う際に致命的なボトルネックとなる。これらの設定は、処理の開始時にオフにし、終了時に必ず元に戻す(`CleanUp`セクション)のがベストプラクティスだ。エラー発生時にもリセットされるように`ErrorHandler`から`CleanUp`へジャンプさせることで、ロバスト性を確保している。

3. エラーハンドリング:

  • `On Error GoTo ErrorHandler`は、予期せぬエラー(例: `ActiveDocument`が`Nothing`、破損した図形データなど)が発生した場合にスクリプトが異常終了するのを防ぐ。業務システムとしてVBAを利用する場合、安定稼働は最優先事項であり、エラー時の振る舞いを明確に定義することは不可欠である。エラーログの出力やユーザーへの適切な通知も考慮すべきだ。

4. `Connect.FromPart` / `Connect.ToPart`の精密解析:

  • `GetPartTypeString`関数内で、`visFromConnect`というマジックナンバー(`&H80000000`)をビット演算で検出している点に注目してほしい。これは、Visioが内部的に`Connections`セクションの固定接続点を表現する際の特殊なフラグであり、Visio SDKのドキュメントを深く読み込んでいないと見落としがちな情報だ。このビットが立っている場合、残りのビットが`Connections`セクションの行インデックスを示す。このようなVisioの内部モデルに関する洞察こそが、単なる機能利用を超えた「極限の知見」と言える。

5. ShapeSheetセルへの直接アクセス:

  • `vsoShape.Cells(strToCellX).ResultIU`のように、`ShapeSheet`のセル名を用いて直接値を取得している。`ResultIU`プロパティは、内部単位での値を返すため、より正確な座標情報が必要な場合に有用だ。システム間連携でVisioデータを他のCAD/GISシステムに渡す場合、この内部単位での座標値が非常に重要となる。

4. レガシー環境とシステム連携における価値

この詳細な接続情報抽出の技術は、特に以下のシナリオでその真価を発揮する。

  • レガシーVisioシステムの保守・移行:

古いVisioバージョンで作成された図面は、APIの互換性やパフォーマンス特性が現在の環境と異なる場合がある。しかし、`Connects`コレクションの基本的な構造は比較的安定しているため、この技術は既存の膨大な資産から正確な情報を抽出し、新しいシステムへのデータ移行や、既存システムの保守性を高める基盤となる。VBAの実行環境が32bit/64bitで異なる場合でも、COMオブジェクトとしてのVisio APIは透過的に動作するが、Windows APIのP/Invokeを多用するようなケースでは注意が必要だ(今回は直接的なWindows APIは使用していないが、Extensibilityの観点からは考慮すべき点である)。

  • 異種システム間連携の強化:

Visioはビジネスプロセス、ネットワーク、インフラストラクチャなど、多岐にわたるドメインの視覚化に用いられる。これらの図面から、ノード(図形)とエッジ(コネクタ)のリレーションシップを正確に抽出し、データベース、CMDB (Configuration Management Database)、ワークフローエンジン、シミュレーションツール、さらには他のCAD/BIMシステムなどへ連携させることは、現代のエンタープライズアーキテクチャにおいて不可欠である。
抽出された接続情報は、CSV、XML、JSON形式で出力され、各システムのAPIやデータインポート機能を通じて、Visioを「真の情報源」として活用することを可能にする。単なる図形IDと接続図形IDのペアだけでなく、「図形Aの接続点X1と、図形Bの上辺が、動的接着で結合している」といった粒度の高い情報は、より高度な自動化や分析を可能にする。

  • 自動解析と診断:

例えば、ネットワーク図において二重接続や未接続のポートを検出したり、プロセスフロー図において非効率な経路やループを自動的に特定したりする診断ツールをVisio VBAで構築する際の、まさに「骨格」となる情報だ。

5. 結論:Visio VBAの真髄を極める

Visio VBAは、古くから存在する技術スタックであるものの、その奥深さは未だ多くの可能性を秘めている。特に`Connects`コレクションと`Connect`オブジェクトをここまで詳細に解析する能力は、単なるマクロ作成者と、Visioをシステムの一部として駆動させるチーフアーキテクトとの間にある、決定的な知見の差を浮き彫りにする。

オブジェクトのライフサイクルを意識した堅牢なコード、パフォーマンスを最大限に引き出す最適化、そしてVisio内部モデルへの深い洞察。これらすべてが融合して初めて、Visioは単なる描画ツールから、業務の真実を語る強力なデータソースへと昇華する。本稿で紹介した技術が、読者諸氏のVisio VBAによる自動化プロジェクトにおいて、新たな価値創造の一助となることを願ってやまない。

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