How to force validation errors update on View from ViewModel using IDataErrorInfo?
Asked Answered
G

3

18

I have a MVVM-based Window with many controls, and my Model implements IDataErrorInfo.

There is also a SaveCommand button, which performs validation by analysing Model.Error property.

The view displays the default red border around controls with errors only when I change the value of a particular control, or when I notify about the change of that property using PropertyChanged.

How can I force View to display all Validation errors even when I didn't touch the controls?

All my validation bindings include ValidatesOnDataErrors=True, NotifyOnValidationError=True.

I know one solution is to have an aggregate box with all the errors, but I would prefer to display errors on per-control basis.

I don't want to trigger Model.NotifyPropertyChanged for each bound property from ViewModel.

I use WPF 4.0, not Silverlight, so INotifyDataErrorInfo won't work.

Gemination answered 3/4, 2012 at 2:20 Comment(0)
D
15

You mention that you don't want to raise property changed for the properties you bind to, but that's really the simplest way to accomplish this. Calling PropertyChanged with no parameter will raise for all properties in your viewmodel.

Alternatively you can update the bindings (and force revalidation) on any control like this:

myControl.GetBindingExpression(ControlType.ControlProperty).UpdateSource();
Dimity answered 15/5, 2012 at 14:9 Comment(3)
Thanks for the trick with PropertyChanged. I didn't know it was possible. I've found another discussion on this topic: #1135512 if anybody is interested. This is a good answer if someone has a single, simple viewModel. However I have a complex view with nested ViewModels so I would have to to write code to call PropertyChanged once for each nested bound Model/ViewModel that implements INotifyPropertyChangedGemination
It's good to know this trick if one want's to update only a part of the view related to a particular ViewModelGemination
myControl.GetBindingExpression(ControlType.ControlProperty).UpdateTarget(); actually gets your validation up-to-date without updating your source Property.Cisalpine
P
2

This 'Hack' worked for me temporarily, to force the InotifyChanged event, just assign that control back it's own content. Do this before evaluating the HasError function of bindings. For example a textbox would be:

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

And then a complete example(before I hear this is not true MVVM, I directly got a handle on the grid for ease of showing this code 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;
    }
Porush answered 16/3, 2015 at 22:11 Comment(0)
G
1

The best solution I've found so far that works is to change DataContext to null and back to the instance of ViewModel.

This triggers the update for controls on the view that has DataContext bound to InnerViewModel:

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

It's recommended to check if no data is lost after this trick. I had a case that this code triggered source update for ComboBox.SelectedItem with null but I managed to solve it. It was caused by using a resource-based BindingProxy and the order of DataContext=null propagation across control hierarchy.

Gemination answered 15/5, 2012 at 11:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.