こんにちは! Word VBAの自動化の世界へようこそ。
マクロの記録ボタンを押すだけの世界から一歩踏み出し、「自分の手でドキュメントを自在に操りたい」と願うあなたにとって、今日はとてもエキサイティングな一日になりますよ。
さて、Word VBAで業務効率化を進めていると、必ずと言っていいほど「ある大きな壁」にぶつかります。
それが今回取り上げる「テキストボックスの中身が検索できない問題」です。
「あれ? ちゃんと `Selection.Find` で検索ワードを指定したのに、文書中にあるはずの文字がヒットしない……」
そんな経験はありませんか?
実はこれ、Word VBAの歴史が生んだ仕様の罠。でも大丈夫。ここをクリアすれば、あなたのWord VBAスキルは間違いなく中級者の領域に到達します。今日は、図形やテキストボックスの奥底まで容赦なく検索を届ける「再帰アルゴリズム」の極意を、優しく紐解いていきましょう。
—
1. なぜ、普通の検索ではテキストボックスが見つからないのか?
Wordのドキュメント構造をイメージしてみてください。
私たちが普段文字を入力しているメインのキャンバス(本文)は、言わば「表通り」です。
しかし、テキストボックスや吹き出し、図形の中に書かれた文字は、表通りから一本裏に入った「マンションの部屋(Shape / InlineShape)」のようなもの。
Wordの標準的な `Find` 機能や `Selection.Find` は、表通りしかパトロールしません。「裏通り」にあるテキストボックスの存在には、デフォルトでは気づいてくれないのです。
これを解決するためには、VBAを使って「本文のパトロールが終わったら、次はすべての図形(Shape)という部屋をノックして回り、その中にあるテキストも検索する」というプログラムを組む必要があります。
—
2. 形状(Shape)の階層構造と「再帰処理」の魔力
さらに話は少し複雑になります。
テキストボックスの中に、さらに別の図形が入っていたり、グループ化された図形の中にテキストボックスがあったり……。つまり、図形の中には「さらに小さな部屋」が存在しうるのです。
ここで登場するのが、プログラミングの美しきテクニック「再帰処理(Recursive Processing)」です。
再帰とは、ざっくり言うと「自分自身を呼び出す関数」のこと。
- 「大きな箱を開ける」
- 「もし中からさらに箱が出てきたら、もう一度同じ『箱を開ける手順』を実行する」
- 中身がただのテキストになるまで、これを繰り返す。
このアルゴリズムを実装することで、どんなに複雑にネストされた(入れ子になった)図形やテキストボックスであっても、取りこぼしなく検索・置換することが可能になります。
—
3. 【実践】テキストボックスを完全制覇するVBAコード
百聞は一見に如かず。実務でそのままコピー&ペーストして使える、極限まで洗練されたコードをプレゼントしましょう。
このコードは、メイン本文の検索はもちろん、文書内のすべての図形・テキストボックスを再帰的に巡回し、指定したキーワードを置換するプロシージャです。
‘ =====================================================================
‘ 概要: 文書本文およびすべての図形(テキストボックス等)を再帰的に巡回し、
‘ 指定した文字列を置換するプロシージャ
‘ =====================================================================
Sub ReplaceTextInShapesAndBody()
Dim targetText As String
Dim replaceText As String
‘ 検索する文字と置換後の文字を指定(必要に応じて変更してください)
targetText = “旧ワード”
replaceText = “新ワード”
Dim count As Long
count = 0
‘ 1. まずは通常の本文(Story)に対して置換を実行
count = count + ExecuteFindReplace(ActiveDocument.Content, targetText, replaceText)
‘ 2. 次に、メインストーリー上のすべての図形(Shape)を巡回
Dim shp As Shape
For Each shp in ActiveDocument.Shapes
count = count + ProcessShape(shp, targetText, replaceText)
Next shp
‘ 3. インライン(文字列と一緒に配置される)図形についても同様に巡回
Dim inlineShp As InlineShape
For Each inlineShp in ActiveDocument.InlineShapes
‘ インライン図形を通常のShapeに変換して処理する場合もあるが、
‘ 今回はテキストフレームを持つものを直接操作
If inlineShp.Type = wdInlineShapeCanvas Then
‘ キャンバス等の特殊なケースは省略しますが、基本はTextRangeを狙います
End If
‘ ※通常のInlineShapeのテキスト操作は Range オブジェクト経由で行えます
If inlineShp.Range.Find.Execute(FindText:=targetText, ReplaceWith:=replaceText, Replace:=wdReplaceAll) Then
count = count + 1
End If
Next inlineShp
‘ 完了メッセージ
MsgBox “置換が完了しました。合計 ” & count & ” 箇所を変更しました。”, vbInformation, “処理完了”
End Sub
‘ =====================================================================
‘ 関数: Shapeオブジェクトを再帰的に検査し、テキストを置換する
‘ =====================================================================
Function ProcessShape(shp As Shape, targetText As String, replaceText As String) As Long
Dim replacedCount As Long
replacedCount = 0
‘ 1. 図形自体がテキストフレーム(文字を含められる状態)を持っているか?
If shp.TextFrame.HasText Then
‘ テキスト範囲に対してFind/Replaceを実行
If ExecuteFindReplace(shp.TextFrame.TextRange, targetText, replaceText) > 0 Then
replacedCount = replacedCount + 1
End If
End If
‘ 2. グループ化された図形(GroupShape)の場合の再帰処理
‘ グループの中にさらに図形が入っているため、中身を一つずつバラしてループします
If shp.Type = msoGroup Then
Dim subShp As Shape
For Each subShp in shp.GroupItems
‘ ★ここで自分自身(ProcessShape)を再度呼び出す(=再帰)
replacedCount = replacedCount + ProcessShape(subShp, targetText, replaceText)
Next subShp
End If
ProcessShape = replacedCount
End Function
‘ =====================================================================
‘ 補助関数: 指定されたRangeオブジェクトに対してFind/Replaceを実行する
‘ =====================================================================
Function ExecuteFindReplace(rng As Range, targetText As String, replaceText As String) As Long
Dim findResult As Long
findResult = 0
With rng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = targetText
.Replacement.Text = replaceText
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
‘ 一括置換を実行し、置換成功ならTrueが返る
If .Execute(Replace:=wdReplaceAll) Then
findResult = 1 ‘ 簡略化のため、ここでは置換が行われたフラグとして1を返す
End If
End With
ExecuteFindReplace = findResult
End Function
—
4. 現場でありがちな「エラー」と、その回避の極意
このコードを実務で動かす際、中級者が必ずハマる「罠」がいくつかあります。プロのエンジニアとしての知見をシェアしておきますね。
罠1:図形の中に図形がある(グループ化の悲劇)
先ほどのコードの `ProcessShape` 関数を見てください。`shp.Type = msoGroup` の判定が入っていますよね。
もしこの再帰処理を入れ忘れると、「グループ化されたテキストボックスの中」にある文字だけが綺麗にスルーされてしまいます。「なぜか一部だけ置換されない……」という現象の9割はこれが原因です。
罠2:画像やアイコンなどの「文字を持たない図形」
世の中のすべてのShapeがテキストボックスを持っているわけではありません。ただの写真(Picture)や四角形の図形(Rectangle)には、文字を入れるエリア(TextFrame)がない場合があります。
これを考慮せずにいきなり `shp.TextFrame.TextRange` にアクセスすると、「実行時エラー ‘445’: オブジェクトはこのプロパティまたはメソッドをサポートしていません」という冷酷なエラーメッセージが飛んできます。
だからこそ、コード内にある `If shp.TextFrame.HasText Then`(テキストを持っているか?)という事前の安全確認(ガード条項)が絶対に不可欠なのです。
—
5. まとめ:ここをクリアすれば、Word VBAは怖くない!
お疲れ様でした!
「本文だけでなく、図形の中までくまなく検索する」という一見難しそうな処理も、「Shapeの構造を理解し、再帰関数でスマートに巡回する」というアプローチを取れば、こんなにも美しく解決できます。
マクロの記録の殻を破り、オブジェクトのライフサイクルや階層構造を意識したコードを書けるようになったあなたなら、どんなに分厚い社内文書やフォーマットの厳しい申請書であっても、一瞬で自動化できるはずです。
ここをクリアしたあなたなら、もうWord VBAの基本はバッチリです。自信を持って、日々の業務の自動化に挑んでくださいね! それでは、また次の極限の知見でお会いしましょう。
