【テクニカル・上級編】【上級者向け】段落の「書式変更」をUndoスタックに統合し、ユーザー操作とシームレスに連携させる設計 – Word VBA解析バイブル

スポンサーリンク

Word VBAのUndoスタックを掌握せよ:Undoを「事故」にしないためのアーキテクチャ設計

Word VBAでドキュメントを自動化する際、多くの開発者が突き当たる壁がある。それが「Undo(元に戻す)スタックの断片化」だ。

数千の段落をループで処理し、個別にフォントやスタイルを適用するコードを書いたとしよう。ユーザーが「Ctrl+Z」を押したとき、Wordは1つ前の変更しか戻さない。ユーザーの期待は「このスクリプトによる全変更を1回で取り消すこと」だが、現実は数百回キーを叩かせる残酷な仕様だ。

これは単なるUXの問題ではない。Undoスタックを制御できないコードは、Wordのメモリ管理と競合し、大規模文書においてアプリケーションのクラッシュ(Runtime Error 4605等)を誘発する爆弾となる。

本稿では、Undoスタックを「単一のトランザクション」として封じ込める極限の設計パターンを公開する。

1. Undoスタックを制御する唯一の解:`UndoRecord`オブジェクト

Word 2010以降、Microsoftは `UndoRecord` オブジェクトを導入した。これを使わずに自動化を行うのは、地図を持たずに未開のジャングルへ飛び込むようなものだ。

`UndoRecord` を使えば、一連のVBA操作を1つの「論理的アクション」としてWordの履歴に登録できる。

実装パターン:トランザクション・ラッパー

‘ 伝説的なエンジニアが現場で愛用するUndo管理クラスの雛形
Public Sub ApplyComplexFormatting(doc As Document)
Dim undoObj As UndoRecord
Set undoObj = Application.UndoRecord

‘ トランザクション開始:ここから先は1つの操作として記録される
undoObj.StartCustomRecord “一括書式適用エンジン”

On Error GoTo Cleanup

‘ ここに重厚な処理を記述する
ProcessParagraphs doc

Cleanup:
‘ 処理の成否に関わらずスタックを閉じる(忘れるとWordが不安定になる)
undoObj.EndCustomRecord

If Err.Number <> 0 Then
Err.Raise Err.Number, “UndoManager”, “処理中に致命的なエラーが発生しました: ” & Err.Description
End If
End Sub

2. パフォーマンスの深淵:画面描画とメモリの最適化

Undoスタックを束ねただけでは不十分だ。大規模な文書で `Paragraph` オブジェクトをループする際、`Selection` や `ActiveDocument` を多用するのは素人の所業である。

極限のパフォーマンスを叩き出すための鉄則

1. `ScreenUpdating` の強制停止: API呼び出し以前の問題として、Wordの再描画を止めるのは基本中の基本。
2. `Range` オブジェクトの事前定義: ループ内で `Paragraphs(i).Range` を呼ぶたびにCOMオブジェクトのオーバーヘッドが発生する。可能であれば `Range` を一度取得し、そこを起点に走査せよ。
3. オブジェクトの解放: VBAはガベージコレクションが甘い。大規模処理の終わりには、明示的に `Set x = Nothing` を行い、スタックをクリーンに保て。

‘ メモリリークを最小化し、高速にループを回すための定石
Private Sub ProcessParagraphs(doc As Document)
Dim rng As Range
Dim p As Paragraph

Application.ScreenUpdating = False

‘ 段落オブジェクトを直接走査するよりもRangeの移動が高速な場合が多い
For Each p In doc.Paragraphs
Set rng = p.Range
With rng.Font
.Name = “Meiryo UI”
.Size = 10.5
End With
Next p

Application.ScreenUpdating = True
Set rng = Nothing
Set p = Nothing
End Sub

3. レガシーシステムとの連携:Windows APIの活用

もし君が管理しているシステムが、Word単体ではなく、外部のデータベースや他言語(C#等)からのCOM呼び出しで制御されているなら、「Wordがビジー状態か否か」をAPIで監視する必要がある。

`FindWindow` や `GetWindowThreadProcessId` を使い、Wordのウィンドウハンドルを取得。`WaitForInputIdle` を組み合わせることで、Undoスタックが書き込まれる前に外部から強制介入されるリスクを回避できる。

If VBA7 Then
Private Declare PtrSafe Function WaitForInputIdle Lib “user32” (ByVal hProcess As LongPtr, ByVal dwMilliseconds As Long) As Long
Else
Private Declare Function WaitForInputIdle Lib “user32” (ByVal hProcess As Long, ByVal dwMilliseconds As Long) As Long
End If

※APIの利用は諸刃の剣だ。32bit/64bitのポインタ混在環境では `PtrSafe` 属性を忘れないこと。これを怠ることは、シニアエンジニアとして最大の汚点となる。

結論:安定こそが正義である

Undoスタックの制御、画面描画の抑制、そしてメモリ管理。これら3つが揃って初めて、業務自動化ツールは「システム」として昇華する。

ユーザーが「元に戻す」を押したとき、君が書いた何千行ものコードがまるで最初から存在しなかったかのように、一瞬で消え去る。その静かなる安定感こそが、我々エンジニアが追求すべき至高の芸術だ。

君のコードが、今日もどこかのオフィスで、誰かの無駄な作業をエレガントに消し去ることを願っている。健闘を祈る。

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