【実務・中級編】【中級者向け】ページ名に「連番」を付与し、Excelの目次と完全に同期させる自動リネーム処理 – Visio VBA解析バイブル

スポンサーリンク
スポンサーリンク

【設計思想】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)である。

この視点を持てば、君の書くコードは単なる「便利ツール」から、企業の業務基盤を支える「堅牢なシステム」へと昇華するはずだ。

以上。作業に戻れ。

タイトルとURLをコピーしました