committed by
GitHub
8 changed files with 913 additions and 672 deletions
File diff suppressed because it is too large
@ -0,0 +1,160 @@ |
|||
using Avalonia.Data; |
|||
using Avalonia.Reactive; |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Subjects; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
public interface ICellEditBinding |
|||
{ |
|||
bool IsValid { get; } |
|||
IEnumerable<Exception> ValidationErrors { get; } |
|||
IObservable<bool> ValidationChanged { get; } |
|||
bool CommitEdit(); |
|||
} |
|||
|
|||
internal class CellEditBinding : ICellEditBinding |
|||
{ |
|||
private readonly Subject<bool> _changedSubject = new Subject<bool>(); |
|||
private readonly List<Exception> _validationErrors = new List<Exception>(); |
|||
private readonly SubjectWrapper _inner; |
|||
|
|||
public bool IsValid => _validationErrors.Count <= 0; |
|||
public IEnumerable<Exception> ValidationErrors => _validationErrors; |
|||
public IObservable<bool> ValidationChanged => _changedSubject; |
|||
public ISubject<object> InternalSubject => _inner; |
|||
|
|||
public CellEditBinding(ISubject<object> bindingSourceSubject) |
|||
{ |
|||
_inner = new SubjectWrapper(bindingSourceSubject, this); |
|||
} |
|||
|
|||
private void AlterValidationErrors(Action<List<Exception>> action) |
|||
{ |
|||
var wasValid = IsValid; |
|||
action(_validationErrors); |
|||
var isValid = IsValid; |
|||
|
|||
if(!isValid || !wasValid) |
|||
{ |
|||
_changedSubject.OnNext(isValid); |
|||
} |
|||
} |
|||
|
|||
public bool CommitEdit() |
|||
{ |
|||
_inner.CommitEdit(); |
|||
return IsValid; |
|||
} |
|||
|
|||
class SubjectWrapper : LightweightObservableBase<object>, ISubject<object>, IDisposable |
|||
{ |
|||
private readonly ISubject<object> _sourceSubject; |
|||
private readonly CellEditBinding _editBinding; |
|||
private IDisposable _subscription; |
|||
private object _controlValue; |
|||
private bool _isControlValueSet = false; |
|||
private bool _settingSourceValue = false; |
|||
|
|||
public SubjectWrapper(ISubject<object> bindingSourceSubject, CellEditBinding editBinding) |
|||
{ |
|||
_sourceSubject = bindingSourceSubject; |
|||
_editBinding = editBinding; |
|||
} |
|||
|
|||
private void SetSourceValue(object value) |
|||
{ |
|||
_settingSourceValue = true; |
|||
|
|||
_sourceSubject.OnNext(value); |
|||
|
|||
_settingSourceValue = false; |
|||
} |
|||
private void SetControlValue(object value) |
|||
{ |
|||
PublishNext(value); |
|||
} |
|||
|
|||
private void OnValidationError(BindingNotification notification) |
|||
{ |
|||
if(notification.Error != null) |
|||
{ |
|||
_editBinding.AlterValidationErrors(errors => |
|||
{ |
|||
errors.Clear(); |
|||
var unpackedErrors = ValidationUtil.UnpackException(notification.Error); |
|||
if(unpackedErrors != null) |
|||
errors.AddRange(unpackedErrors); |
|||
}); |
|||
} |
|||
} |
|||
private void OnControlValueUpdated(object value) |
|||
{ |
|||
_controlValue = value; |
|||
_isControlValueSet = true; |
|||
|
|||
if(!_editBinding.IsValid) |
|||
{ |
|||
SetSourceValue(value); |
|||
} |
|||
} |
|||
private void OnSourceValueUpdated(object value) |
|||
{ |
|||
void OnValidValue(object val) |
|||
{ |
|||
SetControlValue(val); |
|||
_editBinding.AlterValidationErrors(errors => errors.Clear()); |
|||
} |
|||
|
|||
if (value is BindingNotification notification) |
|||
{ |
|||
if (notification.ErrorType != BindingErrorType.None) |
|||
OnValidationError(notification); |
|||
else |
|||
OnValidValue(value); |
|||
} |
|||
else |
|||
{ |
|||
OnValidValue(value); |
|||
} |
|||
} |
|||
|
|||
protected override void Deinitialize() |
|||
{ |
|||
_subscription?.Dispose(); |
|||
_subscription = null; |
|||
} |
|||
protected override void Initialize() |
|||
{ |
|||
_subscription = _sourceSubject.Subscribe(OnSourceValueUpdated); |
|||
} |
|||
|
|||
void IObserver<object>.OnCompleted() |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
void IObserver<object>.OnError(Exception error) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
void IObserver<object>.OnNext(object value) |
|||
{ |
|||
OnControlValueUpdated(value); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_subscription?.Dispose(); |
|||
_subscription = null; |
|||
} |
|||
public void CommitEdit() |
|||
{ |
|||
if(_isControlValueSet) |
|||
SetSourceValue(_controlValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
// (c) Copyright Microsoft Corporation.
|
|||
// This source is subject to the Microsoft Public License (Ms-PL).
|
|||
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
|
|||
// All other rights reserved.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
internal static class ValidationUtil |
|||
{ |
|||
/// <summary>
|
|||
/// Searches a ValidationResult for the specified target member name. If the target is null
|
|||
/// or empty, this method will return true if there are no member names at all.
|
|||
/// </summary>
|
|||
/// <param name="validationResult">ValidationResult to search.</param>
|
|||
/// <param name="target">Member name to search for.</param>
|
|||
/// <returns>True if found.</returns>
|
|||
public static bool ContainsMemberName(this ValidationResult validationResult, string target) |
|||
{ |
|||
int memberNameCount = 0; |
|||
foreach (string memberName in validationResult.MemberNames) |
|||
{ |
|||
if (string.Equals(target, memberName)) |
|||
{ |
|||
return true; |
|||
} |
|||
memberNameCount++; |
|||
} |
|||
return (memberNameCount == 0 && string.IsNullOrEmpty(target)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finds an equivalent ValidationResult if one exists.
|
|||
/// </summary>
|
|||
/// <param name="collection">ValidationResults to search through.</param>
|
|||
/// <param name="target">ValidationResult to find.</param>
|
|||
/// <returns>Equal ValidationResult if found, null otherwise.</returns>
|
|||
public static ValidationResult FindEqualValidationResult(this ICollection<ValidationResult> collection, ValidationResult target) |
|||
{ |
|||
foreach (ValidationResult oldValidationResult in collection) |
|||
{ |
|||
if (oldValidationResult.ErrorMessage == target.ErrorMessage) |
|||
{ |
|||
bool movedOld = true; |
|||
bool movedTarget = true; |
|||
IEnumerator<string> oldEnumerator = oldValidationResult.MemberNames.GetEnumerator(); |
|||
IEnumerator<string> targetEnumerator = target.MemberNames.GetEnumerator(); |
|||
while (movedOld && movedTarget) |
|||
{ |
|||
movedOld = oldEnumerator.MoveNext(); |
|||
movedTarget = targetEnumerator.MoveNext(); |
|||
|
|||
if (!movedOld && !movedTarget) |
|||
{ |
|||
return oldValidationResult; |
|||
} |
|||
if (movedOld != movedTarget || oldEnumerator.Current != targetEnumerator.Current) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public static bool IsValid(this ValidationResult result) |
|||
{ |
|||
return result == null || result == ValidationResult.Success; |
|||
} |
|||
|
|||
public static IEnumerable<Exception> UnpackException(Exception exception) |
|||
{ |
|||
if (exception != null) |
|||
{ |
|||
var aggregate = exception as AggregateException; |
|||
var exceptions = aggregate == null ? |
|||
(IEnumerable<Exception>)new[] { exception } : |
|||
aggregate.InnerExceptions; |
|||
var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList(); |
|||
|
|||
if (filtered.Count > 0) |
|||
{ |
|||
return filtered; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the collection contains an equivalent ValidationResult
|
|||
/// </summary>
|
|||
/// <param name="collection">ValidationResults to search through</param>
|
|||
/// <param name="target">ValidationResult to search for</param>
|
|||
/// <returns></returns>
|
|||
public static bool ContainsEqualValidationResult(this ICollection<ValidationResult> collection, ValidationResult target) |
|||
{ |
|||
return (collection.FindEqualValidationResult(target) != null); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a new ValidationResult to the collection if an equivalent does not exist.
|
|||
/// </summary>
|
|||
/// <param name="collection">ValidationResults to search through</param>
|
|||
/// <param name="value">ValidationResult to add</param>
|
|||
public static void AddIfNew(this ICollection<ValidationResult> collection, ValidationResult value) |
|||
{ |
|||
if (!collection.ContainsEqualValidationResult(value)) |
|||
{ |
|||
collection.Add(value); |
|||
} |
|||
} |
|||
|
|||
private static bool ExceptionsMatch(Exception e1, Exception e2) |
|||
{ |
|||
return e1.Message == e2.Message; |
|||
} |
|||
public static void AddExceptionIfNew(this ICollection<Exception> collection, Exception value) |
|||
{ |
|||
if(!collection.Any(e => ExceptionsMatch(e, value))) |
|||
{ |
|||
collection.Add(value); |
|||
} |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Performs an action and catches any non-critical exceptions.
|
|||
/// </summary>
|
|||
/// <param name="action">Action to perform</param>
|
|||
public static void CatchNonCriticalExceptions(Action action) |
|||
{ |
|||
try |
|||
{ |
|||
action(); |
|||
} |
|||
catch (Exception exception) |
|||
{ |
|||
if (IsCriticalException(exception)) |
|||
{ |
|||
throw; |
|||
} |
|||
// Catch any non-critical exceptions
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines if the specified exception is un-recoverable.
|
|||
/// </summary>
|
|||
/// <param name="exception">The exception.</param>
|
|||
/// <returns>True if the process cannot be recovered from the exception.</returns>
|
|||
public static bool IsCriticalException(Exception exception) |
|||
{ |
|||
return (exception is OutOfMemoryException) || |
|||
(exception is StackOverflowException) || |
|||
(exception is AccessViolationException) || |
|||
(exception is ThreadAbortException); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue