2016-01-07 10 views
10

私は、次のリンクで説明したとおりにINotifyDataErrorInfoを実装している:フォースINotifyDataErrorInfo検証

http://blog.micic.ch/net/easy-mvvm-example-with-inotifypropertychanged-and-inotifydataerrorinfo

私は、モデル内の文字列プロパティにバインドされているTextBoxを持っています。

XAML

<TextBox Text="{Binding FullName, 
         ValidatesOnNotifyDataErrors=True, 
         NotifyOnValidationError=True, 
         UpdateSourceTrigger=PropertyChanged}" /> 

モデル

private string _fullName; 
public string FullName 
{ 
    get { return _fullName; } 
    set 
    { 
     // Set raises OnPropertyChanged 
     Set(ref _fullName, value); 

     if (string.IsNullOrWhiteSpace(_fullName)) 
      AddError(nameof(FullName), "Name required"); 
     else 
      RemoveError(nameof(FullName));     
    } 
} 

INotifyDataErrorコード

private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>(); 

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

// get errors by property 
public IEnumerable GetErrors(string propertyName) 
{ 
    if (_errors.ContainsKey(propertyName)) 
     return _errors[propertyName]; 
    return null; 
} 

public bool HasErrors => _errors.Count > 0; 

// object is valid 
public bool IsValid => !HasErrors; 

public void AddError(string propertyName, string error) 
{ 
    // Add error to list 
    _errors[propertyName] = new List<string>() { error }; 
    NotifyErrorsChanged(propertyName); 
} 

public void RemoveError(string propertyName) 
{ 
    // remove error 
    if (_errors.ContainsKey(propertyName)) 
     _errors.Remove(propertyName); 
    NotifyErrorsChanged(propertyName); 
} 

public void NotifyErrorsChanged(string propertyName) 
{ 
    // Notify 
    if (ErrorsChanged != null) 
     ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
} 

は今、このすべては、Fiを提供して動作しますしかし、それは私のテキストボックスに私が何かを入力するとただちに有効になります。ボタンをクリックするなどして、テキストボックスに触れることなく、オンデマンドで検証する方法がいくつかあります。

this質問に記載されているようにすべてのプロパティに対してPropertyChangedを発生させようとしましたが、エラーは検出されません。私は何とか私の不動産セッターが呼び出される必要があるので、エラーを検出することができます。私はMVVMソリューションを探しています。

+0

なぜNotifyErrorsChangedメソッドを呼び出すだけですか?これにより、ErrorsChangedイベントが発生し、すべてのバインドされたコントロールは、ValidatesOnNotifyDataErrors = Trueの場合、それに反応します。 – Stipo

+0

私はそれを試しました、それは何もしないと、私はその_errors辞書がその時点で空であるためだと仮定しています。 – kskyriacou

+0

オンデマンド検証の目的は何ですか?モデルのプロパティが変更されると、モデルはすぐに検証されます。マニュアル検証は、モデルが既にそれ自身で検証されているため、同じ結果が得られます。 – TreeTree

答えて

1

あなたの最善の策は、リレーコマンドインターフェイスを使用することです。これを見てください:

public class RelayCommand : ICommand 
{ 
    Action _TargetExecuteMethod; 
    Func<bool> _TargetCanExecuteMethod; 

    public RelayCommand(Action executeMethod) 
    { 
     _TargetExecuteMethod = executeMethod; 
    } 

    public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod) 
    { 
     _TargetExecuteMethod = executeMethod; 
     _TargetCanExecuteMethod = canExecuteMethod; 
    } 

    public void RaiseCanExecuteChanged() 
    { 
     CanExecuteChanged(this, EventArgs.Empty); 
    } 
    #region ICommand Members 

    bool ICommand.CanExecute(object parameter) 
    { 
     if (_TargetCanExecuteMethod != null) 
     { 
      return _TargetCanExecuteMethod(); 
     } 
     if (_TargetExecuteMethod != null) 
     { 
      return true; 
     } 
     return false; 
    } 

    public event EventHandler CanExecuteChanged = delegate { }; 

    void ICommand.Execute(object parameter) 
    { 
     if (_TargetExecuteMethod != null) 
     { 
      _TargetExecuteMethod(); 
     } 
    } 
    #endregion 
} 

あなたが好きなあなたのビューモデルでは、このリレーコマンドを宣言します:あなたが拡張するので、OnSaveCanSave方法であなたのSaveCommandの登録に加えて、今

public RelayCommand SaveCommand { get; private set; } 

INotifyDataErrorInfoから、あなたにも、あなたのコンストラクタでErrorsChangedにサインアップすることができます

public YourViewModel() 
{ 
    SaveCommand = new RelayCommand(OnSave, CanSave); 
    ErrorsChanged += RaiseCanExecuteChanged; 
} 

A NDあなたは方法が必要になります。この設定で

private void ValidateProperty<T>(string propertyName, T value) 
    { 
     var results = new List<ValidationResult>(); 
     ValidationContext context = new ValidationContext(this); 
     context.MemberName = propertyName; 
     Validator.TryValidateProperty(value, context, results); 

     if (results.Any()) 
     { 
      _errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); 
     } 
     else 
     { 
      _errors.Remove(propertyName); 
     } 

     ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
    } 

、そしてあなたのviewmodelがINotifyPropertyChangedから延び、両方の場合:

