Hello,
I am using INotifyDataErrorInfo with a DataGrid to enforce cell level business rules (please see related code below). The issue is that when an error occurs in a cell, a red text box appears around the cell border as expected, however a tooltip containing
the specific error message is not displayed during a mouse hover.
I have noticed during debugging that when the "<Trigger Property="Validation.HasError" Value="False">" line of XAML code executes, "Validation.HasError" is always "False" - even when the related
DataGridCell has a red border. I suspect that the correct validation object is not being accessed.
Any insights into what may be wrong with my XAML syntax/approach would be appreciated.
Thank you,
Chris
XAML:
<DataGrid Grid.Row="1" Grid.Column="0" Margin="10" AutoGenerateColumns="False" CanUserAddRows="False" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding EmployeeList, ValidatesOnDataErrors=True}" SelectionMode="Single"><DataGrid.CellStyle><Style TargetType="{x:Type DataGridCell}"><Style.Triggers>
// Result is always false.<Trigger Property="Validation.HasError" Value="False"><Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)/ErrorContent}" /></Trigger></Style.Triggers></Style></DataGrid.CellStyle><DataGrid.Style><Style TargetType="DataGrid"><Setter Property="AlternatingRowBackground" Value="LightGray" /></Style></DataGrid.Style><DataGrid.Columns><DataGridTextColumn Width="Auto" MinWidth="150" MaxWidth="150" Binding="{Binding FirstName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Header="First Name" /><DataGridTextColumn Width="Auto" MinWidth="150" MaxWidth="150" Binding="{Binding LastName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Header="Last Name" /></DataGrid.Columns></DataGrid>
EmployeeList and Employee classes:
public class EmployeeList : ObservableCollection<Employee> { public bool IsValid() { return this.Items.All(employee => employee.IsEmployeeValid()); } }
public class Employee : ViewModelBase { private readonly EmployeePresubmitValidator presubmitValidator; private readonly EventAggregator eventAggregator; private string firstName; private string lastName; public Employee( EmployeePresubmitValidator presubmitValidator, EventAggregator eventAggregator) { this.presubmitValidator = presubmitValidator; this.eventAggregator = eventAggregator; this.Initialize(); } public string FirstName { get { return this.firstName; } set { this.SetProperty(ref this.firstName, value); this.ValidatePropertyPresubmit("FirstName"); } } public string LastName { get { return this.lastName; } set { this.SetProperty(ref this.lastName, value); this.ValidatePropertyPresubmit("LastName"); } } public bool IsEmployeeValid() { return this.presubmitValidator.Validate(this).IsValid; } public override void ValidateViewModelPresubmit() { var validationResults = this.presubmitValidator.Validate(this); this.RefreshErrors(validationResults); } public override void ValidatePropertyPresubmit(string propertyName) { var validationResults = this.presubmitValidator.Validate(this); this.RefreshErrors(propertyName, validationResults); } private void Initialize() { this.ValidateViewModelPresubmit(); } }
The Employee class implements ViewModelBase which, in turn, implements INotifyDataErrorInfo:
public abstract class ViewModelBase : BindableBase, INotifyDataErrorInfo { // Public Events public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; // Public Properties public bool HasErrors => this.ValidationErrors.Count > 0; protected Dictionary<string, List<string>> ValidationErrors { get; } = new Dictionary<string, List<string>>(); // Public Abstract Methods public abstract void ValidateViewModelPresubmit(); public abstract void ValidatePropertyPresubmit(string propertyName); // Public Methods public IEnumerable GetErrors(string propertyName) { List<string> currentErrors; return this.ValidationErrors.TryGetValue(propertyName, out currentErrors) ? currentErrors : null; }
public void RefreshErrors(ValidationResult validationResult) { this.ClearAllErrors(); this.AddErrors(validationResult); } public void RefreshErrors(string propertyName, ValidationResult result) { this.ClearPropertyErrors(propertyName); this.AddPropertyErrors(propertyName, result); } // Implement INotifyDataErrorInfo public void RaiseErrorsChanged(string propertyName) { this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); }
private void ClearAllErrors() { this.ValidationErrors.Clear(); } private void AddErrors(ValidationResult result) { foreach (var error in result.Errors) { this.AddError(error.PropertyName, error.ErrorMessage); } } private void AddError(string propertyName, string error) { if (!this.ValidationErrors.ContainsKey(propertyName)) { this.ValidationErrors[propertyName] = new List<string>(); } if (this.ValidationErrors[propertyName].Contains(error)) { return; } this.ValidationErrors[propertyName].Insert(0, error); this.RaiseErrorsChanged(propertyName); } private void ClearPropertyErrors(string propertyName) { if (!this.ValidationErrors.ContainsKey(propertyName)) { return; } this.ValidationErrors[propertyName].Clear(); this.ValidationErrors.Remove(propertyName); } private void AddPropertyErrors(string propertyName, ValidationResult result) { foreach (var error in result.Errors.Where(error => error.PropertyName == propertyName)) { this.AddError(propertyName, error.ErrorMessage); } } }