WPF Validation and Controlling TextBox Styles

textboxvalidationwpf

I have build my own custom validation framework for WPF using attribute based validation. I am stuck on the last step which is to highlight the TextBox. Actually, it does highlight the textboxes but all the textboxes are dependent on a single property HasError.

public class RegistrationViewModel  : ViewModel
    {
        [NotNullOrEmpty("FirstName should not be null or empty")] 
        public string FirstName { get; set; }

        [NotNullOrEmpty("Middle Name is required!")]
        public string MiddleName { get; set; } 

        [NotNullOrEmpty("LastName should not be null or empty")] 
        public string LastName { get; set; }

        public bool HasError
        {
            get
            {
                **return Errors.Count > 0; // THIS IS THE PROBLEM** 
            }
        }

    }

And here is the XAML code:

 <Style x:Key="textBoxStyle" TargetType="{x:Type TextBox}">                   

            <Style.Triggers>

                <DataTrigger Binding="{Binding Path=HasError}" Value="True">
                    <Setter Property="Background" Value="Red" />
                </DataTrigger>

            </Style.Triggers>

        </Style>

The problem with the above code is that it will highlight all the textboxes that uses "textBoxStyle" even though they are valid. This is because the HasError does not validate on individual property basis but as a whole.

Any ideas?

UPDATE 1:

The ViewModel contains the Errors collection:

 public class ViewModel : ContentControl, INotifyPropertyChanged
    {
        public static DependencyProperty ErrorsProperty; 

        static ViewModel()
        {
            ErrorsProperty = DependencyProperty.Register("Errors", typeof(ObservableCollection<BrokenRule>), typeof(ViewModel)); 
        }

        public ObservableCollection<BrokenRule> Errors
        {
            get { return (ObservableCollection<BrokenRule>)GetValue(ErrorsProperty); }
            set 
            { 
                SetValue(ErrorsProperty,value);
                OnPropertyChanged("HasError");
             }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }

    }

UPDATE 2:

My validation engine:

public bool Validate(object viewModel)
        {
            _brokenRules = new List<BrokenRule>();

            // get all the properties 

            var properties = viewModel.GetType().GetProperties(); 

            foreach(var property in properties)
            {
                // get the custom attribute 

                var attribues = property.GetCustomAttributes(typeof (EStudyValidationAttribute), false); 

                foreach(EStudyValidationAttribute att in attribues)
                {
                    bool isValid = att.IsValid(property.GetValue(viewModel,null));

                    if(isValid) continue; 

                    // add the broken rule 

                    var brokenRule = new BrokenRule()
                                         {
                                             PropertyName = property.Name,
                                             Message = att.Message
                                         }; 

                    _brokenRules.Add(brokenRule);
                }

            }

            var list = _brokenRules.ToObservableCollection(); 

            viewModel.GetType().GetProperty("Errors").SetValue(viewModel,list,null);

            return (_brokenRules.Count() == 0); 
        }

Best Answer

You can implement IDataErrorInfo interface in your ViewModels, and in XAML check attached properties Validation.HasError on elements for controls with validation error; it's better because it's standart mechanizm in .Net.

<Style x:Key="textBoxStyle" TargetType="{x:Type TextBox}">                   

            <Style.Triggers>

                <DataTrigger Binding="{Binding Path=Validation.HasError}" Value="True">
                    <Setter Property="Background" Value="Red" />
                </DataTrigger>

            </Style.Triggers>

        </Style>

When binding to property in TextBoxes, you need to set binding ValidatesOnDataError property to true.

<TextBox x:Name="someTextBox" Text="{Binding Path=someProperty, ValidatesOnDataErrors=True}">



public class ViewModel : ContentControl, INotifyPropertyChanged,IDataErrorInfo
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }

        public string this[string propertyName]
        {
            get
            {
                return ValidateProperty(this,propertyName);
            }
        }

        public string Error
        {
            get
            {
                            return "";
            }
        }

    }

You can even use your implemented validation method, but check validation by property.