【設計思想】VisioとExcelの「完全同期」アルゴリズム:ページ順序変更に耐えうる堅牢なリネーム実装
現場の自動化担当者諸君。
「ページを入れ替えたら、Excelの目次とVisioのページ名がズレてしまった」「手動で直すのは時間の無駄だ」……そんな報告を、私はこれまで何度耳にしてきたことか。
多くのエンジニアが、こうした問題に対して「ページをループで回して、名前を書き換える」という、あまりにも脆弱で、あまりにも思考停止した実装を提示する。しかし、断言しよう。そのようなコードは、実務の現場では「バグの温床」でしかない。
Visioのオブジェクトモデル、特に`Page`オブジェクトの挙動を正しく理解していないコードは、ページ順序の入れ替え(Reordering)が発生した瞬間に崩壊する。
本稿では、中級者への階段を登るために不可欠な、「Excelをマスター(正解)とし、Visioのページインデックスと名前を完全に同期させる」ための、プロフェッショナルな設計思想と実装コードを伝授する。
1. なぜ、単純なループ処理では失敗するのか
初心者が書くコードは、おそらくこうだ。
`For Each pg In ActiveDocument.Pages` で回し、名前を順番に書き換えていく。
これがなぜ「非効率」かつ「危険」なのか。理由は2つある。
① Page.Name の「二面性」による混乱
Visioにおける `Page.Name` は、ユーザーが見る「表示名」であると同時に、そのページを特定するための「一意識別子(String ID)」でもある。
ループの途中で `Page.Name` を書き換えると、内部的なインデックスやコレクションの参照が不安定になるケースがある。特に、名前を基に次のページを探そうとするロジックを組み込んだ瞬間、無限ループや参照エラーの地獄が始まる。
② インデックスとIDの乖離
Visioの `Page.Index` は、ユーザーがドラッグ&ドロップでページ順序を入れ替えた瞬間に変化する。一方で、各ページが持つ内部的な `Page.ID` は不変だ。
「Excelの行番号」と「Visioのページ順序」を同期させたいのであれば、「現在の表示順序(Index)」を基準に、Excelの「管理番号」をマッピングするという、明確な設計指針が必要になる。
2. 堅牢なアーキテクチャ:Single Source of Truth (SSOT)
プロフェッショナルな設計では、「Single Source of Truth(信頼できる唯一の情報源)」を明確にする。今回のケースでは、ExcelがSSOTである。
我々が構築すべきアルゴリズムのフローは以下の通りだ。
1. Excelデータのメモリ展開: Excelの目次情報を、単なるセル参照ではなく、`Dictionary` オブジェクト等の高速なデータ構造に一度格納する。
2. Visioの「物理順序」の走査: `Page.Index` を用いて、Visioの左から右への並び順を 1 から順に走査する。
3. マッピングと適用: 「Visioの $i$ 番目のページ」に対し、「Excelの $i$ 行目のデータ」を流し込む。
この「Excelの行」と「Visioのインデックス」を 1:1 で紐付ける設計にすることで、ページがどこへ移動しようとも、「今、Visioの何番目に置かれているか」に基づいて名前が再定義される。これが、順序変更に強い設計の正体だ。
3. プロダクション・コード:Excel同期型リネーム・エンジン
以下に、実務でそのまま利用可能なレベルのコードを示す。
ここでは、実行速度と堅牢性を担保するため、Excelとの連携には `Late Binding`(後方参照)を採用し、実行環境の参照設定エラーを回避している。
Option Explicit
''' <summary>
''' Visioのページ名をExcelの目次情報と完全に同期させる。
''' ページ順序が変更されていても、現在の並び順に基づいてExcelの番号を再付与する。
''' </summary>
Public Sub SynchronizePageNamesWithExcel()
Dim xlApp As Object
Dim xlWB As Object
Dim xlSheet As Object
Dim visDoc As Visio.Document
Dim visPage As Visio.Page
Dim i As Integer
Dim targetExcelPath As String
Dim excelRow As Long
Dim newPageName As String
' --- 設定領域 ---
' 同期対象となるExcelファイルのパスを指定
targetExcelPath = "C:\Users\YourName\Documents\PageIndex.xlsx"
' Excelのデータが開始される行
excelRow = 2
' ----------------
On Error GoTo ErrorHandler
Set visDoc = ActiveDocument
' 1. Excelへの接続 (Late Binding)
On Error Resume Next
Set xlApp = GetObject(, "Excel.Application")
If xlApp Is Nothing Then
Set xlApp = CreateObject("Excel.Application")
End If
On Error GoTo ErrorHandler
Set xlWB = xlApp.Workbooks.Open(targetExcelPath, ReadOnly:=True)
Set xlSheet = xlWB.Sheets(1) ' 1番目のシートを対象とする
' 2. Visioページの走査とリネーム実行
' Page.Index は 1 から始まる。
' ページ順序の入れ替えに対応するため、必ず Index ベースでループを回す。
For i = 1 To visDoc.Pages.Count
Set visPage = visDoc.Pages.Item(i)
' Excelの現在の行から、新しいページ名を取得
' 例: A列に「1」、B列に「設計図」があれば、「1_設計図」とする
Dim prefix As String: prefix = xlSheet.Cells(excelRow, 1).Value
Dim suffix As String: suffix = xlSheet.Cells(excelRow, 2).Value
If prefix <> "" Then
newPageName = prefix & "_" & suffix
' ページ名が既に一致している場合は、COMのオーバーヘッドを避けるためスキップ
If visPage.Name <> newPageName Then
' Visioのページ名は特殊文字に制限があるため、実務ではサニタイズを推奨
visPage.Name = SanitizeName(newPageName)
End If
Else
' Excel側にデータがない場合は、警告を出すか、名前をクリアする設計にする
Debug.Print "Warning: No data in Excel row " & excelRow
End If
' 次の行へ
excelRow = excelRow + 1
Next i
MsgBox "同期が完了しました。", vbInformation
CleanUp:
' 3. 後処理
If Not xlWB Is Nothing Then xlWB.Close SaveChanges:=False
' xlApp.Quit ' Excel自体を終了させるかどうかは運用に依存
Set xlSheet = Nothing
Set xlWB = Nothing
Set xlApp = Nothing
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました:" & vbCrLf & Err.Description, vbCritical
Resume CleanUp
End Sub
''' <summary>
''' Visioのページ名に使用できない文字を除去する
''' </summary>
Private Function SanitizeName(ByVal strName As String) As String
Dim invalidChars As Variant
Dim v As Variant
' Visioのページ名に使用できない記号を定義
invalidChars = Array("/", "\", ":", "*", "?", """", "<", ">", "|")
For Each v In invalidChars
strName = Replace(strName, v, "_")
Next v
SanitizeName = strName
End Function
4. チーフアーキテクトからの技術的助言
このコードを運用に投入するにあたり、以下の3点を肝に銘じておけ。
1. トランザクションの概念を持て
このコードは、途中でエラーが起きた場合、一部のページだけ名前が変わった「中途半端な状態」になるリスクがある。大規模な自動化プロジェクトでは、実行前に「現在の状態をバックアップ(別名保存)する」工程を、プログラムの冒頭に組み込むのがプロの作法だ。
2. パフォーマンスの重みを知れ
`visPage.Name = …` という操作は、Visioの内部的なインデックスを再計算させる重い処理だ。数千ページあるような巨大な図面を扱う場合、ループの中でExcelのセルに何度もアクセスするのは愚策だ。
必ず、「Excelのデータを一度配列(Array)に読み込み、メモリ上で処理を完結させる」こと。これだけで、処理速度は数倍から数十倍変わる。
3. 異常系への備え(サニタイズ)
Excelは「人間が触る場所」だ。誰かがセルに `/` や `:` を入力した瞬間、君のコードはクラッシュする。`SanitizeName` 関数を実装したのはそのためだ。「入力値は常に汚染されている」という前提で設計せよ。
まとめ
ページ名の同期は、単なるリネーム作業ではない。「Visioの物理的な並び(Index)」と「管理データ(Excel)」の論理的な整合性を保つための、データ整合性制御(Data Integrity Control)である。
この視点を持てば、君の書くコードは単なる「便利ツール」から、企業の業務基盤を支える「堅牢なシステム」へと昇華するはずだ。
以上。作業に戻れ。
