Browse Source

Move `IBinding` to `BindingBase` and tidy up some binding APIs (#19589)

* Update ncrunch config.

* Tidy up reflection and multi-binding APIs:

- Move `BindingBase` and `MultiBinding` into Avalonia.Base
- `BindingBase` becomes a true base class for all bindings, and contains only the `Instance` method
- Properties common between reflection and compiled bindings are moved into `StandardBindingBase`
- `Binding` is moved to Avalonia.Base and renamed to `ReflectionBinding`
- A compatibility shim for `Binding` remains in Avalonia.Markup
- Remove `IBinding` and `IBinding2`
- Remove `ITreeDataTemplate's usage of `InstancedBinding`
- Remove `NativeMenuBarPresenter`s usage of `InstancedBinding`
- Remove `InstancedBinding` as it is now unused

This required an update to the DataGrid submodule: cell data validation has been temporarily removed as this used `InstancedBinding`.

* `Instance()` => `CreateInstance()`.

The use of "Instance" as a verb is quite unusual apparently ;)

* Seal classes where appropriate.

* Seal classes where appropriate.

* Remove `StandardBindingBase`.

Simply duplicate the members in reflection and compiled binding classes.

* Delete deleted submodule directory.

* Add missing attribute.

Fixes compile error.

* Fix reference to removed class.

* Update suppressions.
pull/20249/merge
Steven Kirk 4 weeks ago
committed by GitHub
parent
commit
f2fc8d02f4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      .ncrunch/Avalonia.Diagnostics.net6.0.v3.ncrunchproject
  2. 5
      .ncrunch/Avalonia.Diagnostics.net8.0.v3.ncrunchproject
  3. 5
      .ncrunch/Avalonia.Diagnostics.netstandard2.0.v3.ncrunchproject
  4. 508
      api/Avalonia.nupkg.xml
  5. 2
      src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
  6. 22
      src/Avalonia.Base/AvaloniaObject.cs
  7. 25
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  8. 2
      src/Avalonia.Base/ClassBindingManager.cs
  9. 28
      src/Avalonia.Base/Data/BindingBase.cs
  10. 2
      src/Avalonia.Base/Data/BindingExpressionBase.cs
  11. 122
      src/Avalonia.Base/Data/BindingOperations.cs
  12. 2
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  13. 20
      src/Avalonia.Base/Data/Core/IBinding2.cs
  14. 11
      src/Avalonia.Base/Data/Core/MultiBindingExpression.cs
  15. 3
      src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs
  16. 2
      src/Avalonia.Base/Data/Core/Parsers/BindingExpressionGrammar.cs
  17. 2
      src/Avalonia.Base/Data/Core/Parsers/ExpressionNodeFactory.cs
  18. 9
      src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs
  19. 35
      src/Avalonia.Base/Data/IBinding.cs
  20. 17
      src/Avalonia.Base/Data/IndexerBinding.cs
  21. 172
      src/Avalonia.Base/Data/InstancedBinding.cs
  22. 23
      src/Avalonia.Base/Data/MultiBinding.cs
  23. 237
      src/Avalonia.Base/Data/ReflectionBinding.cs
  24. 0
      src/Avalonia.Base/Data/RelativeSource.cs
  25. 21
      src/Avalonia.Base/Data/TemplateBinding.Observable.cs
  26. 209
      src/Avalonia.Base/Data/TemplateBinding.cs
  27. 170
      src/Avalonia.Base/Data/TemplateBindingExpression.cs
  28. 2
      src/Avalonia.Base/StyledElementExtensions.cs
  29. 8
      src/Avalonia.Base/Styling/Setter.cs
  30. 4
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  31. 2
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
  32. 8
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  33. 8
      src/Avalonia.Controls/ComboBox.cs
  34. 2
      src/Avalonia.Controls/Control.cs
  35. 8
      src/Avalonia.Controls/ItemsControl.cs
  36. 5
      src/Avalonia.Controls/NativeMenuBarPresenter.cs
  37. 5
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  38. 5
      src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
  39. 12
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  40. 8
      src/Avalonia.Controls/Primitives/TextSearch.cs
  41. 11
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  42. 13
      src/Avalonia.Controls/Templates/ITreeDataTemplate.cs
  43. 6
      src/Avalonia.Controls/Utils/BindingEvaluator.cs
  44. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  45. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs
  46. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  47. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  48. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  49. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  50. 10
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  51. 107
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  52. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  53. 18
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  54. 71
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
  55. 25
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  56. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  57. 1
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  58. 219
      src/Markup/Avalonia.Markup/Data/Binding.cs
  59. 137
      src/Markup/Avalonia.Markup/Data/BindingBase.cs
  60. 6
      src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs
  61. 2
      src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs
  62. 4
      src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
  63. 19
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  64. 51
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Obsolete.cs
  65. 2
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
  66. 4
      tests/Avalonia.Base.UnitTests/Data/Core/Parsers/BindingExpressionGrammarTests.cs
  67. 2
      tests/Avalonia.Base.UnitTests/Data/Core/Parsers/BindingExpressionGrammarTests_Errors.cs
  68. 4
      tests/Avalonia.Benchmarks/Data/TemplateBinding_Setup.cs
  69. 4
      tests/Avalonia.Benchmarks/Data/TemplateBinding_Values.cs
  70. 4
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  71. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  72. 32
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  73. 9
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs
  74. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeFactoryTests.cs
  75. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs
  76. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AvaloniaProperty.cs
  77. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs
  78. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs
  79. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs
  80. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs
  81. 4
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  82. 4
      tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
  83. 10
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AssignBindingTests.cs
  84. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  85. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs

5
.ncrunch/Avalonia.Diagnostics.net6.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Diagnostics.net8.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Diagnostics.netstandard2.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

508
api/Avalonia.nupkg.xml

@ -1,24 +1,84 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids --> <!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.IBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.InstancedBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target> <Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.BindingBase</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.RelativeSourceMode</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.TreeType</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target> <Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
<Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net6.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net6.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.IBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.InstancedBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target> <Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.BindingBase</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.RelativeSourceMode</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.TreeType</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target> <Target>T:Avalonia.Media.Fonts.FontFamilyLoader</Target>
@ -31,6 +91,60 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObject.Bind(Avalonia.AvaloniaProperty,Avalonia.Data.IBinding)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObject.get_Item(Avalonia.Data.IndexerDescriptor)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.IBinding,System.Object)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObjectExtensions.ToBinding``1(System.IObservable{``0})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.BindingOperations.Apply(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.InstancedBinding,System.Object)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.BindingOperations.Apply(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.InstancedBinding)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.TemplateBinding.Initiate(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,System.Object,System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.TemplateBinding.ProvideValue</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.TemplateBinding.Subscribe(System.IObserver{System.Object})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target> <Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
@ -55,6 +169,30 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.StyledElementExtensions.BindClass(Avalonia.StyledElement,System.String,Avalonia.Data.IBinding,System.Object)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.ItemsControl.DisplayMemberBindingProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedValueBindingProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.TextSearch.TextBindingProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target> <Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -67,6 +205,24 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1.#ctor(Avalonia.Data.IBinding)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1.get_ValueBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.AutoCompleteBox.get_ValueMemberBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object)</Target> <Target>M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object)</Target>
@ -133,6 +289,72 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.ItemsControl.get_DisplayMemberBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.SelectingItemsControl.get_SelectedValueBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.GetTextBinding(Avalonia.Interactivity.Interactive)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.SetTextBinding(Avalonia.Interactivity.Interactive,Avalonia.Data.IBinding)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Templates.FuncTreeDataTemplate.ItemsSelector(System.Object)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Templates.ITreeDataTemplate.ItemsSelector(System.Object)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.MultiBinding.get_Bindings</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.MultiBinding.Initiate(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,System.Object,System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Markup.Xaml.MarkupExtensions.DynamicResourceExtension.ProvideValue(System.IServiceProvider)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension.ProvideValue(System.IServiceProvider)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSelector(System.Object)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target> <Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target>
@ -169,6 +391,60 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObject.Bind(Avalonia.AvaloniaProperty,Avalonia.Data.IBinding)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObject.get_Item(Avalonia.Data.IndexerDescriptor)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.IBinding,System.Object)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.AvaloniaObjectExtensions.ToBinding``1(System.IObservable{``0})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.BindingOperations.Apply(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.InstancedBinding,System.Object)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.BindingOperations.Apply(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,Avalonia.Data.InstancedBinding)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.TemplateBinding.Initiate(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,System.Object,System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.TemplateBinding.ProvideValue</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.TemplateBinding.Subscribe(System.IObserver{System.Object})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target> <Target>M:Avalonia.Media.Fonts.FontCollectionBase.Initialize(Avalonia.Platform.IFontManagerImpl)</Target>
@ -193,6 +469,30 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.StyledElementExtensions.BindClass(Avalonia.StyledElement,System.String,Avalonia.Data.IBinding,System.Object)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.ItemsControl.DisplayMemberBindingProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedValueBindingProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Primitives.TextSearch.TextBindingProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target> <Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -205,6 +505,24 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1.#ctor(Avalonia.Data.IBinding)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.AutoCompleteBox.BindingEvaluator`1.get_ValueBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.AutoCompleteBox.get_ValueMemberBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object)</Target> <Target>M:Avalonia.Controls.Design.CreatePreviewWithControl(System.Object)</Target>
@ -271,12 +589,78 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.ItemsControl.get_DisplayMemberBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.SelectingItemsControl.get_SelectedValueBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.GetTextBinding(Avalonia.Interactivity.Interactive)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.SetTextBinding(Avalonia.Interactivity.Interactive,Avalonia.Data.IBinding)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Templates.FuncTreeDataTemplate.ItemsSelector(System.Object)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Templates.ITreeDataTemplate.ItemsSelector(System.Object)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType)</Target> <Target>M:Avalonia.Dialogs.Internal.ManagedFileChooserFilterViewModel.#ctor(Avalonia.Platform.Storage.FilePickerFileType)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Dialogs.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.MultiBinding.get_Bindings</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Data.MultiBinding.Initiate(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,System.Object,System.Boolean)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Markup.Xaml.MarkupExtensions.DynamicResourceExtension.ProvideValue(System.IServiceProvider)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension.ProvideValue(System.IServiceProvider)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSelector(System.Object)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target> <Target>F:Avalonia.Media.Fonts.FontCollectionBase._glyphTypefaceCache</Target>
@ -331,6 +715,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Controls.Templates.ITreeDataTemplate.BindChildren(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,System.Object)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0006</DiagnosticId> <DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target> <Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target>
@ -439,6 +829,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Controls.Templates.ITreeDataTemplate.BindChildren(Avalonia.AvaloniaObject,Avalonia.AvaloniaProperty,System.Object)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0006</DiagnosticId> <DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target> <Target>M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64)</Target>
@ -517,6 +913,114 @@
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Left> <Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Right> <Right>current/Avalonia/lib/netstandard2.0/Avalonia.OpenGL.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0007</DiagnosticId>
<Target>T:Avalonia.Data.TemplateBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0007</DiagnosticId>
<Target>T:Avalonia.Data.TemplateBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Data.TemplateBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Data.Binding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Data.TemplateBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Data.Binding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Data.TemplateBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Data.MultiBinding</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.DynamicResourceExtension</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Data.TemplateBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Data.MultiBinding</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.DynamicResourceExtension</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Markup.Xaml.dll</Right>
</Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0012</DiagnosticId> <DiagnosticId>CP0012</DiagnosticId>
<Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Count</Target> <Target>M:Avalonia.Media.Fonts.FontCollectionBase.get_Count</Target>
@ -637,4 +1141,4 @@
<Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left> <Left>baseline/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right> <Right>current/Avalonia/lib/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression> </Suppression>
</Suppressions> </Suppressions>

2
src/Avalonia.Base/Animation/AnimatorKeyFrame.cs

@ -46,7 +46,7 @@ namespace Avalonia.Animation
Property = setter.Property; Property = setter.Property;
var value = setter.Value; var value = setter.Value;
if (value is IBinding binding) if (value is BindingBase binding)
{ {
return Bind(ValueProperty, binding, targetControl); return Bind(ValueProperty, binding, targetControl);
} }

22
src/Avalonia.Base/AvaloniaObject.cs

