【実務・中級編】【初心者向け】特定のレイヤーに属する図形を全ページから一括選択し、色を一括変更する便利ツール – Visio VBA解析バイブル

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

【極限の設計思想】Visio VBAにおけるレイヤー操作の真髄:全ページ横断・属性一括操作の堅牢な実装

現場のエンジニア諸君、よく聞いてほしい。

「動けばいい」という思考で書かれたコードは、運用フェーズに入った瞬間に牙を剥く。特に、Visioのような複雑なオブジェクトモデルを持つアプリケーションにおいて、単なる「スクリプト」と「プロダクションレベルのツール」の差は、「オブジェクトのライフサイクルを理解しているか」、そして「例外的な状態を設計に組み込んでいるか」という一点に集約される。

今回のテーマは、特定のレイヤーに属する図形を全ページから探し出し、その色を一括変更するツールだ。
初心者は「全図形を選択して、レイヤー名を判定して、色を変える」という、極めて非効率で脆弱なアプローチを取りがちだ。しかし、我々プロフェッショナルが目指すべきは、「選択(Selection)に依存せず、オブジェクトモデルを直接走査する」設計である。

この記事では、なぜその書き方が「素人」なのかを論理的に解体し、実務で即戦力となる堅牢なコードを伝授する。

1. なぜ「Select」を使うコードはゴミなのか

多くの入門書には、`shp.Select` や `ActiveWindow.Selection` を使ったコードが載っている。しかし、業務自動化の現場において、これらは「禁忌」だ。

Selection(選択)への依存が招く3つの災厄

1. パフォーマンスの劇的な低下:
`Select` メソッドは、VisioのGUI(画面描画)を強制的に更新させる。数千個の図形を操作する場合、描画処理がボトルネックとなり、処理時間は指数関数的に増大する。
2. 非決定的な動作:
「現在何が選択されているか」というユーザーの状態にロジックが依存してしまう。ユーザーが作業中にマクロを走らせた場合、予期せぬ図形が操作対象になり、データ破壊を招く。
3. エラーへの脆弱性:
ページが非表示であったり、ウィンドウが最小化されているだけで、`Select` は失敗する。

アーキテクトの教え:
「画面(UI)を操作するな。オブジェクト(Data)を操作せよ。」

2. レイヤー概念の再定義:コンテナではなく「属性」である

ここが最大の勘違いポイントだ。
多くの者は、レイヤーを「図形を入れるフォルダ(コンテナ)」だと考えている。しかし、Visioの内部モデルにおいて、レイヤーは図形が持つ「属性(Property)」の一つに過ぎない。

一つの図形は、複数のレイヤーに同時に所属することができる。
したがって、特定のレイヤーに属する図形を探すロジックは、「その図形が持つレイヤーリストの中に、目的のレイヤー名が含まれているか?」という判定ロジックでなければならない。

3. プロダクション・クオリティの設計:堅牢な実装例

それでは、実際に現場で使えるコードを提示しよう。
このコードは、単に色を変えるだけではない。エラーハンドリング、複数レイヤーへの所属考慮、そして計算コストを最小限に抑える設計を施している。

Option Explicit

