| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Linq.Expressions;
- using System.Threading.Tasks;
- using Avalonia.Controls;
- using Avalonia.Data;
- using Avalonia.Data.Converters;
- using Avalonia.Data.Core;
- using Avalonia.Data.Core.ExpressionNodes;
- using Avalonia.Markup.Parsers;
- using Avalonia.UnitTests;
- using Avalonia.Utilities;
- #nullable enable
- namespace Avalonia.Base.UnitTests.Data.Core;
- [InvariantCulture]
- public abstract partial class BindingExpressionTests
- {
- public partial class Reflection : BindingExpressionTests
- {
- private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn, TOut>(
- Expression<Func<TIn, TOut>> expression,
- AvaloniaProperty targetProperty,
- IValueConverter? converter,
- object? converterParameter,
- object? dataContext,
- bool enableDataValidation,
- Optional<object?> fallbackValue,
- BindingMode mode,
- RelativeSource? relativeSource,
- Optional<TIn> source,
- object? targetNullValue,
- string? stringFormat,
- UpdateSourceTrigger updateSourceTrigger)
- {
- var target = new TargetClass { DataContext = dataContext };
- var (path, resolver) = BindingPathFromExpressionBuilder.Build(expression);
- var fallback = fallbackValue.HasValue ? fallbackValue.Value : AvaloniaProperty.UnsetValue;
- List<ExpressionNode>? nodes = null;
- if (relativeSource is not null && relativeSource.Mode is not RelativeSourceMode.Self)
- throw new NotImplementedException();
- if (!string.IsNullOrEmpty(path))
- {
- var reader = new CharacterReader(path.AsSpan());
- var (astNodes, sourceMode) = BindingExpressionGrammar.Parse(ref reader);
- nodes = ExpressionNodeFactory.CreateFromAst(astNodes, resolver, null, out _);
- }
- if (!source.HasValue && relativeSource is null)
- {
- nodes ??= new();
- nodes.Insert(0, new DataContextNode());
- }
- var bindingExpression = new BindingExpression(
- source.HasValue ? source.Value : target,
- nodes,
- fallback,
- converter: converter,
- converterParameter: converterParameter,
- enableDataValidation: enableDataValidation,
- mode: mode,
- targetNullValue: targetNullValue,
- targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
- stringFormat: stringFormat,
- updateSourceTrigger: updateSourceTrigger);
- target.GetValueStore().AddBinding(targetProperty, bindingExpression);
- return (target, bindingExpression);
- }
- }
- public partial class Compiled : BindingExpressionTests
- {
- private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn, TOut>(
- Expression<Func<TIn, TOut>> expression,
- AvaloniaProperty targetProperty,
- IValueConverter? converter,
- object? converterParameter,
- object? dataContext,
- bool enableDataValidation,
- Optional<object?> fallbackValue,
- BindingMode mode,
- RelativeSource? relativeSource,
- Optional<TIn> source,
- object? targetNullValue,
- string? stringFormat,
- UpdateSourceTrigger updateSourceTrigger)
- {
- var target = new TargetClass { DataContext = dataContext };
- var nodes = new List<ExpressionNode>();
- var fallback = fallbackValue.HasValue ? fallbackValue.Value : AvaloniaProperty.UnsetValue;
- var path = CompiledBindingPathFromExpressionBuilder.Build(expression, enableDataValidation);
- if (relativeSource is not null && relativeSource.Mode is not RelativeSourceMode.Self)
- throw new NotImplementedException();
- path.BuildExpression(nodes, out var _);
- if (!source.HasValue && relativeSource is null)
- nodes.Insert(0, new DataContextNode());
- var bindingExpression = new BindingExpression(
- source.HasValue ? source.Value : target,
- nodes,
- fallback,
- converter: converter,
- converterParameter: converterParameter,
- enableDataValidation: enableDataValidation,
- mode: mode,
- targetNullValue: targetNullValue,
- targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
- stringFormat: stringFormat,
- updateSourceTrigger: updateSourceTrigger);
- target.GetValueStore().AddBinding(targetProperty, bindingExpression);
- return (target, bindingExpression);
- }
- }
- protected TargetClass CreateTarget<TIn, TOut>(
- Expression<Func<TIn, TOut>> expression,
- AvaloniaProperty? targetProperty = null,
- IValueConverter? converter = null,
- object? converterParameter = null,
- object? dataContext = null,
- bool enableDataValidation = false,
- Optional<object?> fallbackValue = default,
- BindingMode mode = BindingMode.OneWay,
- RelativeSource? relativeSource = null,
- Optional<TIn> source = default,
- object? targetNullValue = null,
- string? stringFormat = null)
- where TIn : class?
- {
- var (target, _) = CreateTargetAndExpression(
- expression,
- targetProperty,
- converter,
- converterParameter,
- dataContext,
- enableDataValidation,
- fallbackValue,
- mode,
- relativeSource,
- source,
- targetNullValue,
- stringFormat);
- return target;
- }
- protected TargetClass CreateTargetWithSource<TIn, TOut>(
- TIn source,
- Expression<Func<TIn, TOut>> expression,
- AvaloniaProperty? targetProperty = null,
- IValueConverter? converter = null,
- object? converterParameter = null,
- bool enableDataValidation = false,
- Optional<object?> fallbackValue = default,
- BindingMode mode = BindingMode.OneWay,
- RelativeSource? relativeSource = null,
- object? targetNullValue = null,
- string? stringFormat = null,
- UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
- where TIn : class?
- {
- var (target, _) = CreateTargetAndExpression(
- expression,
- targetProperty,
- converter,
- converterParameter,
- null,
- enableDataValidation,
- fallbackValue,
- mode,
- relativeSource,
- source,
- targetNullValue,
- stringFormat,
- updateSourceTrigger);
- return target;
- }
- private protected (TargetClass, BindingExpression) CreateTargetAndExpression<TIn, TOut>(
- Expression<Func<TIn, TOut>> expression,
- AvaloniaProperty? targetProperty = null,
- IValueConverter? converter = null,
- object? converterParameter = null,
- object? dataContext = null,
- bool enableDataValidation = false,
- Optional<object?> fallbackValue = default,
- BindingMode mode = BindingMode.OneWay,
- RelativeSource? relativeSource = null,
- Optional<TIn> source = default,
- object? targetNullValue = null,
- string? stringFormat = null,
- UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
- where TIn : class?
- {
- targetProperty ??= typeof(TOut) switch
- {
- var t when t == typeof(bool) => TargetClass.BoolProperty,
- var t when t == typeof(double) => TargetClass.DoubleProperty,
- var t when t == typeof(int) => TargetClass.IntProperty,
- var t when t == typeof(string) => TargetClass.StringProperty,
- _ => TargetClass.ObjectProperty,
- };
- return CreateTargetCore(
- expression,
- targetProperty,
- converter,
- converterParameter,
- dataContext,
- enableDataValidation,
- fallbackValue,
- mode,
- relativeSource,
- source,
- targetNullValue,
- stringFormat,
- updateSourceTrigger);
- }
- private protected abstract (TargetClass, BindingExpression) CreateTargetCore<TIn, TOut>(
- Expression<Func<TIn, TOut>> expression,
- AvaloniaProperty targetProperty,
- IValueConverter? converter,
- object? converterParameter,
- object? dataContext,
- bool enableDataValidation,
- Optional<object?> fallbackValue,
- BindingMode mode,
- RelativeSource? relativeSource,
- Optional<TIn> source,
- object? targetNullValue,
- string? stringFormat,
- UpdateSourceTrigger updateSourceTrigger)
- where TIn : class?;
- private static IDisposable StartWithFocusSupport()
- {
- return UnitTestApplication.Start(TestServices.RealFocus);
- }
- protected class ViewModel : NotifyingBase
- {
- private bool _boolValue;
- private double _doubleValue;
- private int _intValue;
- private object? _objectValue;
- private string? _stringValue;
- private ViewModel? _next;
- private IObservable<ViewModel>? _nextObservable;
- private Task<ViewModel>? _nextTask;
- public bool BoolValue
- {
- get => _boolValue;
- set { _boolValue = value; RaisePropertyChanged(); }
- }
- public int IntValue
- {
- get => _intValue;
- set { _intValue = value; RaisePropertyChanged(); }
- }
- public double DoubleValue
- {
- get => _doubleValue;
- set { _doubleValue = value; RaisePropertyChanged(); }
- }
- public object? ObjectValue
- {
- get => _objectValue;
- set { _objectValue = value; RaisePropertyChanged(); }
- }
- public string? StringValue
- {
- get => _stringValue;
- set { _stringValue = value; RaisePropertyChanged(); }
- }
- public ViewModel? Next
- {
- get => _next;
- set { _next = value; RaisePropertyChanged(); }
- }
- public IObservable<ViewModel>? NextObservable
- {
- get => _nextObservable;
- set { _nextObservable = value; RaisePropertyChanged(); }
- }
- public Task<ViewModel> NextTask
- {
- get => _nextTask!;
- set { _nextTask = value; RaisePropertyChanged(); }
- }
- public void SetStringValueWithoutRaising(string value) => _stringValue = value;
- }
- protected class PodViewModel
- {
- public string? StringValue { get; set; }
- }
- protected class AttachedProperties
- {
- public static readonly AttachedProperty<string?> AttachedStringProperty =
- AvaloniaProperty.RegisterAttached<AttachedProperties, AvaloniaObject, string?>("AttachedString");
- }
- protected class SourceControl : Control
- {
- public static readonly StyledProperty<SourceControl?> NextProperty =
- AvaloniaProperty.Register<SourceControl, SourceControl?>("Next");
- public static readonly StyledProperty<string?> StringValueProperty =
- AvaloniaProperty.Register<SourceControl, string?>("StringValue");
- public SourceControl? Next
- {
- get => GetValue(NextProperty);
- set => SetValue(NextProperty, value);
- }
- public string? StringValue
- {
- get => GetValue(StringValueProperty);
- set => SetValue(StringValueProperty, value);
- }
- public string? ClrProperty { get; set; }
- }
- protected class TargetClass : Control
- {
- public static readonly StyledProperty<bool> BoolProperty =
- AvaloniaProperty.Register<TargetClass, bool>("Bool");
- public static readonly StyledProperty<double> DoubleProperty =
- AvaloniaProperty.Register<TargetClass, double>("Double");
- public static readonly StyledProperty<int> IntProperty =
- AvaloniaProperty.Register<TargetClass, int>("Int");
- public static readonly StyledProperty<object?> ObjectProperty =
- AvaloniaProperty.Register<TargetClass, object?>("Object");
- public static readonly StyledProperty<string?> StringProperty =
- AvaloniaProperty.Register<TargetClass, string?>("String");
- public static readonly DirectProperty<TargetClass, string?> ReadOnlyStringProperty =
- AvaloniaProperty.RegisterDirect<TargetClass, string?>(
- nameof(ReadOnlyString),
- o => o.ReadOnlyString);
- private string? _readOnlyString = "readonly";
- static TargetClass()
- {
- FocusableProperty.OverrideDefaultValue<TargetClass>(true);
- }
- public bool Bool
- {
- get => GetValue(BoolProperty);
- set => SetValue(BoolProperty, value);
- }
- public double Double
- {
- get => GetValue(DoubleProperty);
- set => SetValue(DoubleProperty, value);
- }
- public int Int
- {
- get => GetValue(IntProperty);
- set => SetValue(IntProperty, value);
- }
- public object? Object
- {
- get => GetValue(ObjectProperty);
- set => SetValue(ObjectProperty, value);
- }
- public string? String
- {
- get => GetValue(StringProperty);
- set => SetValue(StringProperty, value);
- }
- public string? ReadOnlyString
- {
- get => _readOnlyString;
- private set => SetAndRaise(ReadOnlyStringProperty, ref _readOnlyString, value);
- }
- public Dictionary<AvaloniaProperty, BindingNotification> BindingNotifications { get; } = new();
- public override string ToString() => nameof(TargetClass);
- public void SetReadOnlyString(string? value) => ReadOnlyString = value;
- protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
- {
- base.UpdateDataValidation(property, state, error);
- var type = state switch
- {
- BindingValueType b when b.HasFlag(BindingValueType.BindingError) => BindingErrorType.Error,
- BindingValueType b when b.HasFlag(BindingValueType.DataValidationError) => BindingErrorType.DataValidationError,
- _ => BindingErrorType.None,
- };
- if (type == BindingErrorType.None || error is null)
- BindingNotifications.Remove(property);
- else
- BindingNotifications[property] = new BindingNotification(error, type);
- }
- }
- protected class PrefixConverter : IValueConverter
- {
- public PrefixConverter(string? prefix = null) => Prefix = prefix;
- public string? Prefix { get; set; }
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- if (targetType != typeof(string))
- return value;
- var result = value?.ToString() ?? string.Empty;
- var prefix = parameter?.ToString() ?? Prefix;
- if (prefix is not null)
- result = prefix + result;
- return result;
- }
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- if (targetType != typeof(string) || parameter?.ToString() is not string prefix)
- return value;
- var s = value?.ToString() ?? string.Empty;
-
- if (s.StartsWith(prefix))
- return s.Substring(prefix.Length);
- else
- return value;
- }
- }
- }
|