【実務・中級編】【中級者向け】表の各行をループし、特定のキーワードが含まれる行を赤字にする強調表示マクロ – Word VBA解析バイブル

スポンサーリンク

なぜあなたのWord表操作マクロは遅く、壊れやすいのか

業務自動化の現場において、Word VBAはExcel VBAに比べて敬遠されがちです。その最たる理由が「表(Table)オブジェクトの扱いにくさ」にあります。

Excelと同じ感覚でセルを走査しようとすると、謎の制御文字に判定を狂わされ、結合セルに触れた瞬間にランタイムエラーを吐いて停止する。苛立ちに任せてマクロ記録を取ると、画面が激しく明滅する`Selection`まみれのコードが生成され、数十ページのドキュメントで実行すればフリーズしたかのように固まる――。あなたも一度は経験があるはずです。

今回は中級者へのステップアップとして、「表の各行をループ走査し、特定キーワードが含まれる行全体を動的に赤字強調する」という実務頻出のタスクを題材にします。単に動くだけのコードではありません。大規模文書でも高速に動作し、結合セルやWord特有の内部仕様に足をすくわれない、堅牢なプロダクションコードの書き方を叩き込みます。

—

アーキテクチャ視点で暴く、Word表操作「3つの落とし穴」

コードを書く前に、Wordの内部構造がExcelと決定的に異なるポイントを理解してください。ここを曖昧にしたまま書かれたコードは、本番環境で必ずバグを引き起こします。

【Wordセルの内部構造】
┌─────────────────────────────────┐
│ “承認済み” │ ← Cell.Range.Text
│ (Chr(13) + Chr(7)) │ ← エンド・オブ・セル・マーカー(末尾2文字)
└─────────────────────────────────┘
※ この2文字を取り除かない完全一致判定は100%失敗する

1. エンド・オブ・セル・マーカーの存在

Wordのセル内テキストの末尾には、必ずセル終了マーク(`Chr(13) & Chr(7)` / キャリッジリターン+ベル文字)という2バイトの制御コードが付着しています。
例えばセルに「要確認」と表示されていても、VBAから`Cell.Range.Text`を取得すると実際には `”要確認” & vbCr & Chr(7)` が返されます。部分一致を行う`InStr`関数ではすり抜けることもありますが、後続の処理で完全一致比較を行ったり、正規表現に渡したりした瞬間にバグと化します。セル値のサニタイズ(トリミング)はWord VBAの絶対原則です。

2. 結合セルによる「インデックス崩壊」

表内に縦結合(セル結合)が存在する場合、`Table.Rows.Count` や `Table.Cell(r, c)` を無邪気に呼び出すと、実行時エラー `5991`(「縦に結合されたセルがあるため、コレクションの個々の行にはアクセスできません」)または `5941`(要求されたコレクションのメンバーが存在しません)が容赦なく投げられます。実務の帳票で結合セルを完全に排除することは不可能です。コード側で防御策を講じる必要があります。

3. Selection依存による描画コストの爆発

`Cell.Select` や `Selection.Font.ColorIndex` をループ内で回すのは最悪の手法です。画面再描画のオーバーヘッドが重なり、100行程度の表でも体感で数秒〜数十秒のラグが発生します。Rangeオブジェクトを直接捕捉し、メモリ上でプロパティを変更するのがプロの鉄則です。

—

プロダクション仕様:特定行ハイライト完全実装

それでは、現場のシビアな要件に耐えうるコード例を提示します。
このプロシージャは、アクティブドキュメント内の指定テーブルに対し、指定列のキーワードを検査し、合致した行全体のフォントを赤(太字)に書き換えます。

Option Explicit

”’

”’ 指定テーブルの特定列を検索し、キーワードが含まれる行全体を強調表示する
”’

”’ 対象のWord Tableオブジェクト ”’ 判定対象とする列インデックス(1始まり) ”’ 検索キーワード Public Sub HighlightRowsByKeyword( _
ByVal targetTable As Word.Table, _
ByVal targetColIndex As Long, _
ByVal keyword As String)

