【実務・中級編】Shape.SetFormulasUと配列を用いたバルク更新:数百個のシェイプデータを1回のAPI呼出で一括変更する超高速化テクニック – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを極限まで加速する:`SetFormulasU`と配列によるシェイプデータ一括更新の極意

業務自動化エンジニアの皆さん、日々のVisio VBA開発でお疲れ様です。
数百、数千個のシェイプが並ぶ巨大なダイアグラムを生成・更新する際、「処理が終わるのをコーヒーを飲みながら待つ」ようなコード書いていませんか?

もし、ループの中でシェイプのプロパティを1つずつ叩くようなコードを放置しているなら、今すぐその設計を改める必要があります。VisioのCOM Interop(VBAからVisioを操作する層)において、オブジェクトへの頻繁なアクセスはパフォーマンスを殺す最大の癌です。

今回は、Visio VBAの隠れた(そして最強の)高速化APIである `SetFormulasU` を用い、数百個のシェイプデータをたった1回のAPI呼び出しで爆速アップデートする極限の知見を伝授します。

1. なぜ「1つずつ更新」のコードは遅いのか?(COMの壁)

多くの開発者が最初に書く愚直なコードは、次のようなものです。

‘ 【アンチパターン】絶対にやってはいけないループ処理
Dim shp As Visio.Shape
For Each shp in ActivePage.Shapes
‘ ループのたびにVisioのCOM境界を跨いでいる
shp.CellsU(“Prop.Cost”).FormulaU = “1000”
shp.CellsU(“Prop.Status”).FormulaU = “””Active”””
Next shp

このコードの何が問題か。
VisioのVBAランタイムから、アンダーレイにあるVisioアプリケーション(C++製の実体)へアクセスするたびに、COM interopのコンテキストスイッチ(境界越え)が発生します。

シェイプが500個あり、それぞれに2つのセルがある場合、上のコードは 1,000回もの重いプロセス間通信 を発生させています。これが「処理が遅い」根本原因です。

解決策:バルク処理(一括操作)へのパラダイムシフト

Visio VBAには、このボトルネックを粉砕するために、複数のセルに対して配列を一気に送り込むメソッドが用意されています。それが `Page.SetFormulasU` です。

これを使えば、1,000回の通信を「たった1回」に圧縮できます。処理時間が1/10どころか、規模によっては1/50以下に激減する劇的な効果を生みます。

2. `SetFormulasU` の仕様とデータ構造の罠

`SetFormulasU` を使いこなすには、Visio特有の少し歪な配列の仕様を理解する必要があります。

object.SetFormulasU(SheetNames() As Integer,
CellNames() As Integer,
Flags As Integer,
Formulas() As Variant,
LocaleNames() As Variant)

…と言われても、公式リファレンスだけでは難解です。実務上は、オブジェクトのインデックスを効率よくハンドリングする `Shape.SetFormulasU` の方が直感的かつ安全に扱えます。

必要な2つの配列

1. 対象セルの指定配列(`Int16` または `Long` の1次元、または2次元配列): どのシェイプの、どのセルのプロパティを書き換えるか。
2. 数式の配列(`Variant` の1次元配列): 書き込む値(文字列としての数式)。

※注意点として、Visioの文字列値は `”Active”` のように二重引用符で囲む(エスケープする)必要があります。さもないと、Visioはそれを「数式(変数名)」と解釈してエラーを出します。

3. 【プロダクションコード】実務で使える爆速一括更新エンジン

ここからは、データベースや外部CSVから読み込んだデータを想定し、数百個のシェイプの「コスト」と「ステータス」を一撃で更新する、実戦投入可能なモジュールを公開します。

保守性が高く、エラーハンドリングも考慮された堅牢なコードです。そのままコピー&ペーストしてプロジェクトに組み込んでください。

Option Explicit

” =================================================================
” 概要: 配列と SetFormulasU を用いたシェイプデータ一括更新のサンプル
” 著者: シニア自動化エンジニア
” =================================================================
Public Sub BulkUpdateShapeDataSample()
Dim startTime As Double
startTime = Timer

‘ 1. 対象ページの特定
Dim targetPage As Visio.Page
Set targetPage = ActivePage

‘ 2. 更新対象となるシェイプのコレクションを取得(例:名前に “Process” を含むもの)
Dim shpList As Collection
Set shpList = GetTargetShapes(targetPage, “Process”)

Dim totalShapes As Long
totalShapes = shpList.Count

If totalShapes = 0 Then
MsgBox “更新対象のシェイプが見つかりませんでした。”, vbExclamation
Exit Sub
End If

‘ 3. 配列の準備 (1シェイプにつき2セル更新する場合)
‘ 2次元配列のサイズ: (0 to 1, 0 to totalShapes – 1)
‘ 0次元目: 0 = Costセル, 1 = Statusセル
‘ 1次元目: 各シェイプのインデックス

