csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
7.5 KiB
205 lines
7.5 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Avalonia.Reactive;
|
|
using Avalonia.Controls.Metadata;
|
|
using Avalonia.Controls.Templates;
|
|
using Avalonia.Data;
|
|
|
|
namespace Avalonia.Controls
|
|
{
|
|
/// <summary>
|
|
/// A control which displays an error notifier when there is a DataValidationError.
|
|
/// Provides attached properties to track errors on a control
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// You will probably only want to create instances inside of control templates.
|
|
/// </remarks>
|
|
[PseudoClasses(":error")]
|
|
public class DataValidationErrors : ContentControl
|
|
{
|
|
|
|
/// <summary>
|
|
/// Defines the DataValidationErrors.Errors attached property.
|
|
/// </summary>
|
|
public static readonly AttachedProperty<IEnumerable<object>?> ErrorsProperty =
|
|
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<object>?>("Errors");
|
|
|
|
/// <summary>
|
|
/// Defines the DataValidationErrors.HasErrors attached property.
|
|
/// </summary>
|
|
public static readonly AttachedProperty<bool> HasErrorsProperty =
|
|
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, bool>("HasErrors");
|
|
|
|
/// <summary>
|
|
/// Defines the DataValidationErrors.ErrorConverter attached property.
|
|
/// </summary>
|
|
public static readonly AttachedProperty<Func<object, object>?> ErrorConverterProperty =
|
|
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, Func<object, object>?>("ErrorConverter");
|
|
|
|
/// <summary>
|
|
/// Defines the DataValidationErrors.ErrorTemplate property.
|
|
/// </summary>
|
|
public static readonly StyledProperty<IDataTemplate> ErrorTemplateProperty =
|
|
AvaloniaProperty.Register<DataValidationErrors, IDataTemplate>(nameof(ErrorTemplate));
|
|
|
|
/// <summary>
|
|
/// Stores the original, not converted errors passed by the control
|
|
/// </summary>
|
|
private static readonly AttachedProperty<IEnumerable<object>?> OriginalErrorsProperty =
|
|
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<object>?>("OriginalErrors");
|
|
|
|
/// <summary>
|
|
/// Prevents executing ErrorsChanged after they are updated internally from OnErrorsOrConverterChanged
|
|
/// </summary>
|
|
private static readonly AttachedProperty<bool> OverridingErrorsInternallyProperty =
|
|
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, bool>("OverridingErrorsInternally", defaultValue: false);
|
|
|
|
private Control? _owner;
|
|
|
|
public static readonly DirectProperty<DataValidationErrors, Control?> OwnerProperty =
|
|
AvaloniaProperty.RegisterDirect<DataValidationErrors, Control?>(
|
|
nameof(Owner),
|
|
o => o.Owner,
|
|
(o, v) => o.Owner = v);
|
|
|
|
public Control? Owner
|
|
{
|
|
get => _owner;
|
|
set => SetAndRaise(OwnerProperty, ref _owner, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes static members of the <see cref="DataValidationErrors"/> class.
|
|
/// </summary>
|
|
static DataValidationErrors()
|
|
{
|
|
ErrorsProperty.Changed.Subscribe(ErrorsChanged);
|
|
HasErrorsProperty.Changed.Subscribe(HasErrorsChanged);
|
|
TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>((x, e) => x.OnTemplatedParentChange(e));
|
|
ErrorConverterProperty.Changed.Subscribe(OnErrorConverterChanged);
|
|
}
|
|
|
|
private static void OnErrorConverterChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
OnErrorsOrConverterChanged((Control)e.Sender);
|
|
}
|
|
|
|
private void OnTemplatedParentChange(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
if (Owner == null)
|
|
{
|
|
Owner = (e.NewValue as Control);
|
|
}
|
|
}
|
|
|
|
public IDataTemplate ErrorTemplate
|
|
{
|
|
get => GetValue(ErrorTemplateProperty);
|
|
set => SetValue(ErrorTemplateProperty, value);
|
|
}
|
|
|
|
private static void ErrorsChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
var control = (Control)e.Sender;
|
|
|
|
if (control.GetValue(OverridingErrorsInternallyProperty)) return;
|
|
|
|
var errors = (IEnumerable<object>?)e.NewValue;
|
|
|
|
// Update original errors
|
|
control.SetValue(OriginalErrorsProperty, errors);
|
|
|
|
OnErrorsOrConverterChanged(control);
|
|
}
|
|
|
|
private static void HasErrorsChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
var control = (Control)e.Sender;
|
|
var classes = (IPseudoClasses)control.Classes;
|
|
classes.Set(":error", (bool)e.NewValue!);
|
|
}
|
|
|
|
public static IEnumerable<object>? GetErrors(Control control)
|
|
{
|
|
return control.GetValue(ErrorsProperty);
|
|
}
|
|
public static void SetErrors(Control control, IEnumerable<object>? errors)
|
|
{
|
|
control.SetValue(ErrorsProperty, errors);
|
|
}
|
|
public static void SetError(Control control, Exception? error)
|
|
{
|
|
SetErrors(control, UnpackException(error)?
|
|
.Select(UnpackDataValidationException)
|
|
.Where(e => e is not null)
|
|
.ToArray()!);
|
|
}
|
|
|
|
private static void OnErrorsOrConverterChanged(Control control)
|
|
{
|
|
var converter = GetErrorConverter(control);
|
|
var originalErrors = control.GetValue(OriginalErrorsProperty);
|
|
var newErrors = (converter is null ?
|
|
originalErrors :
|
|
originalErrors?.Select(converter)
|
|
.Where(e => e is not null))?
|
|
.ToArray();
|
|
|
|
control.SetCurrentValue(OverridingErrorsInternallyProperty, true);
|
|
try
|
|
{
|
|
control.SetCurrentValue(ErrorsProperty, newErrors!);
|
|
}
|
|
finally
|
|
{
|
|
control.SetCurrentValue(OverridingErrorsInternallyProperty, false);
|
|
}
|
|
|
|
control.SetValue(HasErrorsProperty, newErrors?.Any() == true);
|
|
}
|
|
|
|
public static void ClearErrors(Control control)
|
|
{
|
|
SetErrors(control, null);
|
|
}
|
|
public static bool GetHasErrors(Control control)
|
|
{
|
|
return control.GetValue(HasErrorsProperty);
|
|
}
|
|
|
|
public static Func<object, object?>? GetErrorConverter(Control control)
|
|
{
|
|
return control.GetValue(ErrorConverterProperty);
|
|
}
|
|
|
|
public static void SetErrorConverter(Control control, Func<object, object>? converter)
|
|
{
|
|
control.SetValue(ErrorConverterProperty, converter);
|
|
}
|
|
|
|
private static IEnumerable<Exception>? UnpackException(Exception? exception)
|
|
{
|
|
if (exception != null)
|
|
{
|
|
var exceptions = exception is AggregateException aggregate ?
|
|
aggregate.InnerExceptions :
|
|
(IEnumerable<Exception>)new[] { exception };
|
|
|
|
return exceptions.Where(x => !(x is BindingChainException)).ToArray();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static object? UnpackDataValidationException(Exception exception)
|
|
{
|
|
if (exception is DataValidationException dataValidationException)
|
|
{
|
|
return dataValidationException.ErrorData;
|
|
}
|
|
|
|
return exception;
|
|
}
|
|
}
|
|
}
|
|
|