【テクニカル・上級編】【上級者向け】段落の「書式設定」をクラスモジュールで抽象化し、再利用可能な書式適用エンジンを作る – Word VBA解析バイブル

スポンサーリンク

Word VBAにおける「書式適用の抽象化」:再利用可能なフォーマッティング・エンジンの構築

多くの開発者がWord VBAで犯す最大の過ちは、マクロ記録の延長線上にある「Selectionオブジェクトの直接操作」と「プロパティへの逐次アクセス」だ。数ページの文書ならそれでも動く。しかし、数千ページの法的文書や、基幹システムから出力される複雑な技術仕様書を自動生成する場合、そのアプローチは破綻する。

我々プロフェッショナルが求めるのは、単なる自動化ではない。「保守性」「堅牢性」そして「圧倒的な実行速度」だ。

今回は、段落書式とフォント設定をクラスモジュールによって抽象化し、ビジネスロジックと書式定義を完全に分離する「書式適用エンジン」の設計思想を詳解する。

1. なぜ「書式」をクラスで包むのか

Wordの`ParagraphFormat`や`Font`オブジェクトは非常に重い。これらを直接操作するコードが散在すると、仕様変更(例:「見出しのインデントを3mmから5mmに変える」)のたびに、全ロジックを走査する羽目になる。

抽象化のメリット

  • デカップリング: 文書構造の解析ロジックと、見た目の装飾ロジックを分離できる。
  • メモリ管理の最適化: オブジェクトのライフサイクルを制御し、COM参照のリークを防ぐ。
  • パフォーマンス: 冗長なプロパティ設定を排除し、必要な差分のみを適用するエンジンの構築が可能になる。

2. アーキテクチャの設計:Paragraph Decoratorパターン

以下に、書式情報を保持する`clsParagraphStyle`と、それを適用する`clsFormattingEngine`の二層構造を示す。

2.1. 書式定義クラス:`clsParagraphStyle`

まず、適用したい書式の「ステート(状態)」を保持するクラスを作る。

‘ @Class: clsParagraphStyle
‘ 段落およびフォントの書式定義をカプセル化する
Option Explicit

Public Name As String
Public Alignment As WdParagraphAlignment
Public LeftIndent As Single
Public RightIndent As Single
Public SpaceBefore As Single
Public SpaceAfter As Single
Public LineSpacingRule As WdLineSpacing
Public FontName As String
Public FontSize As Single
Public FontBold As Boolean

‘ コンストラクタ代わりの初期化メソッド
Public Sub Define(strName As String, _
align As WdParagraphAlignment, _
indent As Single, _
fSize As Single, _
isBold As Boolean)
Me.Name = strName
Me.Alignment = align
Me.LeftIndent = indent
Me.FontSize = fSize
Me.FontBold = isBold
End Sub

2.2. 書式適用エンジン:`clsFormattingEngine`

次に、この定義を受け取り、対象の`Range`へ高速に適用するエンジンを実装する。ここで重要なのは、Rangeオブジェクトを最小限の回数で操作することだ。

‘ @Class: clsFormattingEngine
‘ 定義されたスタイルをWordオブジェクトモデルに効率的に流し込む
Option Explicit

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
Private Const WM_SETREDRAW As Long = &HB

‘ 書式適用メインメソッド
Public Sub Apply(ByRef rng As Word.Range, ByRef styleDef As clsParagraphStyle)
If rng Is Nothing Then Exit Sub

‘ Wordの内部処理を一時停止し、描画負荷を軽減
‘ (Application.ScreenUpdatingよりも低レイヤーでの制御)

On Error GoTo ErrorHandler

With rng.ParagraphFormat
If .Alignment <> styleDef.Alignment Then .Alignment = styleDef.Alignment
If .LeftIndent <> styleDef.LeftIndent Then .LeftIndent = styleDef.LeftIndent
‘ 必要最小限のプロパティ更新(値が同じなら代入しない)
End With

With rng.Font
If .Name <> styleDef.FontName And styleDef.FontName <> “” Then .Name = styleDef.FontName
If .Size <> styleDef.FontSize Then .Size = styleDef.FontSize
If .Bold <> styleDef.FontBold Then .Bold = styleDef.FontBold
End With

Exit Sub

ErrorHandler:
‘ エラーハンドリングとリソース解放のロジック
Debug.Print “Error in Apply: ” & Err.Description
End Sub

3. 極限の最適化:Windows APIとメモリ管理

大規模文書の処理において、VBAの標準機能だけでは限界がある。チーフアーキテクトとして、以下の二点を徹底せよ。

3.1. Windows APIによる描画抑制

`Application.ScreenUpdating = False` は、実はWordにおいて万能ではない。Win32 APIの `SendMessage` を使用し、ウィンドウメッセージレベルで描画を止めることで、特にスクロールが発生するような巨大な文書でのパフォーマンスが劇的に向上する。

3.2. オブジェクトの明示的解放とUndoスタック

Wordは操作の履歴(Undo)をメモリに蓄積する。数万回の書式変更を行うと、これがメモリを圧迫し、最終的に「アウト・オブ・メモリー」を引き起こす。

‘ 処理の途中で定期的にUndoバッファをクリアする
‘ ただし、ユーザーが戻せなくなるため、バッチ処理専用とする
Application.ActiveDocument.UndoClear

また、ループ内で `Range` オブジェクトを生成する場合、必ず `Set rng = Nothing` で参照カウントを減らすこと。VBAのガベージコレクションは非決定論的ではないが、COMオブジェクトの解放をOS任せにするのはプロの仕事ではない。

4. 実践:エンジンの呼び出し例

Public Sub GenerateReport()
Dim engine As New clsFormattingEngine
Dim headerStyle As New clsParagraphStyle

‘ スタイルの定義(一度定義すれば使い回し可能)
headerStyle.Define “MainHeader”, wdAlignParagraphCenter, 0, 18, True
headerStyle.FontName = “MS ゴシック”

Dim doc As Document: Set doc = ActiveDocument
Dim i As Long

‘ 描画停止(APIレベル)
SendMessage Application.hwnd, WM_SETREDRAW, 0, 0

‘ 文書構造のループ処理
For i = 1 To doc.Paragraphs.Count
Dim targetRng As Range
Set targetRng = doc.Paragraphs(i).Range

‘ 特定の条件に基づきエンジンで書式適用
If InStr(targetRng.Text, “【重要】”) > 0 Then
engine.Apply targetRng, headerStyle
End If

Set targetRng = Nothing ‘ 明示的解放
Next i

‘ 描画再開
SendMessage Application.hwnd, WM_SETREDRAW, 1, 0
Application.ScreenRefresh

MsgBox “処理完了”, vbInformation
End Sub

5. 結論:レガシーをモダンに昇華させる

Word VBAというレガシーな環境であっても、設計思想次第でエンタープライズ品質のシステムは構築可能だ。

1. 直書きを捨てる: プロパティを直接いじるのではなく、クラスに語らせよ。
2. 差分のみを更新する: `If .Value <> NewValue Then .Value = NewValue` のパターンを徹底せよ。
3. OSの力を借りる: 必要なら迷わずWindows APIを叩け。

このアプローチは、単にコードが綺麗になるだけでなく、将来的なVB.NETへの移行(VSTO)や、他のOfficeアプリケーションとの連携においても、堅牢なベースラインとなる。

技術の真髄は細部に宿る。段落一つ、フォント一つの扱い方に、エンジニアとしての矜持を込めよ。

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