private void RaiseCanExecuteChanged(object sender, EventArgs e) 
{ 
     SaveCommand.RaiseCanExecuteChanged(); 
} 

public bool CanSave() 
{ 
    return !this.HasErrors; 
} 

private void OnSave() 
{ 
    //Your save logic here. 
} 

また、あなたがPropertyChangedを呼び出した後たびに、あなたは、この検証メソッドを呼び出すことができますおよびINotifyDataErrorInfo(またはこれらの2つから拡張された基底クラス)から、ボタンをSaveCommandにバインドすると、検証エラーが発生した場合、WPFフレームワークは自動的に無効にします。

これが役に立ちます。

+0

私はRelayCommandをよく知っていて、私はすでに同様のものを使用しています。ビューモデルが 'INotifyDataErrorInfo'を実装していないように、私の検証がモデル上で行われています。また、私が持っている問題は、私が欲しいときに呼び出されていないプロパティ設定ツールで検証が行われているので、あなたの答えがこれをどのように解決するのか見当たりません。私は上記の彼のコメントで言及された方法で@ Grx70に行きます。 – kskyriacou

10

使用するINotifyDataErrorInfoの実装には、多少の欠陥があります。これは、オブジェクトに接続された状態(リスト)に保持されているエラーに依存しています。保存された状態の問題は、時には動く世界で、必要なときに更新する機会がありません。ここには、格納された状態に依存しないで、その場でエラー状態を計算する別のMVVM実装があります。

プロパティ設定ツールではなく、中央のGetErrorsメソッド(この中央メソッドから呼び出されるプロパティごとの検証メソッドを作成することができます)に検証コードを入れる必要があるため、処理方法が少し異なります。

public class ModelBase : INotifyPropertyChanged, INotifyDataErrorInfo 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

    public bool HasErrors 
    { 
     get 
     { 
      return GetErrors(null).OfType<object>().Any(); 
     } 
    } 

    public virtual void ForceValidation() 
    { 
     OnPropertyChanged(null); 
    } 

    public virtual IEnumerable GetErrors([CallerMemberName] string propertyName = null) 
    { 
     return Enumerable.Empty<object>(); 
    } 

    protected void OnErrorsChanged([CallerMemberName] string propertyName = null) 
    { 
     OnErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
    } 

    protected virtual void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) 
    { 
     var handler = ErrorsChanged; 
     if (handler != null) 
     { 
      handler(sender, e); 
     } 
    } 

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     var handler = PropertyChanged; 
     if (handler != null) 
     { 
      handler(sender, e); 
     } 
    } 
} 

そして、ここでそれを使用する方法を示す2つのサンプルクラスです:

public class Customer : ModelBase 
{ 
    private string _name; 

    public string Name 
    { 
     get 
     { 
      return _name; 
     } 
     set 
     { 
      if (_name != value) 
      { 
       _name = value; 
       OnPropertyChanged(); 
      } 
     } 
    } 

    public override IEnumerable GetErrors([CallerMemberName] string propertyName = null) 
    { 
     if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Name)) 
     { 
      if (string.IsNullOrWhiteSpace(_name)) 
       yield return "Name cannot be empty."; 
     } 
    } 
} 

public class CustomerWithAge : Customer 
{ 
    private int _age; 
    public int Age 
    { 
     get 
     { 
      return _age; 
     } 
     set 
     { 
      if (_age != value) 
      { 
       _age = value; 
       OnPropertyChanged(); 
      } 
     } 
    } 

    public override IEnumerable GetErrors([CallerMemberName] string propertyName = null) 
    { 
     foreach (var obj in base.GetErrors(propertyName)) 
     { 
      yield return obj; 
     } 

     if (string.IsNullOrEmpty(propertyName) || propertyName == nameof(Age)) 
     { 
      if (_age <= 0) 
       yield return "Age is invalid."; 
     } 
    } 
} 

それは、このような単純なXAMLで魔法のように動作します:

<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" /> 
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" /> 

は(UpdateSourceTriggerはオプションです、あなたがそれを使用しない場合は、フォーカスが失われたときにのみ動作します)。

このMVVM基本クラスでは、強制的に検証する必要はありません。しかし、それが必要な場合は、ModelBaseのForceValidationサンプルメソッドを追加しました。パブリックセッターを使わずに変更された_nameなどのメンバ値でテストしました。

+0

これを行うと、[this example](http://i.imgur.com/uM4iCOg.png)のスタックパネルのように、すべてのコンテナに赤色のエラーボックスが表示されます。私はこのスタックパネルでさえもいないテキストボックスでバリデーションを使用しています。さらに、このメソッドは、モデルが作成されるとすぐに検証を強制します。バリデーション**をご希望の場合**ご利用者によりプロパティが変更された場合のみ**または必要に応じて**変更されます。 – kskyriacou

+0

あなたが聞いていることは不明で不完全です。あなたの質問にstackpanelの言及はありません。検証スタイルは、必要に応じて変更することができます(例:http://www.nbdtech.com/Blog/archive/2010/07/05/wpf-adorners-part-3-ndash-adorners-and-validation)。aspx –

+0

私はバリデーションスタイルを変更しようとしていません。あなたのコードを使用した後、すべてのコンテナが赤い線で強調表示されます。スタックパネルは単なる一例にすぎません。 – kskyriacou

関連する問題