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

时间:2012-04-03 02:20:29

标签: c# wpf validation mvvm idataerrorinfo

我有一个基于MVVM的窗口,里面有很多控件,我的模型实现了IDataErrorInfo

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

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

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

我的所有验证绑定都包含ValidatesOnDataErrors=True, NotifyOnValidationError=True

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

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

我使用的是WPF 4.0,而不是Silverlight,因此INotifyDataErrorInfo无效。

3 个答案:

答案 0 :(得分:13)

你提到你不想为你绑定的属性提高属性,但这实际上是最简单的方法。对于viewmodel中的所有属性,将调用不带参数的PropertyChanged。

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

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

答案 1 :(得分:2)

到目前为止,我发现的最佳解决方案是将DataContext更改为null并返回ViewModel的实例。

这会触发DataContext绑定到InnerViewModel的视图的控件更新:

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

建议在此技巧后检查是否没有数据丢失。我有一个案例,这个代码触发ComboBox.SelectedItem的源更新与null但我设法解决它。这是因为使用基于资源的BindingProxy和跨越控制层次结构的DataContext=null传播顺序。

答案 2 :(得分:1)

这个'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<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;
    }