実装
本稿で作成した検証エンジンの名前はValidatorです。これは私のオープンソースのWebサイトからダウンロードできます(Products >> Documentation >> C# と選択してください)。このエンジンは、前述のTagプロパティで指定した属性を使って、フォーム上のコントロール(TextBoxコントロールやDataGridViewコントロール)を検証します。エラーが発生すると、無効な入力ごとに個別にエラーメッセージを表示するだけでなく、指定された共通のフォーム提出ボタン(通常は[OK]ボタン)を操作不能にすることもできます。検証エラーの表示には、コントロールごとに異なる固有のLabelコントロールでも、標準のErrorProviderコンポーネントでも使用できます。どちらを使うかは、開発者の判断次第です。ErrorProviderには、Labelコンポーネントをわざわざ作成して検証コンポーネントに関連付ける必要がないという長所もありますが、既に説明したように、検証エラーを表示するにはマウスを使わなければならないという欠点もあります。
コントロールを検証するには、そのコントロールをValidatorインスタンスに追加し、コントロールのTextChangedイベントハンドラまたはValidatingイベントハンドラの中でValidate()メソッドを呼び出します。MSDNライブラリではValidatingイベントハンドラが推奨されていますが、Validatingはフォーカスがコントロールから離れたときしか起動しません。もっと繊細なコントロール(キー操作を要するものなど)が必要な場合は、TextChangedをお勧めします。
検証エンジンは、指定されたコントロールのすべての検証属性(ビジネスルール)を評価しますが、1つのコントロールにつき一度に1つのエラーしか報告しません。例えば、1つの入力が最大長テストと最大値テストの両方に不合格だったとしても、エンジンがユーザーに報告するのはそのうちの一方のエラーだけです。そのため、ユーザーがそのエラーを修正した後で、もう一方のエラー条件が表示される可能性があります。
サンプルのコーディング
では、実際のコーディングについて説明しましょうょう。これまで使ってきたシンプルなサンプルフォームのユーザーコードは次のとおりです(デザイナで生成したコードとは異なります)。個々の短いメソッドはイベントハンドラなので、適切なイベントに関連付ける必要があります(どのイベントに関連付けるかは、メソッドの名前から分かるはずです)。
using System; using System.ComponentModel; using System.Windows.Forms; using CleanCode.Forms; namespace ValidationDemo { public partial class Form1 : Form { private Validator validator; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { validator = new Validator(okButton, errorProvider); validator.Add(dataGridView); validator.Add(leftTextBox); validator.Add(rightTextBox); } private void okButton_Click(object sender, EventArgs e) { MessageBox.Show("OK clicked"); } private void cancelButton_Click(object sender, EventArgs e) { MessageBox.Show("cancel clicked"); } private void leftTextBox_Validating(object sender, CancelEventArgs e) { if (!validator.Validate(leftTextBox)) { e.Cancel = true; } } private void rightTextBox_TextChanged(object sender, EventArgs e) { validator.Validate(rightTextBox); } private void dataGridView_CellValidating( object sender, DataGridViewCellValidatingEventArgs e) { if (!validator.Validate((DataGridView)sender, e)) { // e.Cancel = true; // this really traps the user in there! } } } }
これが、図1のサンプルアプリケーションに関してVisual Studio 2005内で記述する検証コードのすべてです。実際の検証コードはValidatorに含まれています。この他に必要なのはビジネスルールだけです。次に、これについて簡単に説明します。
前述のコードで、Form1_Loadイベントハンドラは検証エンジンのインスタンスを作成し、検証する各コントロールをそのインスタンスに追加しています。Validatorコンストラクタがフォームの[OK]ボタンの参照を受け取ることに注意してください。Validatorはその参照を使って、すべてのコントロールが入力チェックに合格した場合は[OK]ボタンを選択可能にし、いずれかのコントロールが入力チェックに合格しなかった場合は[OK]ボタンを選択不能(グレーアウト)にします。また、Validatorコンストラクタは、エラー表示用のErrorProviderも受け取ります。
leftTextBoxコントロールとrightTextBoxコントロールで異なるイベントハンドラを使っているのは、単に前述の動作の違いを示すためです。前者はフォーカスがフィールドを離れたときに検証を行い、後者はキー操作のたびに検証を行います。また、leftTextBoxハンドラでは、エラーがある限りユーザーが別のコントロールに移動できないようにしています。実際には、すべてのコントロールを同じイベントで同じように動作させたい場合は、同じイベントハンドラのメソッドを使用してかまいません。
DataGridViewコントロールのような複数のデータ値を含むコントロールの場合でも、メソッドを呼び出して検証を実行する処理は、単一値のコントロールの場合とほぼ同じです。ただし、ビジネスルールセットに指定する情報の量は多くなります(詳しくは後述)。DataGridView検証の処理は、特にDataGridViewがバインドされている場合にはかなり複雑になる可能性がありますが、本稿では説明を省きます。
サンプルアプリケーションのビジネスルール
前述のコードに加えて、leftTextBoxコントロール、rightTextBoxコントロール、dataGridViewコントロールをビジネスルールに関連付ける必要があります。具体的には、これらのコントロールのTagプロパティを表3のように設定します。
| コントロール | Tagプロパティ |
| leftTextBox | pattern=^\w[\w\.]*\@\w+\.\w[\w\.]*$;minLen=2 |
| rightTextBox | pattern=^-?(?:\d*\.?\d+|\d+\.)$ |
| dataGridView | [Column]pattern=^\w+$;[MatchExpr]pattern=\(.*\) |
これらのルールでは、正規表現を用いて、各コントロールに入力すべき値を記述しています。leftTextBoxには、電子メールアドレス形式の値を入力しなければなりません(「foo@bar.com」も「e@a.b.c.d」も有効です)。rightTextBoxには、5、-25、-3.9999、.81などの数値を入力しなければなりません。dataGridViewでは、列ごとにビジネスルールを割り当てることも可能ですが、必須ではありません。この場合、このルールは、「Column」という名前の列では文字または数字のみを受け付け、「MatchExpr」という列では入力のどこかに1組の始めかっこと終わりかっこが必要であることを表しています。
サンプルを実行する場合の注意事項
アプリケーションをビルドして実行し、どうなるか見てみましょう。入力チェックに不合格であるとボタンは選択不能になります。この場合、ValidatorはErrorProviderを使って検証エラーを表示します。今回のコードではErrorProviderの作成を分離化しているので、検証エンジンに実装されているビジネスルールカテゴリの範囲に含まれない独自の検証チェックをイベントハンドラに自由に追加することができます。
ここでは、ビジネスルールとコードをどのように切り離すか、なぜ切り離すのかを理解していただけるように、検証エンジンを利用しました。検証エンジンそのもののメカニズムについては、ソースコードを参照してください。これは、以前にWebサイト用にPerlとJavaScriptを使って実装した「PageValidator」という検証エンジンの簡易バージョンです。同じエンジンをクライアントとサーバの両方で実行することで、クライアントとサーバの両方で高速応答とセキュリティを実現できます。
