BindingExpressionTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq.Expressions;
  5. using System.Threading.Tasks;
  6. using Avalonia.Controls;
  7. using Avalonia.Data;
  8. using Avalonia.Data.Converters;
  9. using Avalonia.Data.Core;
  10. using Avalonia.Data.Core.ExpressionNodes;
  11. using Avalonia.Markup.Parsers;
  12. using Avalonia.UnitTests;
  13. using Avalonia.Utilities;
  14. #nullable enable
  15. namespace Avalonia.Base.UnitTests.Data.Core;
  16. [InvariantCulture]
  17. public abstract partial class BindingExpressionTests
  18. {
  19. public partial class Reflection : BindingExpressionTests
  20. {
  21. private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn, TOut>(
  22. Expression<Func<TIn, TOut>> expression,
  23. AvaloniaProperty targetProperty,
  24. IValueConverter? converter,
  25. object? converterParameter,
  26. object? dataContext,
  27. bool enableDataValidation,
  28. Optional<object?> fallbackValue,
  29. BindingMode mode,
  30. RelativeSource? relativeSource,
  31. Optional<TIn> source,
  32. object? targetNullValue,
  33. string? stringFormat,
  34. UpdateSourceTrigger updateSourceTrigger)
  35. {
  36. var target = new TargetClass { DataContext = dataContext };
  37. var (path, resolver) = BindingPathFromExpressionBuilder.Build(expression);
  38. var fallback = fallbackValue.HasValue ? fallbackValue.Value : AvaloniaProperty.UnsetValue;
  39. List<ExpressionNode>? nodes = null;
  40. if (relativeSource is not null && relativeSource.Mode is not RelativeSourceMode.Self)
  41. throw new NotImplementedException();
  42. if (!string.IsNullOrEmpty(path))
  43. {
  44. var reader = new CharacterReader(path.AsSpan());
  45. var (astNodes, sourceMode) = BindingExpressionGrammar.Parse(ref reader);
  46. nodes = ExpressionNodeFactory.CreateFromAst(astNodes, resolver, null, out _);
  47. }
  48. if (!source.HasValue && relativeSource is null)
  49. {
  50. nodes ??= new();
  51. nodes.Insert(0, new DataContextNode());
  52. }
  53. var bindingExpression = new BindingExpression(
  54. source.HasValue ? source.Value : target,
  55. nodes,
  56. fallback,
  57. converter: converter,
  58. converterParameter: converterParameter,
  59. enableDataValidation: enableDataValidation,
  60. mode: mode,
  61. targetNullValue: targetNullValue,
  62. targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
  63. stringFormat: stringFormat,
  64. updateSourceTrigger: updateSourceTrigger);
  65. target.GetValueStore().AddBinding(targetProperty, bindingExpression);
  66. return (target, bindingExpression);
  67. }
  68. }
  69. public partial class Compiled : BindingExpressionTests
  70. {
  71. private protected override (TargetClass, BindingExpression) CreateTargetCore<TIn, TOut>(
  72. Expression<Func<TIn, TOut>> expression,
  73. AvaloniaProperty targetProperty,
  74. IValueConverter? converter,
  75. object? converterParameter,
  76. object? dataContext,
  77. bool enableDataValidation,
  78. Optional<object?> fallbackValue,
  79. BindingMode mode,
  80. RelativeSource? relativeSource,
  81. Optional<TIn> source,
  82. object? targetNullValue,
  83. string? stringFormat,
  84. UpdateSourceTrigger updateSourceTrigger)
  85. {
  86. var target = new TargetClass { DataContext = dataContext };
  87. var nodes = new List<ExpressionNode>();
  88. var fallback = fallbackValue.HasValue ? fallbackValue.Value : AvaloniaProperty.UnsetValue;
  89. var path = CompiledBindingPathFromExpressionBuilder.Build(expression, enableDataValidation);
  90. if (relativeSource is not null && relativeSource.Mode is not RelativeSourceMode.Self)
  91. throw new NotImplementedException();
  92. path.BuildExpression(nodes, out var _);
  93. if (!source.HasValue && relativeSource is null)
  94. nodes.Insert(0, new DataContextNode());
  95. var bindingExpression = new BindingExpression(
  96. source.HasValue ? source.Value : target,
  97. nodes,
  98. fallback,
  99. converter: converter,
  100. converterParameter: converterParameter,
  101. enableDataValidation: enableDataValidation,
  102. mode: mode,
  103. targetNullValue: targetNullValue,
  104. targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
  105. stringFormat: stringFormat,
  106. updateSourceTrigger: updateSourceTrigger);
  107. target.GetValueStore().AddBinding(targetProperty, bindingExpression);
  108. return (target, bindingExpression);
  109. }
  110. }
  111. protected TargetClass CreateTarget<TIn, TOut>(
  112. Expression<Func<TIn, TOut>> expression,
  113. AvaloniaProperty? targetProperty = null,
  114. IValueConverter? converter = null,
  115. object? converterParameter = null,
  116. object? dataContext = null,
  117. bool enableDataValidation = false,
  118. Optional<object?> fallbackValue = default,
  119. BindingMode mode = BindingMode.OneWay,
  120. RelativeSource? relativeSource = null,
  121. Optional<TIn> source = default,
  122. object? targetNullValue = null,
  123. string? stringFormat = null)
  124. where TIn : class?
  125. {
  126. var (target, _) = CreateTargetAndExpression(
  127. expression,
  128. targetProperty,
  129. converter,
  130. converterParameter,
  131. dataContext,
  132. enableDataValidation,
  133. fallbackValue,
  134. mode,
  135. relativeSource,
  136. source,
  137. targetNullValue,
  138. stringFormat);
  139. return target;
  140. }
  141. protected TargetClass CreateTargetWithSource<TIn, TOut>(
  142. TIn source,
  143. Expression<Func<TIn, TOut>> expression,
  144. AvaloniaProperty? targetProperty = null,
  145. IValueConverter? converter = null,
  146. object? converterParameter = null,
  147. bool enableDataValidation = false,
  148. Optional<object?> fallbackValue = default,
  149. BindingMode mode = BindingMode.OneWay,
  150. RelativeSource? relativeSource = null,
  151. object? targetNullValue = null,
  152. string? stringFormat = null,
  153. UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
  154. where TIn : class?
  155. {
  156. var (target, _) = CreateTargetAndExpression(
  157. expression,
  158. targetProperty,
  159. converter,
  160. converterParameter,
  161. null,
  162. enableDataValidation,
  163. fallbackValue,
  164. mode,
  165. relativeSource,
  166. source,
  167. targetNullValue,
  168. stringFormat,
  169. updateSourceTrigger);
  170. return target;
  171. }
  172. private protected (TargetClass, BindingExpression) CreateTargetAndExpression<TIn, TOut>(
  173. Expression<Func<TIn, TOut>> expression,
  174. AvaloniaProperty? targetProperty = null,
  175. IValueConverter? converter = null,
  176. object? converterParameter = null,
  177. object? dataContext = null,
  178. bool enableDataValidation = false,
  179. Optional<object?> fallbackValue = default,
  180. BindingMode mode = BindingMode.OneWay,
  181. RelativeSource? relativeSource = null,
  182. Optional<TIn> source = default,
  183. object? targetNullValue = null,
  184. string? stringFormat = null,
  185. UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
  186. where TIn : class?
  187. {
  188. targetProperty ??= typeof(TOut) switch
  189. {
  190. var t when t == typeof(bool) => TargetClass.BoolProperty,
  191. var t when t == typeof(double) => TargetClass.DoubleProperty,
  192. var t when t == typeof(int) => TargetClass.IntProperty,
  193. var t when t == typeof(string) => TargetClass.StringProperty,
  194. _ => TargetClass.ObjectProperty,
  195. };
  196. return CreateTargetCore(
  197. expression,
  198. targetProperty,
  199. converter,
  200. converterParameter,
  201. dataContext,
  202. enableDataValidation,
  203. fallbackValue,
  204. mode,
  205. relativeSource,
  206. source,
  207. targetNullValue,
  208. stringFormat,
  209. updateSourceTrigger);
  210. }
  211. private protected abstract (TargetClass, BindingExpression) CreateTargetCore<TIn, TOut>(
  212. Expression<Func<TIn, TOut>> expression,
  213. AvaloniaProperty targetProperty,
  214. IValueConverter? converter,
  215. object? converterParameter,
  216. object? dataContext,
  217. bool enableDataValidation,
  218. Optional<object?> fallbackValue,
  219. BindingMode mode,
  220. RelativeSource? relativeSource,
  221. Optional<TIn> source,
  222. object? targetNullValue,
  223. string? stringFormat,
  224. UpdateSourceTrigger updateSourceTrigger)
  225. where TIn : class?;
  226. private static IDisposable StartWithFocusSupport()
  227. {
  228. return UnitTestApplication.Start(TestServices.RealFocus);
  229. }
  230. protected class ViewModel : NotifyingBase
  231. {
  232. private bool _boolValue;
  233. private double _doubleValue;
  234. private int _intValue;
  235. private object? _objectValue;
  236. private string? _stringValue;
  237. private ViewModel? _next;
  238. private IObservable<ViewModel>? _nextObservable;
  239. private Task<ViewModel>? _nextTask;
  240. public bool BoolValue
  241. {
  242. get => _boolValue;
  243. set { _boolValue = value; RaisePropertyChanged(); }
  244. }
  245. public int IntValue
  246. {
  247. get => _intValue;
  248. set { _intValue = value; RaisePropertyChanged(); }
  249. }
  250. public double DoubleValue
  251. {
  252. get => _doubleValue;
  253. set { _doubleValue = value; RaisePropertyChanged(); }
  254. }
  255. public object? ObjectValue
  256. {
  257. get => _objectValue;
  258. set { _objectValue = value; RaisePropertyChanged(); }
  259. }
  260. public string? StringValue
  261. {
  262. get => _stringValue;
  263. set { _stringValue = value; RaisePropertyChanged(); }
  264. }
  265. public ViewModel? Next
  266. {
  267. get => _next;
  268. set { _next = value; RaisePropertyChanged(); }
  269. }
  270. public IObservable<ViewModel>? NextObservable
  271. {
  272. get => _nextObservable;
  273. set { _nextObservable = value; RaisePropertyChanged(); }
  274. }
  275. public Task<ViewModel> NextTask
  276. {
  277. get => _nextTask!;
  278. set { _nextTask = value; RaisePropertyChanged(); }
  279. }
  280. public void SetStringValueWithoutRaising(string value) => _stringValue = value;
  281. }
  282. protected class PodViewModel
  283. {
  284. public string? StringValue { get; set; }
  285. }
  286. protected class AttachedProperties
  287. {
  288. public static readonly AttachedProperty<string?> AttachedStringProperty =
  289. AvaloniaProperty.RegisterAttached<AttachedProperties, AvaloniaObject, string?>("AttachedString");
  290. }
  291. protected class SourceControl : Control
  292. {
  293. public static readonly StyledProperty<SourceControl?> NextProperty =
  294. AvaloniaProperty.Register<SourceControl, SourceControl?>("Next");
  295. public static readonly StyledProperty<string?> StringValueProperty =
  296. AvaloniaProperty.Register<SourceControl, string?>("StringValue");
  297. public SourceControl? Next
  298. {
  299. get => GetValue(NextProperty);
  300. set => SetValue(NextProperty, value);
  301. }
  302. public string? StringValue
  303. {
  304. get => GetValue(StringValueProperty);
  305. set => SetValue(StringValueProperty, value);
  306. }
  307. public string? ClrProperty { get; set; }
  308. }
  309. protected class TargetClass : Control
  310. {
  311. public static readonly StyledProperty<bool> BoolProperty =
  312. AvaloniaProperty.Register<TargetClass, bool>("Bool");
  313. public static readonly StyledProperty<double> DoubleProperty =
  314. AvaloniaProperty.Register<TargetClass, double>("Double");
  315. public static readonly StyledProperty<int> IntProperty =
  316. AvaloniaProperty.Register<TargetClass, int>("Int");
  317. public static readonly StyledProperty<object?> ObjectProperty =
  318. AvaloniaProperty.Register<TargetClass, object?>("Object");
  319. public static readonly StyledProperty<string?> StringProperty =
  320. AvaloniaProperty.Register<TargetClass, string?>("String");
  321. public static readonly DirectProperty<TargetClass, string?> ReadOnlyStringProperty =
  322. AvaloniaProperty.RegisterDirect<TargetClass, string?>(
  323. nameof(ReadOnlyString),
  324. o => o.ReadOnlyString);
  325. private string? _readOnlyString = "readonly";
  326. static TargetClass()
  327. {
  328. FocusableProperty.OverrideDefaultValue<TargetClass>(true);
  329. }
  330. public bool Bool
  331. {
  332. get => GetValue(BoolProperty);
  333. set => SetValue(BoolProperty, value);
  334. }
  335. public double Double
  336. {
  337. get => GetValue(DoubleProperty);
  338. set => SetValue(DoubleProperty, value);
  339. }
  340. public int Int
  341. {
  342. get => GetValue(IntProperty);
  343. set => SetValue(IntProperty, value);
  344. }
  345. public object? Object
  346. {
  347. get => GetValue(ObjectProperty);
  348. set => SetValue(ObjectProperty, value);
  349. }
  350. public string? String
  351. {
  352. get => GetValue(StringProperty);
  353. set => SetValue(StringProperty, value);
  354. }
  355. public string? ReadOnlyString
  356. {
  357. get => _readOnlyString;
  358. private set => SetAndRaise(ReadOnlyStringProperty, ref _readOnlyString, value);
  359. }
  360. public Dictionary<AvaloniaProperty, BindingNotification> BindingNotifications { get; } = new();
  361. public override string ToString() => nameof(TargetClass);
  362. public void SetReadOnlyString(string? value) => ReadOnlyString = value;
  363. protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
  364. {
  365. base.UpdateDataValidation(property, state, error);
  366. var type = state switch
  367. {
  368. BindingValueType b when b.HasFlag(BindingValueType.BindingError) => BindingErrorType.Error,
  369. BindingValueType b when b.HasFlag(BindingValueType.DataValidationError) => BindingErrorType.DataValidationError,
  370. _ => BindingErrorType.None,
  371. };
  372. if (type == BindingErrorType.None || error is null)
  373. BindingNotifications.Remove(property);
  374. else
  375. BindingNotifications[property] = new BindingNotification(error, type);
  376. }
  377. }
  378. protected class PrefixConverter : IValueConverter
  379. {
  380. public PrefixConverter(string? prefix = null) => Prefix = prefix;
  381. public string? Prefix { get; set; }
  382. public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
  383. {
  384. if (targetType != typeof(string))
  385. return value;
  386. var result = value?.ToString() ?? string.Empty;
  387. var prefix = parameter?.ToString() ?? Prefix;
  388. if (prefix is not null)
  389. result = prefix + result;
  390. return result;
  391. }
  392. public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
  393. {
  394. if (targetType != typeof(string) || parameter?.ToString() is not string prefix)
  395. return value;
  396. var s = value?.ToString() ?? string.Empty;
  397. if (s.StartsWith(prefix))
  398. return s.Substring(prefix.Length);
  399. else
  400. return value;
  401. }
  402. }
  403. }