【入門編】例外発生時のスクリーンショット自動保存:Windows Formsの描画キャプチャ機能を利用したリモート保守性向上の仕組み – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

こんにちは!開発現場を奔走する君なら、一度はこんな経験があるはずです。

「ユーザーから『エラーが出ました』って連絡が来たけど、肝心のエラーメッセージを読み飛ばされてしまった……」
「『なんか変な画面になった』と言われても、こちらでは再現できない……」

遠隔地にいるユーザーの環境(リモート環境)で起きたバグほど、エンジニアを絶望させるものはありませんよね。再現性がないバグは、原因究明だけで何日も溶けてしまいます。

そこで今回は、「アプリケーションで未処理の例外(予期せぬエラー)が発生した瞬間、自動的に画面のスクリーンショットをキャプチャし、エラーログと一緒に保存する仕組み」を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

”’

”’ 画面全体をキャプチャし、指定フォルダにPNG形式で保存する核心メソッド
”’

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マスターへの道を歩んでいますよ。次の開発も、その調子で自信を持ってコードを書いていきましょう!

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