Browse Source

Don't separate plugins by reflection.

`DataAnnotationsValidationPlugin` is public and so it can't be moved. No point in moving the others if this one will be in the wrong place.
pull/13970/head
Steven Kirk 3 years ago
parent
commit
dbcd8999f3
  1. 1
      src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs
  2. 2
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  3. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  4. 2
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  5. 90
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  6. 27
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin`1.cs
  7. 91
      src/Avalonia.Base/Data/Core/Plugins/Reflection/ObservableStreamPlugin.cs
  8. 85
      src/Avalonia.Base/Data/Core/Plugins/Reflection/TaskStreamPlugin.cs
  9. 2
      src/Avalonia.Base/Data/Core/Plugins/ReflectionMethodAccessorPlugin.cs
  10. 100
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  11. 49
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin`1.cs
  12. 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  13. 2
      tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs
  14. 1
      tests/Avalonia.Base.UnitTests/Data/Core/Plugins/ExceptionValidationPluginTests.cs
  15. 1
      tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs
  16. 2
      tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs
  17. 1
      tests/Avalonia.Benchmarks/Data/PropertyAccessorPluginBenchmarks.cs
  18. 2
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs

1
src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Data.Core.Plugins.Reflection;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
{ {

2
src/Avalonia.Base/Data/Core/Plugins/Reflection/DataAnnotationsValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Avalonia.Data.Core.Plugins.Reflection namespace Avalonia.Data.Core.Plugins
{ {
/// <summary> /// <summary>
/// Validates properties on that have <see cref="ValidationAttribute"/>s. /// Validates properties on that have <see cref="ValidationAttribute"/>s.

2
src/Avalonia.Base/Data/Core/Plugins/Reflection/InpcPropertyAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Data.Core.Plugins.Reflection namespace Avalonia.Data.Core.Plugins
{ {
/// <summary> /// <summary>
/// Reads a property from a standard C# object that optionally supports the /// Reads a property from a standard C# object that optionally supports the

2
src/Avalonia.Base/Data/Core/Plugins/Reflection/MethodAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -2,7 +2,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
namespace Avalonia.Data.Core.Plugins.Reflection; namespace Avalonia.Data.Core.Plugins;
internal class MethodAccessorPlugin : IPropertyAccessorPlugin internal class MethodAccessorPlugin : IPropertyAccessorPlugin
{ {

90
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -1,27 +1,91 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection;
using Avalonia.Reactive; using Avalonia.Reactive;
namespace Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core.Plugins
internal class ObservableStreamPlugin<T> : IStreamPlugin
{ {
public bool Match(WeakReference<object?> reference) /// <summary>
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
internal class ObservableStreamPlugin : IStreamPlugin
{ {
return reference.TryGetTarget(out var target) && target is IObservable<T>; private static MethodInfo? s_observableGeneric;
} private static MethodInfo? s_observableSelect;
public IObservable<object?> Start(WeakReference<object?> reference) [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")]
{ public ObservableStreamPlugin()
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs))
{ {
return Observable.Empty<object?>();
} }
else if (target is IObservable<object?> obj)
/// <summary>
/// Checks whether this plugin handles the specified value.
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference<object?> reference)
{ {
return obj; reference.TryGetTarget(out var target);
return target != null && target.GetType().GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
} }
return obs.Select(x => (object?)x); /// <summary>
/// Starts producing output based on the specified value.
/// </summary>
/// <param name="reference">A weak reference to the object.</param>
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object?> Start(WeakReference<object?> reference)
{
if (!reference.TryGetTarget(out var target) || target is null)
return Observable.Empty<object?>();
// If the observable returns a reference type then we can cast it.
if (target is IObservable<object?> result)
{
return result;
}
// If the observable returns a value type then we need to call Observable.Select on it.
// First get the type of T in `IObservable<T>`.
var sourceType = target.GetType().GetInterfaces().First(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
// Get the BoxObservable<T> method.
var select = GetBoxObservable(sourceType);
// Call BoxObservable(target);
return (IObservable<object?>)select.Invoke(
null,
new[] { target })!;
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static MethodInfo GetBoxObservable(Type source)
{
return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source);
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static MethodInfo GetBoxObservable()
{
return s_observableSelect
??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic)
?? throw new InvalidOperationException("BoxObservable method was not found.");
}
private static IObservable<object?> BoxObservable<T>(IObservable<T> source)
{
return source.Select(v => (object?)v);
}
} }
} }

27
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin`1.cs

@ -0,0 +1,27 @@
using System;
using System.Linq;
using Avalonia.Reactive;
namespace Avalonia.Data.Core.Plugins;
internal class ObservableStreamPlugin<T> : IStreamPlugin
{
public bool Match(WeakReference<object?> reference)
{
return reference.TryGetTarget(out var target) && target is IObservable<T>;
}
public IObservable<object?> Start(WeakReference<object?> reference)
{
if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs))
{
return Observable.Empty<object?>();
}
else if (target is IObservable<object?> obj)
{
return obj;
}
return obs.Select(x => (object?)x);
}
}

91
src/Avalonia.Base/Data/Core/Plugins/Reflection/ObservableStreamPlugin.cs

@ -1,91 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Avalonia.Reactive;
namespace Avalonia.Data.Core.Plugins.Reflection
{
/// <summary>
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
internal class ObservableStreamPlugin : IStreamPlugin
{
private static MethodInfo? s_observableGeneric;
private static MethodInfo? s_observableSelect;
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")]
public ObservableStreamPlugin()
{
}
/// <summary>
/// Checks whether this plugin handles the specified value.
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference<object?> reference)
{
reference.TryGetTarget(out var target);
return target != null && target.GetType().GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
}
/// <summary>
/// Starts producing output based on the specified value.
/// </summary>
/// <param name="reference">A weak reference to the object.</param>
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object?> Start(WeakReference<object?> reference)
{
if (!reference.TryGetTarget(out var target) || target is null)
return Observable.Empty<object?>();
// If the observable returns a reference type then we can cast it.
if (target is IObservable<object?> result)
{
return result;
}
// If the observable returns a value type then we need to call Observable.Select on it.
// First get the type of T in `IObservable<T>`.
var sourceType = target.GetType().GetInterfaces().First(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
// Get the BoxObservable<T> method.
var select = GetBoxObservable(sourceType);
// Call BoxObservable(target);
return (IObservable<object?>)select.Invoke(
null,
new[] { target })!;
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static MethodInfo GetBoxObservable(Type source)
{
return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source);
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static MethodInfo GetBoxObservable()
{
return s_observableSelect
??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic)
?? throw new InvalidOperationException("BoxObservable method was not found.");
}
private static IObservable<object?> BoxObservable<T>(IObservable<T> source)
{
return source.Select(v => (object?)v);
}
}
}

85
src/Avalonia.Base/Data/Core/Plugins/Reflection/TaskStreamPlugin.cs

@ -1,85 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Reactive;
namespace Avalonia.Data.Core.Plugins.Reflection
{
/// <summary>
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
internal class TaskStreamPlugin : IStreamPlugin
{
/// <summary>
/// Checks whether this plugin handles the specified value.
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference<object?> reference)
{
reference.TryGetTarget(out var target);
return target is Task;
}
/// <summary>
/// Starts producing output based on the specified value.
/// </summary>
/// <param name="reference">A weak reference to the object.</param>
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object?> Start(WeakReference<object?> reference)
{
reference.TryGetTarget(out var target);
if (target is Task task)
{
var resultProperty = task.GetType().GetRuntimeProperty("Result");
if (resultProperty != null)
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
case TaskStatus.Faulted:
return HandleCompleted(task);
default:
var subject = new LightweightSubject<object?>();
task.ContinueWith(
x => HandleCompleted(task).Subscribe(subject),
TaskScheduler.FromCurrentSynchronizationContext())
.ConfigureAwait(false);
return subject;
}
}
}
return Observable.Empty<object?>();
}
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static IObservable<object?> HandleCompleted(Task task)
{
var resultProperty = task.GetType().GetRuntimeProperty("Result");
if (resultProperty != null)
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
return Observable.Return(resultProperty.GetValue(task));
case TaskStatus.Faulted:
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error));
default:
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task.");
}
}
return Observable.Empty<object>();
}
}
}

2
src/Avalonia.Base/Data/Core/Plugins/Reflection/ReflectionMethodAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/ReflectionMethodAccessorPlugin.cs

@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace Avalonia.Data.Core.Plugins.Reflection namespace Avalonia.Data.Core.Plugins
{ {
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
internal class ReflectionMethodAccessorPlugin : IPropertyAccessorPlugin internal class ReflectionMethodAccessorPlugin : IPropertyAccessorPlugin

100
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@ -1,49 +1,85 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Reactive; using Avalonia.Reactive;
namespace Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core.Plugins
internal class TaskStreamPlugin<T> : IStreamPlugin
{ {
public bool Match(WeakReference<object?> reference) /// <summary>
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
[RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
internal class TaskStreamPlugin : IStreamPlugin
{ {
return reference.TryGetTarget(out var target) && target is Task<T>; /// <summary>
} /// Checks whether this plugin handles the specified value.
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference<object?> reference)
{
reference.TryGetTarget(out var target);
public IObservable<object?> Start(WeakReference<object?> reference) return target is Task;
{ }
if (!(reference.TryGetTarget(out var target) && target is Task<T> task))
/// <summary>
/// Starts producing output based on the specified value.
/// </summary>
/// <param name="reference">A weak reference to the object.</param>
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object?> Start(WeakReference<object?> reference)
{ {
reference.TryGetTarget(out var target);
if (target is Task task)
{
var resultProperty = task.GetType().GetRuntimeProperty("Result");
if (resultProperty != null)
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
case TaskStatus.Faulted:
return HandleCompleted(task);
default:
var subject = new LightweightSubject<object?>();
task.ContinueWith(
x => HandleCompleted(task).Subscribe(subject),
TaskScheduler.FromCurrentSynchronizationContext())
.ConfigureAwait(false);
return subject;
}
}
}
return Observable.Empty<object?>(); return Observable.Empty<object?>();
} }
switch (task.Status) [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)]
private static IObservable<object?> HandleCompleted(Task task)
{ {
case TaskStatus.RanToCompletion: var resultProperty = task.GetType().GetRuntimeProperty("Result");
case TaskStatus.Faulted:
return HandleCompleted(task);
default:
var subject = new LightweightSubject<object?>();
task.ContinueWith(
_ => HandleCompleted(task).Subscribe(subject),
TaskScheduler.FromCurrentSynchronizationContext())
.ConfigureAwait(false);
return subject;
}
}
if (resultProperty != null)
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
return Observable.Return(resultProperty.GetValue(task));
case TaskStatus.Faulted:
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error));
default:
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task.");
}
}
private static IObservable<object?> HandleCompleted(Task<T> task) return Observable.Empty<object>();
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
return Observable.Return((object?)task.Result);
case TaskStatus.Faulted:
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error));
default:
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task.");
} }
} }
} }

49
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin`1.cs

@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Avalonia.Reactive;
namespace Avalonia.Data.Core.Plugins;
internal class TaskStreamPlugin<T> : IStreamPlugin
{
public bool Match(WeakReference<object?> reference)
{
return reference.TryGetTarget(out var target) && target is Task<T>;
}
public IObservable<object?> Start(WeakReference<object?> reference)
{
if (!(reference.TryGetTarget(out var target) && target is Task<T> task))
{
return Observable.Empty<object?>();
}
switch (task.Status)
{
case TaskStatus.RanToCompletion:
case TaskStatus.Faulted:
return HandleCompleted(task);
default:
var subject = new LightweightSubject<object?>();
task.ContinueWith(
_ => HandleCompleted(task).Subscribe(subject),
TaskScheduler.FromCurrentSynchronizationContext())
.ConfigureAwait(false);
return subject;
}
}
private static IObservable<object?> HandleCompleted(Task<T> task)
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
return Observable.Return((object?)task.Result);
case TaskStatus.Faulted:
return Observable.Return(new BindingNotification(task.Exception!, BindingErrorType.Error));
default:
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task.");
}
}
}

1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@ -5,7 +5,6 @@ using Avalonia.Controls;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Data.Core.Plugins.Reflection;
using Avalonia.Markup.Parsers; using Avalonia.Markup.Parsers;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings

2
tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins.Reflection; using Avalonia.Data.Core.Plugins;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;

1
tests/Avalonia.Base.UnitTests/Data/Core/Plugins/ExceptionValidationPluginTests.cs

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Data.Core.Plugins.Reflection;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;

1
tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs

@ -3,7 +3,6 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Data.Core.Plugins.Reflection;
using Avalonia.Threading; using Avalonia.Threading;
using Xunit; using Xunit;

2
tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs

@ -1,5 +1,5 @@
using System; using System;
using Avalonia.Data.Core.Plugins.Reflection; using Avalonia.Data.Core.Plugins;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Data namespace Avalonia.Benchmarks.Data

1
tests/Avalonia.Benchmarks/Data/PropertyAccessorPluginBenchmarks.cs

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Data.Core.Plugins.Reflection;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Data namespace Avalonia.Benchmarks.Data

2
tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs

@ -6,7 +6,7 @@ using Avalonia.Controls.Converters;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins.Reflection; using Avalonia.Data.Core.Plugins;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;

Loading…
Cancel
Save