こんにちは!開発現場を奔走する君なら、一度はこんな経験があるはずです。
「ユーザーから『エラーが出ました』って連絡が来たけど、肝心のエラーメッセージを読み飛ばされてしまった……」
「『なんか変な画面になった』と言われても、こちらでは再現できない……」
遠隔地にいるユーザーの環境(リモート環境)で起きたバグほど、エンジニアを絶望させるものはありませんよね。再現性がないバグは、原因究明だけで何日も溶けてしまいます。
そこで今回は、「アプリケーションで未処理の例外(予期せぬエラー)が発生した瞬間、自動的に画面のスクリーンショットをキャプチャし、エラーログと一緒に保存する仕組み」をVB.NET(Windows Forms)で実装する方法を解説します!
ここをクリアすれば、Windows Formsのイベント駆動の裏側や、例外処理の本質がぐっと見えてきますよ。さあ、一緒にワンランク上のエンジニアへステップアップしましょう!
—
1. なぜ「キャプチャ付き例外処理」が必要なのか?
Excelのマクロ記録から抜け出し、本格的なWindowsアプリ開発の世界へ足を踏み入れた君なら、`Try…Catch` ブロックを使ったエラー処理はもう知っているはずです。
しかし、通常の `Catch` では、ユーザーが「何を入力していて」「どんな画面状態で」「どんなエラーダイアログが出たのか」を後から知ることはできません。
今回作る仕組みのイメージ図はこうです:
[ユーザーの操作]
↓
[予期せぬ例外が発生! (未処理例外)]
↓
[★ここでキャッチ! (ThreadException)]
├── 1. 瞬時に画面全体をビットマップとして撮影 (描画キャプチャ)
├── 2. タイムスタンプ付きでファイルに保存 (PNG形式等)
└── 3. 優しいエラーメッセージをユーザーに表示 & 開発者へ通知
この仕組みを導入するだけで、保守性は劇的に向上します。「画面の証拠写真」が手元にある状態でデバッグできる安心感は、一度味わうと戻れなくなりますよ。
—
2. 全体像:実装するコードの全貌
それでは早速、Windows Formsにおける「最後の砦」である `Application.ThreadException` を使ったグローバル例外ハンドラーの実装を見ていきましょう。
プロジェクトのスタートアップ位置(通常は `Application.Designer.vb` または `Program.vb`、あるいはメインフォームの `Load` イベントなど)で、未処理例外のフックを設定します。
以下のコードを、そのまま君の開発環境(Visual Studio)の適当なクラスにコピペして確認してみてください。
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.IO
Public Class ErrorHandlerManager
‘ アプリケーション全体の未処理例外をフックする初期設定
Public Shared Sub InitializeGlobalExceptionHandler()
‘ UIスレッドで発生した未処理例外をキャッチするイベントを登録
AddHandler Application.ThreadException, AddressOf Application_ThreadException
‘ 万が一、非UIスレッドで例外が出た場合の保険(オプション)
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException
End Sub
‘ UIスレッド例外のハンドラー(ここに魔法を仕掛けます)
Private Shared Sub Application_ThreadException(sender As Object, e As System.Threading.ThreadExceptionEventArgs)
Dim ex As Exception = e.Exception
Try
‘ 1. スクリーンショットの撮影と保存を実行
Dim screenshotPath As String = CaptureAndSaveScreen()
‘ 2. ユーザーへの通知とログ記録
Dim errorMessage As String = “予期せぬエラーが発生しました。” & vbCrLf &
“管理者へ以下の証拠画像と共に報告してください。” & vbCrLf &
“保存先: ” & screenshotPath & vbCrLf & vbCrLf &
“詳細: ” & ex.Message
MessageBox.Show(errorMessage, “システムエラー”, MessageBoxButtons.OK, MessageBoxIcon.Error)
Catch captureEx As Exception
‘ キャプチャ処理自体の失敗でアプリが落ちるのを防ぐ防衛コード
MessageBox.Show(“エラーが発生しましたが、画面のキャプチャに失敗しました: ” & captureEx.Message, “致命的なエラー”, MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
‘ 必要に応じてアプリケーションを安全に終了させる
Environment.Exit(1)
End Try
End Sub
‘ 非UIスレッド用(バックグラウンド処理など)
Private Shared Sub CurrentDomain_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs)
Dim ex As Exception = TryCast(e.ExceptionObject, Exception)
‘ ここでも同様にログやダンプを残す処理を書くことができます
End Sub
”’
”’
Private Shared Function CaptureAndSaveScreen() As String
‘ 画面全体のサイズを取得(マルチモニタ対応含む)
Dim bounds As Rectangle = Screen.GetBounds(Point.Empty)
‘ 画像をメモリ上に展開するためのBitmapオブジェクトを作成
Using bmp As New Bitmap(bounds.Width, bounds.Height)
‘ グラフィックオブジェクトを生成し、画面をコピー(BitBlt転送)
Using g As Graphics = Graphics.FromImage(bmp)
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size)
End Using
‘ 保存先フォルダのパスを決定(今回はマイドキュメント内の専用フォルダ)
Dim logDir As String = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), “AppErrorScreenshots”)
If Not Directory.Exists(logDir) Then
Directory.CreateDirectory(logDir)
End If
‘ タイムスタンプをファイル名に使用してユニーク化する(重複防止)
Dim timeStamp As String = DateTime.Now.ToString(“yyyyMMdd_HHmmss_fff”)
Dim filePath As String = Path.Combine(logDir, $”ErrorShot_{timeStamp}.png”)
‘ ビットマップをPNG形式で保存
bmp.Save(filePath, Imaging.ImageFormat.Png)
Return filePath
End Using
End Function
End Class
—
3. コードのキモを深掘り解説!
ここからは、チーフアーキテクトとしての視点で、このコードの重要なポイントを紐解いていきます。
① `Application.ThreadException` という「最後の砦」
通常の `Try…Catch` は、コードを書いた「その場所」でしかエラーを拾えません。しかし、このイベントを使うと、「プログラマーが `Catch` し忘れたすべてのUIエラー」を網羅的に横取り(インターセプト)できます。
アプリが強制終了するその寸前に割り込むため、ユーザーの目の前で華麗に証拠を押さえることができるのです。
② `Using` ステートメントによるリソース管理の鉄則
画像処理やファイル操作を行う `Bitmap` や `Graphics` といったオブジェクトは、OSのメモリやハンドル(リソース)を大量に消費します。
VB.NETの `Using … End Using` 構文を使うことで、「処理が終わったら確実にメモリから解放する(Disposeする)」という安全性を担保しています。これをサボると、アプリを長く使っているうちにメモリリーク(メモリ不足)を引き起こす原因になります。プロのエンジニアは `Using` を絶対に忘れません。
③ `Graphics.CopyFromScreen` の圧倒的パワー
Windows Formsの描画機能の真骨頂がこのメソッドです。OSの画面バッファからピクセル情報をダイレクトにごっそりコピーしてくるため、複雑なウィンドウが背後で重なっていても、ユーザーが見ている「その瞬間」の画面を完璧に切り取ることができます。
—
4. 陥りやすい罠と注意点(初心者がハマるポイント)
実際にこの機能を導入する際、開発現場でよくある「落とし穴」をいくつか共有しておきます。
- マルチモニター環境でのサイズ切れ
単に `Form.ActiveForm.Size` などを使うと、サブモニターにまたがったウィンドウや、メイン以外の画面で起きたエラーの時に画像が途切れます。必ず上述のコードのように `Screen.GetBounds(Point.Empty)` を使って「全デスクトップ領域」を対象にしてください。
- セキュリティとプライバシーへの配慮
スクリーンショットには、ユーザーが入力したパスワードや、機密情報(顧客の個人情報など)が偶発的に映り込む可能性があります。「保存された画像は社内・開発者宛ての保守目的以外に使用しない」「ローカルに保存した画像は定期的に古いものを削除する」といった運用ルールや仕組みをセットで考えるのが、優れたエンジニアの嗜みです。
—
まとめ
今回は、Windows Formsの描画キャプチャ機能を利用した、例外発生時のスクリーンショット自動保存の仕組みを解説しました。
- 未処理例外は `Application.ThreadException` で一網打尽にキャッチする。
- `Graphics.CopyFromScreen` でユーザーの「その瞬間」の画面を切り取る。
- `Using` を使ってメモリリソースを美しく管理する。
この実装がプロジェクトに加わるだけで、あなたの作るアプリケーションの「プロっぽさ」と「保守性」は跳ね上がります。エラーに怯える日々から、エラーを華麗に解析してドヤ顔する日々へ――。
ここをクリアした君なら、もう立派なWindows Formsマスターへの道を歩んでいますよ。次の開発も、その調子で自信を持ってコードを書いていきましょう!
