Visio VBAを掌握する極限の知見:Shape.SetFormulasUによる「形状の数式一括注入」の極意
こんにちは。大規模プラントの計装図や、数万ノードに及ぶネットワークトポロジーの自動生成プロジェクトをいくつも率いてきた。
図面の自動化において、最も生産性を殺す悪夢をご存知か?
それは、「ループを回して `Shape.FormulaU` を1回ずつ書き換える」という、素人が最初に書く愚劣なコードだ。
‘ 【絶対にやってはならないアンチパターン】
For Each shp In vsoPage.Shapes
shp.CellsU(“Width”).FormulaU = “=Sheet1!Width0.5” ‘ 1回ごとにCOMを叩き、図形再計算が走る
Next shp
この書き方をした瞬間、Visioの描画エンジンは地獄のような重さになり、数千個のシェイプを処理するのにコーヒーを何杯飲んでも終わらない時間が溶けていく。なぜなら、プロパティを1つ変更するたびに、Visioはドキュメント全体のShapeSheet再計算と画面再描画(レイアウトの再評価)を強制されるからだ。
今回は、このボトルネックを完全に粉砕し、一瞬で数式を同期・注入するための究極の武器`Shape.SetFormulasU`を用いた実務直結のアーキテクチャを伝授する。
—
1. なぜ `SetFormulasU` なのか?(オブジェクトモデルの深層)
VisioのAPIにおいて、複数の数式を高速に操作するためのメソッドには、`SetFormulas` と `SetFormulasU` が存在する。プロフェッショナルであれば、迷わず「U(Universal)」版を選択すべきだ。
- 言語非依存の保証 (U = Universal):
UIの表示言語(日本語や英語など)に依存せず、常に `Width` や `PinX` といった英語のローカライズされていないセル名を指定できる。海外展開するチームや、ローカライズ環境が混在するPCでの不具合を根絶する。
- 一括トランザクション (Batch Processing):
配列を用いてメモリ上で数式を一気に構築し、1回のネイティブ呼び出しでVisioエンジンに流し込む。これにより、COM境界をまたぐオーバーヘッドと、不要な再描画の連鎖(Cascading Calculation)を完全に抑制できる。
—
2. 設計:親シェイプと子シェイプのパラメータ連動モデル
今回構築する自動化のシナリオはこうだ:
1. 基準となる「親シェイプ(マスター・コントロール)」を配置する。
2. データベースや外部CSVから読み込んだパラメータに基づき、無数の「子シェイプ(コンポーネント)」を動的に配置する。
3. 子シェイプの `Width`、`Height`、およびカスタムプロペティ(例:`Prop.Cost`)に対し、親シェイプのサイズや外部データを参照する数式(Formula)を `SetFormulasU` で一括注入する。
これにより、親の大きさを変えるだけで、連動して子パーツ群がミリ単位で自動変形・追従する堅牢なマスター図面が完成する。
—
3. プロダクションコード:一括数式注入モジュール
実務の現場でそのままコピー&ペーストして使える、エラーハンドリング完備の堅牢なVBAコードを提示する。
Option Explicit
‘ ==============================================================================
‘ 処理名: 親子シェイプ連動・数式一括注入エンジン
‘ 概要 : SetFormulasUを活用し、複数シェイプのShapeSheet数式を高速かつアトミックに更新する
‘ ==============================================================================
Public Sub InjectFormulasToChildren()
‘ 画面描画と自動再計算を停止し、パフォーマンスを限界まで引き上げる
Application.ScreenUpdating = False
Application.AutoRecover = False
On Error GoTo ErrorHandler
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage
‘ 1. 親シェイプの特定(例としてNameIDが “MasterBox” のものを想定)
Dim vsoParentShape As Visio.Shape
Set vsoParentShape = GetShapeByNameID(vsoPage, “MasterBox”)
If vsoParentShape Is Nothing Then
Err.Raise vbObjectError + 1001, “InjectFormulasU”, “親シェイプ ‘MasterBox’ が見つかりません。”
End If
‘ 2. 対象となる子シェイプ群の収集(例:名前に “ChildItem” を含むもの)
Dim targetShapes() As Visio.Shape
Dim targetCount As Long
targetCount = CollectShapesByTag(vsoPage, “ChildItem”, targetShapes)
If targetCount = 0 Then
MsgBox “対象となる子シェイプが存在しません。”, vbExclamation, “処理終了”
GoTo Cleanup
End If
‘ 3. SetFormulasU 用の配列準備
‘ 構文仕様:
‘ SourceArgs() のインデックス構成
‘ ( 0, 0 ) = シェイプのインデックス (1-based または 0-based 但し配列の構造に注意)
‘ ( 0, 1 ) = セルのインデックス (CellIndex or Section, Row, Column)
‘ ※今回は Cellオブジェクトを直接指定する配列構成(Indices方式)を採用
Dim intCount As Long
intCount = targetCount 2 ‘ 各シェイプにつき Width と Height の2セルを対象とする
Dim SID_array() As Integer
Dim SCELL_array() As Integer
Dim formulaArray() As String
ReDim SID_array(0 To intCount – 1)
ReDim SCELL_array(0 To intCount – 1)
ReDim formulaArray(0 To intCount – 1)
Dim i As Long, arrayIndex As Long
Dim parentSheetID As String
parentSheetID = “Sheet.” & vsoParentShape.ID
arrayIndex = 0
For i = LBound(targetShapes) To UBound(targetShapes)
‘ シェイプのID(Visio内部ID)を取得
Dim shpID As Integer
shpID = targetShapes(i).ID
‘ — Width の連動設定 —
‘ 親の幅の 25% に設定する数式例
SID_array(arrayIndex) = shpID
SCELL_array(arrayIndex) = Visio.VisCellIndices.visCellWidth
formulaArray(arrayIndex) = “=” & parentSheetID & “!Width 0.25”
arrayIndex = arrayIndex + 1
‘ — Height の連動設定 —
‘ 親の高さと常に等しくする数式例
SID_array(arrayIndex) = shpID
SCELL_array(arrayIndex) = Visio.VisCellIndices.visCellHeight
formulaArray(arrayIndex) = “=” & parentSheetID & “!Height”
arrayIndex = arrayIndex + 1
Next i
‘ 4. 圧巻のパフォーマンス:SetFormulasU による一括流し込み
‘ 第1引数: 対象セルの数式配列
‘ 第2引数: シェイプIDの配列
‘ 第3引数: セルインデックスの配列
‘ 第4引数: フラグ(0 または 0 = visSetFormulasNoShrinkWrap等)
vsoPage.SetFormulasU formulaArray, SID_array, SCELL_array, 0
‘ 変更を反映するためにドキュメントを強制再計算
vsoPage.Document.ForceRecalc
MsgBox targetCount & “個の子シェイプへの数式注入が正常に完了しました。”, vbInformation, “成功”
Cleanup:
‘ 描画・自動復旧設定を必ず復元する
Application.ScreenUpdating = True
Application.AutoRecover = True
Exit Sub
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “異常終了”
Resume Cleanup
End Sub
‘ — ヘルパー関数: 名前からシェイプを安全に取得 —
Private Function GetShapeByNameID(page As Visio.Page, nameID As String) As Visio.Shape
Dim shp As Visio.Shape
For Each shp in page.Shapes
If shp.NameU = nameID Or shp.Name = nameID Then
Set GetShapeByNameID = shp
Exit Function
End If
Next shp
Set GetShapeByNameID = Nothing
End Function
‘ — ヘルパー関数: 特定のタグ・プレフィックスを持つシェイプを動的配列に回収 —
Private Function CollectShapesByTag(page As Visio.Page, prefix As String, ByRef result() As Visio.Shape) As Long
Dim shp As Visio.Shape
ItemCount = 0
Dim tempShps() As Visio.Shape
ReDim tempShps(0 To page.Shapes.Count – 1)
Dim cnt As Long
cnt = 0
For Each shp in page.Shapes
If InStr(1, shp.NameU, prefix, vbTextCompare) > 0 Then
Set tempShps(cnt) = shp
cnt = cnt + 1
End If
Next shp
If cnt > 0 Then
ReDim result(0 To cnt – 1)
Dim j As Long
For j = 0 To cnt – 1
Set result(j) = tempShps(j)
Next j
End If
CollectShapesByTag = cnt
End Function
—
4. 現場で絶対にハマる「落とし穴」と回避策
実務でこのアーキテクチャを導入する際、以下の罠に直面することがある。プロフェッショナルとして、事前にこれらを潰しておくこと。
① シェイプIDの参照切れ(Sheet.ID の罠)
数式内で “=Sheet.1!Width“ のように記述する場合、シェイプが削除されて再作成されるとID(SheetID)がインクリメントされ、数式が破綻(`#REF!` エラー)する。
- 対策: マスターとなる親シェイプは極力削除・再作成を行わない固定IDのレイヤーに置くか、数式ではなくイベント駆動(`ShapeAdded` や `CellChanged`)での動的書き換えを併用する設計にする。
② 外部データベース(CSV / SQL Server)連携時の注意点
パラメータを外部から取得して数式に組み込む場合、文字コードや小数点区切り(ロケール問題)に殺されることがある。
- 対策: `SetFormulasU` に渡す数式文字列内の数値は、必ずVBAの `CStr` や `Format(val, “0.00”)` を用いて、OSのロケール(カンマ区切り圏かドット区切り圏か)に依存しない「ピリオド区切りの数値文字列」に正規化して結合すること。これ怠ると、ドイツ語圏やフランス語圏のPCで動かした瞬間に数式構文エラーで爆発する。
③ 画面更新の抑止忘れ
前述の通り、`ScreenUpdating = False` を記述しないコードは現場の生産性を破壊する。「画面がパカパカ点滅しながら1個ずつ図形が変形していく様」を見るのは苦痛でしかない。トランザクションの基本として、処理の最初で止め、最後で必ず復元(エラー時も含めて)する鉄壁の構造を維持せよ。
—
5. 総括
Visio VBAにおける真のパフォーマンスチューニングとは、いかにCOMオブジェクトとの対話回数を減らし、メモリ上で完結させるかにかかっている。
`SetFormulasU` を手に入れたあなたなら、数千個のオブジェクトを持つ巨大なダイアグラムであっても、一瞬でパラメータ連動を完了させることができるはずだ。
手作業による図面修正の地獄からユーザーを解放し、真に価値のある自動化の仕組みを構築してほしい。
