前回の記事、WPFでもValidationAttributeのメッセージリソースの既定値を設定したいの最後の方で「ReactivePropertyにも対応したい」ということでReactiveProperty用の拡張メソッドも作ってみました。

でも、ひとつひとつに設定するのも面倒ですよねぇ。

ということで、ReactiveProperty用にクラスライブラリプロジェクト(DataValidation.ReactiveProperty)を追加して、まとめて処理するヘルパメソッドを追加してみました。

ソースは前回のものをそのまま修正してGitHubのこちらに置いてあります。


2019/09/28
.NET Core 3.0でWPFしてみました。Nekoni.DataValidation
という記事で.NET Core 3.0のサンプルプロジェクトを追加しています。併せてご覧ください。


まとめて設定したいのです。

  SyainNo = new ReactiveProperty<string>().SetNekoniDataValidationAttribute(() => SyainNo);
  MailAddress = new ReactiveProperty<string>().SetNekoniDataValidationAttribute(() => MailAddress);
  MailAddressConfirm = new ReactiveProperty<string>().SetNekoniDataValidationAttribute(() => MailAddressConfirm);
  KoyouKbn = new ReactiveProperty<string>().SetNekoniDataValidationAttribute(() => KoyouKbn);

いきなりコードでごめんなさい。

前回のWPFプロジェクトでは本家のSetValidationAttributeメソッドに倣って、ViewModelのコンストラクタで上記のように記述していました。

でも、プロパティの数が多くなるとちょっと面倒ですよねぇ。

ForValidationというコンテキストクラスで検証属性が付いたプロパティのリストは保持しているわけで、まとめて設定することは可能だろう! というわけでやってみました。

いやぁ、正直、思ったより面倒でした。

サンプルのプロジェクトではReactiveProperty<string>というように、型パラメータがstringのReactivePropertyしか使っていないのですが、SetNekoniDataValidationAttributeメソッドは一応、ジェネリックメソッドなので、結局今回もリフレクションばりばりです。

詳細はForValidationExtensions.csの SetupReactivePropertiesメソッドのソースをご覧ください。あまり綺麗なソースではありませんけども...。

使い方の方は、SampleWpfApp1プロジェクトのReactivePropertyViewModelBase.csのコンストラクタのように、

  this.ForValidation().SetupReactiveProperties();

とするだけで、検証属性が付いたReactiveProperty全てに対しSetNekoniDataValidationAttributeメソッドを呼び出すようにしています。

実際のReactiveProeprtyViewModelBase.csのソースの方は、次の全エラーをIObservableで返すメソッドコールがくっついています。

全エラーを返すIObservable

まとめて設定したのと同様、全エラーを返すIObservableを得ようとしたら、

  AllErrors = Observable.CombineLatest(
      SyainNo.ObserveErrorChanged.Select(e => e?.OfType<ValidationResult>()),
      MailAddress.ObserveErrorChanged.Select(e => e?.OfType<ValidationResult>()),
      MailAddressConfirm.ObserveErrorChanged.Select(e => e?.OfType<ValidationResult>()),
      KoyouKbn.ObserveErrorChanged.Select(e => e?.OfType<ValidationResult>()),
      (syainNo, mailAddress, mailConfirm, KoyouKbn) =>
      {
          var list = new List<ValidationResult>();
          if (syainNo != null) list.AddRange(syainNo);
          if (mailAddress != null) list.AddRange(mailAddress);
          if (mailConfirm != null) list.AddRange(mailConfirm);
          if (KoyouKbn != null) list.AddRange(KoyouKbn);
          return list;
      }).ToReadOnlyReactiveProperty();

こんな風になっていたわけです。前回は。

Observable.CombineLatestのオーバーロードを見ると、なんと、16個まで行けるんですよ!
それ以上になったらどうすんの!と、私が心配する必要もなく、配列もしくはIEnumerable<IObservable>を受けるオーバーロードもありました。 そりゃそうよね

というわけで、GetAllErrorsObservable というヘルパも追加しています。

SetupReactivePropertiesメソッドはForValidationを返すようにしてあるので、

  // 全エラーリスト
  AllErrors = this.ForValidation().SetupReactiveProperties()
      .GetAllErrorsObservable()
      .ToReadOnlyReactivePropertySlim().AddTo(Disposer);

という感じにメソッドチェーンで書けるようにしました。

そして、これらヘルパのおかげで、UserControl2Vm.csを見てください。
見事に、プロパティ定義だけに!

    public class UserContorol2Vm: ReactivePropertyViewModelBase
    {
        /// <summary>
        /// 社員番号
        /// </summary>
        [Display(Name = "社員番号")]
        [Required]
        [StringLength(10)]
        public ReactiveProperty<string> SyainNo { get; private set; } = new ReactiveProperty<string>();

        /// <summary>
        /// 年齢
        /// </summary>
        [Display(Name = "年齢")]
        [Range(16, 80)]
        public ReactiveProperty<string> Age { get; private set; } = new ReactiveProperty<string>();

        /// <summary>
        /// 入社年月日
        /// </summary>
        [Display(Name = "入社年月日")]
        [Required]
        [CheckDate]
        public ReactiveProperty<string> HireDate { get; private set; } = new ReactiveProperty<string>();

        /// <summary>
        /// メールアドレス
        /// </summary>
        [Display(Name = "メールアドレス")]
        [Required]
        [EmailAddress]
        public ReactiveProperty<string> MailAddress { get; private set; } = new ReactiveProperty<string>();

        /// <summary>
        /// 雇用区分
        /// </summary>
        [Display(Name = "雇用区分")]
        [EnumDataType(typeof(KoyouKbn))]
        public ReactiveProperty<string> KoyouKbn { get; private set; } = new ReactiveProperty<string>();

    }

すっきり~~。

実際にReactivePropertyを本格的に使うと、ReactiveCommandの定義やら、他に依存するReadOnly系のReactiveProperty定義やらで、コンストラクタは長くなりがちになると思います。

検証系だけでも書かなくて済むなら!というほどでも無いんですけど、まぁ、少しは役に立つかしらん...。


まとめ

ValidationAttributeに設定するリソースの既定値を指定できるようにしたい!
という目標は前回の記事で達成できてたと思うのですが、ReactivePropertyを使う際にも楽したい!という今回のテーマ、なんとか達成できたかなと思います。

今回のDataValidation.ReactivePropertyの追加に伴い、他にもちょこちょこいじってある箇所があります。

  • ForValidationを作成する拡張メソッドは、INotifyPropertyChangedを対象にしました。(前回まではobject)
  • 既定のリソースTypeを取得するメソッドもValidationAttributeを引数に取るFuncデリゲートに変更。(リソースキーと同様に)
  • DataValidationプロジェクトと今回追加したDataValidation.ReactivePropertyを.NET Standard 2.0に変更
  • テストプロジェクトは.NET Coreに変更。(VS2019は.NET Coreしかテンプレートが無い?)

と、本筋にはあまり関係ないところなので、もし前回の記事のソースを見ている方がいらっしゃったなら、拾い直してもらえると幸いです。

なんでNekoni~というふざけた名前にするんだよ!という方は適宜ソースを改変して使ってみていただけるとうれしいです。