web-dev-qa-db-ja.com

IDataErrorInfoを使用してViewModelからViewの検証エラーを強制的に更新するにはどうすればよいですか?

多くのコントロールを備えたMVVMベースのウィンドウがあり、モデルはIDataErrorInfoを実装しています。

Model.Errorプロパティを分析して検証を実行するSaveCommandボタンもあります。

ビューには、エラーのあるコントロールの周囲にデフォルトの赤い境界線が表示されます値を変更した場合のみ特定のコントロールの場合、またはPropertyChangedを使用してそのプロパティの変更について通知した場合。

コントロールに触れていない場合でも、Viewにすべての検証エラーを表示させるにはどうすればよいですか?

私のすべての検証バインディングにはValidatesOnDataErrors=True, NotifyOnValidationError=Trueが含まれています。

1つの解決策は、すべてのエラーを含む集計ボックスを作成することですが、コントロールごとにエラーを表示したいと思います。

ViewModelからバインドされたプロパティごとにModel.NotifyPropertyChangedをトリガーしたくありません。

SilverlightではなくWPF4.0を使用しているため、INotifyDataErrorInfoは機能しません。

17
surfen

バインドするプロパティに対して変更されたプロパティを発生させたくないとおっしゃっていますが、これは実際にこれを実現する最も簡単な方法です。パラメータを指定せずにPropertyChangedを呼び出すと、ビューモデルのすべてのプロパティが発生します。

または、次のようなコントロールのバインディングを更新(および強制的に再検証)することもできます。

myControl.GetBindingExpression(ControlType.ControlProperty).UpdateSource();
13
Brandorf

私がこれまでに見つけた最善の解決策は、DataContextをnullに変更し、ViewModelのインスタンスに戻すことです。

これにより、DataContextInnerViewModelにバインドされているビューのコントロールの更新がトリガーされます。

public void ForceUpdateErrors() {
    var tmpInnerVM = _mainViewModel.InnerViewModel;
    _mainViewModel.InnerViewModel = null;
    _mainViewModel.InnerViewModel = tmpInnerVM;
}

このトリックの後でデータが失われていないかどうかを確認することをお勧めします。このコードがComboBox.SelectedItemのソース更新をnullでトリガーする場合がありましたが、なんとか解決できました。これは、リソースベースのBindingProxyと、コントロール階層全体でのDataContext=null伝播の順序を使用したことが原因でした。

2
surfen

この「ハック」は一時的に機能し、InotifyChangedイベントを強制し、そのコントロールを独自のコンテンツに戻すだけです。バインディングのHasError関数を評価する前にこれを行ってください。たとえば、テキストボックスは次のようになります。

 ((TextBox)child).Text = ((TextBox)child).Text;

そして、完全な例(これが本当のMVVMではないと聞く前に、このコードスニペットを簡単に表示するためにグリッド上のハンドルを直接取得しました)

        public bool Validate()
    {           
        bool hasErr = false;

        for (int i = 0; i != VisualTreeHelper.GetChildrenCount(grd); ++i)
        {
            DependencyObject child = VisualTreeHelper.GetChild(grd, i);
            if (child is TextBox)
            {
                bool pp = BindingOperations.IsDataBound(child, TextBox.TextProperty);
                if (pp)
                {

                     ((TextBox)child).Text = ((TextBox)child).Text;

                    hasErr = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).HasError;
                    System.Collections.ObjectModel.ReadOnlyCollection<ValidationError> errors = BindingOperations.GetBindingExpression(child, TextBox.TextProperty).ValidationErrors;
                    if (hasErr)
                    {
                        main.BottomText.Foreground = Brushes.Red;
                        main.BottomText.Text = BindingOperations.GetBinding(child, TextBox.TextProperty).Path.Path.Replace('.', ' ') + ": " + errors[0].ErrorContent.ToString();
                        return false;
                    }
                }
            }
            if (child is DatePicker)
            {
                ...                    
            }
        }

        return true;
    }
1
DR.