【極限の知見】結合セルに屈しない。Word VBAで列幅を「ミリ単位」で精密制御する技術
「マクロの記録」を使って、表の幅を調整するコードを書き出したことはありませんか?
あるいは、結合セルが含まれる複雑な表に対して、列幅を変更しようとして「実行時エラー ‘5941’」や「指定した引数は不正です」という冷たいメッセージに突き当たったことはないでしょうか。
もしあなたが今、その壁に直面しているなら、おめでとうございます。あなたは今、「マクロの記録」という初心者向けの庭から、「オブジェクトモデルの制御」というエンジニアの荒野へ足を踏み入れた証拠です。
今日は、多くのプログラマが挫折する「結合セルが含まれる表の列幅制御」について、その本質的な解決策を伝授します。ここをクリアすれば、あなたのWord VBAスキルは一段上のステージへと昇華するはずですよ。
1. なぜ「Columns(i).Width」では失敗するのか?
まず、なぜエラーが起きるのか、その「正体」を知っておきましょう。
通常の、一切の結合がない綺麗な表であれば、Wordにとって「列(Column)」は垂直に貫通する一本の線です。そのため、`Table.Columns(1).Width = 100` と書けば、すべての行の1列目が一斉に動いてくれます。
しかし、結合セルが存在した瞬間、その「垂直な線」は崩壊します。
- ある行では、セルが2つに分かれている。
- 隣の行では、2つのセルが1つに合体している。
この状態では、Wordのオブジェクトモデルにおいて「列(Column)」という概念が数学的に定義できなくなってしまうのです。存在しない「列」を操作しようとするから、Wordは「そんな列はありません!」と怒ってしまうわけですね。
2. 攻略の鍵:Columnではなく「Cell」を制する
結合セルが混在する表を制御する極意は、「列全体を操作しようとするのを諦め、各行の『セル(Cell)』を個別に、かつ精密に操作する」ことにあります。
さらに、エンジニアとして避けて通れないのが「単位」の問題です。
Wordの内部世界では、長さはすべて 「ポイント (Point)」 という単位で管理されています。しかし、私たちが設計書に書くのは 「ミリメートル (mm)」 ですよね。
この「人間の直感(mm)」と「コンピュータの論理(Point)」の乖離を埋めるために、Wordが用意してくれている魔法の関数 `MillimetersToPoints` を使いこなす必要があります。
3. 実戦コード:精密な列幅変更プロシージャ
それでは、実際に現場でそのまま使える、堅牢なコードを見ていきましょう。
このコードは、結合セルによって列の構造がバラバラな表であっても、指定した列番号のセルを狙い撃ちして幅を変更します。
Option Explicit
''' <summary>
''' 結合セルが混在する表でも、指定した列の幅をミリ単位で一括変更する
''' </summary>
''' <param name="targetTable">対象となるWordのTableオブジェクト</param>
''' <param name="targetColIndex">変更したい列のインデックス(1から開始)</param>
''' <param name="widthInMillimeters">設定したい幅(mm単位)</param>
Sub SetColumnWidthByMillimeters(targetTable As Table, targetColIndex As Integer, widthInMillimeters As Single)
Dim i As Integer
Dim targetCell As Cell
Dim targetWidthPoints As Single
' 1. ミリメートルをポイント単位に変換(ここがエンジニアの嗜み)
targetWidthPoints = MillimetersToPoints(widthInMillimeters)
' 2. 表の自動サイズ調整機能をオフにする
' これを忘れると、Wordが「気を利かせて」幅を勝手に戻してしまいます(重要!)
targetTable.AllowAutoFit = False
' 3. 各行をループして、対象の列に該当するセルを操作する
On Error Resume Next ' 結合セルにより、指定したインデックスのセルが存在しない場合への備え
For i = 1 To targetTable.Rows.Count
' その行の指定した列番号のセルを取得
Set targetCell = targetTable.Rows(i).Cells(targetColIndex)
' セルが存在する場合のみ、幅を適用
If Not targetCell Is Nothing Then
targetCell.Width = targetWidthPoints
End If
' 次のループのためにオブジェクトを解放
Set targetCell = Nothing
Next i
' エラーハンドリングの解除
On Error GoTo 0
MsgBox "列幅の変更が完了しました。", vbInformation
End Sub
''' <summary>
''' テスト用のメインルーチン
''' </summary>
Sub TestTableWidthControl()
Dim doc As Document
Dim tbl As Table
Set doc = ActiveDocument
' ドキュメント内の最初の表を対象とする
If doc.Tables.Count = 0 Then
MsgBox "表が見つかりません。"
Exit Sub
End If
Set tbl = doc.Tables(1)
' 例:1番目の列の幅を 30mm に設定する
Call SetColumnWidthByMillimeters(tbl, 1, 30)
' 例:2番目の列の幅を 50mm に設定する
Call SetColumnWidthByMillimeters(tbl, 2, 50)
End Sub
4. コードの深層解説:なぜこれで動くのか?
このコードが「ただの記録」と決定的に違うポイントを3つ解説します。
① `AllowAutoFit = False` の重要性
Wordの表には「内容に合わせて自動調整する」という非常に強力な(そして時として邪魔な)機能が備わっています。プログラミングで無理やり幅を指定しても、この設定が `True` のままだと、Wordが「あ、文字が多いからもっと広げなきゃ!」と、あなたのコードを無視して幅を書き換えてしまいます。「制御下に置くなら、まず自動調整を殺す」。これが鉄則です。
② `On Error Resume Next` の戦略的利用
結合セルがある表では、ある行には「2列目」が存在するのに、別の行では「1列目と2列目が結合されている」という状況が起こります。このとき、`Rows(i).Cells(2)` を参照しようとすると、「そんなセルはありません」というエラーで止まってしまいます。
ここで、あえてエラーをスキップさせることで、「その行に該当するセルがある場合のみ処理する」という、柔軟なロジックを実現しています。
③ オブジェクトのライフサイクル
ループの中で `Set targetCell = Nothing` と記述しています。これは、メモリ上に残ったオブジェクトの参照を適切に解放するための作法です。小規模なマクロなら問題になりませんが、数千行の表を扱う大規模な業務自動化では、こうした「リソースの管理」がパフォーマンスと安定性を左右します。
5. まとめ:次なるステップへ
いかがでしたでしょうか?
「列(Column)」という概念に縛られず、「セル(Cell)」という最小単位のオブジェクトに目を向ける。これこそが、Word VBAの複雑なレイアウトを掌握するための思考法です。
今回のポイント:
1. 結合セルがあるなら `Columns` ではなく `Rows(i).Cells(j)` を使う。
2. 単位変換 `MillimetersToPoints` を使い、人間にとって分かりやすい設計にする。
3. `AllowAutoFit = False` で、Wordの「おせっかい」を封じる。
ここをマスターすれば、もう結合セルのレイアウト崩れに怯えることはありません。自信を持って、さらに複雑な自動化の世界へ踏み出してください。応援していますよ!
