描画の限界を超える:Visio VBAで実現する「動的オートフィット」の極意
こんにちは。今日もコードの海を、論理の羅針盤を持って進んでいますか?
Visioで図面を作成しているとき、「あ、シェイプを増やしすぎてページからはみ出してしまった……」という経験はありませんか? 手動でページサイズを調整するのは簡単ですが、もしあなたが数百、数千のシェイプを扱う自動生成ツールを作っているとしたら、その手動作業は「自動化」の精神に反しますよね。
多くの初学者は、ここで「マクロの記録」を使おうとします。しかし、残念ながら「マクロの記録」では、図面の大きさに合わせてページサイズを「計算」することはできません。 記録できるのは、「今、この瞬間にこのサイズに変更した」という一回限りの操作だけだからです。
今日は、マクロの記録という「点」の操作から脱却し、図面の状況を読み取って動的に判断する「線」のプログラミングへとステップアップしましょう。シェイプの配置を計算し、ページサイズを自動で拡張する「オートフィット機能」の作り方を伝授します。
ここをクリアすれば、Visio VBAの基本はバッチリですよ。
1. 本質的な考え方:バウンディングボックスを捉える
「ページサイズを自動調整する」というタスクを、エンジニアの思考で分解してみましょう。私たちが知りたいのは、「すべてのシェイプを包み込める、最小限の長方形のサイズ」です。
これを専門用語でバウンディングボックス(Bounding Box)と呼びます。
ロジックのステップは以下の3つです。
1. 走査(Scan): ページ内にあるすべてのシェイプを一つずつ確認する。
2. 極値の特定(Find Extremes): 全シェイプの中で「一番左」「一番右」「一番上」「一番下」の座標を特定する。
3. 適用(Apply): 特定した座標に基づき、ページサイズ(PageSheetのプロパティ)を書き換える。
単にシェイプのサイズを合わせるのではなく、「座標の最大値と最小値の差」を計算するのが、プロフェッショナルなアプローチです。
2. 実装:動的オートフィット・エンジン
それでは、実際に動作するコードを見ていきましょう。このコードは、単にサイズを変えるだけでなく、図面が窮屈にならないよう「余白(Margin)」を持たせる設計にしています。
Option Explicit
''' <summary>
''' ページ内の全シェイプを解析し、ページサイズを自動的に最適化する
''' </summary>
Sub AutoFitPageToShapes()
Dim pg As Visio.Page
Dim shp As Visio.Shape
Dim minX As Double, maxX As Double
Dim minY As Double, maxY As Double
Dim margin As Double
Dim foundShape As Boolean
' --- 設定領域 ---
' 余白(ポイント単位)。図面が端に寄りすぎるのを防ぎます。
margin = 36 ' 0.5インチ相当
' ----------------
Set pg = ActivePage
foundShape = False
' 1. 初期値の設定
' 最初のシェイプを見つけるまで、極値の初期化は慎重に行う必要があります。
' ここでは、非常に大きな値と小さな値で初期化します。
minX = 1000000: maxX = -1000000
minY = 1000000: maxY = -1000000
' 2. 全シェイプの走査
' Page.Shapes コレクションをループして、各シェイプの境界を調べます。
For Each shp In pg.Shapes
' グループ化されたシェイプや、非表示のシェイプをどう扱うかは設計次第ですが、
' ここでは全てのシェイプの境界を計算対象にします。
' GetBoundingBox メソッドを使用すると、シェイプの四隅の座標が即座に取得できます。
' 引数: (左, 上, 右, 下)
Dim bBox(0 To 3) As Double
shp.GetBoundingBox bBox(0), bBox(1), bBox(2), bBox(3)
' 極値を更新
If bBox(0) < minX Then minX = bBox(0) ' Left
If bBox(2) > maxX Then maxX = bBox(2) ' Right
If bBox(1) > maxY Then maxY = bBox(1) ' Top
If bBox(3) < minY Then minY = bBox(3) ' Bottom
foundShape = True
Next shp
' 3. ページサイズへの適用
If foundShape Then
' 計算された幅と高さに余白を加える
Dim newWidth As Double
Dim newHeight As Double
newWidth = (maxX - minX) + (margin * 2)
newHeight = (maxY - minY) + (margin * 2)
' VisioのPageSheet(ページの設定シート)にアクセスして値を書き込む
' CellsU("プロパティ名") を使うのが、Unicode対応の確実な方法です。
On Error GoTo ErrorHandler
With pg.PageSheet
.CellsU("PageWidth").FormulaU = newWidth
.CellsU("PageHeight").FormulaU = newHeight
' オプション:図面が中央に来るように、ページ原点を調整する場合もありますが、
' 今回はシンプルにサイズ変更のみを行います。
End With
MsgBox "ページサイズを最適化しました。" & vbCrLf & _
"幅: " & Round(newWidth, 1) & " pt" & vbCrLf & _
"高さ: " & Round(newHeight, 1) & " pt", vbInformation, "完了"
Else
MsgBox "ページ内にシェイプが見つかりませんでした。", vbExclamation, "警告"
End If
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Description, vbCritical, "エラー"
End Sub
3. 伝説のアーキテクトが教える「陥りやすい罠」
このコードを動かす際、あるいは自分で改良する際に、絶対に避けて通れない「設計の急所」が3つあります。
① 座標系の理解(Points vs Millimeters)
Visio VBAの世界では、サイズや位置の基本単位は 「ポイント (pt)」 です。
もしあなたが「ミリメートルで指定したい」と考えて、`30`(30mmのつもり)と入力すると、Visioは「30ポイント(約1cm)」として扱ってしまいます。設計段階で、単位の変換ロジック(1mm $\approx$ 2.834pt)を組み込んでおくのがプロの仕事です。
② `GetBoundingBox` の威力
初心者の方は、`shp.x` や `shp.width` を組み合わせて計算しようとしがちです。しかし、複雑な図形(回転しているものや、複数の線が組み合わさったもの)の場合、それだけでは正確な境界を計算できません。
`shp.GetBoundingBox` メソッドは、オブジェクトの幾何学的な外枠を瞬時に計算してくれる、非常に強力な武器です。これを使うだけで、計算ロジックの複雑さが劇的に軽減されます。
③ パフォーマンスとPageSheet
`pg.PageSheet.CellsU(“PageWidth”).FormulaU = …` という記述に注目してください。
Visioのページ設定は、実は「PageSheet」という名前の、Excelのセルのような構造を持った隠れたシートによって管理されています。
ループの中で何度も `PageSheet` の値を書き換えるのは厳禁です。書き換えは「計算が終わった後、最後に一度だけ」。これが、大量のオブジェクトを扱う際のパフォーマンスを左右する鉄則です。
まとめ:次のステップへ
いかがでしたか?
「マクロの記録」という自動化の入り口から一歩踏み出し、「オブジェクトの形状を計算し、ページの属性を操作する」という、より高度なエンジニアリングの世界へ足を踏み入れました。
今回学んだ「走査 $\rightarrow$ 計算 $\rightarrow$ 適用」というフローは、Visioに限らず、あらゆる業務自動化(Excel VBAや、さらにはGUIアプリの開発)に応用できる普遍的なアルゴリズムです。
「なぜ動くのか」を理解したあなたなら、次は「もっと効率的にするには?」「特定のレイヤーにあるシェイプだけを対象にするには?」といった、自分なりの問いが見つかっているはずです。
その問いこそが、あなたを伝説のエンジニアへと導く原動力になります。
また次の記事でお会いしましょう。Happy Coding!
