【テクニカル・上級編】Pageオブジェクトの座標系を完全理解:図面の描画領域とページサイズをVBAで動的制御する – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:Pageオブジェクトの座標系と動的レイアウト制御

Visioの自動化において、多くのエンジニアが最初に直面し、そして深い絶望を味わうのが「座標系と単位系」の壁である。
ExcelやWordの二次元平面とは異なり、VisioはCADに肉薄する厳密な数学的モデル――ページ座標、図面座標、プリンター用紙のマージン、そして縮尺(Scale)という複雑なレイヤー構造を持っている。

本稿では、Visio VBAの根幹をなす`Page`オブジェクトと座標系を完全に掌握し、動的な図面生成システムを構築するための極限の知見を公開する。

1. Visio座標系の核心:インチ、内部単位、そして「PageSheet」の罠

Visioの内部エンジンは、一貫して「インチ(Inches)」をベースに構築されている。VBAでどのような単位を指定しようとも、最終的には内部インチに換算されて処理される。さらに厄介なのは、Visioの画面上の「ミリメートル表示」や「縮尺」は、あくまでプレゼンテーション層のマスクに過ぎないという点だ。

座標変換の数理モデル

プログラムからシェイプを配置する際、以下の3つの空間を意識する必要がある。

1. ページ空間 (Page Space): ページ左下を原点 $(0, 0)$ とする絶対座標(単位:インチ)。
2. 図面空間 (Drawing Space): 縮尺(Scale)が適用された論理座標。
3. マスター/シェイプローカル空間: シェイプ自体の中心を原点とする相対座標。

これを動的に制御するためには、`Page`オブジェクトの背後にある「PageSheet(ページのシェイプシート)」、すなわち `Page.PageSheet` セル群を直接叩く必要がある。

2. ページサイズ・向き・マージンの動的最適化ロジック

自動生成するダイアグラムの規模に応じて、用紙サイズ(A4、A3、あるいはカスタムサイズ)や向き(縦・横)をVBAから完全に制御する。単にプロパティを設定するだけでは、既存のシェイプ群が切れたり、意図しないオフセットが発生する。

以下のコードは、指定されたページを用紙サイズ(例: A4横)に自動フィットさせ、マージンを極限まで切り詰める実用プロシージャである。

Option Explicit

‘ ==============================================================================
‘ 担当: チーフアーキテクト
‘ 概要: Pageオブジェクトの用紙サイズ・向き・描画領域をプログラムから動的に最適化する
‘ ==============================================================================
Public Sub OptimizePageLayout(ByVal targetPage As Visio.Page, ByVal isLandscape As Boolean)
Dim vsoPageSheet As Visio.Shape
Set vsoPageSheet = targetPage.PageSheet

‘ オブジェクトのライフサイクル管理:エラーハンドリングと参照の確実な解放
On Error GoTo ErrorHandler

‘ 画面描画の凍結(パフォーマンス爆速化の基本)
Application.ScreenUpdating = False

‘ 1. 用紙サイズの指定(A4: 幅 8.27インチ, 高さ 11.69インチ)
‘ ※Visioの内部単位は常にインチであることを忘れてはならない
Dim pageWidth As Double, pageHeight As Double

If isLandscape Then
pageWidth = 11.69
pageHeight = 8.27
Else
pageWidth = 8.27
pageHeight = 11.69
End If

‘ PageSheetのセルの値を直接書き換える(Visioセルのインデックス指定)
‘ visSectionObject = 1, visRowPageLayout = 3 (または Page Properties セル)
vsoPageSheet.CellsU(“PageWidth”).FormulaU = pageWidth & ” in”
vsoPageSheet.CellsU(“PageHeight”).FormulaU = pageHeight & ” in”

‘ 2. プリンター用紙の設定と連動させる場合のマージンゼロ化
vsoPageSheet.CellsU(“PrintLeftMargin”).FormulaU = “0.25 in”
vsoPageSheet.CellsU(“PrintRightMargin”).FormulaU = “0.25 in”
vsoPageSheet.CellsU(“PrintTopMargin”).FormulaU = “0.25 in”
vsoPageSheet.CellsU(“PrintBottomMargin”).FormulaU = “0.25 in”

‘ 3. ページの自動サイズ調整(Auto-Size)を明示的に無効化し、カスタムレイアウトを死守する
‘ 自動サイズが有効なままだと、シェイプ配置時に意図せずページが拡張される
If vsoPageSheet.CellExistsU(“AutoSizeActive”, 0) Then
vsoPageSheet.CellsU(“AutoSizeActive”).FormulaU = “0”
End If