‘ 画面描画とバックグラウンド処理の抑止による高速化
Application.ScreenUpdating = False

On Error GoTo ErrHandler

‘ 引数のバリデーション
If targetTable Is Nothing Then Exit Sub
If Len(Trim$(keyword)) = 0 Then Exit Sub

Dim r As Long
Dim totalRows As Long
Dim cellText As String
Dim targetCellRange As Word.Range
Dim targetRowRange As Word.Range

‘ 縦結合セルが存在する場合、Rowsコレクションへのアクセスで実行時エラーが発生する
‘ そのため、エラーをトラップしてCellベースの安全走査へフォールバックするか判定する
Dim hasUniformRows As Boolean
hasUniformRows = True

On Error Resume Next
totalRows = targetTable.Rows.Count
If Err.Number <> 0 Then
hasUniformRows = False
Err.Clear
End If
On Error GoTo ErrHandler

‘ ————————————————————-
‘ パターンA: 均一な表構造(結合セルなし)の場合の高速走査
‘ ————————————————————-
If hasUniformRows Then
For r = 1 To totalRows
‘ 列インデックスの存在チェック
If targetColIndex <= targetTable.Rows(r).Cells.Count Then Set targetCellRange = targetTable.Cell(r, targetColIndex).Range cellText = CleanCellText(targetCellRange.Text) ' InStrによるキーワード判定(大文字小文字を区別しない場合はvbTextCompare) If InStr(1, cellText, keyword, vbTextCompare) > 0 Then
‘ 行全体のRangeを取得してフォントを変更
Set targetRowRange = targetTable.Rows(r).Range
With targetRowRange.Font
.ColorIndex = wdRed
.Bold = True
End With
End If
End If
Next r

‘ ————————————————————-
‘ パターンB: 縦結合セルが存在する場合のフェイルセーフ走査
‘ ————————————————————-
Else
‘ Rowsが使えないため、全セルをフラットに走査して該当セルの親行を処理する
Dim singleCell As Word.Cell
For Each singleCell In targetTable.Range.Cells
If singleCell.ColumnIndex = targetColIndex Then
cellText = CleanCellText(singleCell.Range.Text)

If InStr(1, cellText, keyword, vbTextCompare) > 0 Then
‘ 結合セルを含む行全体の取得を試みる安全策
On Error Resume Next
singleCell.Range.Rows(1).Range.Font.ColorIndex = wdRed
singleCell.Range.Rows(1).Range.Font.Bold = True
If Err.Number <> 0 Then
‘ Rowsが一切取れない極端な結合構造の場合は、セル単体をハイライト
singleCell.Range.Font.ColorIndex = wdRed
singleCell.Range.Font.Bold = True
Err.Clear
End If
On Error GoTo ErrHandler
End If
End If
Next singleCell
End If

CleanUp:
Application.ScreenUpdating = True
Set targetCellRange = Nothing
Set targetRowRange = Nothing
Exit Sub

ErrHandler:
‘ 本番用ロギング処理をここに挟む
Debug.Print “Error [” & Err.Number & “]: ” & Err.Description
Resume CleanUp
End Sub

”’

”’ Wordセル特有のエンド・オブ・セル・マーカー (Chr(13) & Chr(7)) を完全に除去する
”’

Private Function CleanCellText(ByVal rawText As String) As String
Dim textLength As Long
textLength = Len(rawText)

‘ セル終了文字は必ず末尾2文字に存在する
If textLength >= 2 Then
If Right$(rawText, 2) = vbCr & Chr(7) Then
CleanCellText = Left$(rawText, textLength – 2)
Exit Function
End If
End If

‘ 例外的なケースに対するフォールバック
CleanCellText = Replace(Replace(rawText, vbCr, “”), Chr(7), “”)
End Function

呼び出し側のコード(実行エントリポイント)

