【実務・中級編】【初心者向け】表の特定の列に連番を振る際、結合セルをスキップして正しくカウントするロジック – Word VBA解析バイブル

スポンサーリンク
スポンサーリンク

「結合セルの罠」を回避せよ:Word VBAにおける堅牢な連番付与ロジックの設計思想

業務自動化の現場において、Wordの「表(Table)」は避けては通れない領域だ。請求書、報告書、契約書——これら実務で使われる文書の多くは、複雑なレイアウトを持つ表で構成されている。

しかし、多くの開発者がここで躓く。「結合セル」の存在だ。

単純な `For i = 1 To Table.Rows.Count` というループを書いて、結合セルが含まれる表に対して実行した瞬間、ランタイムエラー `5941`(指定されたメンバーはコレクションに存在しません)が牙を剥く。これは初心者によるミスではない。Wordのオブジェクトモデルが持つ「行(Row)」という概念の危うさを理解していない者が陥る、設計上の欠陥である。

本記事では、結合セルをスマートに、かつ確実にスキップして連番を振るための「プロフェッショナルな実装手法」を伝授する。

1. なぜ、あなたのコードは壊れるのか?

まず、問題の本質を理解しよう。

Wordにおける `Table.Rows` コレクションは、すべてのセルが均一なグリッド状に並んでいることを前提としている。しかし、セルが垂直方向に結合(Vertical Merge)された瞬間、その「行」の概念は崩壊する。

ある行に結合セルが含まれていると、Wordはその行を「独立した行」としてカウントできなくなる場合がある。つまり、`Rows(i)` でアクセスしようとした瞬間に、インデックスの整合性が取れなくなり、実行時エラーが発生するのだ。

「エラーが出たら `On Error Resume Next` で飛ばせばいい」と考えているなら、今すぐその考えを捨ててほしい。それはバグを隠蔽しているだけであり、後続のデータ処理で致命的なデータの欠落や、予期せぬ上書きを引き起こす原因となる。

2. アーキテクチャの設計:Row-Tracking パターン

結合セルが存在する表において、特定の列に正しく連番を振るための設計思想は、「行インデックスによる走査」ではなく、「セル単位の走査と、処理済み行のトラッキング」である。

設計指針

1. `Rows` コレクションを直接ループしない: 結合セルによるエラーを物理的に回避するため、`Table.Range.Cells` コレクションを走査する。
2. 列インデックスを判定条件にする: 全セルの中から、目的の列(Target Column)に属するセルのみを抽出する。
3. 「処理済み行」を記憶する: 垂直結合されたセルは、複数のセルが同じ `RowIndex` を共有、あるいは特殊な挙動を示す。そのため、一度連番を書き込んだ `RowIndex` を記録しておき、同じ行に属する結合セルをスキップするロジックを組み込む。

3. プロダクション・コード:堅牢な連番付与ロジック

以下に、そのまま実務に投入可能なレベルのコードを示す。単なる動くコードではない。エラーハンドリング、パフォーマンス、保守性を考慮した設計だ。

Option Explicit

