こんにちは!Visioの自動化の世界へようこそ。
マクロの記録ボタンを押して生成されたコードを眺めて、「おや、なんだか同じようなコードの繰り返しだな…」「図形を全選択して処理しようとすると、なぜかエラーが出るぞ…」と悩んだ経験はありませんか?
Visio VBA(Visual Basic for Applications)の基礎であり、最も重要な心臓部、それが「オブジェクトモデルの走査(トラバーサル)」です。
今回は、ページ上にある膨大な図形たちを、1つ残らず、しかも安全かつエレガントに料理するための「効率的なループ構文の書き方」を伝授しましょう。ここをクリアすれば、あなたのVisioマクロ開発スキルは一気にプロの領域へと引き上げられますよ。
—
1. Visioオブジェクトモデルの基本階層を理解する
まず、Visioのドキュメントがどのような構造(ツリー構造)でメモリ上に存在しているかを押さえましょう。Visio VBAの世界は、ロシアの人形「マトリョーシカ」のように階層構造になっています。
[Application] (Visioアプリ全体)
└ [Document] (開いているファイル)
└ [Page] (図面ページ)
└ [Shape] (ページ上の個々の図形)
└ [Shape] (※グループ化されている子図形)
私たちが日常的に触る図形(Shape)は、すべて `Page.Shapes` というコレクション(集まり)の中に格納されています。このコレクションをどうやって隅々まで舐めるように走査するかが、今回のメインディッシュです。
—
2. 王道:`For Each` 構文で全図形を美しく巡回する
図形を「見るだけ」「プロパティを変更するだけ」の読み取り専用、あるいは単純な一括処理であれば、迷わず `For Each` 構文 を使いましょう。コードが非常に直感的で、読みやすくなります。
基本のコード例
以下のコードは、アクティブページにあるすべての図形の名前をイミディエイトウィンドウに出力するシンプルなマクロです。
Sub PrintAllShapeNames()
Dim shp As Visio.Shape
Dim pg As Visio.Page
‘ 現在アクティブなページを取得
Set pg = ActivePage
‘ ページ内のすべての図形を走査
For Each shp in pg.Shapes
Debug.Print “図形名: ” & shp.Name & ” (ID: ” & shp.ID & “)”
Next shp
MsgBox “すべての図形の走査が完了しました!”, vbInformation
End Sub
先輩エンジニアからのワンポイントアドバイス
`For Each` は、VBAの裏側でコレクションのポインタを自動管理してくれるため、コードがスッキリします。「図形を削除しない、追加もしない」という前提であれば、この書き方がパフォーマンス面でもベストプラクティスです。
—
3. 現場の罠:図形を「削除」するときに `For Each` が引き起こす惨劇
さて、ここからが本番です。実務の自動化では、「特定の条件に一致する図形を削除したい」という要件が頻繁に発生します。
ここで、多くの初学者が次のようなコードを書いて自爆してしまいます。
‘ 【やってはいけないアンチパターン】
For Each shp in pg.Shapes
If shp.Text = “不要なテキスト” Then
shp.Delete ‘ ← これをやるとループが狂う!
End If
Next shp
なぜ、これではいけないのでしょうか?
`For Each` でループ中にコレクションの要素(図形)を削除すると、Visioの内部インデックスが詰まり、「要素がスキップされる」あるいは「実行時エラー(インデックスが範囲外です)」が発生します。列車の座席数が減ったのに、車掌さんが名簿の番号通りに移動しようとして乗客を飛ばしてしまうようなものです。
—
4. 解決策:安全な「逆順ループ(カウントダウン・ループ)」をマスターする
コレクションから要素を削除する処理を含む場合、プログラミング界の定石である「後ろから数える(逆順ループ)」を採用します。
最後の図形から最初の図形に向かって処理を進めれば、仮に途中で図形が削除されても、「これから処理しようとしている前方のインデックス」には一切影響を与えないため、安全に全図形を処理できます。
削除処理を伴う安全なループのコード例
以下のコードは、ページ内にある特定の条件(例:特定のレイヤーや文字列など)に合致する図形を安全に削除する実用的なマクロです。
Sub RemoveSpecificShapesSafely()
Dim pg As Visio.Page
Dim shp As Visio.Shape
Dim i As Long
Set pg = ActivePage
‘ 最後の図形(Count)から最初の図形(1)へ向かって逆順ループ
For i = pg.Shapes.Count To 1 Step -1
Set shp = pg.Shapes(i)
‘ 例として、シェイプのテキストに “Draft” が含まれていたら削除する
If InStr(shp.Text, “Draft”) > 0 Then
‘ ログを出力して削除
Debug.Print “削除対象: ” & shp.Name
shp.Delete
End If
Next i
MsgBox “不要な図形のクレンジングが完了しました!”, vbInformation
End Sub
—
5. グループ化された図形(子シェイプ)への配慮
Visioの奥深いところは、図形がグループ化(Group)されている場合、`Page.Shapes` だけを走査しても、グループの「中身(子シェイプ)」まで自動的には手が届かない点です。
もし図面内にグループ図形があり、その中のパーツまで操作したい場合は、再帰処理(自分自身を呼び出す関数)や、`shp.Shapes`(グループ内のShapesコレクション)の存在をチェックするロジックを追加する必要があります。
今回は基本に集中するため深追いしませんが、「あれ、グループの中の図形がヒットしないぞ?」と気づいたときは、この「親子関係(シェイプツリー)」の存在を思い出してくださいね。
—
まとめ
今回の極限の知見をまとめます。
1. 基本は `For Each`:
図形を見るだけ、プロパティを変えるだけの安全な読み取り処理なら、美しく簡潔な `For Each shp In pg.Shapes` を使う。
2. 削除するなら「逆順」:
`pg.Shapes.Count To 1 Step -1` によるカウントダウンループを組むことで、インデックスズレによるバグを完全にハザード回避する。
ここをクリアすれば、Visio VBAのオブジェクト操作における「予期せぬエラー」の大部分を防げるようになります。あなたの手動作業をゼロにする自動化ツール作り、ぜひ自信を持って進めてください!
それでは、次回の高度なVBA解説でお会いしましょう。バッチリ効率化していきましょう!
