Word VBAの深淵:InlineShapeとShapeの「二重構造」を制圧する
Word VBAを扱う上で、多くのエンジニアが「なぜ画像操作がこれほどまでに不安定なのか」という壁に突き当たる。その原因は、Wordのドキュメントモデルが、文書内に流れるテキストの一部である`InlineShape`と、レイヤーとして浮遊する`Shape`という、全く異なる二つの世界を共存させていることにある。
本稿では、この「二重構造」を完全掌握し、メモリリークを許さない堅牢な自動化コードの書き方を伝授する。
—
1. 混在するオブジェクトの「境界線」を定義せよ
Wordの画像は、アンカー(基準位置)の持ち方によって以下の二つに大別される。
- InlineShape: 文字列と一緒に流れる。「文字として」扱われるため、制御は容易だがレイアウトの自由度が低い。
- Shape: `WrapFormat`(折り返し)が設定された浮動オブジェクト。座標指定が可能だが、アンカーの移動により予期せぬ挙動を引き起こす。
現場のコードで遭遇する「画像が取得できない」「リサイズが反映されない」というトラブルの9割は、この両者へのアプローチを統合できていないことに起因する。
—
2. 極限のメモリ管理とオブジェクトの最適化
VBAのガーベジコレクションは、C#やJavaのように優秀ではない。特にWordの`Selection`や`Range`をループ内で乱用すれば、スタックは即座に飽和し、COMオブジェクトの解放漏れによる「アプリケーションのハングアップ」を招く。
実装の鉄則
1. `Selection`を絶対に使わない: `.Select`は描画処理を伴うため、パフォーマンスを殺す。`Range`オブジェクトを直接操作せよ。
2. 明示的解放: 長いループの最後には `Set obj = Nothing` を徹底し、参照カウンタを確実に減らす。
3. エラーハンドリング: 画像が破損している場合、プロパティアクセス時にCOM例外が発生する。`On Error Resume Next`で局所的に捕捉し、ログを残すのがアーキテクトの矜持だ。
—
3. 実践コード:全画像抽出とリサイズ処理の雛形
以下は、文書内の全画像を走査し、アスペクト比を維持しつつ最大幅を制限する高耐久コードである。
‘ @description: 文書内の全画像を走査し、幅を指定値以下にリサイズする最適化ルーチン
Public Sub OptimizeDocumentImages(ByVal maxPixelWidth As Single)
Dim doc As Document: Set doc = ActiveDocument
Dim ils As InlineShape
Dim shp As Shape
‘ 1. InlineShapeの処理
For Each ils In doc.InlineShapes
If ils.Type = wdInlineShapePicture Then
Call ResizeInlineShape(ils, maxPixelWidth)
End If
Next ils
‘ 2. Floating Shapeの処理
‘ Shapeコレクションは描画順に依存するため、逆順走査が安全な場合もある
For Each shp In doc.Shapes
If shp.Type = msoPicture Then
Call ResizeShape(shp, maxPixelWidth)
End If
Next shp
‘ オブジェクト解放
Set ils = Nothing
Set shp = Nothing
Set doc = Nothing
End Sub
Private Sub ResizeInlineShape(ByRef ils As InlineShape, ByVal maxWidth As Single)
On Error Resume Next
With ils
.LockAspectRatio = msoTrue
If .Width > maxWidth Then .Width = maxWidth
End With
On Error GoTo 0
End Sub
Private Sub ResizeShape(ByRef shp As Shape, ByVal maxWidth As Single)
On Error Resume Next
With shp
.LockAspectRatio = msoTrue
‘ 浮動オブジェクトはWidthの単位に注意。ポイント(pt)計算が基本。
If .Width > maxWidth Then .Width = maxWidth
End With
On Error GoTo 0
End Sub
—
4. レガシー環境とシステム連携の極意
もし、この処理が数千ページの文書や、社内の基幹システムから生成された複雑なWordファイルを対象とする場合、VBA単体では力不足になる。その際は、以下の戦略をとるべきだ。
- Win32 APIの活用: `LockWindowUpdate`を呼び出し、画面描画を停止させることで、処理速度を3〜5倍に引き上げることが可能だ。
- OpenXML SDKへの移行判断: 処理対象が `.docx` (OpenXML形式) であれば、VBAでオブジェクトを操作するよりも、バックエンドのC#で `DocumentFormat.OpenXml` ライブラリを使用してXMLを直接操作する方が圧倒的に高速かつ安全である。VBAはそのインターフェース役に徹するべきだ。
最後に
シニアエンジニアにとって、コードは「動けばいい」ものではない。オブジェクトのライフサイクルを理解し、メモリ負荷を予測し、予期せぬエラーに対してもシステム全体が停止しない設計を行うこと。それがプロフェッショナルの仕事だ。
あなたの書いたコードが、次のメンテナンス担当者にとっての「正解」であることを祈る。