@ -98,7 +98,7 @@ namespace Avalonia
/// Gets or sets a binding for a <see cref="AvaloniaProperty"/>. /// Gets or sets a binding for a <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <param name="binding">The binding information.</param> /// <param name="binding">The binding information.</param>
public IBinding this[IndexerDescriptor binding] public BindingBase this[IndexerDescriptor binding]
{ {
get { return new IndexerBinding(this, binding.Property!, binding.Mode); } get { return new IndexerBinding(this, binding.Property!, binding.Mode); }
set { this.Bind(binding.Property!, value); } set { this.Bind(binding.Property!, value); }
@ -417,14 +417,14 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an <see cref="IBinding"/>. /// Binds a <see cref="AvaloniaProperty"/> to an <see cref="BindingBase"/>.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="binding">The binding.</param> /// <param name="binding">The binding.</param>
/// <returns> /// <returns>
/// The binding expression which represents the binding instance on this object. /// The binding expression which represents the binding instance on this object.
/// </returns> /// </returns>
public BindingExpressionBase Bind(AvaloniaProperty property, IBinding binding) public BindingExpressionBase Bind(AvaloniaProperty property, BindingBase binding)
{ {
return Bind(property, binding, null); return Bind(property, binding, null);
} }
@ -474,9 +474,9 @@ namespace Avalonia
VerifyAccess(); VerifyAccess();
ValidatePriority(priority); ValidatePriority(priority);
if (source is IBinding2 b) if (source is BindingBase b)
{ {
if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression) if (b.CreateInstance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}."); throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
if (priority != expression.Priority) if (priority != expression.Priority)
@ -574,9 +574,9 @@ namespace Avalonia
throw new ArgumentException($"The property {property.Name} is readonly."); throw new ArgumentException($"The property {property.Name} is readonly.");
} }
if (source is IBinding2 b) if (source is BindingBase b)
{ {
if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression) if (b.CreateInstance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}."); throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
return GetValueStore().AddBinding(property, expression); return GetValueStore().AddBinding(property, expression);
} }
@ -643,7 +643,7 @@ namespace Avalonia
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property); public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
/// <summary> /// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an <see cref="IBinding"/>. /// Binds a <see cref="AvaloniaProperty"/> to an <see cref="BindingBase"/>.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="binding">The binding.</param> /// <param name="binding">The binding.</param>
@ -656,11 +656,9 @@ namespace Avalonia
/// <returns> /// <returns>
/// The binding expression which represents the binding instance on this object. /// The binding expression which represents the binding instance on this object.
/// </returns> /// </returns>
internal BindingExpressionBase Bind(AvaloniaProperty property, IBinding binding, object? anchor) internal BindingExpressionBase Bind(AvaloniaProperty property, BindingBase binding, object? anchor)
{ {
if (binding is not IBinding2 b) if (binding.CreateInstance(this, property, anchor) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Unsupported IBinding implementation '{binding}'.");
if (b.Instance(this, property, anchor) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}."); throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
return GetValueStore().AddBinding(property, expression); return GetValueStore().AddBinding(property, expression);

25
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -11,12 +11,12 @@ namespace Avalonia
public static class AvaloniaObjectExtensions public static class AvaloniaObjectExtensions
{ {
/// <summary> /// <summary>
/// Converts an <see cref="IObservable{T}"/> to an <see cref="IBinding"/>. /// Converts an <see cref="IObservable{T}"/> to an <see cref="BindingBase"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type produced by the observable.</typeparam> /// <typeparam name="T">The type produced by the observable.</typeparam>
/// <param name="source">The observable</param> /// <param name="source">The observable</param>
/// <returns>An <see cref="IBinding"/>.</returns> /// <returns>An <see cref="BindingBase"/>.</returns>
public static IBinding ToBinding<T>(this IObservable<T> source) public static BindingBase ToBinding<T>(this IObservable<T> source)
{ {
return new BindingAdaptor( return new BindingAdaptor(
typeof(T).IsValueType typeof(T).IsValueType
@ -228,7 +228,7 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="IBinding"/>. /// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="BindingBase"/>.
/// </summary> /// </summary>
/// <param name="target">The object.</param> /// <param name="target">The object.</param>
/// <param name="property">The property to bind.</param> /// <param name="property">The property to bind.</param>
@ -244,7 +244,7 @@ namespace Avalonia
public static IDisposable Bind( public static IDisposable Bind(
this AvaloniaObject target, this AvaloniaObject target,
AvaloniaProperty property, AvaloniaProperty property,
IBinding binding, BindingBase binding,
object? anchor = null) object? anchor = null)
{ {
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
@ -359,7 +359,7 @@ namespace Avalonia
return observable.Subscribe(new ClassHandlerObserver<TTarget, TValue>(action)); return observable.Subscribe(new ClassHandlerObserver<TTarget, TValue>(action));
} }
private class BindingAdaptor : IBinding2 private class BindingAdaptor : BindingBase
{ {
private readonly IObservable<object?> _source; private readonly IObservable<object?> _source;
@ -368,17 +368,10 @@ namespace Avalonia
this._source = source; this._source = source;
} }
public InstancedBinding? Initiate( internal override BindingExpressionBase CreateInstance(
AvaloniaObject target, AvaloniaObject target,
AvaloniaProperty? targetProperty, AvaloniaProperty? property,
object? anchor = null, object? anchor)
bool enableDataValidation = false)
{
var expression = new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue);
return new InstancedBinding(expression, BindingMode.OneWay, BindingPriority.LocalValue);
}
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
{ {
return new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue); return new UntypedObservableBindingExpression(_source, BindingPriority.LocalValue);
} }

2
src/Avalonia.Base/ClassBindingManager.cs

@ -12,7 +12,7 @@ namespace Avalonia
private static readonly Dictionary<string, AvaloniaProperty> s_RegisteredProperties = private static readonly Dictionary<string, AvaloniaProperty> s_RegisteredProperties =
new Dictionary<string, AvaloniaProperty>(); new Dictionary<string, AvaloniaProperty>();
public static IDisposable Bind(StyledElement target, string className, IBinding source, object anchor) public static IDisposable Bind(StyledElement target, string className, BindingBase source, object anchor)
{ {
var prop = GetClassProperty(className); var prop = GetClassProperty(className);
return target.Bind(prop, source); return target.Bind(prop, source);

28
src/Avalonia.Base/Data/BindingBase.cs

@ -0,0 +1,28 @@
namespace Avalonia.Data;
/// <summary>
/// Base class for the various types of binding supported by Avalonia.
/// </summary>
public abstract class BindingBase
{
/// <summary>
/// Creates a <see cref="BindingExpressionBase"/> from a binding.
/// </summary>
/// <param name="target">The target of the binding.</param>
/// <param name="targetProperty">The target property of the binding.</param>
/// <param name="anchor">
/// If <paramref name="target"/> is not a control, provides an anchor object from which to
/// locate a data context or other controls.
/// </param>
/// <returns>
/// A newly instantiated <see cref="BindingExpressionBase"/>.
/// </returns>
/// <remarks>
/// This is a low-level method which returns a binding expression that is not yet connected to
/// a binding sink, and so is inactive.
/// </remarks>
internal abstract BindingExpressionBase CreateInstance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor);
}

2
src/Avalonia.Base/Data/BindingExpressionBase.cs

@ -10,8 +10,6 @@ public abstract class BindingExpressionBase : IDisposable, ISetterInstance
{ {
} }
internal BindingMode Mode { get; private protected set; }
public virtual void Dispose() public virtual void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

122
src/Avalonia.Base/Data/BindingOperations.cs

@ -1,6 +1,4 @@
using System; using System;
using Avalonia.Diagnostics;
using Avalonia.Reactive;
namespace Avalonia.Data namespace Avalonia.Data
{ {
@ -8,99 +6,6 @@ namespace Avalonia.Data
{ {
public static readonly object DoNothing = new DoNothingType(); public static readonly object DoNothing = new DoNothingType();
/// <summary>
/// Applies an <see cref="InstancedBinding"/> a property on an <see cref="AvaloniaObject"/>.
/// </summary>
/// <param name="target">The target object.</param>
/// <param name="property">The property to bind.</param>
/// <param name="binding">The instanced binding.</param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
[Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
public static IDisposable Apply(
AvaloniaObject target,
AvaloniaProperty property,
InstancedBinding binding)
{
_ = target ?? throw new ArgumentNullException(nameof(target));
_ = property ?? throw new ArgumentNullException(nameof(property));
_ = binding ?? throw new ArgumentNullException(nameof(binding));
if (binding.Expression is { } expression)
{
return target.GetValueStore().AddBinding(property, expression);
}
var mode = binding.Mode;
if (mode == BindingMode.Default)
{
mode = property.GetMetadata(target).DefaultBindingMode;
}
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
return target.Bind(property, binding.Source, binding.Priority);
case BindingMode.TwoWay:
{
if (binding.Source is not IObserver<object?> observer)
throw new InvalidOperationException("InstancedBinding does not contain a subject.");
return new TwoWayBindingDisposable(
target.Bind(property, binding.Source, binding.Priority),
target.GetObservable(property).Subscribe(observer));
}
case BindingMode.OneTime:
{
// Perf: Avoid allocating closure in the outer scope.
var targetCopy = target;
var propertyCopy = property;
var bindingCopy = binding;
return binding.Source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1)
.Subscribe(x => targetCopy.SetValue(
propertyCopy,
BindingNotification.ExtractValue(x),
bindingCopy.Priority));
}
case BindingMode.OneWayToSource:
{
if (binding.Source is not IObserver<object?> observer)
throw new InvalidOperationException("InstancedBinding does not contain a subject.");
return Observable.CombineLatest(
binding.Source,
target.GetObservable(property),
(_, v) => v)
.Subscribe(x => observer.OnNext(x));
}
default:
throw new ArgumentException("Invalid binding mode.");
}
}
/// <summary>
/// Applies an <see cref="InstancedBinding"/> a property on an <see cref="AvaloniaObject"/>.
/// </summary>
/// <param name="target">The target object.</param>
/// <param name="property">The property to bind.</param>
/// <param name="binding">The instanced binding.</param>
/// <param name="anchor">Obsolete, unused.</param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
[Obsolete("Use the Apply(AvaloniaObject, AvaloniaProperty, InstancedBinding) overload.")]
public static IDisposable Apply(
AvaloniaObject target,
AvaloniaProperty property,
InstancedBinding binding,
object? anchor)
{
return Apply(target, property, binding);
}
/// <summary> /// <summary>
/// Retrieves the <see cref="BindingExpressionBase"/> that is currently active on the /// Retrieves the <see cref="BindingExpressionBase"/> that is currently active on the
/// specified property. /// specified property.
@ -119,33 +24,6 @@ namespace Avalonia.Data
{ {
return target.GetValueStore().GetExpression(property); return target.GetValueStore().GetExpression(property);
} }
private sealed class TwoWayBindingDisposable : IDisposable
{
private readonly IDisposable _toTargetSubscription;
private readonly IDisposable _fromTargetSubsription;
private bool _isDisposed;
public TwoWayBindingDisposable(IDisposable toTargetSubscription, IDisposable fromTargetSubsription)
{
_toTargetSubscription = toTargetSubscription;
_fromTargetSubsription = fromTargetSubsription;
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_fromTargetSubsription.Dispose();
_toTargetSubscription.Dispose();
_isDisposed = true;
}
}
} }
public sealed class DoNothingType public sealed class DoNothingType

2
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -23,7 +23,7 @@ namespace Avalonia.Data.Core;
/// A <see cref="BindingExpression"/> represents a untyped binding which has been /// A <see cref="BindingExpression"/> represents a untyped binding which has been
/// instantiated on an object. /// instantiated on an object.
/// </remarks> /// </remarks>
internal partial class BindingExpression : UntypedBindingExpressionBase, IDescription, IDisposable internal class BindingExpression : UntypedBindingExpressionBase, IDescription, IDisposable
{ {
private static readonly List<ExpressionNode> s_emptyExpressionNodes = new(); private static readonly List<ExpressionNode> s_emptyExpressionNodes = new();
private readonly WeakReference<object?>? _source; private readonly WeakReference<object?>? _source;

20
src/Avalonia.Base/Data/Core/IBinding2.cs

@ -1,20 +0,0 @@
namespace Avalonia.Data.Core;
/// <summary>
/// Internal interface for instancing bindings on an <see cref="AvaloniaObject"/>.
/// </summary>
/// <remarks>
/// TODO12: The presence of this interface is a hack needed because we can't break our API until
/// 12.0. The Instance method would ideally be located as an internal method on a BindingBase
/// class, but we already have a BindingBase in 11.x which is not suitable for this as it contains
/// extra members that are not needed on all of the binding types. The current BindingBase should
/// be renamed to something like BindingMarkupExtensionBase and a new BindingBase created with the
/// Instance method from this interface. This interface should then be removed.
/// </remarks>
internal interface IBinding2 : IBinding
{
BindingExpressionBase Instance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor);
}

11
src/Avalonia.Base/Data/Core/MultiBindingExpression.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core;
internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingExpressionSink internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingExpressionSink
{ {
private static readonly object s_uninitialized = new object(); private static readonly object s_uninitialized = new object();
private readonly IBinding[] _bindings; private readonly BindingBase[] _bindings;
private readonly IMultiValueConverter? _converter; private readonly IMultiValueConverter? _converter;
private readonly CultureInfo? _converterCulture; private readonly CultureInfo? _converterCulture;
private readonly object? _converterParameter; private readonly object? _converterParameter;
@ -22,7 +22,7 @@ internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingEx
public MultiBindingExpression( public MultiBindingExpression(
BindingPriority priority, BindingPriority priority,
IList<IBinding> bindings, IList<BindingBase> bindings,
IMultiValueConverter? converter, IMultiValueConverter? converter,
CultureInfo? converterCulture, CultureInfo? converterCulture,
object? converterParameter, object? converterParameter,
@ -63,12 +63,7 @@ internal class MultiBindingExpression : UntypedBindingExpressionBase, IBindingEx
for (var i = 0; i < _bindings.Length; ++i) for (var i = 0; i < _bindings.Length; ++i)
{ {
var binding = _bindings[i]; var expression = _bindings[i].CreateInstance(target, null, null);
if (binding is not IBinding2 b)
throw new NotSupportedException($"Unsupported IBinding implementation '{binding}'.");
var expression = b.Instance(target, null, null);
if (expression is not UntypedBindingExpressionBase e) if (expression is not UntypedBindingExpressionBase e)
throw new NotSupportedException($"Unsupported BindingExpressionBase implementation '{expression}'."); throw new NotSupportedException($"Unsupported BindingExpressionBase implementation '{expression}'.");

3
src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs → src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs

@ -1,8 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Data.Core;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Markup.Parsers namespace Avalonia.Data.Core.Parsers
{ {
internal static class ArgumentListParser internal static class ArgumentListParser
{ {

2
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs → src/Avalonia.Base/Data/Core/Parsers/BindingExpressionGrammar.cs

@ -6,7 +6,7 @@ using System.Collections.Generic;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Markup.Parsers namespace Avalonia.Data.Core.Parsers
{ {
internal enum SourceMode internal enum SourceMode
{ {

2
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionNodeFactory.cs → src/Avalonia.Base/Data/Core/Parsers/ExpressionNodeFactory.cs

@ -7,7 +7,7 @@ using Avalonia.Data;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.ExpressionNodes.Reflection; using Avalonia.Data.Core.ExpressionNodes.Reflection;
namespace Avalonia.Markup.Parsers namespace Avalonia.Data.Core.Parsers
{ {
/// <summary> /// <summary>
/// Creates <see cref="ExpressionNode"/>s from a <see cref="BindingExpressionGrammar"/>. /// Creates <see cref="ExpressionNode"/>s from a <see cref="BindingExpressionGrammar"/>.

9
src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs

@ -220,11 +220,10 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
/// The binding expression is already instantiated on an AvaloniaObject. /// The binding expression is already instantiated on an AvaloniaObject.
/// </exception> /// </exception>
/// <remarks> /// <remarks>
/// This method is mostly here for backwards compatibility with <see cref="InstancedBinding"/> /// This method is mostly here for unit testing and we may want to remove it in future. In
/// and unit testing and we may want to remove it in future. In particular its usefulness in /// particular its usefulness is limited in that it preserves the semantics of binding
/// terms of unit testing is limited in that it preserves the semantics of binding expressions /// expressions as expected by unit tests, not necessarily the semantics that will be used
/// as expected by unit tests, not necessarily the semantics that will be used when the /// when the expression is used as an <see cref="IValueEntry"/> instantiated in a
/// expression is used as an <see cref="IValueEntry"/> instantiated in a
/// <see cref="ValueStore"/>. Unit tests should be migrated to not test the behaviour of /// <see cref="ValueStore"/>. Unit tests should be migrated to not test the behaviour of
/// binding expressions through an observable, and instead test the behaviour of the binding /// binding expressions through an observable, and instead test the behaviour of the binding
/// when applied to an <see cref="AvaloniaObject"/>. /// when applied to an <see cref="AvaloniaObject"/>.

35
src/Avalonia.Base/Data/IBinding.cs

@ -1,35 +0,0 @@
using System;
using Avalonia.Diagnostics;
using Avalonia.Metadata;
namespace Avalonia.Data
{
/// <summary>
/// Holds a binding that can be applied to a property on an object.
/// </summary>
[NotClientImplementable]
public interface IBinding
{
/// <summary>
/// Initiates the binding on a target object.
/// </summary>
/// <param name="target">The target instance.</param>
/// <param name="targetProperty">The target property. May be null.</param>
/// <param name="anchor">
/// An optional anchor from which to locate required context. When binding to objects that
/// are not in the logical tree, certain types of binding need an anchor into the tree in
/// order to locate named controls or resources. The <paramref name="anchor"/> parameter
/// can be used to provide this context.
/// </param>
/// <param name="enableDataValidation">Whether data validation should be enabled.</param>
/// <returns>
/// A <see cref="InstancedBinding"/> or null if the binding could not be resolved.
/// </returns>
[Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
InstancedBinding? Initiate(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor = null,
bool enableDataValidation = false);
}
}

17
src/Avalonia.Base/Data/IndexerBinding.cs

@ -4,7 +4,7 @@ using Avalonia.Diagnostics;
namespace Avalonia.Data namespace Avalonia.Data
{ {
internal class IndexerBinding : IBinding2 internal class IndexerBinding : BindingBase
{ {
public IndexerBinding( public IndexerBinding(
AvaloniaObject source, AvaloniaObject source,
@ -16,22 +16,15 @@ namespace Avalonia.Data
Mode = mode; Mode = mode;
} }
private AvaloniaObject Source { get; }
public AvaloniaProperty Property { get; } public AvaloniaProperty Property { get; }
private AvaloniaObject Source { get; }
private BindingMode Mode { get; } private BindingMode Mode { get; }
[Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)] internal override BindingExpressionBase CreateInstance(
public InstancedBinding? Initiate(
AvaloniaObject target, AvaloniaObject target,
AvaloniaProperty? targetProperty, AvaloniaProperty? targetProperty,
object? anchor = null, object? anchor)
bool enableDataValidation = false)
{
var expression = new IndexerBindingExpression(Source, Property, target, targetProperty, Mode);
return new InstancedBinding(expression, Mode, BindingPriority.LocalValue);
}
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
{ {
return new IndexerBindingExpression(Source, Property, target, targetProperty, Mode); return new IndexerBindingExpression(Source, Property, target, targetProperty, Mode);
} }

172
src/Avalonia.Base/Data/InstancedBinding.cs

@ -1,172 +0,0 @@
using System;
using System.ComponentModel;
using Avalonia.Data.Core;
using Avalonia.Reactive;
using ObservableEx = Avalonia.Reactive.Observable;
namespace Avalonia.Data
{
/// <summary>
/// Holds the result of calling <see cref="IBinding.Initiate"/>.
/// </summary>
/// <remarks>
/// Whereas an <see cref="IBinding"/> holds a description of a binding such as "Bind to the X
/// property on a control's DataContext"; this class represents a binding that has been
/// *instanced* by calling <see cref="IBinding.Initiate(AvaloniaObject, AvaloniaProperty, object, bool)"/>
/// on a target object.
/// </remarks>
public sealed class InstancedBinding
{
private readonly AvaloniaObject? _target;
private readonly UntypedBindingExpressionBase? _expression;
private IObservable<object?>? _observable;
/// <summary>
/// Initializes a new instance of the <see cref="InstancedBinding"/> class.
/// </summary>
/// <param name="source">The binding source.</param>
/// <param name="mode">The binding mode.</param>
/// <param name="priority">The priority of the binding.</param>
/// <remarks>
/// This constructor can be used to create any type of binding and as such requires an
/// <see cref="IObservable{Object}"/> as the binding source because this is the only binding
/// source which can be used for all binding modes. If you wish to create an instance with
/// something other than a subject, use one of the static creation methods on this class.
/// </remarks>
internal InstancedBinding(IObservable<object?> source, BindingMode mode, BindingPriority priority)
{
Mode = mode;
Priority = priority;
_observable = source ?? throw new ArgumentNullException(nameof(source));
}
internal InstancedBinding(
UntypedBindingExpressionBase source,
BindingMode mode,
BindingPriority priority)
{
Mode = mode;
Priority = priority;
_expression = source ?? throw new ArgumentNullException(nameof(source));
}
internal InstancedBinding(
AvaloniaObject? target,
UntypedBindingExpressionBase source,
BindingMode mode,
BindingPriority priority)
{
Mode = mode;
Priority = priority;
_expression = source ?? throw new ArgumentNullException(nameof(source));
_target = target;
}
/// <summary>
/// Gets the binding mode with which the binding was initiated.
/// </summary>
public BindingMode Mode { get; }
/// <summary>
/// Gets the binding priority.
/// </summary>
public BindingPriority Priority { get; }
/// <summary>
/// Gets the binding source observable.
/// </summary>
public IObservable<object?> Source => _observable ??= _expression!.ToObservable(_target);
[Obsolete("Use Source property"), EditorBrowsable(EditorBrowsableState.Never)]
public IObservable<object?> Observable => Source;
internal UntypedBindingExpressionBase? Expression => _expression;
/// <summary>
/// Creates a new one-time binding with a fixed value.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneTime(
object value,
BindingPriority priority = BindingPriority.LocalValue)
{
return new InstancedBinding(ObservableEx.SingleValue(value), BindingMode.OneTime, priority);
}
/// <summary>
/// Creates a new one-time binding.
/// </summary>
/// <param name="observable">The source observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneTime(
IObservable<object?> observable,
BindingPriority priority = BindingPriority.LocalValue)
{
_ = observable ?? throw new ArgumentNullException(nameof(observable));
return new InstancedBinding(observable, BindingMode.OneTime, priority);
}
/// <summary>
/// Creates a new one-way binding.
/// </summary>
/// <param name="observable">The source observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneWay(
IObservable<object?> observable,
BindingPriority priority = BindingPriority.LocalValue)
{
_ = observable ?? throw new ArgumentNullException(nameof(observable));
return new InstancedBinding(observable, BindingMode.OneWay, priority);
}
/// <summary>
/// Creates a new one-way to source binding.
/// </summary>
/// <param name="observer">The binding source.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding OneWayToSource(
IObserver<object?> observer,
BindingPriority priority = BindingPriority.LocalValue)
{
_ = observer ?? throw new ArgumentNullException(nameof(observer));
return new InstancedBinding((IObservable<object?>)observer, BindingMode.OneWayToSource, priority);
}
/// <summary>
/// Creates a new two-way binding.
/// </summary>
/// <param name="observable">The binding source.</param>
/// <param name="observer">The binding source.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public static InstancedBinding TwoWay(
IObservable<object?> observable,
IObserver<object?> observer,
BindingPriority priority = BindingPriority.LocalValue)
{
_ = observable ?? throw new ArgumentNullException(nameof(observable));
_ = observer ?? throw new ArgumentNullException(nameof(observer));
var subject = observable == observer ? observable : new CombinedSubject<object?>(observer, observable);
return new InstancedBinding(subject, BindingMode.TwoWay, priority);
}
/// <summary>
/// Creates a copy of the <see cref="InstancedBinding"/> with a different priority.
/// </summary>
/// <param name="priority">The priority of the binding.</param>
/// <returns>An <see cref="InstancedBinding"/> instance.</returns>
public InstancedBinding WithPriority(BindingPriority priority)
{
return new InstancedBinding(Source, Mode, priority);
}
}
}

23
src/Markup/Avalonia.Markup/Data/MultiBinding.cs → src/Avalonia.Base/Data/MultiBinding.cs

@ -1,25 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq;
using Avalonia.Reactive;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Metadata;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using System.ComponentModel; using Avalonia.Metadata;
namespace Avalonia.Data namespace Avalonia.Data
{ {
/// <summary> /// <summary>
/// A XAML binding that calculates an aggregate value from multiple child <see cref="Bindings"/>. /// A XAML binding that calculates an aggregate value from multiple child <see cref="Bindings"/>.
/// </summary> /// </summary>
public class MultiBinding : IBinding2 public sealed class MultiBinding : BindingBase
{ {
/// <summary> /// <summary>
/// Gets the collection of child bindings. /// Gets the collection of child bindings.
/// </summary> /// </summary>
[Content, AssignBinding] [Content, AssignBinding]
public IList<IBinding> Bindings { get; set; } = new List<IBinding>(); public IList<BindingBase> Bindings { get; set; } = new List<BindingBase>();
/// <summary> /// <summary>
/// Gets or sets the <see cref="IMultiValueConverter"/> to use. /// Gets or sets the <see cref="IMultiValueConverter"/> to use.
@ -77,18 +75,7 @@ namespace Avalonia.Data
TargetNullValue = AvaloniaProperty.UnsetValue; TargetNullValue = AvaloniaProperty.UnsetValue;
} }
/// <inheritdoc/> internal override BindingExpressionBase CreateInstance(
public InstancedBinding? Initiate(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor = null,
bool enableDataValidation = false)
{
var expression = InstanceCore(target, targetProperty);
return new InstancedBinding(target, expression, Mode, Priority);
}
BindingExpressionBase IBinding2.Instance(
AvaloniaObject target, AvaloniaObject target,
AvaloniaProperty? targetProperty, AvaloniaProperty? targetProperty,
object? anchor) object? anchor)

237
src/Avalonia.Base/Data/ReflectionBinding.cs

@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.Parsers;
using Avalonia.Utilities;
namespace Avalonia.Data
{
/// <summary>
/// A binding that uses reflection to access members.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(TrimmingMessages.ReflectionBindingRequiresDynamicCodeMessage)]
public class ReflectionBinding : BindingBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ReflectionBinding"/> class.
/// </summary>
public ReflectionBinding()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReflectionBinding"/> class.
/// </summary>
/// <param name="path">The binding path.</param>
public ReflectionBinding(string path)
{
Path = path;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReflectionBinding"/> class.
/// </summary>
/// <param name="path">The binding path.</param>
/// <param name="mode">The binding mode.</param>
public ReflectionBinding(string path, BindingMode mode)
{
Path = path;
Mode = mode;
}
/// <summary>
/// Gets or sets the amount of time, in milliseconds, to wait before updating the binding
/// source after the value on the target changes.
/// </summary>
/// <remarks>
/// There is no delay when the source is updated via <see cref="UpdateSourceTrigger.LostFocus"/>
/// or <see cref="BindingExpressionBase.UpdateSource"/>. Nor is there a delay when
/// <see cref="BindingMode.OneWayToSource"/> is active and a new source object is provided.
/// </remarks>
public int Delay { get; set; }
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets the culture in which to evaluate the converter.
/// </summary>
/// <value>The default value is null.</value>
/// <remarks>
/// If this property is not set then <see cref="CultureInfo.CurrentCulture"/> will be used.
/// </remarks>
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
public CultureInfo? ConverterCulture { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the name of the element to use as the binding source.
/// </summary>
public string? ElementName { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
/// </summary>
public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
/// <summary>
/// Gets or sets the binding mode.
/// </summary>
public BindingMode Mode { get; set; }
/// <summary>
/// Gets or sets the binding path.
/// </summary>
public string Path { get; set; } = "";
/// <summary>
/// Gets or sets the binding priority.
/// </summary>
public BindingPriority Priority { get; set; }
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource? RelativeSource { get; set; }
/// <summary>
/// Gets or sets the source for the binding.
/// </summary>
public object? Source { get; set; } = AvaloniaProperty.UnsetValue;
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string? StringFormat { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding result is null.
/// </summary>
public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
/// <summary>
/// Gets or sets a value that determines the timing of binding source updates for
/// <see cref="BindingMode.TwoWay"/> and <see cref="BindingMode.OneWayToSource"/> bindings.
/// </summary>
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
/// </summary>
public Func<string?, string, Type>? TypeResolver { get; set; }
internal WeakReference? DefaultAnchor { get; set; }
internal WeakReference<INameScope?>? NameScope { get; set; }
internal override BindingExpressionBase CreateInstance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor)
{
List<ExpressionNode>? nodes = null;
var isRooted = false;
var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false;
// Build the expression nodes from the binding path.
if (!string.IsNullOrEmpty(Path))
{
var reader = new CharacterReader(Path.AsSpan());
var (astPool, sourceMode) = BindingExpressionGrammar.ParseToPooledList(ref reader);
nodes = ExpressionNodeFactory.CreateFromAst(
astPool,
TypeResolver,
GetNameScope(),
out isRooted);
}
// If the binding isn't rooted (i.e. doesn't have a Source or start with $parent, $self,
// #elementName etc.) then we need to add a source node. The type of source node will
// depend on the ElementName and RelativeSource properties of the binding and if
// neither of those are set will default to a data context node.
if (Source == AvaloniaProperty.UnsetValue && !isRooted && CreateSourceNode(targetProperty) is { } sourceNode)
{
nodes ??= new();
nodes.Insert(0, sourceNode);
}
// If the first node is an ISourceNode then allow it to select the source; otherwise
// use the binding source if specified, falling back to the target.
var source = nodes?.Count > 0 && nodes[0] is SourceNode sn ?
sn.SelectSource(Source, target, anchor ?? DefaultAnchor?.Target) :
Source != AvaloniaProperty.UnsetValue ? Source : target;
var (mode, trigger) = ResolveDefaultsFromMetadata(target, targetProperty);
return new BindingExpression(
source,
nodes,
FallbackValue,
delay: TimeSpan.FromMilliseconds(Delay),
converter: Converter,
converterCulture: ConverterCulture,
converterParameter: ConverterParameter,
enableDataValidation: enableDataValidation,
mode: mode,
priority: Priority,
stringFormat: StringFormat,
targetProperty: targetProperty,
targetNullValue: TargetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
updateSourceTrigger: trigger);
}
private INameScope? GetNameScope()
{
INameScope? result = null;
NameScope?.TryGetTarget(out result);
return result;
}
private ExpressionNode? CreateSourceNode(AvaloniaProperty? targetProperty)
{
if (!string.IsNullOrEmpty(ElementName))
{
var nameScope = GetNameScope() ?? throw new InvalidOperationException(
"Cannot create ElementName binding when NameScope is null");
return new NamedElementNode(nameScope, ElementName);
}
if (RelativeSource is not null)
return ExpressionNodeFactory.CreateRelativeSource(RelativeSource);
return ExpressionNodeFactory.CreateDataContext(targetProperty);
}
private (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata(
AvaloniaObject target,
AvaloniaProperty? targetProperty)
{
var mode = Mode;
var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ?
UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger;
if (mode == BindingMode.Default)
{
if (targetProperty?.GetMetadata(target) is { } metadata)
mode = metadata.DefaultBindingMode;
else
mode = BindingMode.OneWay;
}
return (mode, trigger);
}
}
}

0
src/Markup/Avalonia.Markup/Data/RelativeSource.cs → src/Avalonia.Base/Data/RelativeSource.cs

21
src/Avalonia.Base/Data/TemplateBinding.Observable.cs

@ -1,21 +0,0 @@
using System;
using Avalonia.Reactive;
namespace Avalonia.Data
{
// TODO12: Remove IAvaloniaSubject<object?> support from TemplateBinding.
public partial class TemplateBinding : IAvaloniaSubject<object?>
{
private IAvaloniaSubject<object?>? _observableAdapter;
public IDisposable Subscribe(IObserver<object?> observer)
{
_observableAdapter ??= ToObservable();
return _observableAdapter.Subscribe(observer);
}
void IObserver<object?>.OnCompleted() => _observableAdapter?.OnCompleted();
void IObserver<object?>.OnError(Exception error) => _observableAdapter?.OnError(error);
void IObserver<object?>.OnNext(object? value) => _observableAdapter?.OnNext(value);
}
}

209
src/Avalonia.Base/Data/TemplateBinding.cs

@ -1,35 +1,21 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Logging;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Data namespace Avalonia.Data
{ {
/// <summary> /// <summary>
/// A XAML binding to a property on a control's templated parent. /// A XAML binding to a property on a control's templated parent.
/// </summary> /// </summary>
public partial class TemplateBinding : UntypedBindingExpressionBase, public sealed partial class TemplateBinding : BindingBase
IBinding,
IBinding2,
IDescription,
ISetterValue,
IDisposable
{ {
private bool _isSetterValue;
private bool _hasPublishedValue;
public TemplateBinding() public TemplateBinding()
: base(BindingPriority.Template)
{ {
} }
public TemplateBinding([InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)] AvaloniaProperty property) public TemplateBinding([InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)] AvaloniaProperty property)
: base(BindingPriority.Template)
{ {
Property = property; Property = property;
} }
@ -57,11 +43,7 @@ namespace Avalonia.Data
/// <summary> /// <summary>
/// Gets or sets the binding mode. /// Gets or sets the binding mode.
/// </summary> /// </summary>
public new BindingMode Mode public BindingMode Mode { get; set; }
{
get => base.Mode;
set => base.Mode = value;
}
/// <summary> /// <summary>
/// Gets or sets the name of the source property on the templated parent. /// Gets or sets the name of the source property on the templated parent.
@ -69,190 +51,19 @@ namespace Avalonia.Data
[InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)] [InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)]
public AvaloniaProperty? Property { get; set; } public AvaloniaProperty? Property { get; set; }
/// <inheritdoc/> public BindingBase ProvideValue() => this;
public override string Description => "TemplateBinding: " + Property;
public IBinding ProvideValue() => this;
public InstancedBinding? Initiate(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor = null,
bool enableDataValidation = false)
{
return new(target, InstanceCore(), Mode, BindingPriority.Template);
}
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
{
return InstanceCore();
}
internal override bool WriteValueToSource(object? value)
{
if (Property is not null && TryGetTemplatedParent(out var templatedParent))
{
if (Converter is not null)
value = ConvertBack(Converter, ConverterCulture, ConverterParameter, value, TargetType);
if (value != BindingOperations.DoNothing)
templatedParent.SetCurrentValue(Property, value);
return true;
}
return false; internal override BindingExpressionBase CreateInstance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
}
/// <inheritdoc/>
void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true;
protected override void StartCore()
{
_hasPublishedValue = false;
OnTemplatedParentChanged();
if (TryGetTarget(out var target))
target.PropertyChanged += OnTargetPropertyChanged;
}
protected override void StopCore()
{
if (TryGetTarget(out var target))
{
if (target is StyledElement targetElement &&
targetElement?.TemplatedParent is { } templatedParent)
{
templatedParent.PropertyChanged -= OnTemplatedParentPropertyChanged;
}
if (target is not null)
{
target.PropertyChanged -= OnTargetPropertyChanged;
}
}
}
private object? ConvertToTargetType(object? value)
{
var converter = TargetTypeConverter.GetDefaultConverter();
if (converter.TryConvert(value, TargetType, CultureInfo.InvariantCulture, out var result))
{
return result;
}
else
{
if (TryGetTarget(out var target))
{
var valueString = value?.ToString() ?? "(null)";
var valueTypeName = value?.GetType().FullName ?? "null";
var message = $"Could not convert '{valueString}' ({valueTypeName}) to '{TargetType}'.";
Log(target, message, LogEventLevel.Warning);
}
return AvaloniaProperty.UnsetValue;
}
}
private TemplateBinding InstanceCore()
{ {
if (Mode is BindingMode.OneTime or BindingMode.OneWayToSource) if (Mode is BindingMode.OneTime or BindingMode.OneWayToSource)
throw new NotSupportedException("TemplateBinding does not support OneTime or OneWayToSource bindings."); throw new NotSupportedException("TemplateBinding does not support OneTime or OneWayToSource bindings.");
// Usually each `TemplateBinding` will only be instantiated once; in this case we can return new TemplateBindingExpression(
// use the `TemplateBinding` object itself as the binding expression in order to save Property,
// allocating a new object. Converter,
// ConverterCulture,
// If the binding appears in a `Setter`, then make a clone and instantiate that because ConverterParameter,
// because the setter can outlive the control and cause a leak. Mode);
if (!_isSetterValue)
{
return this;
}
else
{
var clone = new TemplateBinding
{
Converter = Converter,
ConverterCulture = ConverterCulture,
ConverterParameter = ConverterParameter,
Mode = Mode,
Property = Property,
};
return clone;
}
}
private void PublishValue()
{
if (Mode == BindingMode.OneWayToSource)
return;
if (TryGetTemplatedParent(out var templatedParent))
{
var value = Property is not null ?
templatedParent.GetValue(Property) :
templatedParent;
BindingError? error = null;
if (Converter is not null)
value = Convert(Converter, ConverterCulture, ConverterParameter, value, TargetType, ref error);
value = ConvertToTargetType(value);
PublishValue(value, error);
_hasPublishedValue = true;
if (Mode == BindingMode.OneTime)
Stop();
}
else if (_hasPublishedValue)
{
PublishValue(AvaloniaProperty.UnsetValue);
}
}
private void OnTemplatedParentChanged()
{
if (TryGetTemplatedParent(out var templatedParent))
templatedParent.PropertyChanged += OnTemplatedParentPropertyChanged;
PublishValue();
}
private void OnTemplatedParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == Property)
PublishValue();
}
private void OnTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == StyledElement.TemplatedParentProperty)
{
if (e.OldValue is AvaloniaObject oldValue)
oldValue.PropertyChanged -= OnTemplatedParentPropertyChanged;
OnTemplatedParentChanged();
}
else if (Mode is BindingMode.TwoWay or BindingMode.OneWayToSource && e.Property == TargetProperty)
{
WriteValueToSource(e.NewValue);
}
}
private bool TryGetTemplatedParent([NotNullWhen(true)] out AvaloniaObject? result)
{
if (TryGetTarget(out var target) &&
target is StyledElement targetElement &&
targetElement.TemplatedParent is { } templatedParent)
{
result = templatedParent;
return true;
}
result = null;
return false;
} }
} }
} }

170
src/Avalonia.Base/Data/TemplateBindingExpression.cs

@ -0,0 +1,170 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Logging;
namespace Avalonia.Data;
internal class TemplateBindingExpression : UntypedBindingExpressionBase
{
private IValueConverter? _converter;
private CultureInfo? _converterCulture;
private object? _converterParameter;
private BindingMode _mode;
private readonly AvaloniaProperty? _property;
private bool _hasPublishedValue;
public TemplateBindingExpression(
AvaloniaProperty? property,
IValueConverter? converter,
CultureInfo? converterCulture,
object? converterParameter,
BindingMode mode)
: base(BindingPriority.Template)
{
_property = property;
_converter = converter;
_converterCulture = converterCulture;
_converterParameter = converterParameter;
_mode = mode;
}
public override string Description => $"{{TemplateBinding {_property}}}";
protected override void StartCore()
{
_hasPublishedValue = false;
OnTemplatedParentChanged();
if (TryGetTarget(out var target))
target.PropertyChanged += OnTargetPropertyChanged;
}
protected override void StopCore()
{
if (TryGetTarget(out var target))
{
if (target is StyledElement targetElement &&
targetElement?.TemplatedParent is { } templatedParent)
{
templatedParent.PropertyChanged -= OnTemplatedParentPropertyChanged;
}
if (target is not null)
{
target.PropertyChanged -= OnTargetPropertyChanged;
}
}
}
internal override bool WriteValueToSource(object? value)
{
if (_property is not null && TryGetTemplatedParent(out var templatedParent))
{
if (_converter is not null)
value = ConvertBack(_converter, _converterCulture, _converterParameter, value, TargetType);
if (value != BindingOperations.DoNothing)
templatedParent.SetCurrentValue(_property, value);
return true;
}
return false;
}
private object? ConvertToTargetType(object? value)
{
var converter = TargetTypeConverter.GetDefaultConverter();
if (converter.TryConvert(value, TargetType, CultureInfo.InvariantCulture, out var result))
{
return result;
}
else
{
if (TryGetTarget(out var target))
{
var valueString = value?.ToString() ?? "(null)";
var valueTypeName = value?.GetType().FullName ?? "null";
var message = $"Could not convert '{valueString}' ({valueTypeName}) to '{TargetType}'.";
Log(target, message, LogEventLevel.Warning);
}
return AvaloniaProperty.UnsetValue;
}
}
private void PublishValue()
{
if (_mode == BindingMode.OneWayToSource)
return;
if (TryGetTemplatedParent(out var templatedParent))
{
var value = _property is not null ?
templatedParent.GetValue(_property) :
templatedParent;
BindingError? error = null;
if (_converter is not null)
value = Convert(_converter, _converterCulture, _converterParameter, value, TargetType, ref error);
value = ConvertToTargetType(value);
PublishValue(value, error);
_hasPublishedValue = true;
if (_mode == BindingMode.OneTime)
Stop();
}
else if (_hasPublishedValue)
{
PublishValue(AvaloniaProperty.UnsetValue);
}
}
private void OnTemplatedParentChanged()
{
if (TryGetTemplatedParent(out var templatedParent))
templatedParent.PropertyChanged += OnTemplatedParentPropertyChanged;
PublishValue();
}
private void OnTemplatedParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
PublishValue();
}
private void OnTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == StyledElement.TemplatedParentProperty)
{
if (e.OldValue is AvaloniaObject oldValue)
oldValue.PropertyChanged -= OnTemplatedParentPropertyChanged;
OnTemplatedParentChanged();
}
else if (_mode is BindingMode.TwoWay or BindingMode.OneWayToSource && e.Property == TargetProperty)
{
WriteValueToSource(e.NewValue);
}
}
private bool TryGetTemplatedParent([NotNullWhen(true)] out AvaloniaObject? result)
{
if (TryGetTarget(out var target) &&
target is StyledElement targetElement &&
targetElement.TemplatedParent is { } templatedParent)
{
result = templatedParent;
return true;
}
result = null;
return false;
}
}

2
src/Avalonia.Base/StyledElementExtensions.cs

@ -6,7 +6,7 @@ namespace Avalonia
{ {
public static class StyledElementExtensions public static class StyledElementExtensions
{ {
public static IDisposable BindClass(this StyledElement target, string className, IBinding source, object anchor) => public static IDisposable BindClass(this StyledElement target, string className, BindingBase source, object anchor) =>
ClassBindingManager.Bind(target, className, source, anchor); ClassBindingManager.Bind(target, className, source, anchor);
public static AvaloniaProperty GetClassProperty(string className) => public static AvaloniaProperty GetClassProperty(string className) =>

8
src/Avalonia.Base/Styling/Setter.cs

@ -79,10 +79,8 @@ namespace Avalonia.Styling
throw new InvalidOperationException( throw new InvalidOperationException(
$"Cannot set Class Binding property '(Classes.{classPropertyName})' in '{instance.Source}' because the style has an activator."); $"Cannot set Class Binding property '(Classes.{classPropertyName})' in '{instance.Source}' because the style has an activator.");
if (Value is IBinding2 binding) if (Value is BindingBase binding)
return SetBinding((StyleInstance)instance, ao, binding); return SetBinding((StyleInstance)instance, ao, binding);
else if (Value is IBinding)
throw new AvaloniaInternalException("TODO: Make all IBindings implement IBinding2.");
else if (Value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(Property.PropertyType)) else if (Value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(Property.PropertyType))
return new PropertySetterTemplateInstance(Property, template); return new PropertySetterTemplateInstance(Property, template);
else if (!Property.IsValidValue(Value)) else if (!Property.IsValidValue(Value))
@ -108,11 +106,11 @@ namespace Avalonia.Styling
return Property ?? throw new InvalidOperationException("Setter.Property must be set."); return Property ?? throw new InvalidOperationException("Setter.Property must be set.");
} }
private ISetterInstance SetBinding(StyleInstance instance, AvaloniaObject target, IBinding2 binding) private ISetterInstance SetBinding(StyleInstance instance, AvaloniaObject target, BindingBase binding)
{ {
if (!Property!.IsDirect) if (!Property!.IsDirect)
{ {
var expression = binding.Instance(target, Property, null); var expression = binding.CreateInstance(target, Property, null);
expression.Attach(target.GetValueStore(), null, target, Property, instance.Priority); expression.Attach(target.GetValueStore(), null, target, Property, instance.Priority);
return expression; return expression;
} }

4
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -71,13 +71,13 @@
<Link>Markup\AssetLoader.cs</Link> <Link>Markup\AssetLoader.cs</Link>
</Compile> </Compile>
<Compile Include="..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" /> <Compile Include="..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs"> <Compile Include="..\Avalonia.Base\Data\Core\Parsers\ArgumentListParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="../Avalonia.Base/Utilities/KeywordParser.cs"> <Compile Include="../Avalonia.Base/Utilities/KeywordParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs"> <Compile Include="..\Avalonia.Base\Data\Core\Parsers\BindingExpressionGrammar.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="../Avalonia.Base/Utilities/StyleClassParser.cs"> <Compile Include="../Avalonia.Base/Utilities/StyleClassParser.cs">

2
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs

@ -320,7 +320,7 @@ namespace Avalonia.Controls
/// when binding to a collection property.</value> /// when binding to a collection property.</value>
[AssignBinding] [AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))] [InheritDataTypeFromItems(nameof(ItemsSource))]
public IBinding? ValueMemberBinding public BindingBase? ValueMemberBinding
{ {
get => _valueBindingEvaluator?.ValueBinding; get => _valueBindingEvaluator?.ValueBinding;
set set

8
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -439,7 +439,7 @@ namespace Avalonia.Controls
if (!_settingItemTemplateFromValueMemberBinding) if (!_settingItemTemplateFromValueMemberBinding)
_itemTemplateIsFromValueMemberBinding = false; _itemTemplateIsFromValueMemberBinding = false;
} }
private void OnValueMemberBindingChanged(IBinding? value) private void OnValueMemberBindingChanged(BindingBase? value)
{ {
if (_itemTemplateIsFromValueMemberBinding) if (_itemTemplateIsFromValueMemberBinding)
{ {
@ -2032,7 +2032,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the string value binding used by the control. /// Gets or sets the string value binding used by the control.
/// </summary> /// </summary>
private IBinding? _binding; private BindingBase? _binding;
/// <summary> /// <summary>
/// Identifies the Value dependency property. /// Identifies the Value dependency property.
@ -2054,7 +2054,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the value binding. /// Gets or sets the value binding.
/// </summary> /// </summary>
public IBinding? ValueBinding public BindingBase? ValueBinding
{ {
get => _binding; get => _binding;
set set
@ -2076,7 +2076,7 @@ namespace Avalonia.Controls
/// setting the initial binding to the provided parameter. /// setting the initial binding to the provided parameter.
/// </summary> /// </summary>
/// <param name="binding">The initial string value binding.</param> /// <param name="binding">The initial string value binding.</param>
public BindingEvaluator(IBinding? binding) public BindingEvaluator(BindingBase? binding)
: this() : this()
{ {
ValueBinding = binding; ValueBinding = binding;

8
src/Avalonia.Controls/ComboBox.cs

@ -656,15 +656,15 @@ namespace Avalonia.Controls
private void HandleTextValueBindingValueChanged(AvaloniaPropertyChangedEventArgs? textSearchPropChange, private void HandleTextValueBindingValueChanged(AvaloniaPropertyChangedEventArgs? textSearchPropChange,
AvaloniaPropertyChangedEventArgs? displayMemberPropChange) AvaloniaPropertyChangedEventArgs? displayMemberPropChange)
{ {
IBinding? textValueBinding; BindingBase? textValueBinding;
//prioritise using the TextSearch.TextBindingProperty if possible //prioritise using the TextSearch.TextBindingProperty if possible
if (textSearchPropChange == null && TextSearch.GetTextBinding(this) is IBinding textSearchBinding) if (textSearchPropChange == null && TextSearch.GetTextBinding(this) is BindingBase textSearchBinding)
textValueBinding = textSearchBinding; textValueBinding = textSearchBinding;
else if (textSearchPropChange != null && textSearchPropChange.NewValue is IBinding eventTextSearchBinding) else if (textSearchPropChange != null && textSearchPropChange.NewValue is BindingBase eventTextSearchBinding)
textValueBinding = eventTextSearchBinding; textValueBinding = eventTextSearchBinding;
else if (displayMemberPropChange != null && displayMemberPropChange.NewValue is IBinding eventDisplayMemberBinding) else if (displayMemberPropChange != null && displayMemberPropChange.NewValue is BindingBase eventDisplayMemberBinding)
textValueBinding = eventDisplayMemberBinding; textValueBinding = eventDisplayMemberBinding;
else else

2
src/Avalonia.Controls/Control.cs

@ -162,7 +162,7 @@ namespace Avalonia.Controls
get => GetValue(TagProperty); get => GetValue(TagProperty);
set => SetValue(TagProperty, value); set => SetValue(TagProperty, value);
} }
/// <summary> /// <summary>
/// Occurs when the user has completed a context input gesture, such as a right-click. /// Occurs when the user has completed a context input gesture, such as a right-click.
/// </summary> /// </summary>

8
src/Avalonia.Controls/ItemsControl.cs

@ -64,18 +64,18 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="DisplayMemberBinding" /> property /// Defines the <see cref="DisplayMemberBinding" /> property
/// </summary> /// </summary>
public static readonly StyledProperty<IBinding?> DisplayMemberBindingProperty = public static readonly StyledProperty<BindingBase?> DisplayMemberBindingProperty =
AvaloniaProperty.Register<ItemsControl, IBinding?>(nameof(DisplayMemberBinding)); AvaloniaProperty.Register<ItemsControl, BindingBase?>(nameof(DisplayMemberBinding));
private static readonly AttachedProperty<ControlTheme?> AppliedItemContainerTheme = private static readonly AttachedProperty<ControlTheme?> AppliedItemContainerTheme =
AvaloniaProperty.RegisterAttached<ItemsControl, Control, ControlTheme?>("AppliedItemContainerTheme"); AvaloniaProperty.RegisterAttached<ItemsControl, Control, ControlTheme?>("AppliedItemContainerTheme");
/// <summary> /// <summary>
/// Gets or sets the <see cref="IBinding"/> to use for binding to the display member of each item. /// Gets or sets the <see cref="BindingBase"/> to use for binding to the display member of each item.
/// </summary> /// </summary>
[AssignBinding] [AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))] [InheritDataTypeFromItems(nameof(ItemsSource))]
public IBinding? DisplayMemberBinding public BindingBase? DisplayMemberBinding
{ {
get => GetValue(DisplayMemberBindingProperty); get => GetValue(DisplayMemberBindingProperty);
set => SetValue(DisplayMemberBindingProperty, value); set => SetValue(DisplayMemberBindingProperty, value);

5
src/Avalonia.Controls/NativeMenuBarPresenter.cs

@ -25,6 +25,7 @@ internal class NativeMenuBarPresenter : Menu
nativeItem.GetObservable(NativeMenuItem.HeaderProperty).ToBinding(), nativeItem.GetObservable(NativeMenuItem.HeaderProperty).ToBinding(),
[!MenuItem.IconProperty] = nativeItem.GetObservable(NativeMenuItem.IconProperty) [!MenuItem.IconProperty] = nativeItem.GetObservable(NativeMenuItem.IconProperty)
.Select(i => i is { } bitmap ? new Image { Source = bitmap } : null).ToBinding(), .Select(i => i is { } bitmap ? new Image { Source = bitmap } : null).ToBinding(),
[!!MenuItem.IsCheckedProperty] = nativeItem[!!NativeMenuItem.IsCheckedProperty],
[!MenuItem.IsEnabledProperty] = nativeItem.GetObservable(NativeMenuItem.IsEnabledProperty).ToBinding(), [!MenuItem.IsEnabledProperty] = nativeItem.GetObservable(NativeMenuItem.IsEnabledProperty).ToBinding(),
[!MenuItem.IsVisibleProperty] = nativeItem.GetObservable(NativeMenuItem.IsVisibleProperty).ToBinding(), [!MenuItem.IsVisibleProperty] = nativeItem.GetObservable(NativeMenuItem.IsVisibleProperty).ToBinding(),
[!MenuItem.CommandProperty] = nativeItem.GetObservable(NativeMenuItem.CommandProperty).ToBinding(), [!MenuItem.CommandProperty] = nativeItem.GetObservable(NativeMenuItem.CommandProperty).ToBinding(),
@ -38,10 +39,6 @@ internal class NativeMenuBarPresenter : Menu
nativeItem.GetObservable(NativeMenuItem.ToolTipProperty).ToBinding(), nativeItem.GetObservable(NativeMenuItem.ToolTipProperty).ToBinding(),
}; };
BindingOperations.Apply(newItem, MenuItem.IsCheckedProperty, InstancedBinding.TwoWay(
nativeItem.GetObservable(NativeMenuItem.IsCheckedProperty).Select(v => (object)v),
new AnonymousObserver<object?>(v => nativeItem.SetValue(NativeMenuItem.IsCheckedProperty, v))));
newItem.Click += MenuItemOnClick; newItem.Click += MenuItemOnClick;
return newItem; return newItem;

5
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@ -123,10 +123,9 @@ namespace Avalonia.Controls.Primitives
} }
if (headerTemplate is ITreeDataTemplate treeTemplate && if (headerTemplate is ITreeDataTemplate treeTemplate &&
treeTemplate.Match(item) && treeTemplate.Match(item))
treeTemplate.ItemsSelector(item) is { } itemsBinding)
{ {
_itemsBinding = BindingOperations.Apply(this, ItemsSourceProperty, itemsBinding, null); _itemsBinding = treeTemplate.BindChildren(this, ItemsSourceProperty, item);
} }
} }

5
src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs

@ -121,10 +121,9 @@ namespace Avalonia.Controls.Primitives
} }
if (headerTemplate is ITreeDataTemplate treeTemplate && if (headerTemplate is ITreeDataTemplate treeTemplate &&
treeTemplate.Match(item) && treeTemplate.Match(item))
treeTemplate.ItemsSelector(item) is { } itemsBinding)
{ {
_itemsBinding = BindingOperations.Apply(this, ItemsSourceProperty, itemsBinding, null); _itemsBinding = treeTemplate.BindChildren(this, ItemsSourceProperty, item);
} }
} }

12
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -75,8 +75,8 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="SelectedValueBinding"/> property /// Defines the <see cref="SelectedValueBinding"/> property
/// </summary> /// </summary>
public static readonly StyledProperty<IBinding?> SelectedValueBindingProperty = public static readonly StyledProperty<BindingBase?> SelectedValueBindingProperty =
AvaloniaProperty.Register<SelectingItemsControl, IBinding?>(nameof(SelectedValueBinding)); AvaloniaProperty.Register<SelectingItemsControl, BindingBase?>(nameof(SelectedValueBinding));
/// <summary> /// <summary>
/// Defines the <see cref="SelectedItems"/> property. /// Defines the <see cref="SelectedItems"/> property.
@ -246,12 +246,12 @@ namespace Avalonia.Controls.Primitives
} }
/// <summary> /// <summary>
/// Gets the <see cref="IBinding"/> instance used to obtain the /// Gets the <see cref="BindingBase"/> instance used to obtain the
/// <see cref="SelectedValue"/> property /// <see cref="SelectedValue"/> property
/// </summary> /// </summary>
[AssignBinding] [AssignBinding]
[InheritDataTypeFromItems(nameof(ItemsSource))] [InheritDataTypeFromItems(nameof(ItemsSource))]
public IBinding? SelectedValueBinding public BindingBase? SelectedValueBinding
{ {
get => GetValue(SelectedValueBindingProperty); get => GetValue(SelectedValueBindingProperty);
set => SetValue(SelectedValueBindingProperty, value); set => SetValue(SelectedValueBindingProperty, value);
@ -648,7 +648,7 @@ namespace Avalonia.Controls.Primitives
return; return;
} }
var value = change.GetNewValue<IBinding?>(); var value = change.GetNewValue<BindingBase?>();
if (value is null) if (value is null)
{ {
// Clearing SelectedValueBinding makes the SelectedValue the item itself // Clearing SelectedValueBinding makes the SelectedValue the item itself
@ -1413,7 +1413,7 @@ namespace Avalonia.Controls.Primitives
return -1; return -1;
} }
private BindingEvaluator<object?> GetSelectedValueBindingEvaluator(IBinding binding) private BindingEvaluator<object?> GetSelectedValueBindingEvaluator(BindingBase binding)
{ {
_selectedValueBindingEvaluator ??= new(); _selectedValueBindingEvaluator ??= new();
_selectedValueBindingEvaluator.UpdateBinding(binding); _selectedValueBindingEvaluator.UpdateBinding(binding);

8
src/Avalonia.Controls/Primitives/TextSearch.cs

@ -21,8 +21,8 @@ namespace Avalonia.Controls.Primitives
/// Defines the TextBinding attached property. /// Defines the TextBinding attached property.
/// The binding will be applied to each item during text search in <see cref="SelectingItemsControl"/> (such as <see cref="ComboBox"/>). /// The binding will be applied to each item during text search in <see cref="SelectingItemsControl"/> (such as <see cref="ComboBox"/>).
/// </summary> /// </summary>
public static readonly AttachedProperty<IBinding?> TextBindingProperty public static readonly AttachedProperty<BindingBase?> TextBindingProperty
= AvaloniaProperty.RegisterAttached<Interactive, IBinding?>("TextBinding", typeof(TextSearch)); = AvaloniaProperty.RegisterAttached<Interactive, BindingBase?>("TextBinding", typeof(TextSearch));
// TODO12: Control should be Interactive to match the property definition. // TODO12: Control should be Interactive to match the property definition.
/// <summary> /// <summary>
@ -47,7 +47,7 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
/// <param name="interactive">The interactive element.</param> /// <param name="interactive">The interactive element.</param>
/// <param name="value">The search text binding to set.</param> /// <param name="value">The search text binding to set.</param>
public static void SetTextBinding(Interactive interactive, IBinding? value) public static void SetTextBinding(Interactive interactive, BindingBase? value)
=> interactive.SetValue(TextBindingProperty, value); => interactive.SetValue(TextBindingProperty, value);
/// <summary> /// <summary>
@ -56,7 +56,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="interactive">The interactive element.</param> /// <param name="interactive">The interactive element.</param>
/// <returns>The search text binding.</returns> /// <returns>The search text binding.</returns>
[AssignBinding] [AssignBinding]
public static IBinding? GetTextBinding(Interactive interactive) public static BindingBase? GetTextBinding(Interactive interactive)
=> interactive.GetValue(TextBindingProperty); => interactive.GetValue(TextBindingProperty);
/// <summary> /// <summary>

11
src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
@ -51,14 +52,10 @@ namespace Avalonia.Controls.Templates
_itemsSelector = itemsSelector; _itemsSelector = itemsSelector;
} }
/// <summary> public IDisposable BindChildren(AvaloniaObject target, AvaloniaProperty targetProperty, object item)
/// Selects the child items of an item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The child items, or null if no child items.</returns>
public InstancedBinding ItemsSelector(object item)
{ {
return InstancedBinding.OneTime(_itemsSelector(item)); target.SetCurrentValue(targetProperty, _itemsSelector(item));
return Disposable.Empty;
} }
/// <summary> /// <summary>

13
src/Avalonia.Controls/Templates/ITreeDataTemplate.cs

@ -1,4 +1,4 @@
using Avalonia.Data; using System;
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
@ -8,13 +8,14 @@ namespace Avalonia.Controls.Templates
public interface ITreeDataTemplate : IDataTemplate public interface ITreeDataTemplate : IDataTemplate
{ {
/// <summary> /// <summary>
/// Selects the child items of an item. /// Binds the children of the specified item to a property on a target object.
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="target">The target object.</param>
/// <param name="targetProperty">The target property.</param>
/// <param name="item">The item whose children should be bound.</param>
/// <returns> /// <returns>
/// An <see cref="InstancedBinding"/> holding the items, or an observable that tracks the /// An <see cref="IDisposable"/> that can be used to remove the binding.
/// items. May return null if no child items.
/// </returns> /// </returns>
InstancedBinding? ItemsSelector(object item); IDisposable BindChildren(AvaloniaObject target, AvaloniaProperty targetProperty, object item);
} }
} }

6
src/Avalonia.Controls/Utils/BindingEvaluator.cs

@ -10,7 +10,7 @@ namespace Avalonia.Controls.Utils;
internal sealed class BindingEvaluator<T> : StyledElement, IDisposable internal sealed class BindingEvaluator<T> : StyledElement, IDisposable
{ {
private BindingExpressionBase? _expression; private BindingExpressionBase? _expression;
private IBinding? _lastBinding; private BindingBase? _lastBinding;
[SuppressMessage( [SuppressMessage(
"AvaloniaProperty", "AvaloniaProperty",
@ -37,7 +37,7 @@ internal sealed class BindingEvaluator<T> : StyledElement, IDisposable
return GetValue(ValueProperty); return GetValue(ValueProperty);
} }
public void UpdateBinding(IBinding binding) public void UpdateBinding(BindingBase binding)
{ {
if (binding == _lastBinding) if (binding == _lastBinding)
return; return;
@ -59,7 +59,7 @@ internal sealed class BindingEvaluator<T> : StyledElement, IDisposable
} }
[return: NotNullIfNotNull(nameof(binding))] [return: NotNullIfNotNull(nameof(binding))]
public static BindingEvaluator<T>? TryCreate(IBinding? binding) public static BindingEvaluator<T>? TryCreate(BindingBase? binding)
{ {
if (binding is null) if (binding is null)
return null; return null;

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Markup.Parsers; using Avalonia.Data.Core.Parsers;
using XamlX; using XamlX;
using XamlX.Ast; using XamlX.Ast;
using XamlX.Transform; using XamlX.Transform;

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs

@ -74,12 +74,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{ {
_types = types; _types = types;
_className = className; _className = className;
Parameters = new[] {types.IBinding}; Parameters = new[] {types.BindingBase};
} }
public void Emit(IXamlILEmitter emitter) public void Emit(IXamlILEmitter emitter)
{ {
using (var bloc = emitter.LocalsPool.GetLocal(_types.IBinding)) using (var bloc = emitter.LocalsPool.GetLocal(_types.BindingBase))
emitter emitter
.Stloc(bloc.Local) .Stloc(bloc.Local)
.Ldstr(_className) .Ldstr(_className)

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -140,7 +140,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
} }
IXamlType? itemsCollectionType = null; IXamlType? itemsCollectionType = null;
if (context.GetAvaloniaTypes().IBinding.IsAssignableFrom(parentItemsValue.Type.GetClrType())) if (context.GetAvaloniaTypes().BindingBase.IsAssignableFrom(parentItemsValue.Type.GetClrType()))
{ {
if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension) if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)
&& parentItemsValue is XamlMarkupExtensionNode ext && ext.Value is XamlAstConstructableObjectNode parentItemsBinding) && parentItemsValue is XamlMarkupExtensionNode ext && ext.Value is XamlAstConstructableObjectNode parentItemsBinding)
@ -175,7 +175,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj) private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj)
{ {
var bindingType = context.GetAvaloniaTypes().IBinding; var bindingType = context.GetAvaloniaTypes().BindingBase;
if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension)) if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension))
{ {
return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType()); return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType());

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -168,7 +168,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{ {
Getter = setterType.Methods.First(m => m.Name == "get_Value"); Getter = setterType.Methods.First(m => m.Name == "get_Value");
var method = setterType.Methods.First(m => m.Name == "set_Value"); var method = setterType.Methods.First(m => m.Name == "set_Value");
Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding, false)); Setters.Add(new XamlIlDirectCallPropertySetter(method, types.BindingBase, false));
Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType, false)); Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType, false));
Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType, targetType.AcceptsNull())); Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType, targetType.AcceptsNull()));
} }

8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -21,7 +21,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType StyledPropertyT { get; } public IXamlType StyledPropertyT { get; }
public IXamlMethod AvaloniaObjectSetStyledPropertyValue { get; } public IXamlMethod AvaloniaObjectSetStyledPropertyValue { get; }
public IXamlType AvaloniaAttachedPropertyT { get; } public IXamlType AvaloniaAttachedPropertyT { get; }
public IXamlType IBinding { get; } public IXamlType BindingBase { get; }
public IXamlType MultiBinding { get; } public IXamlType MultiBinding { get; }
public IXamlMethod AvaloniaObjectBindMethod { get; } public IXamlMethod AvaloniaObjectBindMethod { get; }
public IXamlMethod AvaloniaObjectSetValueMethod { get; } public IXamlMethod AvaloniaObjectSetValueMethod { get; }
@ -195,7 +195,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& m.Parameters.Count == 3 && m.Parameters.Count == 3
&& m.Parameters[0].Name == "StyledProperty`1" && m.Parameters[0].Name == "StyledProperty`1"
&& m.Parameters[2].Equals(BindingPriority)); && m.Parameters[2].Equals(BindingPriority));
IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding"); BindingBase = cfg.TypeSystem.GetType("Avalonia.Data.BindingBase");
MultiBinding = cfg.TypeSystem.GetType("Avalonia.Data.MultiBinding"); MultiBinding = cfg.TypeSystem.GetType("Avalonia.Data.MultiBinding");
IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable");
ICommand = cfg.TypeSystem.GetType("System.Windows.Input.ICommand"); ICommand = cfg.TypeSystem.GetType("System.Windows.Input.ICommand");
@ -213,7 +213,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On"); OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.GetMethod("Bind", IDisposable, false, AvaloniaObject, AvaloniaObjectBindMethod = AvaloniaObjectExtensions.GetMethod("Bind", IDisposable, false, AvaloniaObject,
AvaloniaProperty, AvaloniaProperty,
IBinding, cfg.WellKnownTypes.Object); BindingBase, cfg.WellKnownTypes.Object);
UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType"); UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType");
StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement"); StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement");
INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope"); INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope");
@ -299,7 +299,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions") ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
.GetMethod("BindClass", IDisposable, false, StyledElement, .GetMethod("BindClass", IDisposable, false, StyledElement,
cfg.WellKnownTypes.String, cfg.WellKnownTypes.String,
IBinding, cfg.WellKnownTypes.Object); BindingBase, cfg.WellKnownTypes.Object);
IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush"); IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush");
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");

