はじめに
入力チェックはどのプログラムにも必要なものであり、どのプログラマにとっても悩みの種です。大抵の場合は、「電話番号が7桁または10桁である」「IPアドレスが4つのオクテット型から成り立つ」「国名が193ある選択肢の1つと一致している」といったことを確認するプログラムロジックをハードコーディングすることになります。.NET 2.0では、入力チェックを合理化するためのサポートがいくつか取り入れられましたが、これは入力チェックプロセスの自動化や単純化を少しばかり手助けするものにすぎません。入力チェックに関してMSDNを検索しても、見つかる情報の大半は、検証コードをハードコーディングする方法を紹介しています。しかし、検証を必要とする個々の要素に手書きのカスタムコードを用意することは、保守という観点から見れば悪夢としか言いようがありません。
また、検証のステップをプログラミングのステップと同様に「AならばBする」といった形で概念化することは不自然です。それよりも、検証するデータの「質」に焦点を当てた方がずっと分かりやすいはずです。例えば、入力データを説明するときには、「5~8文字にする」「負でない数字にする」「必ず最後は『5』で終わる」などというのが普通でしょう。
本稿で紹介する検証エンジンは、検証プロセスよりもデータの質に焦点を当てるという考え方から生まれています。モジュール形式でデータ主導の検証エンジンアプローチを採用することで、より自然なインターフェイスが実現され、柔軟性が向上し、保守も簡単になりました。Visual Studioでは、デザイン時にさまざまなコンポーネントにプロパティ値を指定することができます。このアプローチを検証属性の定義に使用することは自然の流れであり、これによって、開発者は堅牢な検証メカニズムをもっと簡単に導入できるようになります。また、このアプローチでは検証の設定を構成ファイルに格納するという柔軟な仕組みを採用しているため、検証の変更も簡単になり、開発者や管理者はコンパイルやデプロイをやり直したり、ソースコードを書いたりせずに、検証プロパティの修正やカスタマイズを行うことができます。本稿で紹介する検証エンジンを使えば、わずか数行のコードで幅広い検証基準を活用することができます。
目標とするソリューション
フォームの入力チェックを実行し、ユーザーにフィードバックし、できればその条件を満たすために必要なその他のアクションを実行できるプログラムを実装します。さらに、できるだけ単純かつ簡単な実装にすることを目指します。
本稿のソリューションの制限
本稿で紹介するソリューションは、有益ではありますが、どのケースにも適した完全なソリューションではありません。むしろ、80対20の法則を採用しています。つまり、この検証エンジンは、対処する必要があるユースケースの約80%に対して役立ちます。さらに、これまた80対20の法則に従い、残りの20%のユースケースをカバーするには労力の80%が必要になると考えられます(つまり、これまで費やしてきた労力の5倍が必要になります)。
そのため、今回の検証エンジンでは、例えば複数のフィールド間の関係に基づく検証は実装していません。その代わりに、単一フィールドの検証に焦点を当て、基本的な仕組みを見ていくことにします。
基本となるソリューション:ErrorProviderコンポーネント
まず、ErrorProviderという便利なMicrosoftコンポーネントを知る必要があります。
- 『ErrorProviderコンポーネントの概要(Windowsフォーム)』(MSDNライブラリ)
ErrorProviderをビジュアルデザイナにドラッグすると(またはコード内でそのインスタンスを作成すると)、検証メッセージを表示するために必要な基本コンポーネントが作成されます。ErrorProviderの代表的な使用例を次に示します(MSDNライブラリの『方法 : WindowsフォームErrorProviderコンポーネントを使用してフォーム妥当性検査でエラーアイコンを表示する』より引用)。
protected void textBox1_Validating(object sender, CancelEventArgs e) { try { int x = Int32.Parse(textBox1.Text); errorProvider1.SetError(textBox1, ""); } catch (Exception e) { errorProvider1.SetError(textBox1, "Not an integer value."); } }
このコードはTextBoxへの入力を検証し、入力値が整数かどうかを確認します。入力値をテストするため、このコードではInt32.Parse
メソッドを使って、TextBoxの文字列値を整数に変換できない場合に例外をスローします。入力が有効な場合は、ErrorProvider.SetError
呼び出しにより、エラーメッセージの消去が実行されます。入力が無効な場合はcatch
ブロックが実行され、「Not an integer value」というメッセージを表示するErrorProvider.SetError
メソッドが呼び出されます。
このメソッドを、ビジュアルデザイナを使うか、次のコードをプログラミングすることで、textBox1という特定のTextBoxコントロールのValidating
イベントに関連付けます。
this.textBox1.Validating += new CancelEventHandler(this.textBox1_Validating);
ここに、MSDNライブラリの情報からはまったくわからない重要な事実があります。
Validating
イベントは、コントロールが別のコントロールにフォーカスを譲ったときに発生します。つまり、イベントが発生するのは、[Tab]キーを使って現在のコントロールから移動したり、マウスで別のコントロールをクリックしたりしたときです。
この例のSetError
メソッドが、1つ目の引数でコントロールを受け取っていることに注意してください。これにより、1つのErrorProviderで同時に複数のコントロールのエラーメッセージを処理することが可能になります。
SetError
メソッドの2つ目の引数は表示するメッセージです。空の文字列を渡すと、ErrorProviderはエラーメッセージインジケータを完全に抑止します。従って、エラーメッセージを設定することだけでなく、エラー条件が存在しない場合にエラーメッセージを削除することも重要です。
ErrorProviderには、他のエラー表示テクニックにはない次のような長所があります。
- ポップアップダイアログを使った場合は、ユーザーが[OK]をクリックするとメッセージが消えてしまうので、メッセージを再表示するためには特定のコーディングが必要です。ErrorProviderはユーザーが要求すればいつでも自動的にエラーを再表示します。
- 1つのTextBoxまたはLabelコントロールを使ってエラーメッセージを表示する場合は、ポップアップダイアログよりもエラーメッセージを長い期間表示できますが、1つのエラーメッセージしか表示できないという問題があります。複数のエラーを持つフォームでは、新たなエラーが発生するたびに直前のエラーを置き換えることになります。ErrorProviderは、フォーム上のコントロールと同じ数のエラーメッセージを同時に表示することができます。
- 検証対象のコントロールごとに専用のTextBoxまたはLabelコントロールを追加することも有効なソリューションです(エラーメッセージを表示しなければならない場合のみ、対応するコントロールを表示します)。この場合、ユーザーはマウスを使ってエラーを表示する必要がなくなるので、ErrorProviderよりもやや好ましい面はありますが、これらのTextBoxとLabelを配置するためのフォーム領域とリソースが必要になりますし、かなりの量のUIコードを作成して保守しなければなりません。