Dim sourceStreamIndices() As Integer
ReDim sourceStreamIndices(0 To 1, 0 To totalShapes – 1)

Dim formulas() As Variant
ReDim formulas(0 To (totalShapes 2) – 1)

Dim shp As Visio.Shape
制約チェック用Dim i As Long
Dim arrayIdx As Long

arrayIdx = 0
For i = 1 To totalShapes
Set shp = shpList(i)

‘ 【重要】Shape.CellsU を使って対象セルのストリームインデックス(ID)を取得
‘ これにより、個別のCellオブジェクトを直接叩くことなく、インデックス番号でバッチ処理できる

‘ — 1つ目のセル (Cost) —
sourceStreamIndices(0, i – 1) = shp.CellsU(“Prop.Cost”).Index
formulas(arrayIdx) = CStr(1500 + i 10) ‘ ダミーの計算値
arrayIdx = arrayIdx + 1

‘ — 2つ目のセル (Status) —
‘ 文字列をセットする場合はエスケープ(ダブルクォーテーションで囲む)を忘れない
sourceStreamIndices(1, i – 1) = shp.CellsU(“Prop.Status”).Index
formulas(arrayIdx) = “””” & IIf(i Mod 2 = 0, “Approved”, “Pending”) & “”””
arrayIdx = arrayIdx + 1
Next i

‘ 4. 爆速の極み:1回のAPIコールで一括適用
On Error GoTo ErrorHandler

‘ 画面描画を停止してさらにパフォーマンスを極限まで引き上げる
Application.ScreenUpdating = False

targetPage.SetFormulasU sourceStreamIndices, formulas, 0

Application.ScreenUpdating = True
MsgBox “処理完了: ” & totalShapes & ” 個のシェイプを ” & Format(Timer – startTime, “0.00秒”) & ” で更新しました。”, vbInformation
Exit Sub

ErrorHandler:
Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub

” —————————————————————–
” 補助関数: 指定条件に合致するシェイプのコレクションを返す
” —————————————————————–
Private Function GetTargetShapes(pg As Visio.Page, namePattern As String) As Collection
Dim col As New Collection
Dim shp As Visio.Shape

For Each shp in pg.Shapes
‘ シェイプ名や名前にパターンが含まれるものを抽出
If InStr(shp.Name, namePattern) > 0 Then
col.Add shp
End If
Next shp

Set GetTargetShapes = col
End Function

4. 現場で絶対にハマる「3つの落とし穴」と対策

このテクニックを実務のシステム(Excel連携やDB連携ツール)に組み込む際、熟練者でもハマりがちなポイントを共有します。

① 文字列のエスケープ漏れ

前述の通り、`FormulaU` や `SetFormulasU` に渡す値が文字列(String)である場合、`”Active”` のようにクォーテーションを含めた文字列(`”\”Active\””`)にする必要があります。数値や数式(例: `=SUM(A1+A2)`)を渡す場合は不要ですが、リテラル文字列のときは必ず二重引用符の包み方に注意してください。

② シェイプにプロパティが存在しない場合のエラー

動的に生成されたダイアグラムでは、特定のシェイプに `Prop.Cost` が定義されていない場合があります。その状態で `shp.CellsU(“Prop.Cost”)` を呼ぶと、即座に 「セルが見つかりません (Error -2147352567)」 でクラッシュします。

  • 対策: あらかじめシェイプシートにプロパティ行が存在するかチェックするか、`On Error Resume Next` でガードして存在するものだけを配列に組み込むロジックにしてください。

③ 画面描画の抑制(`ScreenUpdating`)との併用

`SetFormulasU` 自体は爆速ですが、Visioはデフォルトでシェイプが変化するたびに画面を再描画しようとします。必ず `Application.ScreenUpdating = False` と `Application.StatusText` の無効化をセットで行い、OSのリソースをVBAの計算だけに集中させましょう。

5. まとめ:プロフェッショナルなVBAコードを目指して

今回のテクニックの要点をまとめます。

  • ループ内で `CellsU().FormulaU = …` を回すな。COMのオーバーヘッドを呪え。
  • 複数のシェイプ・プロパティを更新する時は `SetFormulasU` と配列(Array)を活用せよ。
  • 文字列を渡すときはエスケープ(ダブルクォーテーション付与)を忘れるな。

業務自動化ツールにおいて、「動くこと」はスタートラインに過ぎません。数百・数千のオブジェクトをストレスなく一瞬で書き換える洗練されたコードこそが、エンジニアとしての価値を高めてくれます。

あなたのVisio自動化スクリプトを、今日から「爆速」へと生まれ変わらせてください。

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