【実務・中級編】図形内テキストの高度な段落制御:ParagraphおよびTabsセルのVBAダイレクト操作 – Visio VBA解析バイブル

スポンサーリンク

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ダイレクト制御に乗り換えたまえ。健闘を祈る。

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