Visio VBAを掌握する極限の知見:印刷トラブルを未然に防ぐ「ページ設定監査ツール」の設計思想
現場のエンジニアがよく陥る罠がある。それは、「動けばいい」という思考停止だ。
特にVisioのような、図面設計と印刷設定が密接に絡み合うアプリケーションにおいて、プロパティの値を単に取得して表示するだけのコードは、実務レベルでは「ゴミ」に等しい。なぜなら、ユーザーが本当に求めているのは「値の表示」ではなく、「設定の不整合による、印刷時のレイアウト崩れという致命的なリスクの検知」だからだ。
本稿では、Visioにおける「プリンタ用紙サイズ(PaperSize)」と「図面サイズ(PageWidth/Height)」の不一致を検出し、監査レポートを出力する堅牢なツールの設計と実装について、アーキテクトの視点から解説する。
1. 致命的な設計ミス:なぜ「単純な比較」では不十分なのか
初心者は、`Page.PageSetup.PaperSize` の値を見て、それが期待通りかどうかを判定しようとする。しかし、これでは不十分だ。
概念の乖離を理解せよ
Visioには、以下の2つの異なる概念が存在する。
1. Printer Paper Size (`PaperSize`): 物理的なプリンタが吐き出す用紙の規格(A4, A3, Letter等)。これは列挙型(Enum)で管理されている。
2. Drawing Canvas Size (`PageWidth`, `PageHeight`): Visioのキャンバスそのものの大きさ。これは実数値(Points)で管理されている。
「A4用紙に印刷したいから、PaperSizeをA4に設定した。よし、完了だ」 —— この考え方が、印刷トラブルの元凶だ。
もし、キャンバスサイズ(PageWidth/Height)がA4の寸法と1ポイントでも異なっていれば、印刷時に「余白が異常に広くなる」「図面が端で切れる」「意図しないスケーリングがかかる」といった、設計品質を著しく損なう現象が発生する。
我々が作るべきは、「物理的な規格(Enum)が、実数値(Points)として正しくキャンバスに反映されているか」を検証する監査エンジンである。
2. プロダクション・レベルの設計指針
実務で使えるツールにするためには、以下の3点を設計に組み込む必要がある。
① 浮動小数点誤差への対処(Tolerance)
Visioのサイズは内部的に `Points`(1/72インチ)という浮動小数点数で扱われる。`If Width = 595.27 Then` のような厳密な比較は、計算誤差によって必ず失敗する。必ず許容誤差(Epsilon)を設けた比較ロジックを実装せよ。
② 責務の分離(Separation of Concerns)
「ページを巡回するロジック」「サイズを判定するロジック」「結果をレポート出力するロジック」を混ぜてはならない。これらが混ざると、出力先を「Excel」から「テキストファイル」や「GUIダイアログ」に変更したい時に、コード全体を書き直す羽目になる。
③ 外部依存の最小化と堅牢なエラーハンドリング
プリンタドライバの状態によって `PaperSize` の取得が不安定になるケースがある。オブジェクトのライフサイクルを意識し、エラーが発生してもプロセス全体を止めず、どのページで失敗したかをログに残す設計が求められる。
3. 実装:ページ設定監査エンジン
以下に、保守性と堅牢性を極限まで高めたプロダクションコードを示す。このコードは、単なる比較ではなく、用紙規格とキャンバス寸法の「整合性」を検証する。
Option Explicit
' ==============================================================================
' 監査結果を保持するための構造体
' プロダクションコードでは、クラスモジュール化してより高度な管理を行うことも検討せよ
' ==============================================================================
Private Type AuditResult
PageName As String
IsError As Boolean
Message As String
ExpectedWidth As Double
ExpectedHeight As Double
ActualWidth As Double
ActualHeight As Double
End Type
' 許容誤差(ポイント単位)
Private Const EPSILON As Double = 0.1
' ==============================================================================
' メインエントリポイント
' ==============================================================================
Public Sub RunPageSetupAudit()
Dim doc As Visio.Document
Dim pg As Visio.Page
Dim results() As AuditResult
Dim errorCount As Long
Dim i As Long
On Error GoTo ErrorHandler
Set doc = ActiveDocument
If doc Is Nothing Then
MsgBox "ドキュメントが開かれていません。", vbCritical
Exit Sub
End If
' 結果格納用配列の初期化
ReDim results(1 To doc.Pages.Count)
errorCount = 0
' --- 監査プロセス開始 ---
For i = 1 To doc.Pages.Count
Set pg = doc.Pages(i)
results(i) = PerformAudit(pg)
If results(i).IsError Then
errorCount = errorCount + 1
End If
Next i
' --- レポート出力 ---
OutputAuditReport results, errorCount
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub
' ==============================================================================
' 監査ロジック:単一ページの整合性を検証する
' ==============================================================================
Private Function PerformAudit(pg As Visio.Page) As AuditResult
Dim res As AuditResult
Dim ps As Visio.PageSetup
Set ps = pg.PageSetup
res.PageName = pg.Name
res.IsError = False
' 1. 用紙規格から期待される寸法を取得(マッピングテーブルの役割)
' 本来はより広範な定義が必要だが、ここでは主要な規格のみを例示
Dim expectedW As Double, expectedH As Double
GetExpectedDimensions ps.PaperSize, expectedW, expectedH
' 2. 現在のキャンバスサイズを取得
res.ActualWidth = ps.PageWidth
res.ActualHeight = ps.PageHeight
res.ExpectedWidth = expectedW
res.ExpectedHeight = expectedH
' 3. 整合性チェック(浮動小数点誤差を考慮)
Dim widthDiff As Double: widthDiff = Abs(res.ActualWidth - res.ExpectedWidth)
Dim heightDiff As Double: heightDiff = Abs(res.ActualHeight - res.ExpectedHeight)
If widthDiff > EPSILON Or heightDiff > EPSILON Then
res.IsError = True
res.Message = "用紙サイズ不一致: 規格の寸法とキャンバスサイズが異なります。"
End If
PerformAudit = res
End Function
' ==============================================================================
' 規格マッピング:PaperSize Enum を実寸(Points)に変換する
' ここに定義がない規格は、設計上のリスクとして扱うべきである
' ==============================================================================
Private Sub GetExpectedDimensions(paperSize As Visio.PaperSize, ByRef w As Double, ByRef h As Double)
' Visioの単位はポイント。1 inch = 72 points.
' 以下の値は標準的なA規格等の寸法に基づく(要検証)
Select Case paperSize
Case visPaperA4
w = 595.27: h = 841.89
Case visPaperA3
w = 841.89: h = 1190.55
Case visPaperLetter
w = 612: h = 792
Case Else
' 未知の規格は、現在のPaperSizeから取得を試みるか、
' 監査不能としてエラーを返すべき設計にする
w = 0: h = 0
End Select
End Sub
' ==============================================================================
' レポート出力:結果をデバッグウィンドウとメッセージボックスに集約
' 実務ではここを Excel 出力や CSV 出力に差し替える
' ==============================================================================
Private Sub OutputAuditReport(results() As AuditResult, errorCount As Long)
Dim i As Long
Dim reportMsg As String
If errorCount = 0 Then
MsgBox "監査完了: すべてのページの用紙設定は正常です。", vbInformation
Exit Sub
End If
reportMsg = "【監査警告】" & vbCrLf & errorCount & " 個のページに不一致が見つかりました。" & vbCrLf & vbCrLf
Debug.Print "--- Audit Report Start ---"
For i = LBound(results) To UBound(results)
If results(i).IsError Then
' デバッグ出力(詳細ログ)
Debug.Print "Error in [" & results(i).PageName & "]: " & _
"Expected(" & results(i).ExpectedWidth & "x" & results(i).ExpectedHeight & ") " & _
"Actual(" & results(i).ActualWidth & "x" & results(i).ActualHeight & ")"
' ユーザーへの概要表示用
reportMsg = reportMsg & "- " & results(i).PageName & ": " & results(i).Message & vbCrLf
End If
Next i
Debug.Print "--- Audit Report End ---"
MsgBox reportMsg, vbExclamation
End Sub
4. 伝説のアーキテクトからのアドバイス
このコードをベースに、さらに実務へ適応させるための「次の一手」を伝えておく。
外部連携の注意点
もしこの結果を Excel に出力する場合、`Excel.Application` を参照設定して直接操作するのは避けよ。COM参照は動作を重くし、実行環境のバージョン差異によるランタイムエラーを誘発する。
可能であれば、「CSV形式でファイルに書き出す」か、「クリップボードにテキストとしてコピーする」設計にせよ。これが、環境に依存しない「疎結合」な設計である。
拡張性の確保
今回、`GetExpectedDimensions` 内で `Select Case` を用いて寸法をハードコーディングした。これは小規模なら許容されるが、大規模なプロジェクトでは、このマッピングデータ自体を 外部の構成ファイル(JSONやXML) または データベース から読み込むように設計変更せよ。用紙規格の追加に際して、VBAのロジック(コード)を触らなくて済む状態こそが、真のプロフェッショナルだ。
結論
自動化ツールを作る際、我々は「コードを書いている」のではない。「業務の品質を担保する仕組みを構築している」のだ。
単なるプロパティの参照で満足するな。その値が持つ「意味」を読み解き、エラーの予兆を捉える。それこそが、エンジニアリングの本質である。
