【実務・中級編】【中級者向け】表のセル結合状態を維持したまま、特定の列幅をミリ単位で一括変更する制御 – Word VBA解析バイブル

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

Columns(i) の罠を越えて:結合セルを含む表の列幅をミリ単位で完全制御する設計論

業務自動化の現場において、Wordの「表(Table)」の操作は避けて通れない。しかし、多くの開発者が直面する「壁」がある。それは、「セルが結合された瞬間に、`Columns` オブジェクトが使い物にならなくなる」という事実だ。

「列幅を調整するだけなのに、なぜか実行時エラー 5941 が発生する」「指定したミリメートルに正確に収まらない」……。もし君がこのような問題に直面しているなら、それはWordのオブジェクトモデルが持つ「論理構造」と「物理構造」の乖離を理解していないからだ。

本稿では、中級者以上のエンジニアに向けて、結合セルが存在する複雑な表であっても、レイアウトを破壊することなく、特定の列幅をミリ単位で厳密に制御するための、堅牢な設計手法を伝授する。

1. なぜ `Columns(i)` は「設計」として不適切なのか

まず、君がこれまで書いてきたであろうコードを振り返ってほしい。

' 典型的な「動かない」コードの例
Sub BadPractice()
    Dim tbl As Table
    Set tbl = ActiveDocument.Tables(1)
    ' 結合セルがあると、ここで「指定されたコレクションのメンバーが見つかりません」と落ちる
    tbl.Columns(2).Width = CentimetersToPoints(3) 
End Sub

なぜこれが起きるのか。Wordの `Table.Columns` コレクションは、表が「完全な格子状(Grid)」であることを前提としている。セルが結合されているということは、その垂直方向の「列」という概念が、物理的なセルによって分断されていることを意味する。

結合セルが含まれる表において、`Columns(i)` を叩く行為は、「存在しない垂直なスライス」にアクセスしようとする行為に等しい。これは設計段階で排除すべき「脆弱な実装」だ。

2. プロフェッショナルが採用すべき戦略:Cellベースの制御

堅牢なツールを作るなら、`Columns` オブジェクトには一切頼らない。代わりに、「各行(Row)を走査し、その行に含まれるセル(Cell)に対して、個別に幅(PreferredWidth)を規定する」というアプローチを取る。

ここで重要な設計指針が2つある。

1. `AllowAutoFit` を無効化する: Wordのオートフィット機能が有効なままだと、コードで指定した幅が、コンテンツ量に応じて勝手に書き換えられる。これは「制御不能な状態」を意味する。
2. `PreferredWidth` を使用する: `Width` プロパティは現在の物理的な幅を指すが、`PreferredWidth` は「この幅を維持せよ」という命令に近い。結合セルを扱う際は、`PreferredWidthType` を `wdPreferredWidthPoints` に明示的に設定するのが定石だ。

3. 実装例:ミリ単位の精密制御エンジン

以下に、実務でそのままプロダクションコードとして組み込めるレベルの、保守性の高い関数を示す。このコードは、結合セルの有無を問わず、指定した「論理的な列インデックス」に対して幅を適用しようと試みる。

Option Explicit

