【実務・中級編】Shape.SetFormulasUを活用した複数幾何図形への動的計算式一括注入とパラメータ連動 – Visio VBA解析バイブル

スポンサーリンク

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` を手に入れたあなたなら、数千個のオブジェクトを持つ巨大なダイアグラムであっても、一瞬でパラメータ連動を完了させることができるはずだ。
手作業による図面修正の地獄からユーザーを解放し、真に価値のある自動化の仕組みを構築してほしい。

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