多くのコントロールを備えたMVVMベースのウィンドウがあり、モデルはIDataErrorInfo
を実装しています。
Model.Error
プロパティを分析して検証を実行するSaveCommand
ボタンもあります。
ビューには、エラーのあるコントロールの周囲にデフォルトの赤い境界線が表示されます値を変更した場合のみ特定のコントロールの場合、またはPropertyChangedを使用してそのプロパティの変更について通知した場合。
コントロールに触れていない場合でも、Viewにすべての検証エラーを表示させるにはどうすればよいですか?
私のすべての検証バインディングにはValidatesOnDataErrors=True, NotifyOnValidationError=True
が含まれています。
1つの解決策は、すべてのエラーを含む集計ボックスを作成することですが、コントロールごとにエラーを表示したいと思います。
ViewModelからバインドされたプロパティごとにModel.NotifyPropertyChanged
をトリガーしたくありません。
SilverlightではなくWPF4.0を使用しているため、INotifyDataErrorInfo
は機能しません。
バインドするプロパティに対して変更されたプロパティを発生させたくないとおっしゃっていますが、これは実際にこれを実現する最も簡単な方法です。パラメータを指定せずにPropertyChangedを呼び出すと、ビューモデルのすべてのプロパティが発生します。
または、次のようなコントロールのバインディングを更新(および強制的に再検証)することもできます。
myControl.GetBindingExpression(ControlType.ControlProperty).UpdateSource();
私がこれまでに見つけた最善の解決策は、DataContextをnullに変更し、ViewModelのインスタンスに戻すことです。
これにより、DataContext
がInnerViewModel
にバインドされているビューのコントロールの更新がトリガーされます。
public void ForceUpdateErrors() {
var tmpInnerVM = _mainViewModel.InnerViewModel;
_mainViewModel.InnerViewModel = null;
_mainViewModel.InnerViewModel = tmpInnerVM;
}
このトリックの後でデータが失われていないかどうかを確認することをお勧めします。このコードがComboBox.SelectedItemのソース更新をnullでトリガーする場合がありましたが、なんとか解決できました。これは、リソースベースのBindingProxyと、コントロール階層全体でのDataContext=null
伝播の順序を使用したことが原因でした。
この「ハック」は一時的に機能し、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;
}