8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@ -287,13 +287,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
AvaloniaXamlIlWellKnownTypes types, AvaloniaXamlIlWellKnownTypes types,
IXamlType declaringType, IXamlType declaringType,
IXamlField avaloniaProperty) IXamlField avaloniaProperty)
: base(types, declaringType, avaloniaProperty, false, [types.IBinding]) : base(types, declaringType, avaloniaProperty, false, [types.BindingBase])
{ {
} }
public override void Emit(IXamlILEmitter emitter) public override void Emit(IXamlILEmitter emitter)
{ {
using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding)) using (var bloc = emitter.LocalsPool.GetLocal(Types.BindingBase))
emitter emitter
.Stloc(bloc.Local) .Stloc(bloc.Local)
.Ldsfld(AvaloniaProperty) .Ldsfld(AvaloniaProperty)
@ -325,13 +325,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
AvaloniaXamlIlWellKnownTypes types, AvaloniaXamlIlWellKnownTypes types,
IXamlType declaringType, IXamlType declaringType,
IXamlField avaloniaProperty) IXamlField avaloniaProperty)
: base(types, declaringType, avaloniaProperty, false, [types.BindingPriority, types.IBinding]) : base(types, declaringType, avaloniaProperty, false, [types.BindingPriority, types.BindingBase])
{ {
} }
public override void Emit(IXamlILEmitter emitter) public override void Emit(IXamlILEmitter emitter)
{ {
using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding)) using (var bloc = emitter.LocalsPool.GetLocal(Types.BindingBase))
emitter emitter
.Stloc(bloc.Local) .Stloc(bloc.Local)
.Pop() // ignore priority .Pop() // ignore priority

