Word VBAの「ページ番号取得」という幻想:Informationプロパティの限界を突破する技術
Word VBAを操る多くの開発者が、ある日突然、絶望に突き当たる。「`Selection.Information(wdActiveEndPageNumber)` が、なぜか期待した数値を返さない」。
そう、Wordのページ番号取得は、単純なプロパティ呼び出しで解決できるほど甘い世界ではない。今日は、この泥沼から脱出し、大規模ドキュメントでも正確にページ位置を特定するための「真の設計論」を伝授しよう。
—
なぜ `wdActiveEndPageNumber` は裏切るのか
多くのチュートリアル記事は、`Selection.Information(wdActiveEndPageNumber)` を使えと説く。しかし、実務の現場でこのコードが「地雷」である理由は明確だ。
1. 再計算の遅延: Wordはページレイアウトを「バックグラウンド」で計算する。VBA実行時にレイアウトが確定していない場合、キャッシュされた古い値を平気で返してくる。
2. セクション区切りの罠: セクションごとにページ番号をリセットしたり、書式を変えたりしている場合、単一の整数値は物理的なページ順序と乖離する。
3. Rangeの非同期性: `Range` オブジェクトに対してこのプロパティを叩く際、そのRangeがアクティブなビューの外にあると、Wordは計算をサボる。
結論を言おう。Word VBAにおいて「ページ番号」とはプロパティから取得するものではなく、レンダリングエンジンの状態を強制的に同期させてから「観測」するものだ。
—
極限の安定性を実現するロジック
正確なページ番号を取得するための鉄則は、「対象のRangeを可視化し、レイアウト更新を強制的に走らせる」ことにある。
以下に、実務で絶対にバグらせないためのプロダクションコードを提示する。
実装:正確なページ位置特定関数
‘ @brief 指定したRangeの正確なページ番号を取得する
‘ @param rng 対象とするRangeオブジェクト
‘ @return 正確なページ番号(エラー時は-1)
Public Function GetAccuratePageNumber(ByVal rng As Range) As Long
Dim currentView As Long
Dim pNum As Long
On Error GoTo ErrorHandler
‘ 1. 現在の表示モードを保存
currentView = ActiveWindow.View.Type
‘ 2. ページレイアウト表示に強制切り替え
‘ これによりWordのレイアウトエンジンに計算を強制する
ActiveWindow.View.Type = wdPrintView
‘ 3. Rangeを動的に評価
‘ Informationプロパティは「その場所が可視状態であること」で精度が最大化される
pNum = rng.Information(wdActiveEndPageNumber)
‘ 4. 表示モードを復元
ActiveWindow.View.Type = currentView
GetAccuratePageNumber = pNum
Exit Function
ErrorHandler:
GetAccuratePageNumber = -1
End Function
—
現場で役立つ「設計の勘所」
このコードをただ使うだけではプロとは言えない。さらに高みを目指すための注意点を二つ共有する。
1. 巨大ファイルにおける「パフォーマンスの制約」
ドキュメントが数百ページに及ぶ場合、上記のコードをループ内で叩くのは自殺行為だ。`ActiveWindow.View.Type` の切り替えは極めて重い。
- 対策: ループ内でページ番号を判定する必要がある場合は、`Application.ScreenUpdating = False` を活用しつつ、可能な限り処理単位を絞る。あるいは、ドキュメント全体の構造を解析するなら、`Range.Information` ではなく、`ActiveDocument.BuiltInDocumentProperties` や `Sections` コレクションを直接操作する設計に切り替えるべきだ。
2. データベース連携時の「整合性」
Wordから抽出したページ番号をデータベースに保存する場合、「ページ番号はあくまで表示上の情報である」ということを忘れてはならない。
- 鉄則: ページ番号でデータを紐付けるのはNGだ。必ず「ブックマーク」や「ユニークな段落スタイル」を基準にRangeを特定し、その上で動的にページ番号を計算させること。文書が修正されるたびにページ番号は変動する。この「変動性」を前提としたデータ構造を設計するのが、エンジニアとしての責任だ。
—
最後に:コードの保守性について
私が現場でよく見るのは、`Selection` を多用した不安定なコードだ。`Selection` は人間の操作をシミュレートするオブジェクトであり、プログラムの制御用ではない。
「`Selection` を使うな。`Range` を使い倒せ。」
今回紹介したロジックは、オブジェクトのライフサイクルを制御することで、Wordという気まぐれなエンジンの機嫌を取るためのテクニックだ。これをベースに、あなたの開発するツールが、誰よりも堅牢で、誰よりも速いことを証明してほしい。
技術の深淵へ、健闘を祈る。
