コレクション操作の陥穽を突破せよ:CorelDRAW VBAにおける「インデックス崩壊」の回避と堅牢なページ削除アルゴリズム
業務自動化の現場において、「動くコード」を書くことは容易だ。しかし、「壊れないコード」を設計することは、全く別の次元のスキルを要求される。
CorelDRAWの自動化ツールを開発していると、必ずと言っていいほど直面する壁がある。それは、「ページやレイヤー、あるいはオブジェクトの集合(コレクション)を操作する際のインデックスのズレ」だ。
多くの開発者が、単純な `For i = 1 To Pages.Count` というループの中で削除処理を実行し、予期せぬエラーや、削除対象のスキップ、あるいは「実行時エラー 9: インデックスが範囲外です」という無慈悲な宣告を食らって立ち尽くす。
今日は、そんな初歩的なミスを排除し、プロフェッショナルとして通用する「逆順ループによる安全なコレクション操作」の設計思想を伝授する。
1. なぜ「前方ループ」は破綻するのか:インデックス・シフトの正体
まず、なぜ君たちが書いたコードが失敗するのか、そのメカニズムを脳内に叩き込んでほしい。
例えば、3つのページ(Page 1, Page 2, Page 3)があるとしよう。君は「特定の条件に一致するページをすべて削除する」というロジックを、前から順番に実行しようとしている。
1. ループ 1回目 ($i=1$): Page 1 をチェック。条件に一致したので `Pages(1).Delete` を実行。
2. ここで「事象」が発生する: Page 1 が消滅した瞬間、残された Page 2 と Page 3 のインデックスが繰り上がる。 つまり、元の Page 2 は「新しい Page 1」になり、Page 3 は「新しい Page 2」になる。
3. ループ 2回目 ($i=2$): 次の対象は `Pages(2)` だ。しかし、先ほどのシフトにより、`Pages(2)` は「元々の Page 3」を指している。
4. 結果: 元々の Page 2 はチェックすらされずにスキップされた。
これが「インデックスの前方崩れ」だ。コレクションの要素数を動的に変化させながら、増分(Step 1)で走査することは、数学的に、そして構造的に矛盾している。
2. プロフェッショナルの定石:逆順ループ(Reverse Loop)
この問題をエレガントに、かつ確実に解決する方法はただ一つ。「後ろから処理する」ことだ。
`For i = Pages.Count To 1 Step -1`
このアルゴリズムがなぜ完璧なのか。理由は単純だ。
インデックスが後ろから削られていく場合、「次に処理すべき要素のインデックス」には一切影響を与えないからだ。
Page 3 を消そうが、Page 2 を消そうが、それより前にある Page 1 や Page 2 のインデックス番号は変わらない。ターゲットが消えても、走査の進行方向(インデックスの減少方向)に対して、残りの要素は「安全な領域」に留まる。これが、コレクション操作における黄金律である。
3. プロダクション・レディな実装例
単にループを逆転させるだけでは、一流のエンジニアとは呼べない。実務で使用するツールには、「パフォーマンスの最適化」「エラーハンドリング」「CorelDRAW特有の制約への配慮」が不可欠だ。
以下に、実戦投入可能なレベルのコード構造を示す。
'' ==============================================================================
'' Module: AutomationUtils
'' Description: CorelDRAW ページ操作の堅牢な実装例
'' Author: Lead Architect
'' ==============================================================================
Option Explicit
''' <summary>
''' 条件に合致するページを安全に一括削除する
''' </summary>
''' <param name="searchKeyword">削除対象とするページ名に含まれるキーワード</param>
Public Sub SafeDeletePagesByKeyword(ByVal searchKeyword As String)
Dim i As Long
Dim targetPage As Page
Dim deletionCount As Long
' 1. パフォーマンス最適化の開始
' 画面更新と再計算を停止し、処理速度を劇的に向上させる
Optimization = True
EventsEnabled = False
On Error GoTo ErrorHandler
deletionCount = 0
' 2. 逆順ループによる安全な走査
' Pages.Count から 1 に向かって Step -1 で回すことで、インデックスのズレを完全に回避
For i = ActiveDocument.Pages.Count To 1 Step -1
Set targetPage = ActiveDocument.Pages(i)
' 3. 条件判定(ここではページ名にキーワードが含まれているか)
If InStr(1, targetPage.Name, searchKeyword, vbTextCompare) > 0 Then
' 【重要】CorelDRAWの制約回避:
' ドキュメントには最低1枚のページが必要である。全削除を試みるとエラーになる。
If ActiveDocument.Pages.Count > 1 Then
targetPage.Delete
deletionCount = deletionCount + 1
Else
' 最後の1枚を消そうとした場合のログ出力
Debug.Print "Warning: 最後のページ '" & targetPage.Name & "' は削除できません。"
End If
End If
Next i
MsgBox "処理完了: " & deletionCount & " 枚のページを削除しました。", vbInformation
CleanExit:
' 4. 状態の復元 (必ず実行すること)
Optimization = False
EventsEnabled = True
Application.Refresh
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical, "Error " & Err.Number
Resume CleanExit
End Sub
4. 設計における高度な注意点(チーフアーキテクトの視点)
このコードを実務のシステムに組み込む際、以下の3点を忘れてはならない。
① オブジェクトのライフサイクルとメモリ
`Set targetPage = ActiveDocument.Pages(i)` のように、一度オブジェクト変数に受けてから操作するのは、コードの可読性を高めるだけでなく、デバッグ時の挙動を安定させる。ただし、`Delete` メソッドを呼んだ直後のオブジェクト変数は「虚無(Nothingではないが、参照先が消滅している状態)」になる。ループの先頭で常に再代入する設計を徹底せよ。
② 外部データ(DB/Excel)との同期
もし、君が作成しているツールが「Excelのリストに基づいてページを削除する」ようなものなら、「CorelDRAW側の削除」と「Excel側のデータ更新」のトランザクションをどう設計するかが問われる。
CorelDRAWでページを消したのに、Excel側の管理リストにそのページが残っていたら、そのツールは「ゴミ」だ。削除に成功したインデックス、あるいはページ名を、必ず同期プロセスにフィードバックする設計にせよ。
③ ユーザーへのフィードバックと「取り消し」
大量のページを削除する処理は、破壊的な操作だ。
プロフェッショナルなツールであれば、実行前に `If MsgBox(“本当に削除しますか?”, vbYesNo) = vbNo Then Exit Sub` のような確認を挟むか、あるいは処理全体を `ActiveDocument.BeginCommandGroup` でラップし、ユーザーが `Ctrl+Z` 一回で元に戻せるように設計するのが、ユーザーへの最低限のマナーである。
結論
コレクションの操作において、インデックスの管理は基礎中の基礎だ。しかし、その基礎を「なんとなく」で済ませるか、「構造的な必然性」を持って実装するかで、君の作るツールの信頼性は天と地ほど変わる。
「前から削るな、後ろから削れ」
この原則を、血肉に変えておけ。
