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

17

我有一个基于MVVM的窗口,其中包含许多控件,并且我的Model实现了IDataErrorInfo

同时,我有一个SaveCommand按钮,该按钮通过分析Model.Error属性执行验证。

当特定控件的值发生更改或我使用PropertyChanged通知更改时,视图仅在这些情况下显示具有错误的控件周围的默认红色边框。

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

所有我的验证绑定均包括ValidatesOnDataErrors=True, NotifyOnValidationError=True

我知道一种解决方案是使用一个聚合框来显示所有错误,但我更倾向于按控件显示错误。

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

我使用WPF 4.0,而不是Silverlight,所以INotifyDataErrorInfo无法使用。

3个回答

14

你提到你不想为你绑定到的属性引发属性更改事件,但这实际上是实现这一目标最简单的方式。调用没有参数的 PropertyChanged 方法将为 View Model 中的所有属性引发事件。

或者,您可以通过以下方式更新任何控件的绑定(并强制重新验证):

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

2
谢谢你提供了关于PropertyChanged的技巧,我之前并不知道这是可能的。如果有人感兴趣,我在这个话题上找到了另一个讨论:https://dev59.com/JXNA5IYBdhLWcg3wAItP。如果有人只有一个简单的viewModel,那么这是一个好答案。然而,我有一个包含嵌套ViewModel的复杂视图,所以我需要编写代码为每个实现INotifyPropertyChanged的嵌套绑定的Model/ViewModel调用一次PropertyChanged。 - surfen
如果想要更新与特定ViewModel相关的视图的部分内容,了解这个技巧是很有用的。 - surfen
1
myControl.GetBindingExpression(ControlType.ControlProperty).UpdateTarget(); 实际上可以使您的验证保持最新,而无需更新源属性。 - r41n

2

这个“Hack”暂时为我解决了问题,要强制触发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;
    }

2
到目前为止我发现最好的解决方案是将DataContext更改为null,然后再改回ViewModel实例。这会触发绑定到InnerViewModel的DataContext的视图控件的更新:
public void ForceUpdateErrors() {
    var tmpInnerVM = _mainViewModel.InnerViewModel;
    _mainViewModel.InnerViewModel = null;
    _mainViewModel.InnerViewModel = tmpInnerVM;
}

建议在使用此技巧后检查是否有数据丢失。我曾经遇到过这样的情况,这段代码会触发ComboBox.SelectedItem的源更新,但是我最终解决了这个问题。这是由于使用基于资源的BindingProxy以及控件层次结构中DataContext=null传播顺序引起的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接