MahApps.Metro: Invalid behavior of the icon in row header in data grid when record implements INotifyDataErrorInfo
Description
- Add DataGrid
- Set as ItemsSource ObservableCollection with records which implement INotifyDataErrorInfo and INotifyPropertyChanged and with errors after initialization
- Run app
- Textbox has red border but icon is missing (strange but I can live with that)
- Double click on cell (icon in row header is shown)
- fix value and hit enter
- Textbox hasn’t got red border (PASS) but icon with tooltip with original error is still there (FAIL)
Am I implemented it incorrectly? Or it’s an issue in header style?
Environment
- .NET Framework 4.7.2
- Windows 10 1903
Nuget libraries
- MahApps.Metro 1.6.5
- FluentValidation 8.5.1 (for validation)
- Fody 6.0.4
- PropertyChanged.Fody 3.1.3 (for autiomatic notifications)
- Caliburn.Micro 3.2.0 (only base class)
Code
using Caliburn.Micro;
using FluentValidation;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace WpfDataGridValidation
{
public class MainWindowViewModel : Conductor<IScreen>.Collection.AllActive
{
public MainWindowViewModel()
{
var validator = new MyRecordValidator();
var record = new MyRecord(validator);
Records = new ObservableCollection<MyRecord>(Enumerable.Repeat(record, 1));
}
public ObservableCollection<MyRecord> Records { get; set; }
}
public class MyRecord : Caliburn.Micro.PropertyChangedBase, INotifyDataErrorInfo
{
private readonly FluentValidation.AbstractValidator<MyRecord> _validator;
public MyRecord(FluentValidation.AbstractValidator<MyRecord> validator)
{
_validator = validator;
Validate();
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => Errors.Any();
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
return null;
if (Errors.TryGetValue(propertyName, out List<string> value))
return value;
return Enumerable.Empty<string>();
}
private void Validate()
{
if (_validator == null)
return;
var previousKeys = Errors.Select(x => x.Key).ToList();
Errors = _validator.Validate(this)
.Errors
.GroupBy(x => x.PropertyName)
.ToDictionary(x => x.Key, x => x.Select(y => y.ErrorMessage).ToList());
if (ErrorsChanged == null)
return;
foreach (var key in Errors.Keys)
ErrorsChanged.Invoke(this, new DataErrorsChangedEventArgs(key));
foreach (var key in previousKeys.Except(Errors.Select(x => x.Key)))
ErrorsChanged.Invoke(this, new DataErrorsChangedEventArgs(key));
}
public override void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
{
if (propertyName != null && propertyName != nameof(Errors) && propertyName != nameof(HasErrors))
Validate();
base.NotifyOfPropertyChange(propertyName);
}
public string Name { get; set; }
public Dictionary<string, List<string>> Errors { get; private set; } = new Dictionary<string, List<string>>();
}
public class MyRecordValidator : FluentValidation.AbstractValidator<MyRecord>
{
public MyRecordValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
}
}
MainWindow view
<Window x:Class="WpfDataGridValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfDataGridValidation"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DataGrid
x:Name="dataGrid"
ItemsSource="{Binding Records}"
CanUserAddRows="False"
CanUserSortColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserReorderColumns="False"
SelectionMode="Single"
SelectionUnit="Cell"
AutoGenerateColumns="False"
VerticalAlignment="Top"
HorizontalAlignment="Left"
GridLinesVisibility="All"
VirtualizingStackPanel.VirtualizationMode="Standard"
HeadersVisibility="All"
RowHeaderWidth="40"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, NotifyOnValidationError=True,ValidatesOnNotifyDataErrors=True}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow bode behind
using System.Windows;
namespace WpfDataGridValidation
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
App.xaml
<Application x:Class="WpfDataGridValidation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfDataGridValidation"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colors.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Green.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseLight.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 16
Hi @timunie I updated my demo with my custom style and everything is working with INotifyDataErrorInfo (second data grid). I used INotifyDataErrorInfo.HasErrors and custom property Error. Probably style should be moved to separate file. Download changes and check it.
I had a problem to use “MahApps.Brushes.Controls.Validation” and “MahApps.Brushes.Text.Validation” styles so I copied it manually. I’m not a fan of this solutions but I don’t see a better way for INotifyDataErrorInfo.
Btw I didn’t tested it with .NET Core - maybe they fixed this interface.
I tested DataGrid with IDataErrorInfo. Everything is working when UpdateSourceTrigger is set to LostFocus. Binding mode can’t be set to TwoWay but we can use Default and control finally use TwoWay as expected. Also I tested it with my implementation of ICustomTypeDescriptor and wverything is correct.
I was not able to get working example for INotifyDataErrorInfo interface. I suspect that something is wrong with this interface ant it can’t work with DataGrid.
For now I think that we can close this thread. @timunie once again thanks for help!