【実務・中級編】ErrorProviderコンポーネントによるスマートな入力検証:リアルタイムエラー表示とフォーム送信前の一括チェック実装 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

ErrorProviderコンポーネントによるスマートな入力検証:リアルタイムエラー表示とフォーム送信前の一括チェック実装

業務アプリケーションの開発現場において、ユーザーの入力ミスとの戦いは永遠のテーマだ。
「とりあえずデータベースに突っ込んでから例外キャッチでいいや」などと考えていないだろうか。それではプロフェッショナルとは言えない。ユーザーがボタンを押した瞬間、あるいはフォーカスが外れた瞬間に、「何がどう間違っているのか」を視覚的かつ直感的に伝えることこそが、優れたUI/UXであり、堅牢な業務システムの必須条件だ。

Windows Forms(VB.NET)において、この要件を最もエレガントに実現するのが `ErrorProvider` コンポーネントである。
今回は、巷によくある「表面的な使い方」の解説にとどまらない。オブジェクトのライフサイクル、イベント駆動の罠、そして保守性を極限まで高めた一括バリデーションのアーキテクチャを、実務の最前線に立つ私の視点から授けよう。

—

1. なぜ「力技のバリデーション」は破綻するのか?

多くの初学者や、場当たり的なコーディングを行うプログラマは、保存ボタン(`Button1_Click`)の中で次のようなコードを書きがちだ。

.NET
‘ 【アンチパターン】これでは保守性もUXも最悪だ
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If TextBox1.Text = “” Then
MessageBox.Show(“氏名を入力してください。”)
TextBox1.Focus()
Return
End If
If Not IsNumeric(TextBox2.Text) Then
MessageBox.Show(“年齢は数値で入力してください。”)
TextBox2.Focus()
Return
End If
‘ …以下延々とIF文のネストとメッセージボックスの嵐…
End Sub

このアプローチの何が問題か?
1. UXの欠如: ユーザーはエラーが出るたびにモーダルダイアログ(メッセージボックス)の「OK」を押さされる羽目になる。非常にストレスフルだ。
2. コードの肥大化: 項目が増えるたびに `If` 文がスパゲッティ状に絡み合い、テストが困難になる。
3. 状態管理の欠如: 「どの項目がエラーで、現在どこが修正されるべきか」というフォーム全体のバリデーション状態が可視化されない。

業務アプリケーションにおいて目指すべきは、「リアルタイムの警告」と「送信前の一括ブロック」の融合である。それをスマートに実現するのが `ErrorProvider` だ。

—

2. 堅牢なバリデーション設計の全体像

今回構築するアーキテクチャの要件は以下の通りだ。

  • リアルタイム検証: ユーザーがテキストボックスからフォーカスを失った瞬間(`Validating` イベント)に検証を行い、瞬時にアイコンでエラーを表示する。
  • 一括チェック: 保存ボタン押下時、フォーム上のすべての入力項目が妥当か再確認し、1つでもエラーがあれば処理を中断する。
  • ロジックの分離: バリデーションルールを散在させず、コントロールのタグや専用の関数でスマートに管理する。

—

3. 実装:プロダクションコード

さあ、実際にコピペして現場で即座に動かせる完全なコードを見ていこう。
Visual Studioのデザイナで、フォーム上に `TextBox1`(氏名)、`TextBox2`(年齢:数値のみ)、`ComboBox1`(区分)、そして `Button1`(保存ボタン)を配置し、コンポーネントトレイから `ErrorProvider1` をドラッグ&ドロップしておいてほしい。

.NET
Public Class Form_UserEdit

‘ フォームロード時の初期化
Private Sub Form_UserEdit_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ ErrorProviderのアイコン点滅間隔を設定(視認性向上)
ErrorProvider1.BlinkRate = 300
ErrorProvider1.BlinkStyle = ErrorBlinkStyle.AlwaysBlink

‘ コンボボックスの初期データ設定
ComboBox1.Items.AddRange(New String() {“選択してください”, “管理者”, “一般”, “ゲスト”})
ComboBox1.SelectedIndex = 0
End Sub

‘ ==========================================================
‘ 1. 各コントロールの Validating イベント(リアルタイム検証)
‘ ==========================================================