''' <summary>
''' 表の特定の列幅をミリ単位で一括設定する。
''' 結合セルが含まれていても、エラーを回避しつつ可能な限り指定幅を維持する。
''' </summary>
''' <param name="targetTable">対象となるWord Tableオブジェクト</param>
''' <param name="columnWidthsMM">各列の幅をミリメートルで指定したDouble型の配列</param>
Public Sub SetTableColumnWidthsStrict(ByRef targetTable As Table, ByRef columnWidthsMM() As Double)
    On Error GoTo ErrorHandler

    ' 定数定義: 1mmあたりのポイント数 (1pt = 1/72 inch, 1 inch = 25.4 mm)
    Const MM_TO_POINTS As Double = 2.83465
    
    Dim i As Long, r As Long
    Dim targetWidthPts As Single
    Dim currentCell As Cell
    
    ' 1. 制御の前提条件を整える(AutoFitの強制解除)
    ' これを行わないと、後続のWidth設定がWordのアルゴリズムによって上書きされる
    targetTable.AllowAutoFit = False
    
    ' 2. 行ごとに走査(Columnsオブジェクトを使わないのがプロの設計)
    For r = 1 To targetTable.Rows.Count
        ' 各行において、指定された列インデックスのセルを処理
        For i = LBound(columnWidthsMM) To UBound(columnWidthsMM)
            
            ' 列インデックスが現在の行のセル数を超えていないかチェック
            ' (結合セルにより、行によってセル数が異なる場合があるため)
            If i <= targetTable.Rows(r).Cells.Count Then
                
                ' エラーハンドリングを内包させ、結合セルによるインデックス不整合を回避
                On Error Resume Next
                Set currentCell = targetTable.Rows(r).Cells(i)
                
                If Err.Number = 0 And Not currentCell Is Nothing Then
                    ' 3. 幅の適用
                    targetWidthPts = columnWidthsMM(i) * MM_TO_POINTS
                    
                    With currentCell
                        ' PreferredWidthTypeをポイント指定に固定
                        .PreferredWidthType = wdPreferredWidthPoints
                        .PreferredWidth = targetWidthPts
                    End With
                End If
                On Error GoTo ErrorHandler ' エラーハンドリングを元に戻す
                
            End If
        Next i
    Next r

    Exit Sub

ErrorHandler:
    ' 実務ではログ出力やユーザーへの通知を行う
    MsgBox "Error in SetTableColumnWidthsStrict: " & Err.Description, vbCritical, "Architecture Error"
End Sub

''' <summary>
''' テスト用メインルーチン
''' </summary>
Sub TestColumnWidthControl()
    Dim tbl As Table
    Dim widths(1 To 3) As Double ' 3列分の幅を指定
    
    ' テストデータの準備
    If ActiveDocument.Tables.Count = 0 Then
        MsgBox "テスト用の表が存在しません。"
        Exit Sub
    End If
    Set tbl = ActiveDocument.Tables(1)
    
    ' 列幅の設定(1列目: 20mm, 2列目: 50mm, 3列目: 30mm)
    widths(1) = 20#
    widths(2) = 50#
    widths(3) = 30#
    
    ' 実行
    SetTableColumnWidthsStrict tbl, widths
    
    MsgBox "列幅の再設定が完了しました。", vbInformation
End Sub

4. アーキテクトの視点:堅牢なツールへの昇華

このコードを単なる「コピペ用スクリプト」で終わらせてはいけない。業務システムとして組み込むなら、以下の3点を考慮せよ。

A. データの整合性(Single Source of Truth)

列幅の定義をVBAコード内にハードコーディングするのは三流だ。実務では、Excelやデータベース、あるいはWord文書内の「設定用プロパティ」から、これらの数値を読み取るように設計すべきだ。これにより、レイアウト変更の要望があった際に、コードをコンパイルし直すことなく、データのみの更新で対応が可能になる。

B. 計算精度の管理

`2.83465` という変換係数は、浮動小数点演算において微小な誤差を生む。累積的な計算(例えば、全列の幅の合計が用紙幅を超えていないか等のバリデーション)を行う場合は、`Round` 関数を用いて、ビジネスロジック上の許容誤差をコントロールすることを忘れないように。

C. 冪等性(Idempotency)の確保

この関数は、何度実行しても同じ結果をもたらすか? `AllowAutoFit = False` を含めているため、この関数は「冪等性」を持っている。これにより、処理の途中でエラーが起きて再実行しても、状態が破壊されるリスクを最小限に抑えられる。

結論

Word VBAにおける表操作の真髄は、「Wordが提供する便利な抽象化(Columnsオブジェクト)を疑い、低レイヤー(Cellsオブジェクト)から制御する勇気」にある。

抽象化されたオブジェクトは使いやすいが、複雑なデータ構造(結合セル)に対しては脆い。エンジニアとして、常に「このオブジェクトはどのような物理構造に基づいているのか?」を問い続け、最も確実な最小単位から制御を組み立てる。それこそが、バグのない、プロフェッショナルな自動化ツールを生む唯一の道である。

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