Public Sub ExecuteSample()
‘ 文書内に表が存在しない場合は早期リターン
If ActiveDocument.Tables.Count = 0 Then
MsgBox “対象となる表が存在しません。”, vbExclamation, “処理中断”
Exit Sub
End If

‘ 例: 文書内の第1テーブル、2列目(ステータス列)に「遅延」が含まれる行を赤色にする
Call HighlightRowsByKeyword( _
targetTable:=ActiveDocument.Tables(1), _
targetColIndex:=2, _
keyword:=”遅延”)

MsgBox “強調表示処理が完了しました。”, vbInformation, “完了”
End Sub

—

チーフアーキテクトが解説する「設計の必然性」

提供したコードの背後にあるアーキテクチャ設計について、3つの視点から解説します。

1. `CleanCellText` 関数の局所化と最適化

Wordセルの末尾除去を `Replace(rawText, vbCr, “”)` だけで済ませてはいけません。セル内に「セル内改行(`Shift + Enter` や 単なる `Enter`)」が含まれている場合、無秩序な `Replace` は改行構造そのものを破壊して結合してしまい、予期しない文字列マッチングを引き起こすからです。
確実に末尾の2文字(`vbCr & Chr(7)`)だけを切り落とすロジックにすることで、「セル内改行を保持したまま、終了マーカーだけを安全に剥ぎ取る」 ことが保証されます。

2. 結合セルに対する「二段構え」の防御

多くのプログラマは、`Table.Rows` で走査するか `Table.Range.Cells` で走査するかの二者択一で設計します。
しかし、結合セルがない通常の表では `Rows` コレクションを経由して `Row.Range` を一度に色付けした方が圧倒的に高速です。一方で、例外的な帳票構造でマクロが落ちることも許されません。
そのため、本コードでは「まず `Rows.Count` の成否で表の健全性をプロービング(打診)し、結合を検知したら `Range.Cells` の低速だが堅牢なループへ自動フォールバックする」 という二段構えの制御構造を敷いています。これが現場で「絶対に落ちないマクロ」を作るための設計です。

3. メモリ管理とCOM参照の解放

ループ内で `Range` 変数を使い回す際、Wordの内部オブジェクト参照がリークしてプロセスの肥大化を招くことがあります。特に数万行におよぶ巨大な技術仕様書などを処理する場合、`Set targetCellRange = Nothing` を含むクリーンアップブロックを確実に通過させるライフサイクル管理が求められます。

—

外部データ連携(CSV/DB)時の注意点

実務では、このマクロを「基幹システムから出力されたデータをWordテンプレートに流し込んだ直後」にキックするケースが多々あります。その際のアーキテクチャ上の注意点を付記しておきます。

  • NULL値のハンドリング: DBから取得したレコードセットのフィールドが `Null` の場合、文字列結合の段階でエラーになります。`Trim$(rs.Fields(“Status”).Value & “”)` のように空文字へ安全にキャストした上でWordセルへ流し込んでください。
  • アンドゥ履歴の肥大化: 大量データの流し込みと書式変更を同時に行うと、WordのUndoバッファがメモリを圧迫します。数千行規模の帳票を自動生成・フィルタリングする場合は、一定行数ごとにドキュメントを一時保存するか、Undo履歴をクリアするAPI呼び出しの導入を検討してください。

—

まとめ:Wordのオブジェクトモデルを支配せよ

Word VBAは「なんとなく」書いているうちは、不可解な挙動で開発者を翻弄し続けます。しかし、
1. セル終了マーカーの挙動を正しく理解し、データをサニタイズする
2. 結合セルを想定し、エラーを握りつぶさずフェイルセーフな分岐を組む
3. Selectionを排除し、Rangeを直接書き換える

この3原則を徹底するだけで、Excelマクロと遜色ない高速かつ極めて堅牢な自動化ツールへと昇華させることができます。ぜひ手元のプロジェクトに組み込み、属人化した帳票チェック作業を瞬時に終わらせる威力を体感してください。

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