Visio VBAの深淵:Page.GetResultsによる「脱・低速ループ」の極意
Visioのオートメーションにおいて、初心者が最初に陥る罠は「シェイプを一つずつ舐める」という悪習だ。
`Page.Shapes(i).CellsU(“Prop.Value”).ResultStr(0)`
もし貴殿のコードにこの記述があるなら、今すぐ破棄していただきたい。数千個のシェイプが存在する図面でこれを行うことは、プロセッサを無駄に回し、COMのオーバーヘッドを幾重にも重ねる愚行に他ならない。Visioのオブジェクトモデルを掌握するということは、「一度の呼び出しで何を得るか」というトランザクションの設計そのものなのである。
今日は、Visioエンジニアが避けて通れない「超高速データ取得」の真髄、`Page.GetResults`を用いた一括処理の技術を伝授する。
—
1. なぜ「個別のセル参照」は死ぬほど遅いのか
Visioの`Cell`オブジェクトにアクセスするたび、VBAからCOM層を経由してVisioの内部データ構造(ShapeSheet)へブリッジが張られる。この往復回数がシェイプ数に比例して増大すれば、通信コストは指数関数的に跳ね上がる。
対して`Page.GetResults`は、「指定したシェイプの指定したセル」をメモリ上の配列として一括で引き抜く。これはデータベースのバルクリードに近い。メモリの連続領域にデータを流し込むため、コンテキストスイッチの回数を最小限に抑えることができる。
—
2. Page.GetResultsの極限実装パターン
以下に、ページ内の全シェイプから特定のセル値を高速抽出し、処理するテンプレートを提示する。
‘ @description Page内の全シェイプからProp.Valueを一括取得する最適化実装
Public Sub FastGetShapeData()
Dim vsoPage As Visio.Page
Dim vsoShapes As Visio.Shapes
Dim count As Long
Dim i As Long
‘ セル参照の配列定義
Dim cellNames() As String
Dim results() As Variant
Dim srcUnits() As Integer
Set vsoPage = ActivePage
Set vsoShapes = vsoPage.Shapes
count = vsoShapes.count
If count = 0 Then Exit Sub
‘ 1. 配列の初期化(要素数はシェイプ数に一致させる)
ReDim cellNames(0 To count – 1)
ReDim srcUnits(0 To count – 1)
‘ 2. 各シェイプに対するクエリを定義
For i = 0 To count – 1
cellNames(i) = “Prop.Value” ‘ 取得したいセル名
srcUnits(i) = Visio.VisUnitCodes.visNoUnit ‘ 単位変換なし(生値)
Next i
‘ 3. 一括取得の実行(これがボトルネックを一掃する)
‘ 第1引数: セル名の配列
‘ 第2引数: 戻り値の型(visGetResultsTyped等で指定)
‘ 第3引数: 取得先配列
‘ 第4引数: 単位
vsoShapes.GetResults cellNames, Visio.VisGetResultsFlags.visGetResultsTyped, results, srcUnits
‘ 4. 高速なループ処理(既にメモリ上にあるデータへのアクセス)
For i = 0 To count – 1
Debug.Print “Shape ID: ” & vsoShapes(i + 1).ID & ” Value: ” & results(i)
Next i
‘ メモリ最適化:明示的な解放はVBAでも習慣化すべき作法
Erase cellNames
Erase results
Erase srcUnits
End Sub
—
3. シニアエンジニアが知るべき「メモリと境界条件」
このメソッドを使う上で、いくつか注意すべき「深淵」がある。
- 存在しないセルへの対処: 指定したセル名(例: `Prop.Value`)が存在しないシェイプが含まれている場合、`GetResults`はエラーを吐く。全シェイプが共通のシェイプシート構造を持っていることが前提だが、複雑な図面では`SectionExists`等で事前にフィルタリングするか、例外処理を組み込む必要がある。
- Variant型の罠: `results`配列は`Variant`型として戻ってくる。`ResultStr`のように自動的に文字列化されるわけではないため、セル値の型(数値、文字列、数式)を意識したパースが必要だ。
- レガシー環境の保守: Windows APIの `GlobalAlloc` 等でメモリを直接操作するような狂気的なコードは不要だが、大量のシェイプを扱う際は、Visioの描画を一時的に停止する `Application.ScreenUpdating = False` を併用すること。これはAPI呼び出し以上にUI描画のオーバーヘッドを抑える定石である。
—
4. 結びに代えて
システム連携の現場において、Visioは単なる「図面作成ソフト」ではない。背後に巨大なメタデータを抱えた「グラフィカルなデータベース」である。
`Page.GetResults`を使いこなすということは、Visioという巨大なエンジンを効率的に駆動させるための「特権的なアクセス権」を手に入れることに等しい。コードの行数を減らすことだけが目的ではない。「計算機資源をいかにエレガントに扱うか」。それこそが、我々エンジニアが追求すべき美学である。
貴殿のコードが、一瞬のレスポンスで数万のデータを処理することを期待している。健闘を祈る。
