皆さん、こんにちは! 最先端の自動化技術を日々探求しているチーフアーキテクトの〇〇です。
Visio、使いこなしていますか? 複雑なシステム構成図、業務フロー、フロアプランなど、私たちの業務を視覚的に表現する上で、Visioは手放せないツールですよね。でも、ちょっと待ってください。そうして作った図面、手動で「あれ、この図形ちょっと重なってるかな?」とか、「枠からはみ出してないかな?」なんて、目で見てチェックしていませんか?
膨大な図形の中からレイアウトのズレをひとつひとつ目視で確認するのは、まさに苦行。しかも、見落としのリスクも常に付きまといます。そんな消耗戦、もう終わりにしましょう。
今回は、Visio VBAの真髄とも言えるオブジェクトモデルの理解を深めながら、`Shape.BoundingBox`メソッドを駆使して、図形の重なりやはみ出しを自動で検知するマクロの極意をお伝えします。
「BoundingBox? 何それ?」というプログラミング初学者の方も、「マクロの記録から一歩踏み出したい!」という方も、大丈夫です。私が皆さんを、Visio自動化の新たな地平へと誘います。ここをクリアすれば、Visio VBAの基本はバッチリですよ!
—
目次
1.
Visioオブジェクトモデルの基本と`Shape`オブジェクトの重要性
2.
`Shape.BoundingBox`とは何か? その「極限の知見」
- 単なる外接矩形ではない、引数の深遠な意味
- Visio座標系の理解
3.
実践! `BoundingBox`で図形の重なりを判定する
- 重なり判定のロジックを理解する
- コード例:選択された図形同士の重なりをチェック
4.
発展! `BoundingBox`で指定範囲内への収まりを判定する
- コード例:全図形がページ内の特定エリアに収まっているかチェック
5.
パフォーマンスとオブジェクトのライフサイクル:伝説のチーフアーキテクトからの助言
6.
まとめ:`BoundingBox`を掌握し、Visio自動化の達人へ
—
1. Visioオブジェクトモデルの基本と`Shape`オブジェクトの重要性
Visio VBAを語る上で、まず避けて通れないのが「オブジェクトモデル」です。簡単に言えば、Visioのアプリケーション全体を構成する部品(オブジェクト)が、どのような親子関係や関連性を持っているかを示した地図のようなもの。
Visioのオブジェクトモデルは、おおよそ以下のような階層構造になっています。
- `Application`: Visioアプリケーションそのもの。全ての親です。
- `Document`: 開いているVisioファイル(.vsd, .vsdx)。
- `Page`: ドキュメント内の各ページ。
- `Shape`: ページ上にある個々の図形(四角形、テキストボックス、コネクタなど)。
私たちがVisio VBAで何かを操作するとき、ほとんどの場合、この階層をたどって目的のオブジェクトに到達します。例えば、「このページのこの図形の色を変えたい」と思ったら、`Application` → `Document` → `Page` → `Shape` の順にアクセスしていくわけです。
今回のテーマである図形の重なり判定では、まさにこの`Shape`オブジェクトが主役となります。一つ一つの図形が持つ「位置」と「サイズ」という情報が、レイアウトチェックの鍵を握るからです。
2. `Shape.BoundingBox`とは何か? その「極限の知見」
さて、本日の主役、`Shape.BoundingBox`メソッドについて深く掘り下げていきましょう。
`BoundingBox`とは、直訳すれば「境界ボックス」。つまり、ある図形を完全に囲む最小の矩形(長方形)を意味します。この矩形の左下隅と右上隅の座標を取得することで、その図形がページのどこに、どれくらいの大きさで存在しているかを正確に把握できます。
単なる外接矩形ではない、引数の深遠な意味
一般的なリファレンスでは「図形の外接矩形を取得するメソッドです」と書かれているかもしれませんが、私が皆さんにお伝えしたいのは、その奥深さです。`BoundingBox`メソッドには、実は非常に重要な引数が存在します。
shapeObj.BoundingBox (Flags as Long, lpr8Left as Double, lpr8Bottom as Double, lpr8Right as Double, lpr8Top as Double)
この`Flags`引数が肝心なのです! ここに指定する`VisBoundingBox`列挙体の定数によって、「何を含めて外接矩形を計算するか」が変わってきます。
- `visBBoxDrawingCoords`: 図形そのものの描画座標(デフォルトに近い)
- `visBBoxIncludeHidden`: 非表示のジオメトリも含む
- `visBBoxIncludeUIVisCtrlPts`: 選択ハンドルやコントロールポイントも含む
- `visBBoxIncludeText`: テキストブロックの領域も含む(これ、かなり重要!)
- `visBBoxUpToText`: 図形本体とテキストブロックの両方を含む(`visBBoxDrawingCoords`と`visBBoxIncludeText`を組み合わせたようなもの)
想像してみてください。テキストブロックが図形本体からはみ出している場合、`visBBoxDrawingCoords`だけでは、テキストのはみ出しを検知できません。しかし、`visBBoxIncludeText`や`visBBoxUpToText`を使えば、テキストを含めた全体の外接矩形を取得できるため、より厳密なレイアウトチェックが可能になるのです。
私が実務でレイアウトチェックマクロを組む際には、ほとんどの場合、`visBBoxUpToText`を指定します。なぜなら、ユーザーが「図形」と認識する範囲は、その見た目全体であり、テキストがはみ出していれば、それは「レイアウト崩れ」と見なされるからです。この小さな選択が、マクロの品質を大きく左右するのです。
Visio座標系の理解
`BoundingBox`メソッドが返す座標値(`lpr8Left`, `lpr8Bottom`, `lpr8Right`, `lpr8Top`)は、Visioのページ座標で表現されます。
- 原点: ページの左下隅が`(0, 0)`です。
- X軸: 右方向へ正の値が増加します。
- Y軸: 上方向へ正の値が増加します。
つまり、`lpr8Left`は図形の左端のX座標、`lpr8Bottom`は下端のY座標、`lpr8Right`は右端のX座標、`lpr8Top`は上端のY座標を意味します。この座標系を頭に入れておくと、重なり判定のロジックがすんなり理解できるはずです。
3. 実践! `BoundingBox`で図形の重なりを判定する
それでは、実際に`BoundingBox`を使って図形の重なりを判定するマクロを作成してみましょう。まずは、最も基本的な「2つの図形が重なっているか」を判定するロジックから解説します。
重なり判定のロジックを理解する
2つの矩形AとBが重なっているかどうかの判定は、次の論理に基づきます。
「矩形Aと矩形BがX軸方向で重なり、かつY軸方向で重なる」
これを具体的に座標で表現すると:
- X軸の重なり:
`A.Left < B.Right` かつ `A.Right > B.Left`
- Y軸の重なり:
`A.Bottom < B.Top` かつ `A.Top > B.Bottom`
この2つの条件が両方とも真であれば、2つの矩形は重なっています。
逆に、「重なっていない」という条件を考えると、以下のいずれかの条件が真であれば、重なっていません。
- `A.Right <= B.Left` (AがBの左にある)
- `A.Left >= B.Right` (AがBの右にある)
- `A.Top <= B.Bottom` (AがBの下にある)
- `A.Bottom >= B.Top` (AがBの上にある)
この「重なっていない」条件のいずれかが真であれば重なっていない、というロジックの方が、コードとしては少しシンプルになる場合があります。
コード例:選択された図形同士の重なりをチェック
ここでは、Visio上で選択されている図形の中から、互いに重なっているものを検出し、メッセージボックスで通知するマクロを作成します。
‘—————————————————————————————————————–
‘ プロシージャ名: CheckSelectedShapesOverlap
‘ 目的: Visioの現在のページで選択されている図形の中から、互いに重なり合っている図形を検出します。
‘ 検出された重なりはメッセージボックスで報告されます。
‘ 前提: 2つ以上の図形がVisioページ上で選択されている必要があります。
‘—————————————————————————————————————–
Sub CheckSelectedShapesOverlap()
‘ 処理速度向上と画面のちらつき防止のため、画面更新を停止します。
‘ 大量の図形を扱う際に特に重要です。
Application.ScreenUpdating = False
Dim vsoPage As Visio.Page
Dim vsoSelection As Visio.Selection
Dim vsoShape1 As Visio.Shape
Dim vsoShape2 As Visio.Shape
Dim overlapCount As Long
Dim msg As String
‘ 重なりを検出した図形のペアを格納するコレクション
‘ 同じペアを複数回報告しないようにするための工夫です。
Dim overlappedPairs As New Collection
‘ 現在アクティブなページを取得します。
Set vsoPage = Application.ActivePage
‘ 現在選択されている図形コレクションを取得します。
Set vsoSelection = Application.ActiveWindow.Selection
‘ 選択されている図形が2つ未満の場合は処理を終了します。
If vsoSelection.Count < 2 Then
MsgBox "重なりをチェックするには、少なくとも2つ以上の図形を選択してください。", vbInformation
Application.ScreenUpdating = True ' 画面更新を再開
Exit Sub
End If
overlapCount = 0
msg = "重なりが検出された図形ペア:" & vbCrLf & "--------------------------" & vbCrLf
' 選択された図形を二重ループで総当たりチェックします。
' Shape1とShape2が同じ図形でないこと、および同じペアを重複してチェックしないように工夫します。
For Each vsoShape1 In vsoSelection
For Each vsoShape2 In vsoSelection
' 自分自身との比較はスキップします。
If vsoShape1.ID = vsoShape2.ID Then
GoTo NextShape2
End If
' 既にチェック済みのペア(例: (A,B)と(B,A))をスキップするためのチェック
' コレクションにキーとして文字列を保存し、存在するかで判断します。
Dim pairKey1 As String: pairKey1 = vsoShape1.ID & "-" & vsoShape2.ID
Dim pairKey2 As String: pairKey2 = vsoShape2.ID & "-" & vsoShape1.ID
On Error Resume Next ' エラーが発生しても処理を続行 (Keyが見つからない場合)
overlappedPairs.Add 1, pairKey1 ' 重複チェック用キーを追加
If Err.Number = 457 Then ' Keyが既に存在する場合 (Key重複エラー)
Err.Clear
On Error GoTo 0 ' エラーハンドリングを元に戻す
GoTo NextShape2 ' このペアは既にチェック済みなのでスキップ
End If
On Error GoTo 0 ' エラーハンドリングを元に戻す
' 2つの図形のBoundingBox座標を取得します。
' visBBoxUpToTextを指定することで、テキスト部分も含めた外接矩形を取得します。
Dim L1 As Double, B1 As Double, R1 As Double, T1 As Double ' Shape1の座標
Dim L2 As Double, B2 As Double, R2 As Double, T2 As Double ' Shape2の座標
vsoShape1.BoundingBox visBBoxUpToText, L1, B1, R1, T1
vsoShape2.BoundingBox visBBoxUpToText, L2, B2, R2, T2
' 重なり判定ロジック
' 2つの矩形がX軸方向で重なり、かつY軸方向で重なる場合に真となります。
If Not (R1 <= L2 Or L1 >= R2 Or T1 <= B2 Or B1 >= T2) Then
‘ 重なりが検出された場合
overlapCount = overlapCount + 1
msg = msg & ” [” & vsoShape1.NameU & ” (ID:” & vsoShape1.ID & “)] と ” _
& “[” & vsoShape2.NameU & ” (ID:” & vsoShape2.ID & “)]” & vbCrLf
End If
NextShape2:
Next vsoShape2
Next vsoShape1
‘ 結果をメッセージボックスで表示します。
If overlapCount > 0 Then
MsgBox msg & vbCrLf & “合計 ” & overlapCount & ” 組の重なりが検出されました。”, vbExclamation
Else
MsgBox “選択された図形の間には、重なりは検出されませんでした。”, vbInformation
End If
‘ 画面更新を再開します。
Application.ScreenUpdating = True
‘ オブジェクトの解放 (ガベージコレクションを促進し、メモリリークを防ぐためのベストプラクティス)
Set vsoShape1 = Nothing
Set vsoShape2 = Nothing
Set vsoSelection = Nothing
Set vsoPage = Nothing
Set overlappedPairs = Nothing ‘ コレクションも解放
End Sub
コードのポイント:
- `Application.ScreenUpdating = False`: 処理中に画面がちらつくのを防ぎ、パフォーマンスを向上させます。マクロの冒頭でオフにし、終了時に必ずオンに戻しましょう。これ、地味だけど超重要ですよ。
- 二重ループ: 選択された図形の中から、すべての図形のペアを総当たりでチェックするために二重ループを使います。`vsoShape1.ID = vsoShape2.ID`で自分自身との比較を避け、`overlappedPairs`コレクションを使って既にチェック済みのペア(例: 図形Aと図形Bをチェックしたら、次に図形Bと図形Aはチェックしない)をスキップする工夫をしています。
- `BoundingBox`メソッド: `visBBoxUpToText`フラグを指定し、テキストを含む正確な外接矩形を取得します。
- 重なり判定ロジック: `If Not (R1 <= L2 Or L1 >= R2 Or T1 <= B2 Or B1 >= T2) Then` の部分が、「2つの矩形が重なっていない条件」の否定、つまり「重なっている条件」を判定しています。この書き方は、論理の反転でシンプルに表現できるため、実務でよく使われます。
- オブジェクトの解放: `Set obj = Nothing`で、使用したオブジェクト変数を明示的に解放します。VisioのようなCOMオブジェクトを扱うVBAでは、これは非常に重要なベストプラクティスです。メモリリークや意図しない動作を防ぐためにも、必ず行いましょう。
4. 発展! `BoundingBox`で指定範囲内への収まりを判定する
次に、さらに実践的な例として、ページ内の全ての図形が、あらかじめ定義した特定のエリア(例えば、印刷可能領域や特定の描画領域)に収まっているかを判定するマクロを見てみましょう。
コード例:全図形がページ内の特定エリアに収まっているかチェック
‘—————————————————————————————————————–
‘ プロシージャ名: CheckAllShapesInBounds
‘ 目的: Visioの現在のページ上の全ての図形が、指定された境界範囲内に収まっているかをチェックします。
‘ 範囲外にある図形が検出された場合、その図形名とIDをメッセージボックスで報告します。
‘—————————————————————————————————————–
Sub CheckAllShapesInBounds()
Application.ScreenUpdating = False ‘ 画面更新を停止
Dim vsoPage As Visio.Page
Dim vsoShape As Visio.Shape
Dim outOfBoundsCount As Long
Dim msg As String
‘ — チェックする境界範囲の定義 (ページ座標、単位はインチ) —
‘ 例: ページの左から1インチ、下から1インチ、右から10インチ、上から8インチの範囲
‘ この値を変更することで、チェック範囲を自由に設定できます。
Const CHECK_LEFT As Double = 1 ‘ 左端のX座標
Const CHECK_BOTTOM As Double = 1 ‘ 下端のY座標
Const CHECK_RIGHT As Double = 10 ‘ 右端のX座標
Const CHECK_TOP As Double = 8 ‘ 上端のY座標
‘ ————————————————————-
Set vsoPage = Application.ActivePage ‘ 現在アクティブなページを取得
outOfBoundsCount = 0
msg = “指定範囲外の図形:” & vbCrLf & “————————–” & vbCrLf
‘ ページ上の全ての図形をループします。
For Each vsoShape In vsoPage.Shapes
‘ 図形がガイドやグループの内部にある場合など、チェック対象外としたいケースをここで除外できます。
‘ 例: コネクタ(接続線)は通常、レイアウトチェックの対象外とすることが多いです。
‘ If vsoShape.Type = visTypeConnect Then GoTo NextShape ‘ コネクタを除外する場合
Dim L As Double, B As Double, R As Double, T As Double ‘ 図形のBoundingBox座標
‘ 図形のBoundingBoxを取得します。ここでもvisBBoxUpToTextを使用し、テキストを含めた正確な範囲を取得。
vsoShape.BoundingBox visBBoxUpToText, L, B, R, T
‘ 図形が指定範囲内に収まっているか判定します。
‘ 図形の左端がチェック範囲の左端より右にあり (L >= CHECK_LEFT)
‘ かつ、図形の下端がチェック範囲の下端より上にあり (B >= CHECK_BOTTOM)
‘ かつ、図形の右端がチェック範囲の右端より左にあり (R <= CHECK_RIGHT)
' かつ、図形の上端がチェック範囲の上端より下にある (T <= CHECK_TOP)
' の全てが真であれば、範囲内に収まっています。
If Not (L >= CHECK_LEFT And B >= CHECK_BOTTOM And R <= CHECK_RIGHT And T <= CHECK_TOP) Then
' 範囲外と判定された場合
outOfBoundsCount = outOfBoundsCount + 1
msg = msg & " [" & vsoShape.NameU & " (ID:" & vsoShape.ID & ")]" & vbCrLf
End If
NextShape: ' GoTo文のターゲットラベル
Next vsoShape
' 結果をメッセージボックスで表示します。
If outOfBoundsCount > 0 Then
MsgBox msg & vbCrLf & “合計 ” & outOfBoundsCount & ” 個の図形が範囲外です。”, vbExclamation
Else
MsgBox “全ての図形が指定された範囲内に収まっています。”, vbInformation
End If
Application.ScreenUpdating = True ‘ 画面更新を再開
‘ オブジェクトの解放
Set vsoShape = Nothing
Set vsoPage = Nothing
End Sub
コードのポイント:
- `Const`でチェック範囲を定義: `CHECK_LEFT`, `CHECK_BOTTOM`などの定数を使って、チェックしたい範囲を明確に定義しています。これにより、マクロを修正する際に、これらの定数の値だけを変更すればよく、保守性が高まります。
- 全ての図形をループ: `vsoPage.Shapes`コレクションを`For Each`ループで回すことで、ページ上の全ての図形にアクセスできます。
- 範囲内判定ロジック: `If Not (L >= CHECK_LEFT And B >= CHECK_BOTTOM And R <= CHECK_RIGHT And T <= CHECK_TOP) Then` の部分が、図形が指定範囲内に収まっていない条件を判定しています。これは、先ほどの重なり判定と同様に、真の条件の否定で表現しています。
- GoTo文の活用: コネクタなど、特定の図形タイプをチェック対象から除外したい場合に`If vsoShape.Type = visTypeConnect Then GoTo NextShape`のように`GoTo`文を使うと、ループの残りの処理をスキップして次の図形へ進むことができます。これは、VBAのループ処理において、条件によっては有効な記述方法の一つです。
5. パフォーマンスとオブジェクトのライフサイクル:伝説のチーフアーキテクトからの助言
ここまでで、`BoundingBox`を使った基本的なレイアウトチェックマクロの作成方法を学びました。しかし、私が皆さんに伝えたい「極限の知見」は、単なるコードの書き方だけではありません。パフォーマンスとオブジェクトのライフサイクル管理は、大規模な自動化システムを構築する上で、絶対に疎かにしてはならない要素です。
- `Application.ScreenUpdating = False` の重要性:
先ほども触れましたが、これは非常に重要です。Visio VBAで大量の図形を操作したり、ループ処理を行ったりする際、画面の再描画が頻繁に発生すると、マクロの実行速度が著しく低下します。`ScreenUpdating = False`を設定することで、VBAが内部的に図形を操作している間は画面の更新を抑制し、処理速度を大幅に向上させることができます。しかし、必ず処理の最後に`ScreenUpdating = True`に戻すことを忘れないでください。そうでなければ、Visioの画面がフリーズしたままになってしまいます。
- オブジェクトの解放 (`Set obj = Nothing`):
VBAがCOM(Component Object Model)オブジェクトを扱う際、明示的にオブジェクトを解放しないと、メモリリークの原因となったり、Visioアプリケーションが終了してもバックグラウンドでプロセスが残り続けたりする可能性があります。
`Set obj = Nothing` は、そのオブジェクト変数が参照していたオブジェクトへの参照を解除し、オブジェクトの参照カウントを減らします。参照カウントが0になると、そのオブジェクトはメモリから解放される準備が整います。
特に、`Application`, `Document`, `Page`, `Shape`といったVisioオブジェクトは、意識的に解放する習慣をつけましょう。これは、安定した堅牢なマクロを開発するための、まさに基本中の基本です。
- エラーハンドリングの導入:
実務で使うマクロには、必ずエラーハンドリングを導入してください。例えば、`On Error GoTo ErrorHandler` のように記述し、予期せぬエラーが発生した場合でも、マクロが途中で停止するのではなく、適切にエラーメッセージを表示したり、クリーンアップ処理(`ScreenUpdating = True`に戻す、オブジェクトを解放するなど)を実行したりするように設計します。これにより、ユーザー体験が向上し、マクロの信頼性が高まります。
これらのポイントは、一見地味に見えるかもしれません。しかし、これらを意識してコードを書くか書かないかで、皆さんが作るマクロの品質、安定性、そして「伝説のエンジニア」への道が開けるかどうかが決まります。
6. まとめ:`BoundingBox`を掌握し、Visio自動化の達人へ
今回の記事では、Visio VBAの根幹をなすオブジェクトモデルの理解から始まり、`Shape.BoundingBox`メソッドの奥深さ、そしてそれを使った実用的なレイアウトチェックマクロの作成方法までを解説しました。
- `BoundingBox`は単なる外接矩形ではなく、`Flags`引数によってその挙動が大きく変わることを知りましたね。特に`visBBoxUpToText`は、テキストを含む正確なサイズを把握するために非常に有効です。
- 重なり判定や指定範囲内への収まり判定のロジックは、X軸とY軸それぞれの関係性を丁寧に考えることで、シンプルかつ確実な判定が可能になります。
- そして、パフォーマンス向上のための`Application.ScreenUpdating`、メモリ管理のためのオブジェクト解放(`Set obj = Nothing`)といった、実践的なベストプラクティスも学びました。これらは、安定したVisio VBAソリューションを開発するために不可欠な要素です。
これらの知識と技術を習得すれば、もうVisioのレイアウト崩れに怯える必要はありません。手作業によるチェックから解放され、よりクリエイティブな作業に集中できるようになるでしょう。
皆さんがVisio VBAの基本をしっかりとマスターし、自動化の力で日々の業務を劇的に改善していくことを心から願っています。さあ、この知識を手に、Visio自動化の新たな一歩を踏み出しましょう!
何か質問があれば、いつでもコメントしてくださいね。私も皆さんのVisio VBAジャーニーを全力でサポートします!