''' <summary>
''' 指定したレイヤーに属する全ページの図形の色を一括変更する
''' </summary>
''' <param name="targetLayerName">対象となるレイヤー名</param>
''' <param name="newColorRGB">変更後の色 (RGB形式)</param>
Public Sub BulkChangeShapeColorByLayer(ByVal targetLayerName As String, ByVal newColorRGB As Long)
    Dim pg As Visio.Page
    Dim shp As Visio.Shape
    Dim i As Integer
    Dim isTarget As Boolean
    Dim changeCount As Long
    
    ' 1. エラーハンドリングの初期化
    ' 業務ツールにおいて、エラーを無視することは、不具合を隠蔽することと同義である
    On Error GoTo ErrorHandler
    
    ' 描画更新を一時停止(パフォーマンス向上と画面のちらつき防止)
    ' これにより、オブジェクト操作中のGUI更新コストを排除する
    Application.ScreenUpdating = False
    
    changeCount = 0
    
    ' 2. 全ページを走査(Page Collectionの走査)
    For Each pg In ThisDocument.Pages
        
        ' 3. 各ページ内の全図形を走査(Shapes Collectionの走査)
        For Each shp In pg.Shapes
            
            isTarget = False
            
            ' 4. 図形が持つレイヤーをチェック
            ' 図形は複数のレイヤーに属する場合があるため、全レイヤーをループする必要がある
            If shp.LayerCount > 0 Then
                For i = 1 To shp.LayerCount
                    If shp.Layer(i).Name = targetLayerName Then
                        isTarget = True
                        Exit For ' 対象が見つかれば、この図形の他のレイヤーチェックは不要
                    End If
                Next i
            End If
            
            ' 5. 条件に合致した場合の属性操作
            If isTarget Then
                ' FillForeColor (塗りつぶし色) を変更
                ' Selectを使わず、オブジェクトのプロパティに直接アクセスする
                shp.Cells("FillForeColor").Formula = "RGB(" & _
                    RgbToComponent(newColorRGB, 1) & "," & _
                    RgbToComponent(newColorRGB, 2) & "," & _
                    RgbToComponent(newColorRGB, 3) & ")"
                
                changeCount = changeCount + 1
            End If
            
        Next shp
    Next pg

    ' 正常終了時の通知
    Application.ScreenUpdating = True
    MsgBox "処理が完了しました。" & vbCrLf & _
           "変更された図形数: " & changeCount, vbInformation, "完了"
    Exit Sub

ErrorHandler:
    ' 失敗時も必ず描画更新を再開させる(これを行わないと、Visioがフリーズしたように見える)
    Application.ScreenUpdating = True
    MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
           "エラー番号: " & Err.Number & vbCrLf & _
           "内容: " & Err.Description, vbCritical, "エラー"
End Sub

''' <summary>
''' Long型のRGB値を各コンポーネント(R, G, B)に分解するヘルパー関数
''' </summary>
Private Function RgbToComponent(ByVal rgbValue As Long, ByVal component As Integer) As Integer
    Select Case component
        Case 1: RgbToComponent = (rgbValue And &HFF0000&) / &H10000& ' Red
        Case 2: RgbToComponent = (rgbValue And &HFF00&) / &H100&      ' Green
        Case 3: RgbToComponent = (rgbValue And &HFF&)               ' Blue
    End Select
End Function

''' <summary>
''' 実行用エントリーポイント
''' </summary>
Public Sub ExecuteLayerColorChange()
    ' 実務では、ここをダイアログやExcelの設定値から取得するように設計する
    Const TARGET_LAYER As String = "重要項目"
    Const TARGET_COLOR As Long = 255 ' 赤 (RGB(255, 0, 0) は Long値で 255)
    
    ' 注意: VBAのRGB関数は Long型を返す。
    ' ここでは例として、赤色を指定。
    Call BulkChangeColor(TARGET_LAYER, RGB(255, 0, 0))
End Sub

' ※ 上記の BulkChangeShapeColorByLayer を呼び出すラッパー
Public Sub BulkChangeColor(layerName As String, colorVal As Long)
    BulkChangeShapeColorByLayer layerName, colorVal
End Sub

4. チーフアーキテクトによるコード解説

パフォーマンスの最適化:`Application.ScreenUpdating = False`

これは「おまじない」ではない。Visioが図形の色を変えるたびに画面を書き換えるコストを、実行前に完全に遮断するための戦略的な命令だ。大規模な図面では、これだけで処理速度が10倍以上変わることもある。

堅牢なロジック:`shp.LayerCount` の走査

初心者は `If shp.Layer.Name = …` と書きがちだが、これはエラーの元だ。図形がレイヤーに属していない場合、あるいは複数のレイヤーに属している場合、この書き方では不十分。`For i = 1 To shp.LayerCount` と、図形の持つレイヤーの数だけ確実に走査するのが、プロの設計だ。

保守性の向上:コンポーネント分解関数

`newColorRGB` を直接 `Formula` に流し込むのではなく、`RgbToComponent` というヘルパー関数を用意した。これにより、色の計算ロジックがメインロジックから分離され、将来的に「色のグラデーション変更」などの要件が出た際も、変更箇所を最小限に抑えられる。

5. 実務への応用:データベース・外部連携を見据えて

このツールをさらに進化させるなら、色やレイヤー名をコード内にハードコード(直書き)してはいけない。

1. Excel連携:
「レイヤー名」「変更後の色」「対象ページ」のリストをExcelに持たせ、それを読み込んでループさせる。これにより、非エンジニアでもツールを使いこなせるようになる。
2. 設定ファイルの外部化:
INIファイルやJSONファイルから設定を読み込むように設計すれば、コードを書き換えることなく、現場のルール変更(例:重要項目の色が赤からオレンジへ変更)に即座に対応できる。

最後に

コードを書くことは、単に命令を並べることではない。「どのような状況下でも、予測可能な結果を、最小のコストで返すこと」。それがエンジニアリングの本質だ。

次に君がコードを書くときは、自分に問いかけてほしい。
「このコードは、ユーザーの操作に依存していないか?」「このコードは、10倍のデータ量に耐えられるか?」

以上だ。健闘を祈る。

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