Visio VBAを掌握する極限の知見:ShapeSheetダイレクト操作で図形内テキストの段落・タブを完全統御する
こんにちは。チーフアーキテクトの私だ。
これまで数々の巨大なフローチャート自動生成エンジンや、インフラ構成図の自動レイアウトシステムを構築してきたが、現場からもっとも多く寄せられる悲鳴のような相談がこれだ。
「Visioの図形内テキストのインデントや行間が、コードからだとどうしても綺麗に揃わない」
GUIでマニュアル操作する分には一瞬だが、何千個ものサーバー定義書やネットワーク図をバッチで自動生成する際、テキストの崩れは致命傷になる。`SendKeys`? 論外だ。そんなものはエンジニアの恥だと思いたまえ。
Visio VBAにおいて、テキストの書式制御を制するには、「Rangeオブジェクトの安易な多用を捨て、ShapeSheetの `Paragraph` セクションおよび `Tabs` セクションをダイレクトに叩く」 という鉄則を理解しなければならない。
今回は、実務の現場で即座に使える、堅牢かつ洗練されたプロフェッショナルコードを授けよう。
—
1. なぜ「Range.Font / Paragraphs」では実務で破綻するのか?
多くの初学者が陥る罠が、`Shape.Text.Range` を使ったテキスト操作だ。
確かに、文字の色を変えたり、太字にしたりする程度の軽微な処理であればそれでも動く。しかし、段落のインデント(左インデント、右インデント、1行目のインデント)、行間(LineSpacing)、そして最も厄介な「タブ位置(TabStops)」を制御しようとした瞬間、以下の壁にぶつかる。
1. マルチバイト文字混入時のオフセット計算の狂い
2. 段落(Paragraph)インデックスのズレによる予期せぬ上書き
3. 描画更新(Redraw)の負荷による絶望的な処理速度の低下
Visioの内部構造において、テキストのレイアウトを決めている本体は、実は ShapeSheet(数式セル) だ。UIやRangeオブジェクトは、そのShapeSheetのラッパーに過ぎない。
ならば、最初から最下層のShapeSheetを直接書き換えればいい。 これが、大規模自動化における唯一にして最大の正解だ。
—
2. ShapeSheetダイレクト操作のアーキテクチャ
VisioのShapeSheetにおける段落制御は、`Paragraph.row` という動的セクションで行われる。
さらに、タブ位置の制御には `Tabs.row` セクションを使用する。
これらをVBAから操作する際の重要ポイントは以下の2点だ。
- セクションの動的追加(AddRow):必要な段落数やタブ数に満たない場合、事前に `AddRow` メソッドでスロットを確保する必要がある。
- トランザクション処理と画面描画の抑制:数千個のシェイプを処理する際、1回ごとに画面を再描画させてはならない。`Application.ScreenUpdating = False` は絶対の儀式である。
—
3. 【プロダクションコード】仕様書形式テキスト自動整形の決定版
以下のコードは、選択されたシェイプ、あるいは指定したシェイプ群のテキストに対し、「1行目を突き出しインデントにし、特定のタブ位置で綺麗に項目名を揃える」 プロフェッショナル仕様のプロシージャだ。
実務のデータベースや外部CSVからの読み込みを想定し、エラーハンドリングとパフォーマンスチューニングを極限まで高めている。
Option Explicit
‘ =================================================================================
‘ 処理名 : FormatShapeTextParagraphs
‘ 概要 : シェイプ内のテキスト段落およびタブ位置をShapeSheet経由で強制制御する
‘ 著者 : Enterprise Architecture Team
‘ =================================================================================
Public Sub FormatShapeTextParagraphs(ByRef targetShape As Visio.Shape)
‘ 予期せぬエラーによるフリーズを防ぐため、オブジェクトの有効性を担保
If targetShape Is Nothing Then Exit Sub
‘ テキストを持たないシェイプ(コネクタや画像など)は除外
If targetShape.Text = “” Then Exit Sub
Dim vsoCell As Visio.Cell
‘ —————————————————————–
‘ 1. パフォーマンス最適化:描画・イベントの完全停止
‘ —————————————————————–
Dim oldScreenState As Boolean
oldScreenState = Application.ScreenUpdating
Application.ScreenUpdating = False
On Error GoTo ErrorHandler
‘ —————————————————————–
‘ 2. 段落セクション(Paragraph)の制御
‘ —————————————————————–
‘ Visioのセクションインデックス
Const visSectionParagraph As Integer = -3 ‘ visSectionFirstParagraph と同等
Const visRowParagraph As Integer = 0
‘ セクションが存在しない場合は行を追加(通常はデフォルトで1行目は存在する)
If targetShape.RowExists(visSectionParagraph, visRowParagraph, visVisioIndices) = False Then
targetShape.AddRow visSectionParagraph, visRowParagraph, visTagDefault
End If
‘ 左インデントの設定(例: 全体で 10mm インデント)
Set vsoCell = targetShape.CellsSRC(visSectionParagraph, visRowParagraph, Visio.Prgs.visParagraphIndLeft)
vsoCell.FormulaU = “10 mm”
‘ 1行目のインデント(例: 左インデントから -5mm の「ぶら下げ(突き出し)インデント」)
Set vsoCell = targetShape.CellsSRC(visSectionParagraph, visRowParagraph, Visio.Prgs.visParagraphIndFirst)
vsoCell.FormulaU = “-5 mm”
‘ 行間の設定(例: 行間を1.2倍に設定)
Set vsoCell = targetShape.CellsSRC(visSectionParagraph, visRowParagraph, Visio.Prgs.visParagraphLineSpacing)
vsoCell.FormulaU = “120%”
‘ —————————————————————–
‘ 3. タブ位置セクション(Tabs)の制御
‘ —————————————————————–
Const visSectionTabs As Integer = -4 ‘ visSectionFirstTab
‘ 既存のタブ位置をすべてクリア(クリーンな状態から再構築する)
Dim tabCount As Integer
tabCount = targetShape.RowCount(visSectionTabs)
Dim i As Integer
For i = tabCount – 1 To 0 Step -1
targetShape.DeleteRow visSectionTabs, i
Next i
‘ 新規タブ位置の追加(例: 40mm の位置に左揃えタブを設定)
Dim newRowIndex As Integer
newRowIndex = targetShape.AddRow(visSectionTabs, visRowParagraph, visTagDefault)
‘ タブの位置 (Position)
Set vsoCell = targetShape.CellsSRC(visSectionTabs, newRowIndex, Visio.Prgs.visTabPosition)
vsoCell.FormulaU = “40 mm”
‘ タブの種類 (Align: 0=左揃え, 1=中央揃え, 2=右揃え, 3=小数点揃え)
Set vsoCell = targetShape.CellsSRC(visSectionTabs, newRowIndex, Visio.Prgs.visTabAlign)
vsoCell.FormulaU = “0”
‘ 更に2番目のタブを追加する場合(例: 80mm の位置に右揃えタブ)
‘ newRowIndex = targetShape.AddRow(visSectionTabs, visRowParagraph, visTagDefault)
‘ Set vsoCell = targetShape.CellsSRC(visSectionTabs, newRowIndex, Visio.Prgs.visTabPosition)
‘ vsoCell.FormulaU = “80 mm”
‘ Set vsoCell = targetShape.CellsSRC(visSectionTabs, newRowIndex, Visio.Prgs.visTabAlign)
‘ vsoCell.FormulaU = “2”
CleanUp:
‘ —————————————————————–
‘ 4. リソースと環境の確実な復元
‘ —————————————————————–
Application.ScreenUpdating = oldScreenState
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “Visio VBA Engine”
Resume CleanUp
End Sub
‘ =================================================================================
‘ 実行ラッパープロシージャ(選択中シェイプの一括処理)
‘ =================================================================================
Public Sub RunBatchFormatOnSelection()
Dim shp As Visio.Shape
If ActiveWindow.Selection.Count = 0 Then
MsgBox “処理対象のシェイプが選択されていません。”, vbExclamation
Exit Sub
End If
‘ 処理の高速化
ActiveDocument.UndoScopeBegin “Format Text Paragraphs”
For Each shp in ActiveWindow.Selection
FormatShapeTextParagraphs shp
Next shp
ActiveDocument.UndoScopeEnd “Format Text Paragraphs”
MsgBox “テキストの段落・タブ制御が正常に完了しました。”, vbInformation
End Sub
—
4. チーフアーキテクトからの実務アドバイス:保守性と拡張性
このコードを実際のプロジェクトに組み込む際、以下の2点を厳守してほしい。
1. 単位(Units)の明示
ShapeSheetに数式(FormulaU)を流し込む際、`”10 mm”` のように単位を文字列として明示すること。省略すると、Visioのドキュメント単位系(インチ、センチ、メートル)に依存してしまい、別環境で動かした時にレイアウトが崩壊する原因になる。`FormulaU` を使う場合は常に内部単位(インチ)か、文字列で明示的な単位を渡すのがプロの作法だ。
2. UndoScope(アンドゥースコープ)の活用
サンプルコード内でも入れているが、一括処理の前後は `ActiveDocument.UndoScopeBegin` と `UndoScopeEnd` で囲むこと。これにより、何千個のシェイプを変更しても、ユーザーは「Ctrl + Z」の1回で安全に元の状態へロールバックできるようになる。この配慮が、ツール全体の品質を劇的に引き上げる。
現場で「なぜかレイアウトがズレる」という泥沼から抜け出せないエンジニアは、今すぐRange依存のコードを捨て、ShapeSheetダイレクト制御に乗り換えたまえ。健闘を祈る。
