【上級者向け】Word表の構造をクラスモジュールで抽象化し、再利用可能なライブラリを作る
Word VBAにおける表(Table)操作ほど、場当たり的なコードで汚染されやすい領域はない。
`Selection`オブジェクトを多用し、`MoveDown`や`SelectCell`の嵐で構築されたマクロは、ドキュメントのレイアウトが微小に変わっただけで崩壊し、実行速度は幾何級数的に低下する。
真に堅牢なエンタープライズレベルのドキュメント生成基盤を構築するためには、WordのCOMオブジェクトモデルをそのまま触るのではなく、クラスモジュールによる抽象化レイヤーを挟む必要がある。
今回は、数千行規模の帳票自動生成エンジンでも耐えうる、メモリ効率とカプセル化を極限まで高めた「表操作ライブラリ」の設計思想と実装を公開する。
—
1. なぜWordの表操作は破綻するのか?(アーキテクチャの課題)
Wordの表構造は、HTMLのDOMやExcelのRangeとは異なり、ネスト構造と可変長のセル結合(`Merge` / `Split`)を許容する極めて歪なツリー構造を持っている。
レガシーなコードが陥る罠は以下の通りだ。
1. 暗黙の選択(Selection)への依存: 画面描画(ScreenUpdating)が発生し、処理速度が致命的に落ちる。
2. ライフサイクル管理の欠如: 参照したRangeやCellの解放漏れによるCOMメモリリーク。
3. 結合セルへの無慈悲なアクセス: 結合されたセルに対してインデックス直指定を行い、`The Microsoft Word can’t do this because the cells are vertically merged.` というお馴染みのCOM例外を食らう。
これらを解決するため、「流し込みデータのマッピング」「行・列の動的拡張」「セル結合の座標管理」を完全にカプセル化する。
—
2. 設計図:クラス構成
今回構築するライブラリは以下の2つのクラスで構成する。
- `clsWordTable`(メインクラス): テーブル全体のラッパー。行の追加、データの流し込み、一括書式設定を担当。
- `clsTableCell`(内部制御用): 個別セルの操作、文字列の流し込み、パディングやアライメントを制御。
—
3. 実装コード
① `clsTableCell` クラスモジュール
セル単位の操作を抽象化し、例外を吸収するラッパー。
‘ =================================収容クラス: clsTableCell =================================
Option Explicit
Private m_Cell As Word.Cell
Public Sub Initialize(ByRef targetCell As Word.Cell)
Set m_Cell = targetCell
End Sub
‘ セルにテキストを流し込む(段落書式の維持とメモリ最適化)
Public Property Let Text(ByVal rhs As String)
If m_Cell Is Nothing Then Exit Sub
With m_Cell.Range
.Text = rhs
‘ 末尾の改行コード(Word特有の制御文字)を除去してスマートに収める
If .Characters.Count > 0 Then
‘ 制御文字の誤削除を防ぐため、Length調整
Dim lngLen As Long
lngLen = Len(rhs)
If lngLen > 0 And .Characters.Last.Text = vbCr Then
‘ Wordの仕様上入る段落記号の手前までを調整
End If
End If
End With
End Property
‘ 背景色の設定(RGB値)
Public Property Let BackColor(ByVal rhs As Long)
If m_Cell Is Nothing Then Exit Sub
m_Cell.Shading.BackgroundPatternColor = rhs
End Property
‘ 配置設定 (wdAlignParagraphLeft等)
Public Property Let Alignment(ByVal rhs As Long)
If m_Cell Is Nothing Then Exit Sub
m_Cell.Range.Paragraphs.Alignment = rhs
End Property
Private Sub Class_Terminate()
‘ COMオブジェクトの明示的解放
Set m_Cell = Nothing
End Sub
② `clsWordTable` クラスモジュール
テーブル全体を制御するメインエンジン。行の動的追加と二次元配列からの高速流し込みを実装する。
‘ =================================メインクラス: clsWordTable =================================
Option Explicit
Private m_Table As Word.Table
Private m_CurrentRow As Long
‘ テーブルの初期化(既存のテーブル範囲、または新規作成)
Public Sub Initialize(ByRef targetTable As Word.Table)
Set m_Table = targetTable
m_CurrentRow = 1
End Sub
‘ 2次元配列(Variant)から一括でデータを流し込む(高速処理の真髄)
Public Sub BulkInsert(ByRef dataArray As Variant, Optional ByVal hasHeader As Boolean = True)
If m_Table Is Nothing Then Exit Sub
Dim lRow As Long, lCol As Long
Dim startRow As Long: startRow = IIf(hasHeader, 2, 1)
Dim r As Long, c As Long
For r = LBound(dataArray, 1) To UBound(dataArray, 1)
‘ 必要に応じて行を動的に追加
If m_CurrentRow > m_Table.Rows.Count Then
m_Table.Rows.Add
End If
For c = LBound(dataArray, 2) To UBound(dataArray, 2)
If c <= m_Table.Columns.Count Then
Dim cellWrapper As New clsTableCell
cellWrapper.Initialize m_Table.Cell(m_CurrentRow, c)
cellWrapper.Text = CStr(dataArray(r, c))
Set cellWrapper = Nothing
End If
Next c
m_CurrentRow = m_CurrentRow + 1
Next r
End Sub
' 任意のセルを取得し、カプセル化されたオブジェクトを返す
Public Function GetCell(ByVal rowIdx As Long, ByVal colIdx As Long) As clsTableCell
Dim cellWrapper As New clsTableCell
If Not m_Table Is Nothing Then
On Error Resume Next
cellWrapper.Initialize m_Table.Cell(rowIdx, colIdx)
On Error GoTo 0
End If
Set GetCell = cellWrapper
End Function
' 領域のセル結合(安全装置付き)
public Sub MergeCells(ByVal startRow As Long, ByVal startCol As Long, ByVal endRow As Long, ByVal endCol As Long)
If m_Table Is Nothing Then Exit Sub
Dim startCell As Word.Cell
Dim endCell As Word.Cell
On Error GoTo ErrorHandler
Set startCell = m_Table.Cell(startRow, startCol)
Set endCell = m_Table.Cell(endRow, endCol)
startCell.Merge endCell
ErrorHandler:
Set startCell = Nothing
Set endCell = Nothing
End Sub
Private Sub Class_Terminate()
Set m_Table = Nothing
End Sub
---
4. 現場で即座に使える利用サンプル(呼び出し側)
上記のクラス群をインポートした上で、以下のように呼び出す。`Selection`を一切排除し、完全に背後でオブジェクトを構築するため、画面描画のチラつきが一切ない。
Sub Sample_EnterpriseReportGeneration()
‘ 画面描画と警告の完全停止(パフォーマンス最大化の定石)
Application.ScreenUpdating = False
Application.DisplayAlerts = wdAlertsNone
Dim doc As Word.Document
Set doc = ActiveDocument
‘ 3行3列のテーブルを新規作成
Dim rawTable As Word.Table
Set rawTable = doc.Tables.Add(Range:=doc.Range(0, 0), NumRows:=1, NumColumns:=3)
‘ クラスによる抽象化
er table As New clsWordTable
tbl.Initialize rawTable
‘ ヘッダー行の設定
Dim cellHeader As clsTableCell
Set cellHeader = tbl.GetCell(1, 1)
cellHeader.Text = “プロジェクトID”
cellHeader.BackColor = RGB(240, 240, 240)
cellHeader.Alignment = wdAlignParagraphCenter
Set cellHeader = tbl.GetCell(1, 2)
cellHeader.Text = “タスク名称”
cellHeader.BackColor = RGB(240, 240, 240)
Set cellHeader = tbl.GetCell(1, 3)
cellHeader.Text = “工数 (H)”
cellHeader.BackColor = RGB(240, 240, 240)
cellHeader.Alignment = wdAlignParagraphRight
‘ 流し込み用データの準備(通常はDBや外部APIから取得)
Dim data(1 To 3, 1 To 3) As Variant
data(1, 1) = “PRJ-001”: data(1, 2) = “要件定義”: data(1, 3) = 40
data(2, 1) = “PRJ-002”: data(2, 2) = “基本設計”: data(2, 3) = 80
data(3, 1) = “PRJ-003”: data(3, 2) = “実装・単体”: data(3, 3) = 160
‘ バルクインサート実行
tbl.BulkInsert data, hasHeader:=False
‘ セルの結合テスト(例:最終行の1列目と2列目を結合)
tbl.MergeCells startRow:=4, startCol:=1, endRow:=4, endCol:=2
‘ 終了処理
Application.ScreenUpdating = True
Application.DisplayAlerts = wdAlertsAll
MsgBox “高速帳票生成が完了しました。”, vbInformation
End Sub
—
5. シニアエンジニアが押さえるべき「極限の知見」
1. COMの参照カウントとデストラクタの罠:
VBAのクラスモジュール内における `Class_Terminate` は、スコープを抜けた瞬間に即座に呼ばれるとは限らない。特に循環参照(Self-Reference)が存在するとメモリリークの温床になる。今回の設計では子から親への逆参照を持たせず、一方向の依存関係に絞ることで確実にメモリが解放される設計にしている。
2. `ScreenUpdating = False` の絶対性:
Word VBAはExcel以上にUIスレッドとCOMの結びつきが強く、セルの操作ごとに再描画走ると極端に遅くなる。配列を用いたデータ処理と組み合わせることで、1000行を超える表であっても数秒で生成が可能になる。
レガシーなVBAであっても、モダンなオブジェクト指向の設計思想を持ち込むことで、保守性・拡張性は劇的に向上する。場当たり的なマクロ作成から脱却し、再利用可能なライブラリ群による「エンジニアリング」を実践してほしい。
