Created
April 7, 2024 21:18
-
-
Save skarllot/82413dfe14832a16f773722734f59377 to your computer and use it in GitHub Desktop.
Subject class for using INotifyDataErrorInfo with Avalonia UI
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; | |
using System.Collections.Immutable; | |
using System.ComponentModel; | |
using System.Reactive.Subjects; | |
using CSharpFunctionalExtensions; | |
namespace Example; | |
public sealed class ChangeErrorsSubject : ISubject<ImmutableList<PropertyDataError>>, INotifyDataErrorInfo, IDisposable | |
{ | |
private static readonly List<string> s_noErrors = []; | |
private readonly Subject<ImmutableList<PropertyDataError>> _subject = new(); | |
private readonly Dictionary<string, List<string>> _propertyErrors = new(StringComparer.Ordinal); | |
private readonly Dictionary<string, DataErrorsChangedEventArgs> _errorsEventArgsCache = new(StringComparer.Ordinal); | |
/// <inheritdoc /> | |
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged; | |
/// <inheritdoc /> | |
public bool HasErrors => _propertyErrors.Count > 0; | |
public IEnumerable GetErrors(string? propertyName) => propertyName is not null | |
? _propertyErrors.TryFind(propertyName).GetValueOrDefault(s_noErrors) | |
: s_noErrors; | |
public void AddError(string? propertyName, string error) | |
{ | |
_propertyErrors.AddOrUpdate( | |
propertyName ?? string.Empty, | |
static (_, arg) => [arg], | |
static (_, v, arg) => v.Add(arg), | |
error); | |
OnErrorsChanged(propertyName); | |
_subject.OnNext(GetCurrentState()); | |
} | |
public bool RemoveErrors(string? propertyName) | |
{ | |
bool isRemoved = _propertyErrors.Remove(propertyName ?? string.Empty); | |
if (!isRemoved) | |
{ | |
return false; | |
} | |
OnErrorsChanged(propertyName); | |
_subject.OnNext(GetCurrentState()); | |
return true; | |
} | |
public void OnNext(ImmutableList<PropertyDataError> value) | |
{ | |
var allNames = value.Select(x => x.PropertyName ?? string.Empty) | |
.Concat(_propertyErrors.Keys) | |
.Distinct(StringComparer.Ordinal); | |
var lookup = value.ToLookup(x => x.PropertyName ?? string.Empty, x => x.Error, StringComparer.Ordinal); | |
List<string> changedProperties = []; | |
foreach (string propertyName in allNames) | |
{ | |
var newErrors = lookup[propertyName].ToList(); | |
if (newErrors.Count == 0) | |
{ | |
_propertyErrors.Remove(propertyName); | |
changedProperties.Add(propertyName); | |
} | |
else | |
{ | |
var currErrors = _propertyErrors.AddOrUpdate( | |
propertyName, | |
static (_, arg) => arg, | |
static (_, v, arg) => v.SequenceEqual(arg) ? v : arg, | |
newErrors); | |
if (currErrors == newErrors) | |
{ | |
changedProperties.Add(propertyName); | |
} | |
} | |
} | |
changedProperties.ForEach(OnErrorsChanged); | |
if (changedProperties.Count > 0) | |
{ | |
_subject.OnNext(value); | |
} | |
} | |
public void OnError(Exception error) => _subject.OnError(error); | |
public void OnCompleted() => _subject.OnCompleted(); | |
public IDisposable Subscribe(IObserver<ImmutableList<PropertyDataError>> observer) => _subject.Subscribe(observer); | |
public void Dispose() => _subject.Dispose(); | |
private ImmutableList<PropertyDataError> GetCurrentState() => _propertyErrors | |
.SelectMany( | |
static kv => | |
string.IsNullOrEmpty(kv.Key) | |
? kv.Value.Select(static v => new PropertyDataError(null, v)) | |
: kv.Value.Select(v => new PropertyDataError(kv.Key, v))) | |
.ToImmutableList(); | |
private void OnErrorsChanged(string? propertyName) => ErrorsChanged?.Invoke( | |
this, | |
_errorsEventArgsCache.GetOrAdd( | |
propertyName ?? string.Empty, | |
static k => new DataErrorsChangedEventArgs(string.IsNullOrEmpty(k) ? null : k))); | |
} |
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
namespace Example; | |
public sealed record PropertyDataError(string? PropertyName, string Error); |
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; | |
using System.Collections.Immutable; | |
using System.ComponentModel; | |
using ReactiveUI; | |
namespace Example; | |
public class RoutableViewModelBase : ViewModelBase, IRoutableViewModel, INotifyDataErrorInfo, IDisposable | |
{ | |
private readonly ChangeErrorsSubject _changeErrorsSubject = new(); | |
protected RoutableViewModelBase(IScreen hostScreen) | |
{ | |
HostScreen = hostScreen; | |
} | |
event EventHandler<DataErrorsChangedEventArgs>? INotifyDataErrorInfo.ErrorsChanged | |
{ | |
add => _changeErrorsSubject.ErrorsChanged += value; | |
remove => _changeErrorsSubject.ErrorsChanged -= value; | |
} | |
public IScreen HostScreen { get; } | |
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString()[..5]; | |
public bool HasErrors => _changeErrorsSubject.HasErrors; | |
protected IObservable<ImmutableList<PropertyDataError>> WhenErrorsChanged => _changeErrorsSubject; | |
protected IObserver<ImmutableList<PropertyDataError>> ChangeErrors => _changeErrorsSubject; | |
public void Dispose() | |
{ | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
IEnumerable INotifyDataErrorInfo.GetErrors(string? propertyName) => _changeErrorsSubject.GetErrors(propertyName); | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (disposing) | |
{ | |
_changeErrorsSubject.Dispose(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment