【実務・中級編】【上級】Shape.GetResultsとSetResultsによる配列一括転送:数千シェイプのデータを一瞬で読み書きする – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:数千シェイプのデータを一瞬で読み書きする `GetResults` / `SetResults` の極意

開発現場でよく見かける光景がある。数千個のシェイプを持つ巨大なネットワーク図やプラント図に対し、VBAでプロパティやカスタムプロパティ(シェイプデータ)を読み書きする処理だ。

‘ 【アンチパターン】絶対にやってはいけない個別アクセスの例
Dim shp As Visio.Shape
For Each shp In ActivePage.Shapes
‘ ループのたびにVisioのCOM境界を跨ぎ、SheetSectセルに個別アクセスする
shp.CellsU(“Prop.Cost”).FormulaU = “1000”
Next shp

もしあなたが、シェイプの数だけこのループを回しているなら、今すぐその手を止めてほしい。この書き方は、Visio VBAにおいて「罪深い遅延」を生み出す最大の原因だ。シェイプが3,000個あれば、COMのコンテキストスイッチが3,000回発生し、画面がフリーズしたかのような重たい処理になる。

今回は、Visioのオブジェクトモデルの深淵を突き詰め、数千のシェイプデータを一瞬で処理するための最強の武器――`Shape.GetResults` と `Shape.SetResults` による配列一括転送の技術を伝授する。

—

1. なぜ個別アクセスは遅いのか?(COM境界の罠)

VisioのVBAからシェイプのセル(Cell)にアクセスするとき、VBAのランタイムとVisioのC++コアエンジンとの間でCOM境界(COM Interop Boundary)を跨ぐ通信が発生する。

1回あたりの通信コストはわずか数ミリ秒であっても、数千〜数万回と累積すれば、人間が耐えられないほどの待ち時間(数分単位)に化ける。さらに、セルに値を書き込むたびにVisioは図面の再計算(Re-evaluate)や再描画(Redraw)をトリガーしようとするため、パフォーマンスはさらに悪化する。

解決策:メモリ空間の直結と一括転送

`GetResults` と `SetResults` は、このCOM境界の往復を「1回」に圧縮するメソッドである。
VBAのメモリ上に確保した二次元配列(Variant型)と、Visioシェイプ群のセル群をダイレクトに結びつけ、一網打尽に読み書きする。これにより、処理速度を最大100倍以上に跳ね上げることが可能だ。

—

2. `GetResults` / `SetResults` の仕様を完全に理解する

この2つのメソッドは、一見すると引数が複雑で敬遠されがちだが、構造さえ掴んでしまえば恐るに足りない。

`GetResults` の構文

retArray = object.GetResults(LocaleFlags, Flags, SID_NamePairs(), SID_Name_Context)

  • LocaleFlags: セル表現のロケール(通常は `visGetFloats` または `visGetNames`。日本語環境なら `visGetNames` を使うことが多い)。
  • Flags: 取得方法のフラグ(通常は `0`)。
  • SID_NamePairs(): 「どのシェイプの」「どのセルか」を交互に格納した1次元配列(Variant)。
  • SID_Name_Context: コンテキストオブジェクト(通常はActiveDocumentやNothing)。
  • 戻り値: 取得した値が格納された1次元のVariant配列。

`SetResults` の構文

object.SetResults LocaleFlags, SID_NamePairs(), Values() [, Flags]

  • SID_NamePairs(): `GetResults` と同様のシェイプ・セル指定配列。
  • Values(): 書き込みたい値を格納した1次元(または2次元)のVariant配列。

—

3. 【プロダクションコード】数千シェイプを一括処理する実用モジュール

ここからは、実務のデータベース連携やCSVインポート/エクスポートを想定した、堅牢でモダンなVBAコードを提示する。エラーハンドリング、画面描画の抑止、配列の動的制御まで網羅したプロダクション品質のコードだ。

Option Explicit

‘ ==============================================================================
‘ 担当者必携:Visioシェイプデータ一括高速処理モジュール
‘ ==============================================================================
Public Sub ProcessShapesInBatch()
Dim startTime As Double
startTime = Timer

‘ 1. パフォーマンス最大化のための定石設定
Dim originalScreenUpdating As Boolean
originalScreenUpdating = Application.ScreenUpdating
Application.ScreenUpdating = False ‘ 画面描画を停止
Application.ShowChanges = False ‘ 変更の視覚化を停止

On Error GoTo ErrorHandler

Dim pg As Visio.Page
Set pg = ActivePage

Dim shps As Visio.Shapes
Set shps = pg.Shapes

Dim totalShapes As Long
totalShapes = shps.Count

If totalShapes = 0 Then
MsgBox “処理対象のシェイプが存在しません。”, vbInformation
GoTo Finally
End If

