Last active
September 26, 2020 13:07
-
-
Save Artfunkel/868e6a88e37bd9769cd8beb04fd9837f to your computer and use it in GitHub Desktop.
DataErrorsControl: displays all errors from an INotifyDataErrorInfo object
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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