10
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs

@ -3,18 +3,16 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection.Emit; using System.Reflection.Emit;
using Avalonia.Markup.Parsers; using Avalonia.Data.Core.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
using XamlX.Ast; using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform; using XamlX.Transform;
using XamlX.Transform.Transformers; using XamlX.Transform.Transformers;
using XamlX.TypeSystem; using XamlX.TypeSystem;
using XamlX;
using XamlX.Emit;
using XamlX.IL;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContextWithLocals<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>; using XamlIlEmitContext = XamlX.Emit.XamlEmitContextWithLocals<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
using System.Xml.Linq;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{ {

107
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@ -1,15 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Diagnostics; using Avalonia.Data.Core.Parsers;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class CompiledBindingExtension : BindingBase public sealed class CompiledBindingExtension : BindingBase
{ {
public CompiledBindingExtension() public CompiledBindingExtension()
{ {
@ -41,25 +43,81 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
}; };
} }
/// <summary>
/// Gets or sets the amount of time, in milliseconds, to wait before updating the binding
/// source after the value on the target changes.
/// </summary>
/// <remarks>
/// There is no delay when the source is updated via <see cref="UpdateSourceTrigger.LostFocus"/>
/// or <see cref="BindingExpressionBase.UpdateSource"/>. Nor is there a delay when
/// <see cref="BindingMode.OneWayToSource"/> is active and a new source object is provided.
/// </remarks>
public int Delay { get; set; }
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets the culture in which to evaluate the converter.
/// </summary>
/// <value>The default value is null.</value>
/// <remarks>
/// If this property is not set then <see cref="CultureInfo.CurrentCulture"/> will be used.
/// </remarks>
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
public CultureInfo? ConverterCulture { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object? ConverterParameter { get; set; }
public Type? DataType { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
/// </summary>
public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
/// <summary>
/// Gets or sets the binding mode.
/// </summary>
public BindingMode Mode { get; set; }
[ConstructorArgument("path")] [ConstructorArgument("path")]
public CompiledBindingPath Path { get; set; } public CompiledBindingPath Path { get; set; }
/// <summary>
/// Gets or sets the binding priority.
/// </summary>
public BindingPriority Priority { get; set; }
/// <summary>
/// Gets or sets the source for the binding.
/// </summary>
public object? Source { get; set; } = AvaloniaProperty.UnsetValue; public object? Source { get; set; } = AvaloniaProperty.UnsetValue;
public Type? DataType { get; set; } /// <summary>
/// Gets or sets the string format.
/// </summary>
public string? StringFormat { get; set; }
[Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)] /// <summary>
public override InstancedBinding? Initiate( /// Gets or sets the value to use when the binding result is null.
AvaloniaObject target, /// </summary>
AvaloniaProperty? targetProperty, public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
object? anchor = null,
bool enableDataValidation = false) /// <summary>
{ /// Gets or sets a value that determines the timing of binding source updates for
var expression = InstanceCore(target, targetProperty, anchor, enableDataValidation); /// <see cref="BindingMode.TwoWay"/> and <see cref="BindingMode.OneWayToSource"/> bindings.
return new InstancedBinding(target, expression, Mode, Priority); /// </summary>
} public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
internal WeakReference? DefaultAnchor { get; set; }
private protected override BindingExpressionBase Instance( internal override BindingExpressionBase CreateInstance(
AvaloniaObject target, AvaloniaObject target,
AvaloniaProperty? targetProperty, AvaloniaProperty? targetProperty,
object? anchor) object? anchor)
@ -140,5 +198,24 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
targetTypeConverter: TargetTypeConverter.GetDefaultConverter(), targetTypeConverter: TargetTypeConverter.GetDefaultConverter(),
updateSourceTrigger: trigger); updateSourceTrigger: trigger);
} }
private (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata(
AvaloniaObject target,
AvaloniaProperty? targetProperty)
{
var mode = Mode;
var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ?
UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger;
if (mode == BindingMode.Default)
{
if (targetProperty?.GetMetadata(target) is { } metadata)
mode = metadata.DefaultBindingMode;
else
mode = BindingMode.OneWay;
}
return (mode, trigger);
}
} }
} }

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