‘ 2. 対象シェイプのID(或いは名前)を特定し、SID_NamePairs配列を構築する
‘ GetResults/SetResults では、奇数番目に「シェイプID (Long または String)」、偶数番目に「セル名 (String)」を入れる
Dim sidNamePairs() As Variant
ReDim sidNamePairs(1 to totalShapes 2)

Dim i As Long, idx As Long
idx = 1
For i = 1 to totalShapes
sidNamePairs(idx) = shps(i).ID ‘ シェイプID
sidNamePairs(idx + 1) = “Prop.Cost” ‘ 対象のシェイプデータセル名(例: コスト)
idx = idx + 2
Next i

‘ 3. 【一括読み込み】GetResultsの実行
Dim valuesArray() As Variant
‘ visGetNames 領域でフォーマットを取得
pg.GetResults Visio.VisGetOrSetFlags.visGetNames, totalShapes, sidNamePairs, valuesArray

‘ 4. 取得したデータの加工(例:すべてのコストを1.1倍にインフレさせる)
For i = LBound(valuesArray) To UBound(valuesArray)
‘ エラー値や空欄のハンドリングを行いつつ演算
If Not IsError(valuesArray(i)) And IsNumeric(valuesArray(i)) Then
valuesArray(i) = valuesArray(i) 1.1
Else
valuesArray(i) = 0 ‘ デフォルト値
End If
Next i

‘ 5. 【一括書き込み】SetResultsの実行
‘ 読み込んだ配列(あるいは外部DBから取得した新しい配列)をそのまま流し込む
pg.SetResults Visio.VisGetOrSetFlags.visGetNames, totalShapes, sidNamePairs, valuesArray

‘ 正常終了ログ
Debug.Print “処理完了: ” & totalShapes & ” 個のシェイプを ” & Format(Timer – startTime, “0.00秒”) & ” で処理しました。”

Finally:
‘ 6. 環境の復元(絶対に忘れてはならない)
Application.ScreenUpdating = originalScreenUpdating
Application.ShowChanges = True
Exit Sub

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

—

4. 現場で絶対に踏み抜いてはならない「3つの地雷」

どれほど優れたコードであっても、Visio固有のオブジェクトモデルの癖を理解していないと、現場でバグを生む。プロのエンジニアが避けるべきポイントを解説する。

① 配列のインデックスのベース(LBound/UBound)に気を付けろ

VBAの `Array` 関数や `GetResults` が返す配列は、原則として0始まり(0-based)になる。一方で、VisioのShapesコレクションや自前で構築する `sidNamePairs` を1始まり(1-based)で設計している場合、インデックスのズレによる `Subscript out of range`(インデックスが有効範囲にありません)エラーが頻発する。
必ず `LBound` と `UBound` を動的に評価してループを回すこと。

② シェイプが存在しないセル名へのアクセス

指定したシェイプに、該当のシェイプデータ(例: `Prop.Cost`)が存在しない場合、`GetResults` は実行時エラーを吐かずに、戻り値の配列の該当要素にエラー値(Error Variant)を格納して処理を続行する。
そのため、書き戻しや集計を行う前に必ず `IsError()` 関数でバリデーションを行わなければならない。ここをサボると、次の計算ロジックで型不一致エラーの泥沼にはまる。

③ 画面更新の復元忘れ (`ScreenUpdating`)

パフォーマンス最適化のために `Application.ScreenUpdating = False` を行うのは鉄則だが、途中でエラーハンドラにジャンプしたり、予期せぬブレークポイントで処理が中断されたりすると、Visioの画面が真っ白なまま、あるいは再描画されないままフリーズしたように見える状態になる。
必ず `Finally` ブロック(疑似的なジャンプラベル)を設けて、環境変数を元の状態に戻すコードを保証すること。

—

5. データベース・外部ファイル連携への応用

この `GetResults` / `SetResults` パターンをマスターすれば、外部システム(SQL Server、PostgreSQL、あるいはCSV/Excel)との連携基盤が一気に強固になる。

  • インポート時(外部DB ⇒ Visio):

外部DBからDataTableをVBAの配列に落とし込み、`SetResults` で一括してVisio図面に流し込む。数千台のサーバー機器が描かれたインフラ構成図のステータスを一瞬で「稼働中/停止中」に塗り替えることが可能だ。

  • エクスポート時(Visio ⇒ 外部DB):

`GetResults` で図面内の全シェイプの座標(PinX, PinY)やカスタムプロパティを丸ごとメモリ上に吸い上げ、ADO経由で一括バルクインサートする。

一つひとつのシェイプを愛でるようにノロノロとループを回すコードは、今日で卒業しよう。配列と一括メソッドを支配した者だけが、真の「Visio自動化の神髄」に到達できる。

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