Windows Forms印刷の深淵:PrintPageEventArgsを支配し、帳票エンジンを自作せよ
業務系アプリケーション開発において、帳票出力は「最後の砦」だ。標準の `PrintPreviewDialog` や `PrintDocument` をそのまま使って、顧客から「ヘッダーに社外秘の透かしを入れたい」「ページ番号をX/Y形式で出せ」「明細の改ページ位置がずれている」といった要求を受けたとき、多くのエンジニアはパッチワーク的な実装で自滅していく。
いいか、標準コンポーネントの挙動に振り回されるな。`PrintPageEventArgs` を完全に制御し、GDI+ の描画コンテキストを直接叩くアーキテクチャこそが、保守性の高い帳票エンジンの正解だ。
今日は、その極意を伝授する。
—
1. なぜ「標準コントロール」では限界があるのか
標準の `PrintDocument` は、OSのプリンタドライバーと密接に結合している。安易に `PrintPage` イベント内で「データベースアクセス」や「複雑な計算」を行うと、以下の地獄を見る。
- プレビューと印刷結果の乖離: プレビュー時の解像度(DPI)とプリンタの解像度は異なる。座標計算をハードコードすると必ず崩れる。
- メモリリーク: `PrintPageEventArgs.Graphics` は使い捨てだ。描画オブジェクト(Pen, Brush, Font)を `Using` ブロックで適切に破棄しないと、数百ページの印刷でメモリが枯渇する。
- 状態管理の崩壊: ページをまたぐ計算でグローバル変数を使うな。印刷状態を保持する「ステートマシン」を設計せよ。
—
2. 堅牢な帳票エンジンの設計:状態管理の分離
印刷プロセスを「データ取得」と「描画ロジック」に分離する。これがバグを生まない唯一の設計だ。
‘ 印刷状態を管理するクラス
Public Class ReportState
Public Property CurrentPage As Integer = 1
Public Property TotalPages As Integer = 0
Public Property DataIndex As Integer = 0
‘ 印刷対象のリストやデータセットを保持
End Class
3. 実践:PrintPageEventArgsを制する描画ロジック
以下は、ヘッダー・フッター・透かし・改ページ制御を実装したプロダクションコードの雛形だ。
Private Sub PrintDocument1_PrintPage(sender As Object, e As PrintPageEventArgs) Handles PrintDocument1.PrintPage
Dim g As Graphics = e.Graphics
Dim pageBounds As RectangleF = e.MarginBounds
‘ 1. 「社外秘」の透かしを背景に描画(透過処理)
Using brush As New SolidBrush(Color.FromArgb(50, Color.Red))
Dim font As New Font(“Arial”, 72, FontStyle.Bold)
g.RotateTransform(-45) ‘ 斜めに傾ける
g.DrawString(“社外秘”, font, brush, -100, 500)
g.ResetTransform() ‘ 変換を戻すことを忘れない
End Using
‘ 2. ヘッダー・フッター描画
DrawHeaderFooter(g, pageBounds, state.CurrentPage)
‘ 3. 明細描画(改ページ制御)
Dim yOffset As Single = 100 ‘ 初期位置
While state.DataIndex < myDataList.Count
' ここで描画ロジック実行
' もし yOffset がページ下限を超えたら HasMorePages = True にして抜ける
If yOffset > pageBounds.Bottom – 50 Then
e.HasMorePages = True
state.CurrentPage += 1
Return
End If
‘ 行描画処理…
yOffset += 30
state.DataIndex += 1
End While
e.HasMorePages = False
End Sub
Private Sub DrawHeaderFooter(g As Graphics, bounds As RectangleF, pageNo As Integer)
‘ ページ番号の描画
g.DrawString($”ページ: {pageNo}”, New Font(“MS UI Gothic”, 9), Brushes.Black, bounds.Right – 50, bounds.Bottom + 10)
End Sub
—
4. 現場で生き残るための「鉄則」
① DPIスケーリングを意識せよ
`g.DpiX` や `g.DpiY` を使って座標を補正しろ。特にプレビュー画面と実際の紙では解像度が異なるため、`GraphicsUnit.Inch` や `GraphicsUnit.Millimeter` を基準に計算する癖をつけろ。
② 描画リソースの即時解放
GDI+ のオブジェクトはマネージドリソースではない。`Using` ステートメントの使用は必須だ。怠れば、WindowsのGDIハンドル制限に抵触し、OSごとフリーズさせることになる。
③ データベース接続は印刷の「前」に完了させる
`PrintPage` イベントの中でクエリを投げるな。ユーザーが印刷ボタンを押した瞬間にデータをメモリ(または一時ファイル)にロードし、描画時はそのキャッシュを参照する。これがパフォーマンスと堅牢性の両立だ。
—
最後に:エンジニアの美学
「コードは動けばいい」という段階は卒業したはずだ。
帳票出力とは、システムから出力される「顔」である。ヘッダーのズレ一つ、透かしの不自然な重なり一つで、ユーザーはそのシステム全体の信頼性を判断する。
`PrintPageEventArgs` は、単なるイベント引数ではない。君のロジックを紙の上に具現化するための、最も強力なキャンバスだ。このキャンバスをどう使いこなすか、それは君の設計思想そのものになる。
さあ、退屈な標準機能の制約を脱ぎ捨て、真の帳票エンジンを組み上げろ。君のコードなら、必ずできるはずだ。