@ -94,9 +94,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal IEnumerable<ICompiledBindingPathElement> Elements => _elements; internal IEnumerable<ICompiledBindingPathElement> Elements => _elements;
internal SourceMode SourceMode => Array.Exists(_elements, e => e is IControlSourceBindingPathElement)
? SourceMode.Control : SourceMode.Data;
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
=> string.Concat((IEnumerable<ICompiledBindingPathElement>) _elements); => string.Concat((IEnumerable<ICompiledBindingPathElement>) _elements);

18
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -7,7 +7,7 @@ using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
public class DynamicResourceExtension : IBinding2 public sealed class DynamicResourceExtension : BindingBase
{ {
private object? _anchor; private object? _anchor;
private BindingPriority _priority; private BindingPriority _priority;
@ -24,7 +24,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object? ResourceKey { get; set; } public object? ResourceKey { get; set; }
public IBinding ProvideValue(IServiceProvider serviceProvider) public BindingBase ProvideValue(IServiceProvider serviceProvider)
{ {
if (serviceProvider.IsInControlTemplate()) if (serviceProvider.IsInControlTemplate())
_priority = BindingPriority.Template; _priority = BindingPriority.Template;
@ -44,19 +44,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return this; return this;
} }
InstancedBinding? IBinding.Initiate( internal override BindingExpressionBase CreateInstance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor,
bool enableDataValidation)
{
if (ResourceKey is null)
return null;
var expression = new DynamicResourceExpression(ResourceKey, _anchor, _themeVariant, _priority);
return new InstancedBinding(target, expression, BindingMode.OneWay, _priority);
}
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor)
{ {
if (ResourceKey is null) if (ResourceKey is null)
throw new InvalidOperationException("DynamicResource must have a ResourceKey."); throw new InvalidOperationException("DynamicResource must have a ResourceKey.");

71
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs

@ -1,10 +1,7 @@
using Avalonia.Data;
using System; using System;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.ComponentModel; using Avalonia.Controls;
using System.Globalization; using Avalonia.Data;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
@ -12,20 +9,29 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
#if NET8_0_OR_GREATER #if NET8_0_OR_GREATER
[RequiresDynamicCode(TrimmingMessages.ReflectionBindingRequiresDynamicCodeMessage)] [RequiresDynamicCode(TrimmingMessages.ReflectionBindingRequiresDynamicCodeMessage)]
#endif #endif
public class ReflectionBindingExtension public sealed class ReflectionBindingExtension : ReflectionBinding
{ {
public ReflectionBindingExtension() /// <summary>
{ /// Initializes a new instance of the <see cref="ReflectionBinding"/> class.
} /// </summary>
public ReflectionBindingExtension() { }
public ReflectionBindingExtension(string path) /// <summary>
{ /// Initializes a new instance of the <see cref="ReflectionBinding"/> class.
Path = path; /// </summary>
} /// <param name="path">The binding path.</param>
public ReflectionBindingExtension(string path) : base(path) { }
/// <summary>
/// Initializes a new instance of the <see cref="ReflectionBinding"/> class.
/// </summary>
/// <param name="path">The binding path.</param>
/// <param name="mode">The binding mode.</param>
public ReflectionBindingExtension(string path, BindingMode mode) : base(path, mode) { }
public Binding ProvideValue(IServiceProvider serviceProvider) public ReflectionBinding ProvideValue(IServiceProvider serviceProvider)
{ {
return new Binding return new ReflectionBinding
{ {
TypeResolver = serviceProvider.ResolveType, TypeResolver = serviceProvider.ResolveType,
Converter = Converter, Converter = Converter,
@ -46,40 +52,5 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
UpdateSourceTrigger = UpdateSourceTrigger, UpdateSourceTrigger = UpdateSourceTrigger,
}; };
} }
/// <inheritdoc cref="BindingBase.Delay"/>
public int Delay { get; set; }
public IValueConverter? Converter { get; set; }
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
public CultureInfo? ConverterCulture { get; set; }
public object? ConverterParameter { get; set; }
public string? ElementName { get; set; }
public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
public BindingMode Mode { get; set; }
[ConstructorArgument("path")]
public string Path { get; set; } = "";
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;
public object? Source { get; set; } = AvaloniaProperty.UnsetValue;
public string? StringFormat { get; set; }
public RelativeSource? RelativeSource { get; set; }
public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
/// <summary>
/// Gets or sets a value that determines the timing of binding source updates for
/// <see cref="BindingMode.TwoWay"/> and <see cref="BindingMode.OneWayToSource"/> bindings.
/// </summary>
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
} }
} }

25
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -1,12 +1,9 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Reactive;
namespace Avalonia.Markup.Xaml.Templates namespace Avalonia.Markup.Xaml.Templates
{ {
@ -34,23 +31,11 @@ namespace Avalonia.Markup.Xaml.Templates
} }
} }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "If ItemsSource is a CompiledBinding, then path members will be preserved")] public IDisposable BindChildren(AvaloniaObject target, AvaloniaProperty targetProperty, object item)
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Dynamic code should be available if reflection bindings are used")]
public InstancedBinding? ItemsSelector(object item)
{ {
if (ItemsSource != null) return ItemsSource is not null ?
{ target.Bind(targetProperty, ItemsSource) :
var expression = ItemsSource switch Disposable.Empty;
{
Binding reflection => reflection.CreateObservableForTreeDataTemplate(item),
CompiledBindingExtension compiled => compiled.CreateObservableForTreeDataTemplate(item),
_ => throw new InvalidOperationException("TreeDataTemplate currently only supports Binding and CompiledBindingExtension!")
};
return new InstancedBinding(null, expression, BindingMode.OneWay, BindingPriority.Style);
}
return null;
} }
public Control? Build(object? data) public Control? Build(object? data)

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -283,7 +283,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public static void ApplyNonMatchingMarkupExtensionV1(object target, object property, IServiceProvider prov, public static void ApplyNonMatchingMarkupExtensionV1(object target, object property, IServiceProvider prov,
object value) object value)
{ {
if (value is IBinding b) if (value is BindingBase b)
{ {
if (property is AvaloniaProperty p) if (property is AvaloniaProperty p)
((AvaloniaObject)target).Bind(p, b); ((AvaloniaObject)target).Bind(p, b);

1
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -5,7 +5,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Markup\Parsers\Nodes\ExpressionGrammer" /> <None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />
<None Include="Markup\Parsers\BindingExpressionGrammar.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />

219
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -1,214 +1,21 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Collections.Pooled;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Diagnostics;
using Avalonia.Markup.Parsers;
using Avalonia.Utilities;
namespace Avalonia.Data namespace Avalonia.Data;
{
/// <summary>
/// A XAML binding.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)]
#if NET8_0_OR_GREATER
[RequiresDynamicCode(TrimmingMessages.ReflectionBindingRequiresDynamicCodeMessage)]
#endif
public class Binding : BindingBase
{
/// <summary>
/// Initializes a new instance of the <see cref="Binding"/> class.
/// </summary>
public Binding()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Binding"/> class.
/// </summary>
/// <param name="path">The binding path.</param>
/// <param name="mode">The binding mode.</param>
public Binding(string path, BindingMode mode = BindingMode.Default)
: base(mode)
{
Path = path;
}
/// <summary>
/// Gets or sets the name of the element to use as the binding source.
/// </summary>
public string? ElementName { get; set; }
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource? RelativeSource { get; set; }
/// <summary> /// <summary>
/// Gets or sets the source for the binding. /// Provides limited compatibility with the 11.x Binding class. Use <see cref="ReflectionBinding"/>
/// </summary> /// for new code.
public object? Source { get; set; } = AvaloniaProperty.UnsetValue; /// </summary>
[RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)]
/// <summary>
/// Gets or sets the binding path.
/// </summary>
public string Path { get; set; } = "";
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
/// </summary>
public Func<string?, string, Type>? TypeResolver { get; set; }
[Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
public override InstancedBinding? Initiate(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor = null,
bool enableDataValidation = false)
{
var expression = InstanceCore(targetProperty, target, anchor, enableDataValidation);
return new InstancedBinding(target, expression, Mode, Priority);
}
private protected override BindingExpressionBase Instance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor)
{
var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false;
return InstanceCore(targetProperty, target, anchor, enableDataValidation);
}
/// <summary>
/// Hack for TreeDataTemplate to create a binding expression for an item.
/// </summary>
/// <param name="source">The item.</param>
/// <remarks>
/// Ideally we'd do this in a more generic way but didn't have time to refactor
/// ITreeDataTemplate in time for 11.0. We should revisit this in 12.0.
/// </remarks>
// TODO12: Refactor
#if NET8_0_OR_GREATER #if NET8_0_OR_GREATER
[RequiresDynamicCode(TrimmingMessages.ExpressionNodeRequiresDynamicCodeMessage)] [RequiresDynamicCode(TrimmingMessages.ReflectionBindingRequiresDynamicCodeMessage)]
#endif #endif
internal BindingExpression CreateObservableForTreeDataTemplate(object source) public class Binding : ReflectionBinding
{ {
if (!string.IsNullOrEmpty(ElementName)) public Binding() { }
throw new NotSupportedException("ElementName bindings are not supported in this context.");
if (RelativeSource is not null && RelativeSource.Mode != RelativeSourceMode.DataContext)
throw new NotSupportedException("RelativeSource bindings are not supported in this context.");
if (Source != AvaloniaProperty.UnsetValue)
throw new NotSupportedException("Source bindings are not supported in this context.");
List<ExpressionNode>? nodes = null;
var isRooted = false;
if (!string.IsNullOrEmpty(Path))
{
var reader = new CharacterReader(Path.AsSpan());
var (astNodes, sourceMode) = BindingExpressionGrammar.ParseToPooledList(ref reader);
nodes = ExpressionNodeFactory.CreateFromAst(
astNodes,
TypeResolver,
GetNameScope(),
out isRooted);
}
if (isRooted)
throw new NotSupportedException("Rooted binding paths are not supported in this context.");
return new BindingExpression(
source,
nodes,
FallbackValue,
delay: TimeSpan.FromMilliseconds(Delay),
converter: Converter,
converterParameter: ConverterParameter,
targetNullValue: TargetNullValue);
}
private UntypedBindingExpressionBase InstanceCore(
AvaloniaProperty? targetProperty,
AvaloniaObject target,
object? anchor,
bool enableDataValidation)
{
List<ExpressionNode>? nodes = null;
var isRooted = false;
// Build the expression nodes from the binding path.
if (!string.IsNullOrEmpty(Path))
{
var reader = new CharacterReader(Path.AsSpan());
var (astPool, sourceMode) = BindingExpressionGrammar.ParseToPooledList(ref reader);
nodes = ExpressionNodeFactory.CreateFromAst(
astPool,
TypeResolver,
GetNameScope(),
out isRooted);
}
// If the binding isn't rooted (i.e. doesn't have a Source or start with $parent, $self,
// #elementName etc.) then we need to add a source node. The type of source node will
// depend on the ElementName and RelativeSource properties of the binding and if
// neither of those are set will default to a data context node.
if (Source == AvaloniaProperty.UnsetValue && !isRooted && CreateSourceNode(targetProperty) is { } sourceNode)
{
nodes ??= new();
nodes.Insert(0, sourceNode);
}
// If the first node is an ISourceNode then allow it to select the source; otherwise
// use the binding source if specified, falling back to the target.
var source = nodes?.Count > 0 && nodes[0] is SourceNode sn ?
sn.SelectSource(Source, target, anchor ?? DefaultAnchor?.Target) :
Source != AvaloniaProperty.UnsetValue ? Source : target;
var (mode, trigger) = ResolveDefaultsFromMetadata(target, targetProperty);
return new BindingExpression(
source,
nodes,
FallbackValue,
delay: TimeSpan.FromMilliseconds(Delay),
converter: Converter,
converterCulture: ConverterCulture,
converterParameter: ConverterParameter,
enableDataValidation: enableDataValidation,
mode: mode,
priority: Priority,
stringFormat: StringFormat,
targetProperty: targetProperty,
targetNullValue: TargetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
updateSourceTrigger: trigger);
}
private INameScope? GetNameScope()
{
INameScope? result = null;
NameScope?.TryGetTarget(out result);
return result;
}
private ExpressionNode? CreateSourceNode(AvaloniaProperty? targetProperty)
{
if (!string.IsNullOrEmpty(ElementName))
{
var nameScope = GetNameScope() ?? throw new InvalidOperationException(
"Cannot create ElementName binding when NameScope is null");
return new NamedElementNode(nameScope, ElementName);
}
if (RelativeSource is not null) public Binding(string path) : base(path) { }
return ExpressionNodeFactory.CreateRelativeSource(RelativeSource);
return ExpressionNodeFactory.CreateDataContext(targetProperty); public Binding(string path, BindingMode mode) : base(path, mode) { }
}
}
} }

137
src/Markup/Avalonia.Markup/Data/BindingBase.cs

@ -1,137 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Diagnostics;
namespace Avalonia.Data
{
public abstract class BindingBase : IBinding, IBinding2
{
/// <summary>
/// Initializes a new instance of the <see cref="Binding"/> class.
/// </summary>
public BindingBase()
{
FallbackValue = AvaloniaProperty.UnsetValue;
TargetNullValue = AvaloniaProperty.UnsetValue;
}
/// <summary>
/// Initializes a new instance of the <see cref="Binding"/> class.
/// </summary>
/// <param name="mode">The binding mode.</param>
public BindingBase(BindingMode mode = BindingMode.Default)
:this()
{
Mode = mode;
}
/// <summary>
/// Gets or sets the amount of time, in milliseconds, to wait before updating the binding
/// source after the value on the target changes.
/// </summary>
/// <remarks>
/// There is no delay when the source is updated via <see cref="UpdateSourceTrigger.LostFocus"/>
/// or <see cref="BindingExpressionBase.UpdateSource"/>. Nor is there a delay when
/// <see cref="BindingMode.OneWayToSource"/> is active and a new source object is provided.
/// </remarks>
public int Delay { get; set; }
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets the culture in which to evaluate the converter.
/// </summary>
/// <value>The default value is null.</value>
/// <remarks>
/// If this property is not set then <see cref="CultureInfo.CurrentCulture"/> will be used.
/// </remarks>
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
public CultureInfo? ConverterCulture { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
/// </summary>
public object? FallbackValue { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding result is null.
/// </summary>
public object? TargetNullValue { get; set; }
/// <summary>
/// Gets or sets the binding mode.
/// </summary>
public BindingMode Mode { get; set; }
/// <summary>
/// Gets or sets the binding priority.
/// </summary>
public BindingPriority Priority { get; set; }
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string? StringFormat { get; set; }
/// <summary>
/// Gets or sets a value that determines the timing of binding source updates for
/// <see cref="BindingMode.TwoWay"/> and <see cref="BindingMode.OneWayToSource"/> bindings.
/// </summary>
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
public WeakReference? DefaultAnchor { get; set; }
public WeakReference<INameScope?>? NameScope { get; set; }
/// <inheritdoc/>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConversionSupressWarningMessage)]
[Obsolete(ObsoletionMessages.MayBeRemovedInAvalonia12)]
public abstract InstancedBinding? Initiate(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor = null,
bool enableDataValidation = false);
private protected abstract BindingExpressionBase Instance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor);
private protected (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata(
AvaloniaObject target,
AvaloniaProperty? targetProperty)
{
var mode = Mode;
var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ?
UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger;
if (mode == BindingMode.Default)
{
if (targetProperty?.GetMetadata(target) is { } metadata)
mode = metadata.DefaultBindingMode;
else
mode = BindingMode.OneWay;
}
return (mode, trigger);
}
BindingExpressionBase IBinding2.Instance(AvaloniaObject target, AvaloniaProperty? property, object? anchor)
{
return Instance(target, property, anchor);
}
}
}

6
src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs

@ -29,7 +29,7 @@ namespace Avalonia.Markup.Data
/// <param name="target">The control.</param> /// <param name="target">The control.</param>
/// <param name="property">The property on the control to bind to.</param> /// <param name="property">The property on the control to bind to.</param>
/// <param name="binding">The binding.</param> /// <param name="binding">The binding.</param>
public static void Add(StyledElement target, AvaloniaProperty property, IBinding binding) public static void Add(StyledElement target, AvaloniaProperty property, BindingBase binding)
{ {
if (target.IsInitialized) if (target.IsInitialized)
{ {
@ -109,13 +109,13 @@ namespace Avalonia.Markup.Data
private class BindingEntry : Entry private class BindingEntry : Entry
{ {
public BindingEntry(AvaloniaProperty property, IBinding binding) public BindingEntry(AvaloniaProperty property, BindingBase binding)
{ {
Binding = binding; Binding = binding;
Property = property; Property = property;
} }
public IBinding Binding { get; } public BindingBase Binding { get; }
public AvaloniaProperty Property { get; } public AvaloniaProperty Property { get; }
public override void Apply(StyledElement control) public override void Apply(StyledElement control)

2
src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs

@ -6,3 +6,5 @@ using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Data")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Data")]
[assembly: TypeForwardedTo(typeof(CultureInfoIetfLanguageTagConverter))] [assembly: TypeForwardedTo(typeof(CultureInfoIetfLanguageTagConverter))]
[assembly: TypeForwardedTo(typeof(MultiBinding))]
[assembly: TypeForwardedTo(typeof(RelativeSource))]

4
src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj

@ -16,6 +16,10 @@
<Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerDynamicDependencies.cs" /> <Compile Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\CompilerDynamicDependencies.cs" />
<Compile Include="..\..\..\src\Avalonia.Base\Compatibility\NullableAttributes.cs" Link="Compatibility\NullableAttributes.cs" /> <Compile Include="..\..\..\src\Avalonia.Base\Compatibility\NullableAttributes.cs" Link="Compatibility\NullableAttributes.cs" />
<Compile Include="..\..\..\src\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="Compatibility\TrimmingAttributes.cs" /> <Compile Include="..\..\..\src\Avalonia.Base\Compatibility\TrimmingAttributes.cs" Link="Compatibility\TrimmingAttributes.cs" />
<Compile Include="..\..\..\src\Avalonia.Base\Data\Core\Parsers\ArgumentListParser.cs" Link="Parsers\ArgumentListParser.cs" />
<Compile Include="..\..\..\src\Avalonia.Base\Data\Core\Parsers\BindingExpressionGrammar.cs" Link="Parsers\BindingExpressionGrammar.cs" />
<Compile Include="..\..\..\src\Avalonia.Base\Utilities\CharacterReader.cs" Link="Utilities\CharacterReader.cs" />
<Compile Include="..\..\..\src\Avalonia.Base\Utilities\IdentifierParser.cs" Link="Utilities\IdentifierParser.cs" />
<Compile Include="..\..\..\src\Avalonia.Base\Diagnostics\TrimmingMessages.cs" Link="Diagnostics/AvaloniaTrimmingMessages.cs" /> <Compile Include="..\..\..\src\Avalonia.Base\Diagnostics\TrimmingMessages.cs" Link="Diagnostics/AvaloniaTrimmingMessages.cs" />
<Compile Include="..\..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" /> <Compile Include="..\..\Shared\StringCompatibilityExtensions.cs" Link="Compatibility\StringCompatibilityExtensions.cs" />
<Compile Include="..\..\..\src\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" /> <Compile Include="..\..\..\src\Shared\IsExternalInit.cs" Link="Compatibility\IsExternalInit.cs" />

19
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -1299,25 +1299,6 @@ namespace Avalonia.Base.UnitTests
AvaloniaProperty.Register<Class2, string>("Bar", "bardefault"); AvaloniaProperty.Register<Class2, string>("Bar", "bardefault");
} }
private class TestOneTimeBinding : IBinding
{
private IObservable<object> _source;
public TestOneTimeBinding(IObservable<object> source)
{
_source = source;
}
public InstancedBinding Initiate(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor = null,
bool enableDataValidation = false)
{
return InstancedBinding.OneTime(_source);
}
}
private class TestStackOverflowViewModel : INotifyPropertyChanged private class TestStackOverflowViewModel : INotifyPropertyChanged
{ {
public int SetterInvokedCount { get; private set; } public int SetterInvokedCount { get; private set; }

51
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Obsolete.cs

@ -1,51 +0,0 @@
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Xunit;
#nullable enable
#pragma warning disable CS0618 // Type or member is obsolete
namespace Avalonia.Base.UnitTests.Data.Core;
public abstract partial class BindingExpressionTests
{
public partial class Reflection
{
[Fact]
public void Obsolete_Initiate_Method_Produces_Observable_With_Correct_Target_Type()
{
// Issue #15081
var viewModel = new ViewModel { DoubleValue = 42.5 };
var target = new TargetClass { DataContext = viewModel };
var binding = new Binding(nameof(viewModel.DoubleValue));
var instanced = binding.Initiate(target, TargetClass.StringProperty);
Assert.NotNull(instanced);
var value = instanced.Observable.First();
Assert.Equal("42.5", value);
}
}
public partial class Compiled
{
[Fact]
public void Obsolete_Initiate_Method_Produces_Observable_With_Correct_Target_Type()
{
// Issue #15081
var viewModel = new ViewModel { DoubleValue = 42.5 };
var target = new TargetClass { DataContext = viewModel };
var path = CompiledBindingPathFromExpressionBuilder.Build<ViewModel, double>(x => x.DoubleValue, true);
var binding = new CompiledBindingExtension(path);
var instanced = binding.Initiate(target, TargetClass.StringProperty);
Assert.NotNull(instanced);
var value = instanced.Observable.First();
Assert.Equal("42.5", value);
}
}
}

2
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@ -8,7 +8,7 @@ using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Markup.Parsers; using Avalonia.Data.Core.Parsers;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities; using Avalonia.Utilities;

4
tests/Avalonia.Markup.UnitTests/Parsers/BindingExpressionGrammarTests.cs → tests/Avalonia.Base.UnitTests/Data/Core/Parsers/BindingExpressionGrammarTests.cs

@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Markup.Parsers; using Avalonia.Data.Core.Parsers;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities; using Avalonia.Utilities;
using Xunit; using Xunit;
namespace Avalonia.Markup.UnitTests.Parsers namespace Avalonia.Base.UnitTests.Data.Core.Parsers
{ {
public partial class BindingExpressionGrammarTests : ScopedTestBase public partial class BindingExpressionGrammarTests : ScopedTestBase
{ {

2
tests/Avalonia.Markup.UnitTests/Parsers/BindingExpressionGrammarTests_Errors.cs → tests/Avalonia.Base.UnitTests/Data/Core/Parsers/BindingExpressionGrammarTests_Errors.cs

@ -1,7 +1,7 @@
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Xunit; using Xunit;
namespace Avalonia.Markup.UnitTests.Parsers namespace Avalonia.Base.UnitTests.Data.Core.Parsers
{ {
public partial class BindingExpressionGrammarTests public partial class BindingExpressionGrammarTests
{ {

4
tests/Avalonia.Benchmarks/Data/TemplateBinding_Setup.cs

@ -26,7 +26,7 @@ public class TemplateBinding_Setup
{ {
// Explicit cast to IBinding is required to prevent the IObservable<object?> // Explicit cast to IBinding is required to prevent the IObservable<object?>
// overload being selected. // overload being selected.
using var d = target.Bind(Control.TagProperty, (IBinding)binding); using var d = target.Bind(Control.TagProperty, (BindingBase)binding);
} }
} }
@ -40,7 +40,7 @@ public class TemplateBinding_Setup
{ {
// Explicit cast to IBinding is required to prevent the IObservable<object?> // Explicit cast to IBinding is required to prevent the IObservable<object?>
// overload being selected. // overload being selected.
using var d = target.Bind(Control.TagProperty, (IBinding)binding); using var d = target.Bind(Control.TagProperty, (BindingBase)binding);
} }
} }
} }

4
tests/Avalonia.Benchmarks/Data/TemplateBinding_Values.cs

@ -23,7 +23,7 @@ public class TemplateBinding_Values
// Explicit cast to IBinding is required to prevent the IObservable<object?> // Explicit cast to IBinding is required to prevent the IObservable<object?>
// overload being selected. // overload being selected.
using var d = target.Bind(Control.TagProperty, (IBinding)binding); using var d = target.Bind(Control.TagProperty, (BindingBase)binding);
for (var i = 0; i < 100; ++i) for (var i = 0; i < 100; ++i)
{ {
@ -39,7 +39,7 @@ public class TemplateBinding_Values
// Explicit cast to IBinding is required to prevent the IObservable<object?> // Explicit cast to IBinding is required to prevent the IObservable<object?>
// overload being selected. // overload being selected.
using var d = target.Bind(Control.TagProperty, (IBinding)binding); using var d = target.Bind(Control.TagProperty, (BindingBase)binding);
for (var i = 0; i < 100; ++i) for (var i = 0; i < 100; ++i)
{ {

4
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -1055,7 +1055,7 @@ namespace Avalonia.Controls.UnitTests
private static ItemsControl CreateTarget( private static ItemsControl CreateTarget(
object? dataContext = null, object? dataContext = null,
IBinding? displayMemberBinding = null, BindingBase? displayMemberBinding = null,
IList? items = null, IList? items = null,
IList? itemsSource = null, IList? itemsSource = null,
ControlTheme? itemContainerTheme = null, ControlTheme? itemContainerTheme = null,
@ -1078,7 +1078,7 @@ namespace Avalonia.Controls.UnitTests
private static T CreateTarget<T>( private static T CreateTarget<T>(
object? dataContext = null, object? dataContext = null,
IBinding? displayMemberBinding = null, BindingBase? displayMemberBinding = null,
IList? items = null, IList? items = null,
IList? itemsSource = null, IList? itemsSource = null,
ControlTheme? itemContainerTheme = null, ControlTheme? itemContainerTheme = null,

2
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -293,7 +293,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var viewModel = new TestViewModel { Content = "foo" }; var viewModel = new TestViewModel { Content = "foo" };
var dataContexts = new List<object?>(); var dataContexts = new List<object?>();
target.Bind(ContentPresenter.ContentProperty, (IBinding)new TemplateBinding(ContentControl.ContentProperty)); target.Bind(ContentPresenter.ContentProperty, (BindingBase)new TemplateBinding(ContentControl.ContentProperty));
canvas.GetObservable(ContentPresenter.DataContextProperty).Subscribe(x => dataContexts.Add(x)); canvas.GetObservable(ContentPresenter.DataContextProperty).Subscribe(x => dataContexts.Add(x));
host.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => canvas)); host.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => canvas));

32
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -1613,7 +1613,17 @@ namespace Avalonia.Controls.UnitTests
}, },
DataTemplates = DataTemplates =
{ {
new TestTreeDataTemplate(), new TreeDataTemplate
{
DataType = typeof(Node),
ItemsSource = new Binding(nameof(Node.Children)),
Content = (IServiceProvider? _) => new TemplateResult<Control>(
new TextBlock
{
[!TextBlock.TextProperty] = new Binding(nameof(Node.Value)),
},
new NameScope())
},
}, },
Child = child, Child = child,
}; };
@ -1879,26 +1889,6 @@ namespace Avalonia.Controls.UnitTests
public override string ToString() => Value ?? string.Empty; public override string ToString() => Value ?? string.Empty;
} }
private class TestTreeDataTemplate : ITreeDataTemplate
{
public Control Build(object? param)
{
var node = (Node)param!;
return new TextBlock { Text = node.Value };
}
public InstancedBinding ItemsSelector(object item)
{
var obs = BindingExpression.Create(item, o => ((Node)o).Children);
return new InstancedBinding(obs, BindingMode.OneWay, BindingPriority.LocalValue);
}
public bool Match(object? data)
{
return data is Node;
}
}
private class DerivedTreeView : TreeView private class DerivedTreeView : TreeView
{ {
} }

9
tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs

@ -25,11 +25,12 @@ namespace Avalonia.Markup.UnitTests.Data
Converter = StringConverters.IsNullOrEmpty, Converter = StringConverters.IsNullOrEmpty,
}; };
var instancedBinding = target.Initiate(textBlock, TextBlock.TextProperty); var expression = (BindingExpression)target.CreateInstance(
Assert.NotNull(instancedBinding); textBlock,
var expressionObserver = Assert.IsType<BindingExpression>(instancedBinding.Expression); TextBlock.TextProperty,
null);
Assert.Same(StringConverters.IsNullOrEmpty, expressionObserver.Converter); Assert.Same(StringConverters.IsNullOrEmpty, expression.Converter);
} }
[Fact] [Fact]

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionNodeFactoryTests.cs

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.ExpressionNodes.Reflection; using Avalonia.Data.Core.ExpressionNodes.Reflection;
using Avalonia.Markup.Parsers; using Avalonia.Data.Core.Parsers;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities; using Avalonia.Utilities;
using Xunit; using Xunit;

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs

@ -5,10 +5,10 @@ using System.Threading.Tasks;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Xunit; using Xunit;
using Avalonia.Markup.Parsers;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Data.Core.Parsers;
namespace Avalonia.Markup.UnitTests.Parsers namespace Avalonia.Markup.UnitTests.Parsers
{ {

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AvaloniaProperty.cs

@ -5,10 +5,10 @@ using System.Threading.Tasks;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Xunit; using Xunit;
using Avalonia.Markup.Parsers;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Data.Core.Parsers;
namespace Avalonia.Markup.UnitTests.Parsers namespace Avalonia.Markup.UnitTests.Parsers
{ {

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs

@ -7,8 +7,8 @@ using Avalonia.Collections;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.Parsers;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Markup.Parsers;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities; using Avalonia.Utilities;

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs

@ -1,7 +1,6 @@
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Markup.Parsers;
using Avalonia.Utilities; using Avalonia.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -11,6 +10,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
using Avalonia.Data.Core.Parsers;
namespace Avalonia.Markup.UnitTests.Parsers namespace Avalonia.Markup.UnitTests.Parsers
{ {

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs

@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Markup.Parsers; using Avalonia.Data.Core.Parsers;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities; using Avalonia.Utilities;
using Xunit; using Xunit;

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes; using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Markup.Parsers; using Avalonia.Data.Core.Parsers;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.Utilities; using Avalonia.Utilities;
using Xunit; using Xunit;

4
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -2596,7 +2596,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public class AssignBindingControl : Control public class AssignBindingControl : Control
{ {
[AssignBinding] public IBinding? X { get; set; } [AssignBinding] public BindingBase? X { get; set; }
} }
public class DataGridLikeControl : Control public class DataGridLikeControl : Control
@ -2622,7 +2622,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{ {
[AssignBinding] [AssignBinding]
[InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))] [InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))]
public IBinding? Binding { get; set; } public BindingBase? Binding { get; set; }
[InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))] [InheritDataTypeFromItems(nameof(DataGridLikeControl.Items), AncestorType = typeof(DataGridLikeControl))]
public IDataTemplate? Template { get; set; } public IDataTemplate? Template { get; set; }

4
tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs

@ -19,7 +19,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml); var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml);
var setter = (Setter)(style.Setters.First()); var setter = (Setter)(style.Setters.First());
Assert.IsType<Binding>(setter.Value); Assert.IsType<ReflectionBinding>(setter.Value);
} }
} }
@ -40,7 +40,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
"""); """);
var setter = (Setter)style.Setters.First(); var setter = (Setter)style.Setters.First();
Assert.IsType<Binding>(setter.Value); Assert.IsType<ReflectionBinding>(setter.Value);
} }
} }

