雑記 - otherwise

最近はDQ10しかやっていないダメ技術者がちまちまと綴る雑記帳

TextBox の入力チェックをバインド先のデータクラスプロパティの属性として書ける様になったらしい

ここのところ、 WPF , M-V-VM , Silverlight 辺りのキーワードについて色々見ているのですが、わんくま Blog 界隈でこんな話が挙がっていたのでちょっと触ってみました。
……これ、すごいですね。
以前から、入力値検証をいちいち Setter にゴリゴリ書かなきゃいけない事に煩わしさを感じていたのですが、この機能を使えばかなり労力を削減出来そうです。
って事で、恒例のサンプルプログラム〜。

SampleViewModel.cs

まずは、入力項目とバインドする ViewModel を用意します。

public class SampleViewModel : INotifyPropertyChanged {
  private SampleModel _model;

  public event PropertyChangedEventHandler PropertyChanged;

  private string _value;

  [Display(Name="なんかのテキスト", Description="必須かつ小数")]
  [Required(ErrorMessage="入力必須です")]
  [CustomValidation(typeof(ValueValidation), "ValidateValueType", ErrorMessage="小数しか入力できません")]
  public string Value {
    get { return this._value; }
    set {
      if (String.Equals(this._value, value)) {
        return;
      }
      Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Value" });
      this._value = value;
      this._model.Value = Double.Parse(value);
      this.OnPropertyChanged("Value");
    }
  }

  public SampleViewModel() {
    this._model = new SampleModel();
  }

  private void OnPropertyChanged(string name) {
    if (this.PropertyChanged != null) {
      this.PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
  }
}

バインド対象のプロパティに、 Required やら CustomValidation やらの属性を付けて、入力値の検証条件を記述します。(属性名で直感的に何の条件なのかは判るかと思うので詳細説明は割愛 :p )
そして、セッター内で内部変数にコピーする手前で検証メソッド (Validator.ValidateProperty()) を呼び出します。
このメソッド内で、ごにょごにょ検証をしている模様。
検証 NG の場合はたぶん冷害を発生させてセッターをこけさせているんだと思います。個人的にこの動きはあまり好きじゃないんだけど。。。

SampleModel.cs

実際のデータ(例えば DB 格納値とか)を保持する Model を用意します。
一応、例としてこちらにも検証条件が書ける事を確認するために、追加の検証条件を書いてみました。(条件の切り分けがこれで正しいかは今回のテストの対象ではありませんので悪しからず ^^; )

public class SampleModel : INotifyPropertyChanged {
  private double _value;

  [CustomValidation(typeof(ValueValidation), "ValidateValueRange", ErrorMessage = "0より大きくなければいけません")]
  public double Value {
    get { return this._value; }
    set {
      if (this._value ==  value) {
        return;
      }
      Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Value" });
      this._value = value;
      this.OnPropertyChanged("Value");
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  private void OnPropertyChanged(string name) {
    if (this.PropertyChanged != null) {
      this.PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
  }
}

書き方は ViewModel と一緒です。

ValueValidation.cs

次に、カスタム検証用のクラスを用意します。
これは static クラスである必要がある様です。( static にしなかったら実行時に例外が発生しました。握りつぶされたけど :p )

public static class ValueValidation {
  public static bool ValidateValueType(string value) {
    double doubleValue;
    return Double.TryParse(value, out doubleValue);
  }

  public static bool ValidateValueRange(double value) {
    return (0 < value);
  }
}

ちなみに、 .NET 4.0 ではカスタム検証の結果を bool で返す必要があるみたいです。
MSDN を見る限り、 Silverlight 3 だと ValidationResult 型っぽいんですけどねぇ。(最初、 Silverlight 3 の方を見ていたので、ちょっとハマりかけた)

Sample.xaml

対象の入力ボックスに "ValidatesOnExceptions=True" と "NotifyOnValidationError=True" を追加します。

<TextBox Name="textBox1" Text="{Binding Value, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />

あとは、 Window / Page の DataContext に SampleViewModel のインスタンスを付けてあげれば完了。
入力ボックスに "A" だとか "-1" だとかを入れると、入力ボックスの枠が赤くなります♪
# (追記)細かい誤記を修正><