如何使用IDataErrorInfo强制validation错误更新ViewModel中的View?

我有一个基于MVVM的窗口,有很多控件,我的Model实现了IDataErrorInfo

还有一个SaveCommand按钮,它通过分析Model.Error属性来执行validation。

仅当我更改特定控件的值时,或者当我使用PropertyChanged通知有关该属性的更改时,视图才会显示控件周围的默认红色边框。

即使我没有触摸控件,如何强制View显示所有validation错误?

我的所有validation绑定都包括ValidatesOnDataErrors=True, NotifyOnValidationError=True

我知道一个解决方案是有一个包含所有错误的聚合框,但我更愿意在每个控件的基础上显示错误。

我不想为ViewModel中的每个绑定属性触发Model.NotifyPropertyChanged

我使用的是WPF 4.0,而不是Silverlight,所以INotifyDataErrorInfo不起作用。

您提到您不希望为绑定的属性引发更改的属性,但这确实是实现此目的的最简单方法。 对于viewmodel中的所有属性,将调用不带参数的PropertyChanged。

或者,您可以在任何控件上更新绑定(并强制重新validation),如下所示:

 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传播的顺序引起的。

这个’Hack’暂时为我工作,强制InotifyChanged事件,只需将控件分配给它自己的内容。 在评估绑定的HasError函数之前执行此操作。 例如,文本框将是:

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

然后是一个完整的例子(在我听到这不是真正的MVVM之前,我直接在网格上获得了一个句柄,以便于显示此代码snipet)

  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 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; }