‘ 氏名:必須入力、最大20文字
Private Sub TextBox1_Validating(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles TextBox1.Validating
If String.IsNullOrWhiteSpace(TextBox1.Text) Then
ErrorProvider1.SetError(TextBox1, “氏名は必須入力です。”)
‘ ※注意: هناでは e.Cancel = True はあえて使わず、リアルタイムでは警告表示に留めることで
‘ ユーザーの自由な画面遷移を阻害しない配慮とする(保存時に一括ブロックする)
ElseIf TextBox1.Text.Length > 20 Then
ErrorProvider1.SetError(TextBox1, “氏名は20文字以内で入力してください。”)
Else
‘ エラーなしの場合はクリア
ErrorProvider1.SetError(TextBox1, String.Empty)
End If
End Sub

‘ 年齢:数値必須、1〜120の範囲
Private Sub TextBox2_Validating(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles TextBox2.Validating
Dim age As Integer
If String.IsNullOrWhiteSpace(TextBox2.Text) Then
ErrorProvider1.SetError(TextBox2, “年齢は必須入力です。”)
ElseIf Not Integer.TryParse(TextBox2.Text, age) Then
ErrorProvider1.SetError(TextBox2, “年齢は半角数値で入力してください。”)
ElseIf age < 1 OrElse age > 120 Then
ErrorProvider1.SetError(TextBox2, “年齢は1から120の範囲で入力してください。”)
Else
ErrorProvider1.SetError(TextBox2, String.Empty)
End If
End Sub

‘ コンボボックス:未選択チェック
Private Sub ComboBox1_Validating(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles ComboBox1.Validating
If ComboBox1.SelectedIndex <= 0 Then ErrorProvider1.SetError(ComboBox1, "有効な区分を選択してください。") Else ErrorProvider1.SetError(ComboBox1, String.Empty) End If End Sub ' ========================================================== ' 2. 保存ボタン押下時の一括バリデーションと処理実行 ' ========================================================== Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click ' 【重要】保存ボタン押下時、明示的に各コントロールの検証を走らせる ' これにより、一度もフォーカスが当たらなかった未入力項目も検証対象となる Me.ValidateChildren() ' ErrorProviderにエラーメッセージが残っていないか総チェック If HasValidationError() Then MessageBox.Show("入力内容に誤りがあります。赤色のアイコンを確認してください。", _ "入力エラー", MessageBoxButtons.OK, MessageBoxIcon.Warning) Return End If ' --- すべてのバリデーションを通過した場合の処理 --- ExecuteSave() End Sub '''

”’ フォーム上にErrorProviderによるエラーが残っているか判定する
”’

Private Function HasValidationError() As Boolean
‘ 各コントロールのエラーメッセージを走査
If Not String.IsNullOrEmpty(ErrorProvider1.GetError(TextBox1)) Then Return True
If Not String.IsNullOrEmpty(ErrorProvider1.GetError(TextBox2)) Then Return True
If Not String.IsNullOrEmpty(ErrorProvider1.GetError(ComboBox1)) Then Return True

Return False
End Function

”’

”’ データベースへの保存処理(モック)
”’

Private Sub ExecuteSave()
Try
‘ TODO: ここにADO.NET等を用いたDB登録・更新処理を記述
‘ 例: Using conn As New SqlConnection(“…”) …

MessageBox.Show(“データを正常に保存しました。”, “完了”, MessageBoxButtons.OK, MessageBoxIcon.Information)

‘ 処理成功後の画面クリアや閉じる処理
Me.DialogResult = DialogResult.OK
Me.Close()

Catch ex As Exception
MessageBox.Show($”保存処理中に予期せぬエラーが発生しました。{Environment.NewLine}{ex.Message}”, _
“システムエラー”, MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub

End Class

—

4. チーフアーキテクトが教える実装の急所と注意点

上記のコードは一見シンプルだが、実務で数万人規模が使うシステムを想定した場合、いくつかの重要なアーキテクチャ上の知見が隠されている。

① `Me.ValidateChildren()` の魔術と必然

保存ボタンが押された瞬間、`Me.ValidateChildren()` を呼び出している点に注目してほしい。
ユーザーがフォームを開いた直後に「保存」ボタンを連打した場合、各テキストボックスの `Validating` イベントは一度も発火していない。つまり、エラーチェックをすり抜けてしまう致命的なバグが生まれる。
`ValidateChildren()` を実行することで、フォーム内のすべてのコントロールに対して強制的に `Validating` イベントを走らせ、エラーをあぶり出すことができる。これが「一括チェック」の正体だ。

② なぜリアルタイム検証で `e.Cancel = True` を使わないのか?

VB.NETの `Validating` イベントの引数である `CancelEventArgs.Cancel = True` にすると、ユーザーがエラーを修正するまでそのコントロールからフォーカスを移動できなくする強制ロックをかけられる。
一見厳格で良さそうに思えるが、業務アプリにおいてこれは最悪のUXをもたらすことがある。「とりあえず隣の項目を確認したい」「キャンセルして画面を閉じたい」というユーザーの自由を奪い、アプリケーションがフリーズしたかのような錯覚を与えてしまうからだ。
エラーの通知は `ErrorProvider` の視覚的アイコン(赤色の点滅やアイコン)に任せ、フォーカス移動自体は妨げない設計にするのが、モダンな業務アプリの鉄則である。

③ 拡張性への配慮

今回はシンプルに `HasValidationError` 関数で個別のコントロールを判定したが、コントロール数が数十個に及ぶ巨大なマスターメンテナンス画面であれば、以下のようにフォーム上のコントロールを再帰的に走査して `ErrorProvider` の状態をチェックする汎用メソッドにするのが美しい。

.NET
Private Function HasValidationErrorRecursive(parent As Control) As Boolean
For Each ctrl As Control In parent.Controls
If Not String.IsNullOrEmpty(ErrorProvider1.GetError(ctrl)) Then
Return True
End If
If ctrl.HasChildren Then
If HasValidationErrorRecursive(ctrl) Then Return True
End If
Next
Return False
End Function

このアプローチをとれば、タブコントロールやパネルの内部にどれだけコントロールがネストしていろうとも、一網打尽にエラーを検知できる。

—

5. おわりに

UIの作り込みは、プログラマの「優しさ」と「エンジニアリングの美学」が最も色濃く出る領域だ。
場当たり的な `If` と `MessageBox.Show` の連打でコードを汚染するのは今日で終わりにしよう。

`ErrorProvider` を正しく手なずけ、イベント駆動のライフサイクルを理解したあなたなら、ユーザーから「このシステムは入力していてストレスがない」「直感的で使いやすい」と称賛される業務アプリケーションを構築できるはずだ。
プロフェッショナルとしてのプライドを持ったコードを、次の現場からさっそく実装して見せてほしい。

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