CleanUp:
Application.ScreenUpdating = True
Set vsoPageSheet = Nothing
Exit Sub

ErrorHandler:
MsgBox “ページレイアウトの最適化中に致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub

3. 単位系の罠を回避する:ミリ/インチの動的変換関数

現場では「ユーザーはミリメートルで入力したいが、Visioはインチで受け付ける」というジレンマに常に直面する。この差異を安全に吸収するため、専用の変換レイヤー関数をモジュール内にカプセル化するべきである。

‘ ミリメートルをVisio内部インチ文字列に変換するヘルパー関数
Public Function MmToInchesStr(ByVal mmValue As Double) As String
Const MM_PER_INCH As Double = 25.4
Dim inches As Double
inches = mmValue / MM_PER_INCH

‘ 浮動小数点の誤差を丸めつつ、Visioが解釈できる文字列を返す
MmToInchesStr = Format(inches, “0.0000”) & ” in”
End Function

この関数を使用することで、コードの可読性が飛躍的に向上し、レガシー環境特有の単位混同バグを根絶できる。

4. 動的描画領域の計算とシェイプのセンタリング配置

単にページサイズを変えるだけでは実務上の自動化としては不十分だ。生成されたシェイプ群のバウンディングボックス(BBox)を動的に算出し、ページの中央に自動配置するアルゴリズムを実装する。

Public Sub CenterShapesOnPage(ByVal targetPage As Visio.Page)
On Error GoTo ErrorHandler

Application.ScreenUpdating = False

Dim vsoPageSheet As Visio.Shape
Set vsoPageSheet = targetPage.PageSheet

Dim pageWidth As Double, pageHeight As Double
pageWidth = vsoPageSheet.CellsU(“PageWidth”).ResultIU
pageHeight = vsoPageSheet.CellsU(“PageHeight”).ResultIU

‘ ページ上の全シェイプのBBox(ピン座標の最小/最大)を取得するため、
‘ Page.BoundingBox を利用する
Dim minX As Double, minY As Double, maxX As Double, maxY As Double
targetPage.BoundingBox visBBoxExtents, minX, minY, maxX, maxY

‘ シェイプ群の幅と高さを算出
Dim shapesWidth As Double, shapesHeight As Double
shapesWidth = maxX – minX
shapesHeight = maxY – minY

‘ ページ中央に配置するためのオフセット計算
Dim offsetX As Double, offsetY As Double
offsetX = (pageWidth – shapesWidth) / 2 – minX
offsetY = (pageHeight – shapesHeight) / 2 – minY

‘ ページ内の全トップレベルシェイプを一括移動
Dim shp As Visio.Shape
For Each shp in targetPage.Shapes
‘ グループの親や特定のレイヤーを除外するロジックをここに挟むと堅牢性が増す
If shp.Master.Type <> visTypeMasterShortcut Then
‘ 既存の位置にオフセットを加算
‘ ※PinX/PinYを直接操作するのが最も確実
Dim currentPinX As Double, currentPinY As Double
currentPinX = shp.CellsU(“PinX”).ResultIU
currentPinY = shp.CellsU(“PinY”).ResultIU

shp.CellsU(“PinX”).FormulaU = (currentPinX + offsetX) & ” in”
shp.CellsU(“PinY”).FormulaU = (currentPinY + offsetY) & ” in”
End If
Next shp

CleanUp:
Set shp = Nothing
Set vsoPageSheet = Nothing
Application.ScreenUpdating = True
Exit Sub

ErrorHandler:
MsgBox “センタリング処理中にエラー: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

5. チーフアーキテクトからの提言:メモリ管理とCOMコンポーネントの解放

Visio VBAにおける最大の性能劣化要因、そしてメモリリークの温床は、暗黙的なオブジェクトの参照保持である。
`targetPage.Shapes` や `vsoPageSheet.CellsU` をループ内で安易に多用すると、VBAの裏側でCOMラッパーが乱立し、ガベージコレクションが追いつかなくなる。

1. 変数の明示的な型宣言 (`Dim … As Visio.XXX`) を徹底する
2. ループを抜ける前、あるいはプロシージャの終了時に必ず `Set obj = Nothing` を実行する
3. 大量のシェイプ生成時は `Application.ScreenUpdating = False` と `Application.EventEnabled = False` をペアで使用する

この基本を徹底したコードベースこそが、数千個のシェイプを扱う巨大なエンタープライズ・アーキテクチャにおいても、破綻することなく高速に動作し続ける唯一の道である。妥協なきコードを書け。

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