GUIの宣言型バリデーション

以前にも言及しましたが拙作のフレームワークにおいてアプリケーションによる単純な入力項目のバリデーションは以下のようなスキームのXMLで制約を記述してそれを項目に結びつけることにより行っています。


  
    
      
    
    
  

まあこれ自体はバリデーションを書く場合に良く使われる方法ではあります。語彙もどこかで見たようなものですので特に細かく説明はしません。ミソはこの語彙をJavaで書くWebアプリケーションと.NETで書くリッチクライアントアプリケーションの両方で運用したいという部分です。
Java側は既に実装を完了していますが、これはブラウザを使用したwebアプリケーションがメインなのでバリデーションはサーバサイドとクライアントサイド(ブラウザ)の両方で同様のバリデーションの実施が必要です。
サーバサイドはこの制約の語彙をそのままバリデータ用の制約オブジェクトに変換してHTMLからPOSTされた内容(大抵はその代理のバウンダリオブジェクト)をバリデーションすればOKです。
クライアント側はブラウザ、それもある程度メジャーなブラウザ全てでホスティングできる実行系が必要になるのでJavascriptを採用するのですが上記の制約を見ながらプログラマがスクリプトを書くのは非常に面倒ですのでサーバサイドで行っていると同様の制約の解釈後に今度はJavascriptに静的又は動的(オンザフライ)に変換する訳です。(自動的に変換されたJavascriptに関しては冗長になりますので省略します)


.NET側でのバリデーションですが元々.NETにはバリデータコンポーネントが用意されています。これを使えば簡単なのですが既に書いたように独自の語彙で制約を書いているので.NETのバリデータは使用しないこととします。そこで独自のバリデータを書かなくてはなりませんが.NET(じゃなくてもいいけれど)普通のGUIのアプリケーションはWebアプリケーションとは違い項目名と値がペアになったデータがまとめてポストされてくるような仕組みは存在しません。従ってGUIコンポーネント(ここではコントロールに限定する)とバリデーションの制約とを結びつける作業が必ず必要となります。この結びつける作業はバリデーションの制約とGUIアプリケーションのフォーム上に配置されているコントロールを全てパースすることで自動化が可能ですが効率が良くない処理ですし、バリデーション制約に付いている名前がコントロールのNameプロパティと一致していることを前提としてしまうと柔軟性が落ちるので普通は手動で行うべきです。

バリデータをコントロールに結びつける

 Form1 form1 = DIContainer.GetComponent(typeof(Form1));
 IGUIValidator validator = DIContainer.GetComponent(typeof(IGUIValidator));
 validator.BindControl("inputItem1", form1.textbox1); // 制約アイテム名, コントロール名

と普通はこのように手動でバリデーションの制約とコントロールとの結びつけを行いますがありがちな進み方として

いちいちコードで書くのは面倒だよね ==> んじゃ今度は制約とGUIコントロールを結びつける為のマッピングをXMLで提供しましょう

となりますがこれはよくある"XMLマッピング無限地獄"に堕ちる前ぶれですので回避したいところです。


何度か採りあげていますが.NETの素晴らしい特徴の一つに「カスタム属性」というものがあります。これを用いてバリデーション制約とGUIコントロールを結びつける方法はどうでしょう。ということで実際にバリデーション制約とコントロールを結びつけるカスタム属性を考えてみました。

Form1.csの例

namespace Foo
{
    public class Form1 : System.Windows.Forms.Form
    {
        public Label lbl1;
        [ValidatorBind(ConstraintName= "inputItem1", IndicatorControlNames = "lbl1, lbl2, lbl3")]
        public TextBox textbox1;
        
        public Label lbl2;
        [ValidatorBind(ConstraintName= "inputItem2", IndicatorControlNames = "lbl1, lbl2, lbl3")]
        public TextBox textbox2;
        
        public Label lbl3;
        [ValidatorBind(ConstraintName= "inputItem3", IndicatorControlNames = "lbl1, lbl2, lbl3")]
        public TextBox textbox3;

        public Form1()
        {
            InitializeComponent();
        }
〜略

意味的にはValidatorBindカスタム属性をコントロールを定義してあるフィールド行に指定することによりバリデーション制約とコントロールを明示的に結びつけることとします。

[ValidatorBind(ConstraintName= "inputItem1", IndicatorControlNames = "lbl1, lbl2, lbl3")]
public TextBox textbox1;

用意したValidatorBind(Attribute)の"ConstraintName"プロパティはバリデーション制約の名前を指定します。この例で言うと制約"inputItem1"はGUIコントロール"textbox1"に入力した値のバリデーションの対象というわけです。
もう一つの属性"IndicatorControlNames"ですがこれはバリデーションの処理に際に行なわれるエラー処理において関連するコントロールの見栄え(色、フォント)を変える際のグルーピングを行う属性です。この場合はどのコントロールのバリデーションでエラーが発生してもlbl1, lbl2, lbl3の3つのラベルコントロールが見栄えの変更の影響を受ける、という振る舞いにしたいなと考えてます。

とここまで書いてふと気がついたことが....

今までの話はWindowsForms1.1を使用していることを前提にしていましたがWindowsForms2.0におけるFormクラスのファイルはパーシャルクラスの導入と共にクラス名.csとクラス名.Designer.csに分かれておりコントロールのフィールドの定義は〜.Designer.csのほうに定義されるのですね。〜.Designer.csってわざわざ分けたのはVisualstudioの管理下のファイルであり人間の手では触られたくないってことだよね。....うーん...どうしよ..