Word VBAの深淵:`Information`プロパティの欺瞞と、真のページ位置特定ロジック
Word VBAを極めようとする者が必ず突き当たる壁がある。それが「ページ番号の取得」だ。
`Selection.Information(wdActiveEndPageNumber)`。初心者はこれを万能だと信じるが、大規模文書や複雑なセクション構成を扱う現場のエンジニアにとって、このプロパティは「運が良ければ正しく動く」程度の信頼性しかない。
なぜなら、Wordのページ計算は「レンダリング(再描画)」という重たいイベントに依存しているからだ。バックグラウンドでの再描画が完了していない状態でこのプロパティを叩けば、返ってくるのは過去のキャッシュや、期待外れの数値だ。
本稿では、レガシー環境でも安定して動作する、堅牢なページ位置特定ロジックの極意を伝授する。
—
1. `Information`プロパティが抱える「不可視の死角」
`Information`プロパティは、Wordの表示エンジンが持つキャッシュを参照する。以下の条件下では、このメソッドは平然と嘘をつく。
- 非表示のセクション: 改ページやセクション区切りが連続する際、キャッシュの更新が追いつかない。
- ヘッダー/フッターの干渉: カーソルが本文外にある場合、`Selection`オブジェクトの挙動は極めて不安定になる。
- 高頻度な更新: `Application.ScreenUpdating = False` で描画を止めている間、ページ番号は更新されない。
これを回避するためには、「強制的な再計算(Repaginate)」と、Rangeオブジェクトを基点とした「空間的思考」が必要不可欠だ。
—
2. 実装:正確なページ位置を特定する「ゴールデン・ロジック」
単に `Information` を呼ぶのではなく、対象のRangeを確実にアクティブ化し、再描画を強制するラッパー関数を実装する。
‘ @description 指定したRangeが文書内の何ページ目にあるかを正確に取得する
‘ @param targetRange ページ位置を判定したい対象のRange
‘ @return ページ番号 (Long)
Public Function GetAbsolutePageNumber(ByVal targetRange As Range) As Long
‘ 1. 最適化:描画エンジンを強制的に同期させる
‘ Wordはバックグラウンドでページ計算を行うため、明示的に再計算を促す
targetRange.Document.Repaginate
‘ 2. オブジェクトの保護:Selectionを利用する場合の副作用を最小化
‘ 以前のカーソル位置を記憶せずとも、Range操作で完結させるのがベストだが、
‘ InformationプロパティはSelectionの状態に強く依存する
Dim tempSelection As Selection
Set tempSelection = targetRange.Application.Selection
‘ Rangeを一時的に選択状態にして情報を引き出す
targetRange.Select
‘ 3. Informationプロパティの呼出
‘ wdActiveEndPageNumber は現在位置のページ番号を返す
GetAbsolutePageNumber = targetRange.Information(wdActiveEndPageNumber)
‘ 4. メモリ管理:オブジェクトの解放
‘ VBAでは明示的なNothing代入がメモリリークを防ぐ唯一の手段
Set tempSelection = Nothing
End Function
—
3. レガシー環境とメモリ最適化の掟
シニアエンジニアとして忘れてはならないのは、Wordのオブジェクトモデルは「COM(Component Object Model)」のラッパーに過ぎないという事実だ。
- `DoEvents` の罠:
大規模な文書でループ処理を行う際、ページ取得と同時に `DoEvents` を多用してはいけない。これはOSのイベントループを占有し、Wordの描画プロセスを中断させる。必要な場合のみ、`Application.ScreenUpdating = True` を一時的に挟むのが定石だ。
- オブジェクトの明示的解放:
Word VBAにおいて、`Selection` や `Range` をループ内で生成し続けるのは自殺行為だ。メモリは消費され、ガベージコレクションが走るタイミングでWordはフリーズする。必ず変数はループの外で定義し、再利用(Set)すること。
—
4. 究極の回避策:Windows APIの活用
もし、Wordの標準機能が完全に沈黙した場合、我々はWindows API(User32.dll)に手を伸ばす。Wordのウィンドウハンドルを取得し、`SendMessage` で `WM_PAINT` を送出することで、強制的に再描画を促す荒技が存在する。
If VBA7 Then
Private Declare PtrSafe Function SendMessage Lib “user32” Alias “SendMessageA” ( _
ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Else
Private Declare Function SendMessage Lib “user32” Alias “SendMessageA” ( _
ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
End If
‘ Wordのウィンドウを強制再描画させる定数
Private Const WM_PAINT As Long = &HF
これを `Repaginate` の前後に差し込むことで、システムは「確実なページ位置」を把握できるようになる。ただし、これは劇薬だ。多用すればパフォーマンスは劇的に低下する。
—
結びに代えて
Word VBAのコードは、単なる命令の羅列ではない。それはWordという巨大なGUIプロセスとの対話であり、交渉だ。`Information`プロパティが期待通りの値を返さないとき、それはシステムが「今はまだ計算中だ」と語りかけているのだと理解すべきである。
技術を盲信するな。オブジェクトの裏側にある、レンダリングの呼吸を感じ取れ。それが、伝説的な自動化エンジニアへの唯一の道だ。
