Skip to content

Instantly share code, notes, and snippets.

@Artfunkel
Last active September 26, 2020 13:07
Show Gist options
  • Save Artfunkel/868e6a88e37bd9769cd8beb04fd9837f to your computer and use it in GitHub Desktop.
Save Artfunkel/868e6a88e37bd9769cd8beb04fd9837f to your computer and use it in GitHub Desktop.
DataErrorsControl: displays all errors from an INotifyDataErrorInfo object
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace Artfunkel
{
using DataErrorsChangedEventManager = WeakEventManager<INotifyDataErrorInfo, DataErrorsChangedEventArgs>;
public class DataErrorsControl : Control
{
private readonly Dictionary<string, CollectionContainer> _errorLookup;
private readonly CompositeCollection _errors;
public CollectionView Errors { get; }
public INotifyDataErrorInfo ErrorSource
{
get => (INotifyDataErrorInfo) GetValue(ErrorSourceProperty);
set => SetValue(ErrorSourceProperty, value);
}
public static readonly DependencyProperty ErrorSourceProperty =
DependencyProperty.Register(nameof(ErrorSource), typeof(INotifyDataErrorInfo), typeof(DataErrorsControl), new PropertyMetadata(null, OnErrorSourceChanged));
private static void OnErrorSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((DataErrorsControl) d).OnErrorSourceChanged(e);
public bool IsExpanded
{
get => (bool) GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register(nameof(IsExpanded), typeof(bool), typeof(DataErrorsControl), new PropertyMetadata(false));
public DataTemplate ErrorTemplate
{
get => (DataTemplate) GetValue(ErrorTemplateProperty);
set => SetValue(ErrorTemplateProperty, value);
}
public static readonly DependencyProperty ErrorTemplateProperty =
DependencyProperty.Register(nameof(ErrorTemplate), typeof(DataTemplate), typeof(DataErrorsControl), new PropertyMetadata(null));
public DataErrorsControl()
{
_errorLookup = new Dictionary<string, CollectionContainer>();
_errors = new CompositeCollection();
var collectionView = CollectionViewSource.GetDefaultView(_errors);
Errors = (CollectionView) collectionView;
collectionView.CollectionChanged += Errors_OnCollectionChanged;
}
private void Errors_OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (Errors.IsCurrentBeforeFirst)
{
Errors.MoveCurrentToFirst();
}
else if (Errors.IsCurrentAfterLast)
{
Errors.MoveCurrentToLast();
}
}
protected virtual void OnErrorSourceChanged(DependencyPropertyChangedEventArgs e)
{
DataErrorsChangedEventManager.RemoveHandler((INotifyDataErrorInfo) e.OldValue, nameof(ErrorSource.ErrorsChanged), ErrorsSource_OnErrorsChanged);
_errorLookup.Clear();
_errors.Clear();
if (ErrorSource == null)
{
return;
}
DataErrorsChangedEventManager.AddHandler(ErrorSource, nameof(ErrorSource.ErrorsChanged), ErrorsSource_OnErrorsChanged);
if (ErrorSource.HasErrors)
{
RegisterPropertyErrors(string.Empty);
foreach (var property in ErrorSource.GetType().GetProperties())
{
RegisterPropertyErrors(property.Name);
}
}
}
private void ErrorsSource_OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
if (sender == ErrorSource)
{
RegisterPropertyErrors(e.PropertyName ?? string.Empty);
}
}));
}
private void RegisterPropertyErrors(string propertyName)
{
var errorEnumerable = ErrorSource.GetErrors(propertyName);
if (_errorLookup.TryGetValue(propertyName, out var container))
{
if (errorEnumerable == container.Collection && !(errorEnumerable is INotifyCollectionChanged))
{
container.Collection = null;
}
container.Collection = errorEnumerable;
}
else
{
container = new CollectionContainer { Collection = errorEnumerable };
_errorLookup[propertyName] = container;
_errors.Add(container);
}
}
static DataErrorsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DataErrorsControl), new FrameworkPropertyMetadata(typeof(DataErrorsControl)));
CommandManager.RegisterClassCommandBinding(typeof(DataErrorsControl), new CommandBinding(NavigationCommands.BrowseForward, Forward_OnExecuted, Forward_OnCanExecute));
CommandManager.RegisterClassCommandBinding(typeof(DataErrorsControl), new CommandBinding(NavigationCommands.BrowseBack, Back_OnExecuted, Back_OnCanExecute));
}
private static void Forward_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
var errors = GetErrors(sender);
e.CanExecute = errors.CurrentPosition < errors.Count - 1;
}
private static void Forward_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
GetErrors(sender).MoveCurrentToNext();
e.Handled = true;
}
private static void Back_OnCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = GetErrors(sender).CurrentPosition > 0;
}
private static void Back_OnExecuted(object sender, ExecutedRoutedEventArgs e)
{
GetErrors(sender).MoveCurrentToPrevious();
e.Handled = true;
}
private static CollectionView GetErrors(object sender) => ((DataErrorsControl) sender).Errors;
}
}
<Style TargetType="artfunkel:DataErrorsControl">
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="artfunkel:DataErrorsControl">
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter Content="{Binding Errors.CurrentItem, RelativeSource={RelativeSource TemplatedParent}}" ContentTemplate="{TemplateBinding ErrorTemplate}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="artfunkel:DataErrorsControl">
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ItemsControl ItemsSource="{Binding Errors, RelativeSource={RelativeSource TemplatedParent}}" ItemTemplate="{TemplateBinding ErrorTemplate}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment