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