Windows Formsの「ドラッグ&ドロップ」を、事故ゼロのプロフェッショナル仕様にする
業務効率化ツールを開発する際、エクスプローラーからのドラッグ&ドロップ(D&D)はUIの基本中の基本だ。しかし、多くのエンジニアが「なんとなく動く」コードで満足し、後に深刻なバグや例外処理の未整備に泣くことになる。
ファイルを投げ込むユーザーは、悪意がなくても「存在しないパス」「巨大なファイル」「アクセス権のないフォルダ」を平気で渡してくる。「渡されたものをそのまま受け取る」という設計は、業務ツールにおいて最も避けるべき脆弱性だ。
本稿では、プロフェッショナルな設計思想に基づき、安全かつ堅牢なD&D処理の実装方法を伝授する。
—
1. なぜ「雑な実装」は破滅を招くのか
多くの初心者が陥る罠は、`DragDrop`イベント内でいきなりロジックを詰め込むことだ。
- バリデーションの欠如: ユーザーがフォルダをドロップしたのか、ファイルなのかを区別していない。
- 非同期処理の不在: 大量のファイルを一度にドラッグされると、メインスレッドがフリーズし、Windowsから「応答なし」のラベルを貼られる。
- リソースの解放: ファイルのストリームを開いたまま放置し、後続のファイル操作で「プロセスが使用中です」という例外を発生させる。
これらを防ぐには、「検証(DragEnter)」「特定(DragDrop)」「処理(バックグラウンド)」の3フェーズを厳格に分ける必要がある。
—
2. 堅牢な実装:プロダクションコードのテンプレート
以下に、実務でそのまま使える堅牢なクラス設計を示す。これを継承、あるいは部品として組み込めば、どんなツールでも安定した挙動を実現できる。
.net
Imports System.IO
”’
”’
Public Class FileDropHandler
‘ 許可する拡張子のホワイトリスト(業務に合わせて調整せよ)
Private ReadOnly _allowedExtensions As String() = {“.xlsx”, “.csv”, “.txt”}
”’
”’
Public Sub OnDragEnter(e As DragEventArgs)
If e.Data.GetDataPresent(DataFormats.FileDrop) Then
e.Effect = DragDropEffects.Copy
Else
e.Effect = DragDropEffects.None
End If
End Sub
”’
”’
Public Function GetValidFilePaths(e As DragEventArgs) As List(Of String)
Dim validFiles As New List(Of String)
‘ DataFormats.FileDropはObject型で来るためキャストに注意
Dim files As String() = DirectCast(e.Data.GetData(DataFormats.FileDrop), String())
For Each path As String In files
‘ ファイルか?存在するか?拡張子は正しいか?を厳格にチェック
If File.Exists(path) AndAlso _allowedExtensions.Contains(Path.GetExtension(path).ToLower()) Then
validFiles.Add(path)
Else
‘ ログ出力:開発時にはここで不正なファイルが弾かれたことを可視化しておく
Debug.WriteLine($”不正なパスまたは形式のため除外: {path}”)
End If
Next
Return validFiles
End Function
End Class
UI側(Form)での呼び出し方
.net
Private Sub Form1_DragEnter(sender As Object, e As DragEventArgs) Handles Me.DragEnter
_handler.OnDragEnter(e)
End Sub
Private Sub Form1_DragDrop(sender As Object, e As DragEventArgs) Handles Me.DragDrop
Dim files = _handler.GetValidFilePaths(e)
If files.Count = 0 Then
MessageBox.Show(“有効なファイルがありません。”, “警告”, MessageBoxButtons.OK, MessageBoxIcon.Warning)
Return
End If
‘ 非同期処理で重い処理を実行し、UIスレッドをブロックしない
Task.Run(Sub() ProcessFiles(files))
End Sub
—
3. 現場で生き残るための「3つの鉄則」
① UIをブロックしない(Task.Runの活用)
数千件のCSVを読み込む際、UIスレッドで回すと操作不能になる。`Task.Run`で別スレッドに逃がし、処理完了後に`Invoke`でUIを更新せよ。これが「使えるツール」と「ゴミツール」の境界線だ。
② ホワイトリスト形式のバリデーション
「許可しない拡張子」を弾くのではなく、「許可する拡張子」のみを通すホワイトリスト方式を採用せよ。未知のファイル(.exeや.batなど)による誤操作を確実に防げる。
③ データベース連携時のトランザクション
もしドロップしたファイルをDBにインポートする場合、必ず「トランザクション」を使用すること。10ファイル中5ファイル目でエラーが出た際、中途半端なデータが残るのが最も悲惨だ。`Try…Catch…Finally`で確実にクリーンアップを行う癖をつけろ。
—
総括
コードを書くことは、単に機能を実装することではない。「ユーザーが起こしうるすべてのミスを先回りして無効化すること」だ。
今回紹介した実装はシンプルだが、この堅牢な境界線が、後のメンテナンスコストを劇的に下げる。まずは自身のコードにこのバリデーション層を組み込んでみてほしい。それが、レガシーなVB開発者から、真の自動化エンジニアへとステップアップする第一歩となるはずだ。