10
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AssignBindingTests.cs

@ -48,15 +48,15 @@ public class AssignBindingTests : XamlTestBase
public sealed class AssignBindingTestControl : Control public sealed class AssignBindingTestControl : Control
{ {
[AssignBinding] [AssignBinding]
public IBinding? ClrBinding { get; set; } public BindingBase? ClrBinding { get; set; }
public static readonly AttachedProperty<IBinding?> AttachedBindingProperty = public static readonly AttachedProperty<BindingBase?> AttachedBindingProperty =
AvaloniaProperty.RegisterAttached<AssignBindingTestControl, Control, IBinding?>("AttachedBinding"); AvaloniaProperty.RegisterAttached<AssignBindingTestControl, Control, BindingBase?>("AttachedBinding");
[AssignBinding] [AssignBinding]
public static IBinding? GetAttachedBinding(Control obj) public static BindingBase? GetAttachedBinding(Control obj)
=> obj.GetValue(AttachedBindingProperty); => obj.GetValue(AttachedBindingProperty);
public static void SetAttachedBinding(Control obj, IBinding? value) public static void SetAttachedBinding(Control obj, BindingBase? value)
=> obj.SetValue(AttachedBindingProperty, value); => obj.SetValue(AttachedBindingProperty, value);
} }

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -568,7 +568,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(BoolConverters.And, target.Converter); Assert.Equal(BoolConverters.And, target.Converter);
var bindings = target.Bindings.Cast<Binding>().ToArray(); var bindings = target.Bindings.Cast<ReflectionBinding>().ToArray();
Assert.Equal("Foo", bindings[0].Path); Assert.Equal("Foo", bindings[0].Path);
Assert.Equal("Bar", bindings[1].Path); Assert.Equal("Bar", bindings[1].Path);

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs

@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml); var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
var template = (TreeDataTemplate)(templates.First()); var template = (TreeDataTemplate)(templates.First());
Assert.IsType<Binding>(template.ItemsSource); Assert.IsType<ReflectionBinding>(template.ItemsSource);
} }
} }

Loading…
Cancel
Save