''' <summary>
''' 結合セルを含む表の特定列に、安全に連番を付与する。
''' </summary>
''' <param name="targetTable">対象となるWord Tableオブジェクト</param>
''' <param name="targetColIndex">連番を振る列のインデックス(1始まり)</param>
Public Sub ApplySequentialNumbers(ByRef targetTable As Table, ByVal targetColIndex As Integer)
    On Error GoTo ErrorHandler

    ' --- 初期設定 ---
    Dim cell As Cell
    Dim lastProcessedRow As Integer
    Dim currentCounter As Long
    Dim processedRows As Collection
    
    Set processedRows = New Collection
    lastProcessedRow = 0
    currentCounter = 1
    
    ' パフォーマンス向上のため画面更新を停止(大量のセルを扱う場合)
    Application.ScreenUpdating = False

    ' --- メインロジック ---
    ' Table.Rows ではなく、Table.Range.Cells を走査することで
    ' 結合セルによる「行の欠落エラー」を根本的に回避する
    For Each cell In targetTable.Range.Cells
        
        ' 1. 目的の列であるか判定
        If cell.ColumnIndex = targetColIndex Then
            
            ' 2. 結合セルによる重複カウントを防ぐため、
            '    既に処理した行インデックスかどうかを判定する
            If Not IsInCollection(processedRows, cell.RowIndex) Then
                
                ' 3. 値の書き込み
                ' セルの内容をクリアしてから書き込む(既存データとの混同防止)
                cell.Range.Text = CStr(currentCounter)
                
                ' 4. 状態の更新
                ' 処理した行インデックスを記録し、カウンターを進める
                processedRows.Add cell.RowIndex, CStr(cell.RowIndex)
                currentCounter = currentCounter + 1
                
            End If
        End If
    Next cell

    Application.ScreenUpdating = True
    MsgBox "連番の付与が完了しました。", vbInformation, "Success"
    Exit Sub

ErrorHandler:
    Application.ScreenUpdating = True
    MsgBox "エラーが発生しました。" & vbCrLf & _
           "エラー番号: " & Err.Number & vbCrLf & _
           "エラー内容: " & Err.Description, vbCritical, "Error"
End Sub

''' <summary>
''' コレクション内に特定のキーが存在するかを確認するヘルパー関数
''' </summary>
Private Function IsInCollection(ByRef col As Collection, ByVal key As Integer) As Boolean
    On Error Resume Next
    Dim dummy As Variant
    dummy = col.Item(CStr(key))
    IsInCollection = (Err.Number = 0)
    On Error GoTo 0
End Function

''' <summary>
''' テスト実行用メインルーチン
''' </summary>
Sub Test_TableAutomation()
    Dim doc As Document
    Set doc = ActiveDocument
    
    ' 文書内に表が存在するかチェック
    If doc.Tables.Count = 0 Then
        MsgBox "文書内に表が見つかりません。", vbExclamation
        Exit Sub
    End If
    
    ' 最初の表の1列目に連番を振る例
    Call ApplySequentialNumbers(doc.Tables(1), 1)
End Sub

4. チーフアーキテクトの視点:実務への適用と注意点

このコードを使いこなすために、以下の3点を肝に銘じておいてほしい。

① データソースとの分離(Decoupling)

もし、この連番が「データベースやExcelから取得したID」を流し込むためのものであるなら、このロジックは「書式整え」のフェーズとして独立させておくべきだ。
「データの整合性(何番か)」と「表示の整合性(結合セルをどう扱うか)」を混同してはいけない。 データの主権は常にソース側にあり、VBAはそれをWordの複雑なDOM構造に「適切にマッピングする」役割に徹するべきだ。

② パフォーマンスの重み

`For Each cell In targetTable.Range.Cells` は、非常に強力だが、表が数百行、数千行に及ぶ場合、セルへのアクセスはオーバーヘッドを生む。
もし大規模な文書を扱うなら、`Application.ScreenUpdating = False` は必須だ。また、可能であれば、Wordのオブジェクトを直接触る回数を最小限にするよう、ロジックを検討せよ。

③ 結合セルの「意味」を考える

結合セルは、単なるデザインではない。それは「データが複数の行にまたがっている」という意味論的な結合である。
今回提示したコードは、「結合されたセルは、その結合の起点となる行にのみ番号を振る」という挙動をとる。これは実務上最も安全な挙動だが、要件によっては「結合された全セルに同じ番号を振る」必要があるかもしれない。その場合は、`IsInCollection` の判定を外し、`cell.RowIndex` の管理方法を微調整する設計変更が必要になる。

結論

Word VBAにおける自動化は、単なる「操作の記録」ではない。
オブジェクトモデルの特性を理解し、例外的な構造(結合セル)に対して、いかに「予測可能で、壊れないロジック」を構築できるか。その設計能力こそが、エンジニアとスクリプターを分かつ境界線である。

このコードをベースに、君たちの業務を、より堅牢で、より美しいものへと昇華させてほしい。

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